Преобразование вершин полигональных объектов в точки с помощью виртуального слоя в QGIS
У меня есть многоугольный слой, и я хочу использовать виртуальный слой, который создает точки из вершин многоугольного слоя. Вопрос в том, как написать запрос, который это сделает?
Я знаю об этом инструменте "Extract vertices"
, но это не то, что я ищу.
Что пробовал:
select fid, nodes_to_points(p.geometry) as vertices from polygon as p
nodes_to_points()
это выражение QGIS, но в документации сказано :
Функции выражений QGIS также можно использовать в запросах виртуального уровня.
Однако в нем ничего не сказано, как включить его в синтаксис запроса (SQL). Обновление: кажется, что не все выражения QGIS могут использоваться в виртуальном слое, см. Здесь список функций, которые можно использовать: Список всех функций, доступных в виртуальном слое QGIS
Я также просмотрел справочный список функций SQL SpatiaLite 4.2.0 , но не смог найти ни одной функции для создания отдельных точек для каждой вершины. Самое близкое, что я пришел, было использовать
ST_DissolvePoints()
:select fid, ST_DissolvePoints (p.geometry) as vertices from polygon as p
Это создает визуальный эффект вершин, но не отдельных точек, а одного многоточечного объекта на полигон:

Ответы
Вот мое решение:
Requirements
- QGIS 3. x
- Слой (мульти) -полигона с
id
полем с различными значениями
Features
- Один запрос
- Работает с мультиполигонами
- Настроить имя слоя только в одном месте
- Выведите слой
id
, номер детали, номер кольца и номер вершины. - Отбросьте последнюю повторяющуюся вершину (в многоугольнике начало вершины = конец вершины)
- Открыть QGIS, перейдите к Database > Database > DB Manager... > Virtual Layers
- Открыть новый SQL Window
- Скопируйте приведенный ниже сценарий SQL и замените его в этой lyrчасти
polygons
своим настоящим именем слоя (или переименуйте в QGIS свой слойpolygons
):
-- list parts
WITH RECURSIVE gs_part(id, part) AS (
SELECT conf_p.id, conf_p.start
FROM conf_p
UNION ALL
SELECT conf_p.id, part + 1
FROM gs_part gs, conf_p
WHERE
gs.id = conf_p.id
AND gs.part + 1 <= conf_p.stop
),
-- list interior rings
gs_ring(id, part, ring) AS (
SELECT conf_i.id, conf_i.part, conf_i.start
FROM conf_i
UNION ALL
SELECT conf_i.id, conf_i.part, ring + 1
FROM gs_ring gs, conf_i
WHERE
gs.id = conf_i.id
AND gs.part = conf_i.part
AND gs.ring + 1 <= conf_i.stop
),
-- list vertices
gs_vert(id, part, ring, vert) AS (
SELECT conf_v.id, conf_v.part, conf_v.ring, conf_v.start
FROM conf_v
UNION ALL
SELECT conf_v.id, conf_v.part, conf_v.ring, vert + 1
FROM gs_vert gs, conf_v
WHERE
gs.id = conf_v.id
AND gs.part = conf_v.part
AND gs.ring = conf_v.ring
AND gs.vert + 1 < conf_v.stop
),
--
parts AS (
SELECT lyr.id, gs_part.part, st_geometryn(lyr.geometry, gs_part.part) AS geometry
FROM gs_part, lyr
WHERE lyr.id = gs_part.id
),
--
rings AS (
SELECT
'interior' AS info,
parts.id,
parts.part,
gs_ring.ring,
interior_ring_n(parts.geometry, ring) AS geometry
FROM gs_ring, parts
WHERE
gs_ring.ring > 0
AND gs_ring.id = parts.id
AND gs_ring.part = parts.part
UNION ALL
SELECT
'exterior',
parts.id,
parts.part,
0,
exterior_ring(parts.geometry)
FROM parts
),
-- configuration
-- for parts
conf_p AS (
SELECT id, 1 AS start, st_numgeometries(geometry) AS stop
FROM lyr
),
-- for interior rings
conf_i AS (
SELECT id, part, 0 AS start, num_interior_rings(geometry) AS stop
FROM parts
),
-- for vertices
conf_v AS (
SELECT id, part, ring, 1 AS start, st_npoints(geometry) AS stop
FROM rings
),
-- for layer
lyr AS (
SELECT
'polygons' AS lyr_name, -- ## replace here with the 'layer name' ##
p.*
FROM polygons p -- ## replace here with the layer name ##
),
-- get layer crs
crs AS (
SELECT CAST(
SUBSTR(layer_property(lyr_name, 'crs'), 6, 15)
AS integer
) AS id
FROM lyr
LIMIT 1
)
SELECT
rings.info,
rings.id,
rings.part,
rings.ring,
gs_vert.vert AS vertex,
SetSRID(st_pointn(rings.geometry, gs_vert.vert), crs.id) AS geometry
FROM gs_vert, rings, crs
WHERE
gs_vert.id = rings.id
AND gs_vert.part = rings.part
AND gs_vert.ring = rings.ring
К сожалению, ST_Dump()и ST_DumpPoints()функция еще не реализована в виртуальном уровне. Поэтому я предлагаю обходной путь, как извлекать вершины полигональных объектов.
Это решение включает в себя несколько функций, а именно NumInteriorRings(), ST_InteriorRingN(), ST_ExteriorRing(), ST_PointN(), и ST_NPoints().
Кто-то может найти это решение неидеальным с точки зрения производительности и сложности из-за обширного вывода CROSS JOINпредложения, которое создает избыточные индексы (для внутренних колец, а также для вершин), которые действительно необходимы. Если кто-то знает, как это разгадать, пожалуйста, доработайте запрос или дайте подсказку.
Предположим, существует многоугольный слой с именем 'polygons'
, см. Изображение ниже.

