import os
from qgis.core import QgsVectorLayer
# wczytanie warstwy wektorowej
= os.path.join("algorytmy-geoprzestrzenne", "dane", "powiaty.gpkg")
filepath = QgsVectorLayer(filepath, "powiaty", "ogr")
vector print(vector.isValid())
True
Algorytmy danych geoprzestrzennych
Przetwarzanie danych wektorowych
Krzysztof Dyba
Na początku wczytajmy warstwę wektorową powiaty.gpkg
, a następnie sprawdźmy podstawowe metadane.
import os
from qgis.core import QgsVectorLayer
# wczytanie warstwy wektorowej
= os.path.join("algorytmy-geoprzestrzenne", "dane", "powiaty.gpkg")
filepath = QgsVectorLayer(filepath, "powiaty", "ogr")
vector print(vector.isValid())
True
# wyświetlenie metadanych
print(vector.featureCount())
print(vector.fields().count())
print(vector.fields().names())
35
5
['fid', 'JPT_KOD_JE', 'JPT_NAZWA_', 'IIP_IDENTY', 'IIP_WERSJA']
Wczytana warstwa składa się z 35 obiektów oraz 5 atrybutów (inne określenie to kolumny bądź pola).
Iterowanie po obiektach warstwy wektorowej umożliwia dostęp lub modyfikowanie geometrii/atrybutów tych obiektów. Do tego celu służy metoda getFeatures()
. Geometria obiektu dostępna jest za pomocą metody geometry()
(klasa QgsGeometry), natomiast atrybuty za pomocą metody attributes()
.
W poniższym przykładzie wykorzystamy funkcję islice
z biblioteki itertools
, która zwróci tylko 5 pierwszych obiektów.
from itertools import islice
# iteracja po obiektach
for feature in islice(vector.getFeatures(), 5):
# dostęp do atrybutów obiektu
= feature.attributes()
attrs # dostęp do geometrii obiektu
= feature.geometry()
geom
print(attrs)
# print(geom)
[1, '3012', 'powiat krotoszyński', 'dc54ce6a-5272-45fc-a49c-824dbf170f2c', '2024-09-05T13:41:42+02:00']
[2, '3002', 'powiat czarnkowsko-trzcianecki', 'b2e00ebb-412b-4ae8-9afc-fe68f65bb1ac', '2021-05-11T10:49:15+02:00']
[3, '3009', 'powiat kolski', '06a4a975-dc89-4ac3-9d51-cf188532908a', '2012-09-27T08:59:03+02:00']
[4, '3029', 'powiat wolsztyński', '6f988fb3-2b2c-467e-9d07-c512d9215c05', '2024-08-27T13:56:13+02:00']
[5, '3026', 'powiat śremski', '2fc58bb6-d095-4f37-8f10-874797cb97fb', '2024-09-03T13:42:56+02:00']
W celu obliczenia powierzchni poligonów musimy wykonać pętle po wszystkich obiektach znajdujących się w warstwie i uzyskać dostęp do ich geometrii. Następnie, należy wykorzystać metodę area()
, która oblicza pole powierzchni w jednostkach układu współrzędnych warstwy (zazwyczaj są to metry kwadratowe) w układzie planarnym (kartezjańskim). Opcjonalnie, możemy dokonać konwersji jednostek, np. na kilometry kwadratowe czy hektary. W niniejszym przykładzie wszystkie obiekty w warstwie posiadają geometrie.
for feature in islice(vector.getFeatures(), 5):
= feature.geometry().area()
area = area / 1000**2 # konwersja na km^2
area print(area)
712.7437283077061
1806.6288683868954
1009.273272845488
679.340479768619
573.5639814826102
Dodawanie atrybutów do warstwy wektorowej obejmuje modyfikację tabeli atrybutów poprzez dodanie nowych atrybutów i opcjonalnie ich wypełnienie. Operacja składa się z kilku kroków:
startEditing()
, aby ją zmodyfikować.QgsField
. Każdy atrybut posiada nazwę, typ danych i opcjonalne ograniczenia (długość, precyzja). Zdefiniowanie kilku atrybutów wymaga przekazanie ich jako listy. Następnie należy użyć metod addAttributes()
oraz updateFields()
, aby dodać nowe pole do tabeli atrybutów i ją zaktualizować.changeAttributeValue()
, w której kolejno określimy ID obiektu, indeks atrybutu oraz wartość.commitChanges()
.Uwaga odnośnie typów danych atrybutów w QGIS! Od wersji 3.38 typy danych zdefiniowane są w klasie QMetaType
(np. QMetaType.Type.Double
, QMetaType.Type.QString
). Wcześniej była to klasa QVariant
(np. QVariant.Double
, QVariant.String
).
from qgis.core import QgsField
from qgis.PyQt.QtCore import QMetaType
# (1) rozpoczęcie trybu edycji warstwy
vector.startEditing()
# (2) dodanie nowego atrybutu do tabeli
= [QgsField("pole_km2", QMetaType.Type.Double)]
new_field
vector.dataProvider().addAttributes(new_field)
vector.updateFields()
# indeks nowego atrybutu
= vector.fields().indexOf("pole_km2")
area_idx
# (3) obliczenie powierzchni dla każdego obiektu
for feature in vector.getFeatures():
= feature.geometry().area()
area = area / 1000**2
area # wprowadzenie wartości do atrybutu obiektu
id(), area_idx, area)
vector.changeAttributeValue(feature.
# (4) zapisanie zmian
vector.commitChanges()
True
Sprawdźmy teraz czy wykonany kod zadziałał prawidłowo.
print("pole_km2" in vector.fields().names())
for feature in islice(vector.getFeatures(), 5):
print(feature.attribute("pole_km2"))
True
712.7437283077061
1806.6288683868954
1009.273272845488
679.340479768619
573.5639814826102
Obiekty z warstwy wektorowej można wybrać (wyselekcjonować) na podstawie różnych kryteriów, takich jak wartości atrybutów czy położenie przestrzenne.
Zasadniczo, istnieją dwa sposoby wybierania obiektów z uwzględnieniem wartości atrybutów:
selectByExpression()
z klasy QgsVectorLayer
.setFilterExpression()
z klasy QgsFeatureRequest
.Pierwszy sposób selectByExpression()
to metoda służąca do wybierania obiektów w warstwie na podstawie określonego wyrażenia. Co najważniejsze, wybór odbywa się z uwzględnieniem wszystkich obiektów i jest tymczasowy.
= '"pole_km2" > 1300'
expression
vector.selectByExpression(expression)= vector.selectedFeatures()
selected_features print(len(selected_features)) # liczba obiektów
# for feature in selected_features:
# print(feature.attributes())
4
Jeśli kod wykonaliśmy w QGIS, to wybrane obiekty możemy podświetlić na mapie w następujący sposób:
from qgis.core import QgsProject
QgsProject.instance().addMapLayer(vector)id() for feature in selected_features]) vector.selectByIds([feature.
Natomiast drugi sposób, setFilterExpression()
stosuje filtr przy wczytywaniu danych (getFeatures()
). Obiekty, które nie spełniają kryteriów, nie są dodawane do sesji. Zwiększa to wydajność przetwarzania poprzez zmniejszenie ilości wczytywanych danych, co jest szczególnie przydatne w przypadku dużych zbiorów.
from qgis.core import QgsFeatureRequest
= '"pole_km2" > 1300'
expression # stworzenie żądania używając wyrażenia
= QgsFeatureRequest().setFilterExpression(expression)
request
# for feature in vector.getFeatures(request):
# print(feature.attributes())
Metoda selectByRect()
umożliwia dokonanie prostej selekcji obiektów używając zakresu przestrzennego (bounding box) zdefiniowanego przez prostokąt (QgsRectangle
) i cztery współrzędne (xmin
, ymin
, xmax
, ymax
). Bardziej zaawansowane zapytania przestrzenne pozwalają na wybór obiektów na podstawie ich relacji przestrzennej z innymi geometriami (np. wybór obiektów w obrębie poligonu, przecięcie linii).
from qgis.core import QgsRectangle
# wybranie obiektów używając prostokąta
= QgsRectangle(340000, 480000, 380000, 520000) # xmin, ymin, xmax, ymax
rect
vector.selectByRect(rect)= vector.selectedFeatures()
selected_features print(len(selected_features)) # liczba obiektów
# for feature in selected_features:
# print(feature.attributes())
6
Do tworzenia nowej warstwy wektorowej stosuje się tę samą klasę, co do wczytywania, czyli QgsVectorLayer
, z tą różnicą iż określa się:
Point
), linie (LineString
), poligony (Polygon
) i multigeometrie (Multi*
). Do zdefiniowania układu odniesienia można wykorzystać kod EPSG, np. EPSG:4326
. Format zapisu jest następujący: Point?crs=EPSG:4326
.memory
).# typ geometrii i układ współrzędnych
= "Polygon"
geometry_type = "EPSG:2180"
crs
# stworzenie nowej warstwy wektorowej
= QgsVectorLayer(geometry_type+"?crs="+crs, "Nowa warstwa", "memory")
newlayer print(newlayer.isValid())
True
Następnie możemy zdefiniować i dodać do warstwy atrybuty, tak jak zaprezentowano to w sekcji “Dodawanie atrybutów”. Należy pamiętać o włączonym trybie edycji warstwy, a na końcu o zatwierdzeniu zmian.
# rozpoczęcie trybu edycji warstwy
newlayer.startEditing()
# stworzenie atrybutów
= [
fields "ID", QMetaType.Type.Int), # ID obiektu
QgsField("nazwa", QMetaType.Type.QString), # nazwa obiektu
QgsField("wartosc", QMetaType.Type.Double) # wartość
QgsField(
]
# dodanie atrybutów do warstwy
= newlayer.dataProvider()
provider
provider.addAttributes(fields) newlayer.updateFields()
W kolejnym kroku możemy stworzyć obiekty (klasa QgsFeature), które składają się z geometrii oraz atrybutów. W poniższym przykładzie użyjemy wcześniej zdefiniowanego prostokąta rect
– najpierw zmienimy jego geometrię na reprezentację WKT (metoda asWkt()
), a następnie stworzymy z niego poligon (metoda fromWkt()
).
Innym sposobem konstruowania geometrii obiektów używając współrzędnych jest zastosowanie klasy QgsPointXY i odpowiednich metod, np. fromPointXY()
(do stworzenia punktów), fromPolylineXY()
(do stworzenia linii) czy fromPolygonXY()
(do stworzenia poligonów).
from qgis.core import QgsFeature, QgsGeometry
# stworzenie obiektów
= QgsFeature()
feature = QgsGeometry.fromRect(rect).asWkt()
polygon_wkt = QgsGeometry.fromWkt(polygon_wkt) polygon_geometry
Finalnie, należy nowemu obiektowi należy nadać geometrię (metoda setGeometry()
) oraz przypisać atrybutom wartości (metoda setAttributes()
), po czym wymagane jest dodanie obiektów do warstwy, aktualizacja zasięgu warstwy oraz zapisanie zmian.
# nadanie geometrii oraz wartości atrybutom
feature.setGeometry(polygon_geometry)1, "Prostokąt", 999])
feature.setAttributes([
# dodanie obiektów do warstwy
newlayer.addFeature(feature)# aktualizacja zasięgu warstwy
newlayer.updateExtents()# zapisanie zmian
newlayer.commitChanges()
True
Dokonajmy jeszcze weryfikacji stworzonej warstwy.
for feature in newlayer.getFeatures():
print(feature.attributes())
print(feature.geometry())
[1, 'Prostokąt', 999.0]
<QgsGeometry: Polygon ((340000 480000, 380000 480000, 380000 520000, 340000 520000, 340000 480000))>
# wyświetlenie w QGIS
# QgsProject.instance().addMapLayer(newlayer)
Powiaty reprezentowane jako osobne geometrie możemy połączyć w jeden obiekt reprezentujący całe województwo. W tym celu można wykorzystać metodę combine
z klasy QgsGeometry
pod warunkiem, iż wszystkie granice są styczne. Na początku musimy zainicjować pustą geometrię, a następnie łączymy ją z kolejnymi w pętli. Wynikiem operacji jest nowa geometria będąca połączeniem wszystkich geometrii.
# stworzenie nowej pustej geometrii
= None
geometry
for feature in vector.getFeatures():
= feature.geometry()
geom if geometry is None: # pierwsza geometria jako początkowa
= QgsGeometry(geom)
geometry else: # łączy kolejne geometrie
= geometry.combine(geom)
geometry
= QgsFeature()
combined combined.setGeometry(geometry)
Stworzenie warstwy z buforami wymaga zainicjowania nowej warstwy poligonowej w pamięci. Następnie iterujemy po wszystkich obiektach i tworzymy bufory używając metody buffer
, w której trzeba określić odległość bufora (wartość może być ujemna w przypadku poligonów) oraz liczbę segmentów. Po wygenerowaniu wszystkich obiektów, należy dodać je do warstwy.
# stworzenie pustej warstwy w pamięci
= QgsVectorLayer("Polygon?crs=" + vector.crs().authid(), "Bufor", "memory")
buffer_layer
# odległość buforu w jednostkach warstwy (m)
= 10000.0 # 10 km
buffer_distance
= []
features for feature in [combined]: # traktujemy jeden obiekt jako listę
= feature.geometry()
geom # stworzenie geometrii bufora
= geom.buffer(buffer_distance, segments = 30)
buffer_geom = QgsFeature()
buffer_feature
buffer_feature.setGeometry(buffer_geom)
features.append(buffer_feature)
# dodanie buforów do warstwy wektorowej
buffer_layer.dataProvider().addFeatures(features)
buffer_layer.updateExtents()
# wyświetlenie w QGIS
# QgsProject.instance().addMapLayer(buffer_layer)
Istnieje kilka różnych sposobów reprojekcji warstwy wektorowej:
transform
z klasy QgsCoordinateTransform.QgsVectorFileWriter
.Pierwsza metoda polega na transformacji wszystkich obiektów w pętli. Należy zwrócić szczególną uwagę na fakt, iż ta operacja modyfikuje bezpośrednio geometrie (można nieświadomie nadpisać oryginalne dane)! Kluczowym elementem jest zainicjowanie klasy QgsCoordinateTransform
z trzema argumentami:
from qgis.core import QgsProject, QgsCoordinateTransform, QgsCoordinateReferenceSystem
= vector.crs()
source_crs = QgsCoordinateReferenceSystem("EPSG:4326")
target_crs = QgsCoordinateTransform(source_crs, target_crs, QgsProject.instance())
transform
= QgsVectorLayer("Polygon?crs=" + target_crs.authid(), "powiaty_4326", "memory")
newlayer
newlayer.dataProvider().addAttributes(vector.fields())
newlayer.updateFields()
= []
features for feature in vector.getFeatures():
= feature
new_feature = feature.geometry()
geom
geom.transform(transform)
new_feature.setGeometry(geom)
features.append(new_feature)
newlayer.dataProvider().addFeatures(features)print(newlayer.crs().authid())
# wyświetlenie w QGIS
# QgsProject.instance().addMapLayer(newlayer)
EPSG:4326
Druga metoda jest zdecydowanie łatwiejsza, ponieważ wymaga wyłącznie zdefiniowania wymaganych argumentów przez klasę QgsCoordinateTransform
i zapisanie nowej warstwy.
from qgis.core import QgsVectorFileWriter
= vector.crs()
source_crs = QgsCoordinateReferenceSystem("EPSG:4326")
target_crs = QgsProject.instance().transformContext()
context
= "powiaty_4326.gpkg"
output_path = QgsVectorFileWriter.SaveVectorOptions()
options = "GPKG"
options.driverName = QgsCoordinateTransform(source_crs, target_crs, context)
options.ct
= QgsVectorFileWriter.writeAsVectorFormatV3(
writer = vector,
layer = output_path,
fileName = context,
transformContext = options
options
)
if writer[0] != 0:
print("Błąd zapisu")
else:
print("OK")
.gpkg
.