Генератор геометрии в QGIS
ТатьянаГенератор геометрии может быть использован для создания сложных стилей объектов, а также визуализации объектов с использованием другого типа геометрического примитива (например, соединение точек одной линией по порядку) или отрисовки других объектов на основе исходных (например, отрисовки буферных зон вокруг объектов).
Фактически генератор геометрии позволяет получить на карте объект, геометрия которого отличается от исходного.
Важно помнить, что генератор геометрии не меняет тип исходных объектов слоя, а просто создает новый тип отрисовки объектов. То есть геометрия останется неизменной, изменится только отображение ее на карте. Поэтому, если вам необходимо создать объект с иным типом геометрии для дальнейшей работы на основе выражения, следует пользоваться инструментом Геометрия из выражения из панели инструментов анализа.
Генератор геометрии создает объекты одного из трех типов: Точка/Мультиточка, Линия/Мультилиния, Полигон/Мультиполигон. Тип геометрии, как уже было сказано выше, может не совпадать с истинной геометрией слоя, но геометрия, создаваемая генератором, должна совпадать с его типом, в противном случае объекты на карте отображаться не будут.
Создание объектов здесь может базироваться как только на геометрических свойствах объектов, так и на их атрибутах.
Базовые примеры
Самыми простыми примерами генераторов геометрии являются - создание центроидов и буферных зон объектов:
centroid( $geometry )

Здесь в выражении $geometry - это функция, возвращающая исходные геометрии объектов.
buffer( $geometry, -0.005 )

Здесь значение буфера приведено в градусах.
Кроме того, можно создавать генераторы геометрии на основе объектов из нескольких слоев. Например, соединить объекты одного слоя с объектами другого линиями:
make_line( centroid( $geometry ), geometry( get_feature( 'Citybike', 'STATION', 'Millennium Tower' ) ) )

Более продвинутый вариант соединения объектов стрелками:
collect_geometries(
with_variable(
'destination_points',
relation_aggregate(
'the_relation_id',
'array_agg',
centroid( $geometry )
),
array_foreach(
@destination_points,
make_line(
centroid( @geometry ),
project(
centroid(
make_line( centroid( @geometry ), @element )
),
10, 50
),
@element
)
)
)
)

Перенос объектов на карте
Также генератор геометрии позволяет переносить объекты из одной части в другую, не меняя их истинного местоположения в слое. Например, визуально переместить штаты Аляску и Гавайи так, чтобы они были размещены неподалеку от остальных.

Или добавить укрупненный фрагмент карты - виньетку:

Соединение объектов внутри мультиобъекта
При наличии в слое объектов мультитипа, то есть объектов, которые состоят из нескольких примитивов, их можно соединить между собой линиями, чтобы показать связи.
Рассмотрим пример, приведенный в статье: соединение между собой точек внутри мультиточки.

Для этого используется сразу три генератора геометрии: точка с порядковым номером 1, вторая и последующая точки в составе мультиобъекта и линии, соединяющие их.
Первая точка:
geometry_n($geometry, 1)
Последующие точки:
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. Первое значение списка — это минимальная длина стрелки, а также зазор до первого узла.
Соединение точек линиями
Можно соединить между собой ближайшие точки.

В первую очередь необходимо соединить между собой ближайшие точки линиями:
make_line(
@geometry,
closest_point(
difference(
aggregate(
@layer,
'collect',
@geometry),
@geometry),
@geometry
)
)
Полученные прямые линии можно скруглить и сделать чуть более толстыми на одном конце с помощью приведенного ниже выражения. Это выражение будет применяться к уже имеющемуся генератору на более низком уровне иерархии символа.
make_line(
start_point($geometry),
centroid(
offset_curve(
make_line(
start_point($geometry),
end_point($geometry)),
length(
make_line(
start_point($geometry),
end_point($geometry)
)
)/8
)
),
end_point($geometry)
)
Можно просто создать линии на основе имеющихся точек. Так например, мной была воспроизведена карта похода Наполеона на Москву Шарля Минара.

В качестве исходных данных использованы координаты городов, через которые проходила армия. Данные размешены по ссылке.
Из имеющихся точечных объектов генератором геометрии были созданы линии похода:
make_line(array_agg(expression:=$geometry, group_by:= "group"))
А далее полученные линии настроены по стилю с использованием интерполированной линии.

Нумерация узлов ломаной

Сами узлы ломаной создаются генератором геометрии:
nodes_to_points( $geometry)
А номера им присваиваются путем изменения типа символа на текстовые маркеры и переопределения маркеров на основе данных:
@geometry_part_num
Создание уникальных стилей
Еще одним вариантом использования генератора геометрий является создание собственных уникальных стилей отрисовки объектов.
Стили полигонов

