6

I am creating a flight path for an aerial survey. I need one continuous line crossing over the area to be surveyed. The line needs to be made up of long horizontal stretches spaced 500m apart and short vertical turns along the left and right boundaries of the area (polygon).

I've tried adding a grid and then editing it using the 'create grid' tool. This could work in conjunction with the points to lines tool. My problem with that is that, when creating the line, the points aren't joined in the correct sequence. The points to line tool uses the id number attributed to each point for determining the sequence. Points are numbered in rows going left to right, I need them to be numbered left to right for the first row and then right to left for the second, alternating like this all the way down. Manually editing the grid is too time consuming and I think there must be a better way.

Example of desired output:

picture depicting an idea of what the desired output would look like

10
  • 1
    Do you have many polygons? Commented Jan 8 at 7:52
  • 1
    Where it suppose to start? Commented Jan 8 at 8:01
  • @Bera, only need to fill one polygon. Commented Jan 8 at 8:09
  • @Taras, my preference would be to offset below (south) by 250m from the top point (most northern). Commented Jan 8 at 8:27
  • 2
    See gis.stackexchange.com/questions/134290/… Commented Jan 8 at 8:39

2 Answers 2

6

You can use QGIS expressions with Geometry generator (for visualization only) or Geometry by Expression (to create actual geometries) using the expression below. See more about the differences between both options here.

The expression first creates a bounding box around the polygon, then generates horizontal lines in a regular interval (that you can freely define) inside the box, then intersects (clips) these lines with the initial polygon. Like this, you get the horizontal lines.

Then it alternates to connect the end point of every second horizontal line with the start point of this same line (to inverse line direction to get an alternating pattern for each second line: left to reght vs. right to left). Then the start point of this line to the start point of the next line and this to the end point of itself. For every second line it connects the two end-points to get a connection there. Like this, you get the desired result, see screenshot.

Red lines, here created using Geometry Generator with the expression below, based on the blue polygon:

enter image description here

If using Geometry by Expression, you might want to use Explode lines on the result to get individual lines for each direction:

enter image description here

Live preview in Geometry generator can be helpful for optimization. The radius (area, stripes) the plane covers can be visualized as a buffer (thick black lines on the following screenshot; add a buffer() function around the expression that generates the lines) and using difference() expression you can see the areas of the initial polygon that remains uncovered (red). Like this you can decide if you can allow to make the distance between the lines (dark blue) larger or not. If you make the distance of the lines the same as the radius the plane covers, every inch of the initial polyvon (light blue) will be covered. But maybe for economization, you want larger distances and fewer overlaps and can accept that certain marginal edges remain uncovered like in this screenshot, where distance between lines is 180 and radius is 90:

enter image description here

Here is the expression to use, it doesn't require any changes. You can, however, adapt the value of 200 in line 3 to change the distance between the lines. On line 6 you can define the direction of the line: starting at the botten left, goin to the right first and changing direction for the next line, or inverse: starting bottom right. If using Geometry generator, you see the changes in realtime:

with_variable (
    'mydist',
    200, -- adapt this value
with_variable(
    'mydir',
    1,  -- change here: 0=start from left to right, 1=right to left
collect_geometries(
    array_filter(
        with_variable(
            'mylines',
            intersection(
                collect_geometries(
                    array_foreach(
                        generate_series( 0, (y_max(bounds($geometry)) - y_min(bounds($geometry)) ),@mydist),
                        with_variable(
                            'xmax',
                            x_max(bounds($geometry)),
                        with_variable(
                            'xmin',
                            x_min(bounds($geometry)),
                        with_variable(
                            'ymin',
                            y_min(bounds($geometry)) + @element,
                        case 
                        when
                            @mydir= 0
                        then 
                            make_line(
                                make_point(@xmax, @ymin),
                                make_point(@xmin, @ymin)
                            )
                        else 
                            make_line(
                                make_point(@xmin, @ymin),
                                make_point(@xmax, @ymin)
                            )
                        end
                        )))
                    )
                ),
                $geometry
            ),
            array_foreach(
                generate_series (0,array_length(geometries_to_array(@mylines))),
                with_variable(
                    'myend0',
                    end_point (geometries_to_array (@mylines)[@element]),
                with_variable(
                    'myend1',
                    end_point(geometries_to_array(@mylines)[@element+1]),
                case 
                when 
                    @element %2 = 0
                then 
                    case
                    when 
                        @element < array_length(geometries_to_array(@mylines))-2
                    then
                        make_line (
                            @myend0,
                            start_point (geometries_to_array(@mylines)[@element]),
                            start_point(geometries_to_array(@mylines)[@element+1]),
                            @myend1
                        )
                    else 
                        make_line (
                            @myend0,
                            start_point (geometries_to_array(@mylines)[@element])
                        )
                    end
                else
                    make_line ( 
                        @myend0,
                        @myend1
                    )
                end
                ))
            )
        ),
        @element is not NULL
    )
)))

Edit:

In your comments, you specified you wanted "to set a start point, say 250m south of the most northerly point and as far to the west as possible". Here is the modified expression:

with_variable(
    'south',
    250,
with_variable (
    'mybounds',
    bounds (
        make_line (
            start_point(
                intersection (
                    make_line (
                        make_point (x_min(bounds($geometry)), y_max(bounds($geometry))-@south),
                        make_point (x_max(bounds($geometry)), y_max(bounds($geometry))-@south)
                    ),
                    $geometry
                )
            ),  
            make_point (x_max (bounds($geometry)), y_max (bounds($geometry)))
        )
    ),
with_variable (
    'mydist',
    200, -- adapt this value
with_variable(
    'myline',
    collect_geometries(
        array_filter(
            with_variable(
                'mylines',
                intersection(
                    collect_geometries(
                        array_foreach(
                            generate_series( 0, (x_max(@mybounds) - x_min(@mybounds) ),@mydist),
                                make_line(
                                    make_point(x_min(@mybounds), y_min(@mybounds) + @element),
                                    make_point(x_max(@mybounds), y_min(@mybounds)+ @element)
                                )
                        )
                    ),
                    $geometry
                ),
                array_foreach(
                    generate_series (0,array_length(geometries_to_array(@mylines))),
                    case 
                    when 
                        @element %2 = 0
                    then 
                        make_line (
                            end_point(geometries_to_array(@mylines)[@element]),
                            start_point (geometries_to_array(@mylines)[@element]),
                            start_point(geometries_to_array(@mylines)[@element+1]),
                            end_point(geometries_to_array(@mylines)[@element+1])
                        )
                    else
                        make_line ( 
                            end_point(geometries_to_array(@mylines)[@element]),
                            end_point(geometries_to_array(@mylines)[@element+1])
                        )
                    end
                )
            ),
            @element is not NULL
        )
    ),
    difference (
        @myline,
        geometry_n(
            segments_to_lines (
                geometry_n (
                    @myline, 
                    num_geometries (@myline)
                )
            ),
            if (num_geometries (@myline) % 2 = 1, 2, 1)
        )
    )
))))
2
  • This works well, is it possible to set a start point, say 250m south of the most northerly point and as far to the west as possible? Commented Jan 8 at 12:15
  • Yes, see updated solution Commented Jan 8 at 13:58
3

If you need it as a layer and only for one polygon you can also create a Virtual layer using this Spatialite query. This works only for one polygon because zig-zag construction needs ordered row-by-row traversal, but QGIS Virtual Layers lack window functions and traversal/splitting etc. so adjacency can’t be reconstructed once clipping removes rows.

WITH RECURSIVE
-- =====================================
-- PARAMETERS
-- =====================================
params AS (
  SELECT 30.0 AS spacing   -- line spacing in map units
),

-- =====================================
-- 1) Select ONE polygon (important!)
--    If your layer has multiple polygons,
--    this intentionally uses only one.
-- =====================================
poly AS (
  SELECT
    geometry
  FROM polygon
  LIMIT 1
),

-- =====================================
-- 2) Bounding box of that polygon
-- =====================================
bbox AS (
  SELECT
    x_min(ext) AS xmin,
    x_max(ext) AS xmax,
    y_min(ext) AS ymin,
    y_max(ext) AS ymax,
    (SELECT spacing FROM params) AS spacing
  FROM (
    SELECT extent(geometry) AS ext
    FROM poly
  )
),

-- =====================================
-- 3) Generate stripe indices (north–south)
-- =====================================
nums(n) AS (
  SELECT 0
  UNION ALL
  SELECT n + 1
  FROM nums, bbox
  WHERE (bbox.ymin + (n + 1) * bbox.spacing) <= bbox.ymax
),

-- =====================================
-- 4) Carrier lines (zig-zag direction)
-- =====================================
carriers AS (
  SELECT
    n AS idx,
    CASE
      WHEN n % 2 = 0 THEN
        make_line(
          make_point(bbox.xmax, bbox.ymin + n * bbox.spacing),
          make_point(bbox.xmin, bbox.ymin + n * bbox.spacing)
        )
      ELSE
        make_line(
          make_point(bbox.xmin, bbox.ymin + n * bbox.spacing),
          make_point(bbox.xmax, bbox.ymin + n * bbox.spacing)
        )
    END AS geom
  FROM nums, bbox
),

-- =====================================
-- 5) Clip carriers to polygon
-- =====================================
clipped AS (
  SELECT
    c.idx,
    intersection(c.geom, p.geometry) AS geom
  FROM carriers c, poly p
  WHERE
    NOT is_empty(intersection(c.geom, p.geometry))
    AND geometrytype(intersection(c.geom, p.geometry))
        IN ('LINESTRING','MULTILINESTRING')
),

-- =====================================
-- 6) Connect each line with the next one
--    (idx + 1 works because there is ONE polygon)
-- =====================================
connectors AS (
  SELECT
    a.idx,
    make_line(
      end_point(a.geom),
      start_point(b.geom)
    ) AS geom
  FROM clipped a
  JOIN clipped b
    ON b.idx = a.idx + 1
)

-- =====================================
-- 7) Merge everything into ONE path
-- =====================================
SELECT
  line_merge(collect(geom)) AS geometry
FROM (
  SELECT geom FROM clipped
  UNION ALL
  SELECT geom FROM connectors
);

sample output

1
  • 1
    Great solution! Nicely done Commented Jan 9 at 11:15

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.