Hard qigong with conventional signs or why you need a geometry generator


Do customer requirements for symbols on maps seem unrealistic to you? Further you will learn how to use geometry generator, QGIS and Python to make your conditioners the best.


Introduction


! , , , , , QGIS Python PyQt. , , () . QGIS , geometry generator — . . , .


«» ?


QGIS MVC . , .. , Model-Based PyQt . , . , .


QGIS:


Sample styles for point, line, and polygon


?


99% . ! , (, ), (, ), , .


, :



«» :


"Teeth"


. , , , ( ). QGIS Python.



, , -, , , , (!) , , ! , .



  • QGIS 3.10.6 Ubuntu 18.04.
  • «» UTM 37N, .
  • GitHub .
  • «» , .
  • , , , .

geometry generator


— .
, : /, /, /. , , , , . : , , .


«»?


, , (expression), . Expressions, Python Expressions, Filtering and Calculating Values.


, , . — , . Python, . QGIS. , a .


: . - , .


, , QGIS, , , , . . .


...


, .


, ( , ), , , , , , :


One object with different styles.


: , , ; , , — , . points.


geometry_n(), :


geometry_n($geometry, 1)

$geometry — , .


, :


if(
    @geometry_part_num > 1, --     
    geometry_n($geometry,  @geometry_part_num ), --   
    NULL
)

, if()— . 2 @geometry_part_num, , ( @). NULL . , , , .


— , :


if(
    @geometry_part_num > 1, --       
    with_variable(
        'inputs',
        array(
            10000, --        
            length( --       
                make_line(
                    start_point($geometry),
                    geometry_n($geometry, @geometry_part_num)
                )
            ),
            azimuth( --       
                geometry_n($geometry, @geometry_part_num),
                start_point($geometry)
            )
        ),
        if(
            @inputs[0] < @inputs[1], --    
            make_line(
                geometry_n($geometry, @geometry_part_num), --  
                project(
                    geometry_n($geometry, @geometry_part_num), --  
                    @inputs[1] - @inputs[0], --  
                    @inputs[2] -- 
                )
            ),
            NULL --     ,   .
        )
    ),
    NULL
)

. , , , NULL.


— . , . , . , .


with_variable(), : , , . , - , with_variable, . .


inputs array() — 0. — , . — , :


...
length( --       
    make_line(
        start_point($geometry),
        geometry_n($geometry, @geometry_part_num)
    )
),
azimuth( --       
    geometry_n($geometry, @geometry_part_num),
    start_point($geometry)
)
...

length(), , make_line()start_point($geometry), ( ). azimuth() , !


, . if() , , . @inputs , , «» NULL.


:


...
make_line(
    geometry_n($geometry, @geometry_part_num), --     
    project(
        geometry_n($geometry, @geometry_part_num), --  
        @inputs[1] - @inputs[0], --  
        @inputs[2] -- 
    )
)
...

project(), , . , .



, Klas Karlsson, QGIS. , , , , :


Route


, . , WKT , , «||», . , , . :


  • , ;
  • , .

( lines1). , :


  • ( ).
  • .

:


collect_geometries(
    array_foreach(
        generate_series(1, num_points($geometry)),
        point_n($geometry, @element)
    )
)

«» . . generate_series(), () 1 num_points($geometry), . . . array_foreach(). array_foreach , point_n($geometry, @element), . . (point_n() — ), @element — . , . — , collect_geometries(). ( ).


. «/». :


with_variable(
    'minimal_length',  --  ,     
    7000.0,
    collect_geometries(  --  
        array_foreach(
            generate_series(1, num_points($geometry) - 1),  --   
            with_variable(
                'inputs',
                array(
                    azimuth( --      
                        point_n($geometry, @element),
                        point_n($geometry, @element + 1)
                    ),
                    length( --      
                        make_line(
                            point_n($geometry, @element),
                            point_n($geometry, @element + 1)
                        )
                    )
                ),
                if(
                    @inputs[1] - @minimal_length * 2 > 0,  -- .  
                    line_substring( --     
                        make_line(
                            point_n($geometry, @element),
                            point_n($geometry, @element+1)
                        ),
                        @minimal_length, @inputs[1] - @minimal_length
                    ),
                    geom_from_wkt('LINESTRING EMPTY') --  
                )
            )
        )
    )
)

, . @minimal_length. collect_geometries , array_foreach , . , , @inputs: .


. , @minimal_length ( ). , , :


geom_from_wkt('LINESTRING EMPTY')

, , NULL, . , . WKT «LINESTRING EMPTY», WKT QGIS geom_from_wkt().


. line_subtring(), . , .



? , , . (. lines2).