Здесь генератор геометрии используется только для создания небольшой буферной зоны, которая будет являться полигоном и к которой можно применять характерные для полигонов символы.
Для того, чтобы граница полигона оставалась на месте, в этом выражении используется отрицательное значение буфера (то есть, линия будет внутри полигона, поэтому внешняя граница не изменится).
Буфер может быть заменен на двусторонний, но это изменит положение внешней границы полигона.
difference( @geometry, buffer(@geometry, -50) )
К полученному буферу применяется градиентная заливка.

В приведенном примере полигонам добавляется фаска - скошенный край полигона.
Для того, чтобы внешний контур полигона остался неизменным, буферная зона имеет отрицательное значение, то есть будет отрисовываться внутрь контура.
difference( @geometry, buffer(@geometry, -100) )
К полученной буферной зоне далее просто применяется тип заливки Shapeburst fill, то есть градиент от границ к центру.

В этом примере стилизации границы полигона внутрь границы добавляются точки, с постепенно уменьшающимся внутрь полигона размером, чтобы создать имитацию полутонового контура.
Этот стиль можно воссоздать с использованием нескольких генераторов геометрии. В первую очередь следует при необходимости скруглить границы полигонов для наилучшего результата. Полученная сглаженная линия будет символом верхнего уровня, на основе которого будет создано несколько линий на разном расстоянии от границы полигона и с уменьшающимся размером маркера.
smooth(@geometry,5)
Далее создаются отдельные линии из маркеров на основе буферных зон с отрицательным расстоянием от границы (то есть они будут создаваться внутрь границ полигона).
boundary(buffer($geometry,-225))
boundary(buffer($geometry,-175))
boundary(buffer($geometry,-125))
boundary(buffer($geometry,-75))
boundary(buffer($geometry,-25))
Представление точек в виде зданий

В этом стиле размер здания базируется на значении одного из атрибутов.
Дверь здания
Дверь здания создается простой линий из маркеров:
with_variable( 'height',30, make_line( make_point( $x+15, $y +30 ), make_point( $x+15, ($y)+30+ @height ) ) )
Окна
Окна, как и дверь, создаются с помощью линии, состоящей из маркеров:
with_variable( 'height',30, make_line( make_point( $x, $y+ @height *3 ), make_point( $x, ($y)+ "value"*@height - @height ) ) )
Фасад
Фасад представляет собой простой прямоугольник, размер которого варьируется в зависимости от величины атрибута, который в выражении указан как "value" и может быть заменен на любой другой числовой атрибут.
with_variable( 'width',30,make_polygon( make_line( make_point( $x-@width,$y), make_point( $x+@width,$y), make_point( $x+@width,($y)+ "value"*@width ), make_point( $x-@width,($y)+ "value"*@width) ) ) )
Крыша
Также как и фасад крыша - это полигон.
with_variable( 'width',30,make_polygon(
make_line(
make_point( $x+@width/2,($y)+@width+ "value"*@width),
make_point( $x+@width*2,($y)+@width+ "value"*@width ),
make_point( $x+@width,($y)+ "value"*@width ),
make_point( $x-@width,($y)+ "value"*@width)
)
)
)
Боковые фасады
Аналогично главному фасаду и крыше боковые стороны здания создаются простыми полигонами.
with_variable( 'width',30,make_polygon(
make_line(
make_point( $x+@width*2,($y)+@width+ "value"*@width),
make_point( $x+@width*2,($y)+@width),
make_point( $x+@width,$y),
make_point( $x+@width,($y)+ "value"*@width)
)
)
)
Символы в виде датчиков, отображающих значение показателя