Со следующим запросом
-- generating the second series required to extract all vertices from polygons' contours
WITH RECURSIVE generate_series2(vert) AS (
SELECT 1
UNION ALL
SELECT vert + 1
FROM generate_series2, max_num_points_per_feature
WHERE vert + 1 <= stop2
),
-- finding max number of vertices in all features
max_num_points_per_feature AS (
SELECT MAX(st_npoints(geom1)) AS stop2
FROM inter_outer_contours
),
-- union of geometries of both outer and inner contours, represented as polylines
inter_outer_contours AS (
-- interior rings from all polygons
WITH interior AS (
-- generating the first series required to extract all interior rings
WITH RECURSIVE generate_series1(ring) AS (
SELECT 1
UNION ALL
SELECT ring + 1
FROM generate_series1, max_num_rings_per_polys
WHERE ring + 1 <= stop1
),
-- finding max number of interior rings within all polygons
max_num_rings_per_polys AS (
SELECT MAX(numrings) AS stop1
FROM num_rings_per_poly
),
-- finding how many interior rings each polygon has
num_rings_per_poly AS (
SELECT id, NumInteriorRings(geometry) AS numrings
FROM "polygons"
GROUP BY id
)
-- query to extract all interior rings from all polygons
SELECT p.id AS origin, -- a field represents the original polygon id
'interior' AS info, -- a text field represents the interior attribute
interior_ring_n(geometry, ring) AS geom1 -- setting geometry for a polyline
FROM "polygons" AS p
CROSS JOIN generate_series1
WHERE geom1 IS NOT NULL -- no null geometries accepted, needed because of the cross join
),
-- exterior rings from all polygons
exterior AS (
SELECT p.id AS origin, -- a field represents the original polygon id
'exterior' AS info, -- a text field represents the exterior attribute
st_exteriorring(geometry) AS geom1 -- setting geometry for a polyline
FROM "polygons" AS p
)
-- a union between interior and exterior rings from all polygons
SELECT *
FROM interior
UNION
SELECT *
FROM exterior
)
-- query to achieve all vertices from all polygons
SELECT ioc.origin,
ioc.info,
gs2.vert, -- a field represents the vertice index
st_pointn(ioc.geom1, gs2.vert) AS geom2 -- setting geometry for a point
FROM inter_outer_contours AS ioc
JOIN generate_series2 AS gs2
WHERE geom2 IS NOT NULL -- no null geometries accepted, needed because of the cross join
можно получить все вершины

Результат совпадает с выводом геоалгоритма «Извлечь вершины» .
Пояснения:
- В приведенном выше запросе не забудьте четыре раза заменить
"polygons"
строку на ваше фактическое имя многоугольного слоя.
ДЕЛАТЬ:
- расширить раздел объяснения
- попробуйте с мультиполигонами
- добавить новое поле
"id"
Использованная литература:
- Использование RECURSIVE на виртуальном уровне