, buffer(), : , 1. .. :



, .



, , , . , . , . :


with_variable(
    'distance',
    4000, --  
    with_variable(
        'offset_lines', --     
        array(
            extend(
                offset_curve($geometry, @distance, join:=2),
                @distance, @distance
            ),
            extend(
                offset_curve($geometry, -@distance, join:=2),
                @distance, @distance
            )
        ),
        collect_geometries(
            @offset_lines[0], --  1
            @offset_lines[1], --  2
            make_line( --     1   2
                start_point(@offset_lines[0]),
                start_point(@offset_lines[1])
            ),
            make_line( --     1   2
                end_point(@offset_lines[0]),
                end_point(@offset_lines[1])
            )
        )
    )
)

, with_variable, , , . . @offset_lines , , ( offset_curve()), extend(). , — start_point()end_point().


— , —


, , . (. lines3). , :


with_variable(
    'lines',
    segments_to_lines($geometry),
    collect_geometries(
        array_foreach(
            generate_series(2, num_geometries(@lines), 2),
            geometry_n(@lines, @element)
        )
    )
)

segments_to_lines() , — , . , generate_series. :




. , ( poly2).



«/» :


with_variable(
    'points_num',
    --     
    num_points($geometry) - 1, -- 
    collect_geometries(
        array_foreach(
            --     
            generate_series(1, round(@points_num / 2.0)),
            make_line(
                point_n( --  
                    $geometry,
                    @element
                ), 
                point_n( --  
                    $geometry,
                    @element + floor(@points_num / 2.0)
                )
            )
        )
    )
)

«» , generate_series() . .



, ( poly3):


:


collect_geometries(
    array_foreach(
        generate_series(1, num_points($geometry) - 1),
        make_line(
            centroid($geometry),
            point_n($geometry, @element)
        )
    )
)

, centroid(), , , .



poly4. , , «», . «» — , , , . :



, , . . . , :


with_variable(
    'lines',
    segments_to_lines($geometry), --    
    collect_geometries(
        array_foreach(
            segments_between_sides_nums( --   Python
                array_foreach(
                    generate_series(1, num_geometries(@lines)),
                    --   [ ,  ]
                    array(
                        @element,
                        length(
                            geometry_n(@lines, @element)
                        )
                    )
                )
            ),
            geometry_n(@lines, @element)
        )
    )
)

, . , , .


segments_between_sides_num(). QGIS Python ( custom.py ~/.local/share/QGIS/QGIS3/profiles/default/python/expressions):


@qgsfunction(args="auto", group="")
def segments_between_sides_nums(sides, feature, parent):
    """
            
    .
    sides -     ( ,  )
    """
    sorted_sides = sorted(sides, key=lambda x: x[1])
    return list(range(int(sorted_sides[0][0]) + 1, int(sorted_sides[1][0])))

Python , .. ( ), .


. , Python?



, : QGIS Python? . , .



  • test_poly 100000 , 3 20.

create_poly_lyr.py, ( ).



  • , , «». , ( ).

Python, QGIS, Python. expression_benchmarking.py:


EXPRESSION = """
collect_geometries(
    array_foreach(
        generate_series(1, num_points($geometry)),
        make_line(
            centroid($geometry),
            point_n($geometry, @element)
        )
    )
)
"""
for counter, poly in enumerate(lyr_polys.getFeatures()):
    exp = QgsExpression(EXPRESSION)
    context = QgsExpressionContext()
    context.setFeature(poly)
    star = exp.evaluate(context)

python_benchmarking.py:


for counter, poly in enumerate(lyr_polys.getFeatures()):
    centroid = QgsPoint(poly.geometry().centroid().asPoint())
    star = QgsMultiLineString()
    for vertex in poly.geometry().vertices():
        line = QgsLineString(centroid, vertex)
        star.addGeometry(line)



Python 5,3 , 23,16 . Python 4,5 . , , , .



:


  • . «» , , «- 1» , Python, .
  • QGIS, . . , .

:


  • Python.
  • .

Python:


  • .
  • Python.

Python:


  • () *.py .
  • Python , .
  • Python, , ( Windows).

, QGIS . , , , , . , «» Python.



? .


: WGS84 (/), — . , , . , . , :


Buffer in degrees


: , , . , . poly3 «» :


Hole polygon


, .


, , /? , :


Parallel hatching


«» , ? , , .


«», ?


, , . , , .


Generators provide almost unlimited possibilities, more precisely, the possibilities are limited only by performance, and are an indispensable tool for creating truly complex conventional signs.


I hope that the article will be useful, and we may consider complex cases in the next article. Thank you for the attention.


References



All Articles