Этот стиль будет являться комплексным, потому что требует создать отдельно символ для датчика, значение показателя для конкретного объекта, стрелку датчика и максимальное значение показателя.
Верхним уровнем символа здесь будет простая точка, на основе которой в виде символов более низкого уровня будут создаваться остальные элементы.
В данном случае будет использоваться выражение _point_on_surface (точка на поверхности)_ для того, чтобы символ был расположен внутри полигона, тогда как при использовании центроидов для невыпуклых многоугольников они будут расположены за пределами полигонов.
point_on_surface(@geometry)
Обратите внимание, что в этом выражении используется @geometry, тогда как в последующих будет использовано $geometry, так как этот первый этап будет являться "родительским символом" и все остальные генераторы будут использовать его, а не исходную истинную геометрию.
Для добавления значения показателя используется текстовый маркер, в котором применено переопределение на основе данных, чтобы текст отображал величину показателя для конкретного объекта.
Чтобы текст был на фоне, отличающемся от общего, создается прямоугольник с закругленными углами в виде буфера.
buffer( make_line( make_point( x($geometry)-100, y($geometry)+50 ), make_point( x($geometry)+100, y($geometry)+50) ) , 90 )
Стрелка датчика создается как обычный символ линии. Для того, чтобы линия располагалась под нужным углом, применяется переопределение на основе данных для поворота линии с использованием выражения:
"value" * (360 / (maximum("value") / 0.9))
Значение 0.9 в выражении нужно для того, чтобы только 90% от 360 градусов полного круга было использовано, так как индикаторная полоса будет занимать только эту часть окружности.
Ноль и максимальное значение на индикаторной полосе датчика, как и значение показателя для конкретного объекта создается в виде текстового маркера. Но нулевое значение просто указывается в виде символа, а максимальное, как и значение показателя использует переопределение на основе данных с выражением:
maximum("value")
Черта у нулевого значения датчики и центральная точка создаются простыми маркерами.
Для того, чтобы между максимальным и минимальным значениями индикаторной полосы была серая вставка, генератором геометрии добавляется серый треугольник (те самые 10% от всей окружности, которые должны остаться незаполненными).
make_triangle( $geometry, make_point(x($geometry)-280,y($geometry)-380), make_point(x($geometry),y($geometry)-410) )
Внутренний круг, закрывающий центральную часть индикатора создается просто как серый круг с использованием простого символа.
Сама индикаторная полоса будет являться буфером вокруг центра с градиентной заливкой. Выражение генератора геометрии:
buffer($geometry,400,20)
Фоновый круг индикатора, как и внутренний создается простым маркером в виде круга с серой заливкой.
Случайные кривые, соединяющие центр охвата и точечные объекты

В этом примере при каждом перемещении по карте или изменении масштаба будут отрисовываться случайные кривые, соединяющие центр текущего охвата с каждой точкой слоя.
with_variable( 'start_x', x( @map_extent_center ),
with_variable( 'start_y', y( @map_extent_center ),
smooth( make_line(
@geometry,
make_point(
4*randf(0,1)*(x(@geometry)-@start_x)/5+@start_x,
4*randf(0,1)*(y(@geometry)-@start_y)/5+@start_y
),
make_point(
3*randf(0,1)*(x(@geometry)-@start_x)/5+@start_x,
3*randf(0,1)*(y(@geometry)-@start_y)/5+@start_y
),
make_point(
2*randf(0,1)*(x(@geometry)-@start_x)/5+@start_x,
2*randf(0,1)*(y(@geometry)-@start_y)/5+@start_y
),
make_point(
1*randf(0,1)*(x(@geometry)-@start_x)/5+@start_x,
1*randf(0,1)*(y(@geometry)-@start_y)/5+@start_y
),
make_point(
@start_x,
@start_y
)
),
5,0.3,0.1,360
)
)
)
Линии с разными стилями для четных и нечетных сегментов

В этом случае понадобится два генератора геометрии: для четных и для нечетных сегментов, но код у них будет отличаться очень незначительно.
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.
Линии расстояний от точки клика на карте

Здесь стиль будет состоять из трех основных элементов: точка местоположения курсора, линии, соединяющие эту точку и объекты слоя, подпись с длиной линии.
Точка, на которой был осуществлен клик на карте, создается как простой маркер:
@canvas_cursor_point
Линии - это кратчайшие линии от точки курсора до каждого объекта слоя:
shortest_line( $geometry, @canvas_cursor_point )
Подпись с расстоянием создается как центроид линии и подстиль от самой линии:
centroid($geometry)
Типом символа будет являться текстовый маркер с переопределением на основе данных, где текст заменяется выражением:
round(length( shortest_line( $geometry, @canvas_cursor_point )))||'m'
Для того, чтобы подписи были перпендикулярны линиям, их поворот задается следующим выражением:
degrees( azimuth( centroid($geometry), @canvas_cursor_point ))-90
Источники:
- Документация генератора геометрий https://docs.qgis.org/3.40/en/docs/user_manual/style_library/symbol_selector.html#the-geometry-generator
- Документация по выражениям в QGIS https://docs.qgis.org/3.40/en/docs/user_manual/expressions/expression.html
- Жесткий цигун с условными знаками или зачем нужен geometry generator https://habr.com/ru/articles/504986/
- Quick guide to geometry generator symbol layers https://anitagraser.com/2017/04/08/a-guide-to-geometry-generator-symbol-layers/
- Geometry generators in QGIS https://www.statsmapsnpix.com/2023/03/geometry-generators-in-qgis.html
- QGIS Geometry Generator examples https://gitlab.com/gis-projects/qgis-geometry-generator-examples