Autor: kontakt@czasnagis.org

  • QGIS i automatyzacja tworzenia mapy do służebności przesyłu

    Dziś zajmiemy się demonstracją automatyzacji działań z poprzedniego wpisu pt.QGIS i tworzenie mapy do slużebności przesyłu.

    Ściągnijmy najpierw paczkę danych przygotowaną w ramach poprzedniego wpisu. W załączeniu projekt z danymi (spakowany 7zip)

    Otwieramy projekt:

    Mamy tu 3 warstwy, najpierw w konsoli Pythona wyszukajmy te wartstwy i przepiszmy do zmiennych. Skoro mamy tylko 3 warstwy i nie musimy sie upewniać że nie istnieje więcej niż 1 warstwa o tej samej nazwie użyjemy prostego :

    https://qgis.org/pyqgis/3.44/core/QgsProject.html#qgis.core.QgsProject.mapLayersByName

    woda_lyr = QgsProject.instance().mapLayersByName('woda')[0]
    kanal_lyr = QgsProject.instance().mapLayersByName('kanal')[0]
    dzialki_lyr = QgsProject.instance().mapLayersByName('dzialki_temp')[0]
    print(woda_lyr)
    print(kanal_lyr)
    print(dzialki_lyr)
    
    output:
    <QgsVectorLayer: 'woda' (ogr)>
    <QgsVectorLayer: 'kanal' (ogr)>
    <QgsVectorLayer: 'dzialki_temp' (ogr)>
    

    Dlaczego [0] bo mapLayersByName zwraca listę warstw spełniających kryteria.

    Stwórzmy także 2 warstwy tymczasowe wyjściowe wynikowy_poligon_lyr (poligonową) i wynikowy_linia_lyr (liniową). Po specyfikację odsyłam do https://qgis.org/pyqgis/3.44/core/QgsVectorLayer.html#module-QgsVectorLayer. My stworzymy 2 warstwy jedną (poligonową) z samym polem id, drugą (liniową) z polem dn (średnica). Na końcu pobieramy instancje projektu i dodajemy stworzone warstwy do listy warstw.

    uri_poligon_lyr = "polygon?crs=epsg:2178&field=id:integer"
    wynikowy_poligon_lyr = QgsVectorLayer(uri_poligon_lyr, "wynikowy_poligon",  "memory")
    
    uri_linia_lyr = "linestring?crs=epsg:2178&field=id:integer&field=dn:string"
    wynikowy_linia_lyr = QgsVectorLayer(uri_linia_lyr, "wynikowy_linia",  "memory")
    
    QgsProject.instance().addMapLayer(wynikowy_poligon_lyr)
    QgsProject.instance().addMapLayer(wynikowy_linia_lyr)
    

    Zaznaczymy teraz ręcznie obiekty na warstwach, wszystkie odcinki kanałowe, wodociągowe i wybraną działkę.

    Nasz skrypt będzie pobierał zaznaczone obiekty z danej warstwy iterował je i przetwarzał. Dodajmy jeszcze zmienną aggr_buf, która w przyszłości będzie przechowywała zagregowany bufor.

    aggr_buf = None
    

    Użyjemy do tego prostej funkcji selectedFeatures()tworzącej listę zaznaczonych obiektów. : https://qgis.org/pyqgis/3.44/core/QgsVectorLayer.html#qgis.core.QgsVectorLayer.selectedFeatures

    for woda_lyr_feats in woda_lyr.selectedFeatures():
        print(woda_lyr_feats['id'])
    for kanal_lyr_feats in kanal_lyr.selectedFeatures():
        print(kanal_lyr_feats['id'])
    
    output:
    1
    2
    3
    4
    5
    1
    2
    3
    4
    5
    

    Potem utworzymy bufor wokół każdego obiektu, plus zagregujemy wszystko w jednej geometrii. Ale najpierw na podstawie średnicy obliczmy szerokość bufora. Przydałaby się jakaś kontrola błędów. Dodajmy w pętli „for” zamiast linii print(…

    try:
        dn = int(woda_lyr_feats['DN'])
    except:
        dn = 0
    zakres_m = 1+dn/2000
    

    To wyeliminuje wartości nieprzetwarzalne na liczbę całkowitą takie jak NULL, tekstowe. Ogólnie wszystko czego nie znamy uznajemy ze ma średnice 0.Teraz utworzymy bufor wokół obiektu. Odsyłam do specyfikacji : https://qgis.org/pyqgis/3.44/core/QgsGeometry.html#qgis.core.QgsGeometry.buffer, i dodamy to do geometrii aggr_buf funkcją combine https://qgis.org/pyqgis/3.44/core/QgsGeometry.html#qgis.core.QgsGeometry.combine.

    bufor_temp = woda_lyr_feats.geometry().buffer(zakres_m,5)
    if aggr_buf is None:
        aggr_buf = bufor_temp
    else:
        aggr_buf = aggr_buf.combine(bufor_temp)
    

    tak samo robimy dla kanal_lyr

    try:
        dn = int(kanal_lyr_feats['DN'])
    except:
        dn = 0
    zakres_m = 1+dn/2000
    bufor_temp = kanal_lyr_feats.geometry().buffer(zakres_m,5)
    if aggr_buf is None:
        aggr_buf = bufor_temp
    else:
        aggr_buf = aggr_buf.combine(bufor_temp)
    

    Teraz bierzemy wszystkie zaznaczone geometrie z dzialki_temp, iterując wycinamy geometrię działki, zagregowaną wcześniej geometrią. Patrz: https://qgis.org/pyqgis/3.44/core/QgsGeometry.html#qgis.core.QgsGeometry.intersection, i jeśli wyjdzie nam geometria niepusta to …

    if aggr_buf is not None:
        for dzialki_lyr_feats in dzialki_lyr.selectedFeatures():
            aggr_buf_dzialka = aggr_buf.intersection(dzialki_lyr_feats.geometry())
            if not aggr_buf_dzialka.isEmpty():
    

    … dodajemy to do naszej tymczasowej warstwy poligonowej. Uwaga, nie dzielę poligonów, nie sprawdzam czy nie ma szczątkowych poligonów, a czasem nawet punkty mogą powstać. To wymaga osobnego potraktowania, ale nie chce gmatwać kodu. Features dodajemy przez dataProvider-a. Wyjaśnienie poniżej.

                feature = QgsFeature(wynikowy_poligon_lyr.fields())
                feature.setGeometry(aggr_buf_dzialka)
                feature["id"] = wynikowy_poligon_lyr.featureCount() + 1
                wynikowy_poligon_lyr.dataProvider().addFeatures([feature])
    

    No i po uruchomieniu kodu mamy:

    Pozostaje nam wyciąć wszystkie domiary. Narzędzia i działanie takie samo jak w przypadku buforów, wycinamy linie przy pomocy geometrii zaznaczonych działek, poniżej kod:

        for kanal_lyr_feats in kanal_lyr.selectedFeatures():
            domiar_wyciety = kanal_lyr_feats.geometry().intersection(dzialki_lyr_feats.geometry())
            if not domiar_wyciety.isEmpty():
                feature = QgsFeature(wynikowy_linia_lyr.fields())
                feature.setGeometry(domiar_wyciety)
                feature["id"] = wynikowy_linia_lyr.featureCount() + 1
                try:
                    dn = int(kanal_lyr_feats['dn'])
                except:
                    dn = 0
                feature["dn"] = dn
                wynikowy_linia_lyr.dataProvider().addFeatures([feature])
        for woda_lyr_feats in woda_lyr.selectedFeatures():
            domiar_wyciety = woda_lyr_feats.geometry().intersection(dzialki_lyr_feats.geometry())
            if not domiar_wyciety.isEmpty():
                feature = QgsFeature(wynikowy_linia_lyr.fields())
                feature.setGeometry(domiar_wyciety)
                feature["id"] = wynikowy_linia_lyr.featureCount() + 1
                try:
                    dn = int(woda_lyr_feats['dn'])
                except:
                    dn = 0
                feature["dn"] = dn
                wynikowy_linia_lyr.dataProvider().addFeatures([feature])
            
    

    Wynikowo mamy:

    Obiekty mają przepisane dn – średnice (wynik informacji na screenie powyżej). A poniżej cały kod…

    woda_lyr = QgsProject.instance().mapLayersByName('woda')[0]
    kanal_lyr = QgsProject.instance().mapLayersByName('kanal')[0]
    dzialki_lyr = QgsProject.instance().mapLayersByName('dzialki_temp')[0]
    
    uri_poligon_lyr = "polygon?crs=epsg:2178&amp;field=id:integer"
    wynikowy_poligon_lyr = QgsVectorLayer(uri_poligon_lyr, "wynikowy_poligon",  "memory")
    
    uri_linia_lyr = "linestring?crs=epsg:2178&amp;field=id:integer&amp;field=dn:string"
    wynikowy_linia_lyr = QgsVectorLayer(uri_linia_lyr, "wynikowy_linia",  "memory")
    
    QgsProject.instance().addMapLayer(wynikowy_poligon_lyr)
    QgsProject.instance().addMapLayer(wynikowy_linia_lyr)
    
    aggr_buf = None
    
    for woda_lyr_feats in woda_lyr.selectedFeatures():
        try:
            dn = int(woda_lyr_feats['dn'])
        except:
            dn = 0
        zakres_m = 1+dn/2000
        bufor_temp = woda_lyr_feats.geometry().buffer(zakres_m,5)
        if aggr_buf is None:
            aggr_buf = bufor_temp
        else:
            aggr_buf = aggr_buf.combine(bufor_temp)
            
    for kanal_lyr_feats in kanal_lyr.selectedFeatures():
        try:
            dn = int(kanal_lyr_feats['dn'])
        except:
            dn = 0
        zakres_m = 1+dn/2000
        bufor_temp = kanal_lyr_feats.geometry().buffer(zakres_m,5)
        if aggr_buf is None:
            aggr_buf = bufor_temp
        else:
            aggr_buf = aggr_buf.combine(bufor_temp)
    aggr_buf_dzialka = None
    if aggr_buf is not None:
        for dzialki_lyr_feats in dzialki_lyr.selectedFeatures():
            aggr_buf_dzialka = aggr_buf.intersection(dzialki_lyr_feats.geometry())
            if not aggr_buf_dzialka.isEmpty():
                feature = QgsFeature(wynikowy_poligon_lyr.fields())
                feature.setGeometry(aggr_buf_dzialka)
                feature["id"] = wynikowy_poligon_lyr.featureCount() + 1
                wynikowy_poligon_lyr.dataProvider().addFeatures([feature])
    
    for dzialki_lyr_feats in dzialki_lyr.selectedFeatures():
        for kanal_lyr_feats in kanal_lyr.selectedFeatures():
            domiar_wyciety = kanal_lyr_feats.geometry().intersection(dzialki_lyr_feats.geometry())
            if not domiar_wyciety.isEmpty():
                feature = QgsFeature(wynikowy_linia_lyr.fields())
                feature.setGeometry(domiar_wyciety)
                feature["id"] = wynikowy_linia_lyr.featureCount() + 1
                try:
                    dn = int(kanal_lyr_feats['dn'])
                except:
                    dn = 0
                feature["dn"] = dn
                wynikowy_linia_lyr.dataProvider().addFeatures([feature])
        for woda_lyr_feats in woda_lyr.selectedFeatures():
            domiar_wyciety = woda_lyr_feats.geometry().intersection(dzialki_lyr_feats.geometry())
            if not domiar_wyciety.isEmpty():
                feature = QgsFeature(wynikowy_linia_lyr.fields())
                feature.setGeometry(domiar_wyciety)
                feature["id"] = wynikowy_linia_lyr.featureCount() + 1
                try:
                    dn = int(woda_lyr_feats['dn'])
                except:
                    dn = 0
                feature["dn"] = dn
                wynikowy_linia_lyr.dataProvider().addFeatures([feature])
            
    

    Na dodatkowe wyjaśnienie zasługuje …dataProvider(), QgsVectorLayer dziedziczy tą metodę z QgsMapLayer (https://qgis.org/pyqgis/3.44/core/QgsMapLayer.html#qgis.core.QgsMapLayer.dataProvider). Zwraca ona klasę Providera danej usługi (https://qgis.org/pyqgis/3.44/core/QgsDataProvider.html#qgis.core.QgsDataProvider), i już z tej klasy wywołujemy addFeatures(list). Da się to zrobić też bezpośrednio z poziomu QgsVectorLayer, ale trzeba by dodać startEditing() a potem zakończyć commit lub rollbaclk (patrz trzeci akapit specyfikacji https://qgis.org/pyqgis/3.44/core/QgsVectorLayer.html ). Podsumowując Ja wybrałem takie rozwiązanie, ale są też inne…

    Czego nie zrobiliśmy to kompletna kontrola błędów. W trakcie generowania i wycinania może powstawać wiele rodzajów geometrii, niekoniecznie potrzebnych. Do automatyki przydałoby się wyrzucenie szczątkowych poligonów, geometrii punktowych i innych błędnych geometrii (artefaktów niedoskonałości obliczeń wektorowych).

    Jak zawsze zapraszam do dyskusji i kontaktu…

  • QGIS i tworzenie mapy do slużebności przesyłu

    Dzisiaj trochę inny model podejścia do problemu. Jak już pisałem w poprzednich wpisach Polska nie publikuje w formie wektorowej obiektów, którymi chciałem się dzisiaj zająć (czyli uzbrojenia terenu). Stworzyłem więc swój własny obszar prac. Działki zostały wycięte w losowym miejscu mapy udostępnianym w ramach https://epodgik.pl/, natomiast sieć wodociągową i kanalizacyjną „wymyśliłem” sam na potrzeby przedstawienia rozwiązania problemu. Chciałbym zaznaczyć, że dane do „prawdziwych” służebności przesyłu powinny wywodzić się z danych urzędowych, a niniejszy przykład przedstawia demonstracyjne rozwiązanie problemu.

    W załączeniu plik z danymi do dzisiejszej zabawy : projekt z danymi (spakowany 7zip) (fix: dodałem brakującą warstwę do pliku)

    Mamy tu 3 warstwy z czego dzialki_temp zawiera pola ID i NR_DZIALKI, a warstwy woda i kanal zawieraja ID i DN (srednice sieci).

    Do stworzenia załącznika do służebności przesyłu potrzebujemy

    • pas i jego powierzchnie
    • długości i średnice sieci objętych

    Zajmijmy się pasem, dla przykładowej działki :

    Zaznaczamy wszystkie obiekty na warstwie woda i kanał które dotykają działki. Z mojego doświadczenia lepiej zaznaczyć więcej sieci żeby nie wynikły problemy, że nagle pas na granicy działki zmniejsza się(powstaje łuk), chociaż wiemy że sieć ta biegnie dalej i pas powinien być równo oddalony po osi aż do granicy.

    Dla przykładu zaznaczymy przez lokalizację wszystko w warstwie kanal co przecina dzialki_temp (tylko zaznaczone) – 5 features selected, tak samo robimy dla warstwy woda, także 5 features selected, czyli mamy po 5 obiektów wodnych i kanalizacyjnych.

    Stwórzmy teraz pasy wokół zaznaczonych sieci. Załóżmy że pas ma mieć metr + połowę średnicy sieci od osi przewodu (jak w niektórych urzędach się przyjmuje). Użyjemy do tego narzędzia Bufor

    Wyjaśnijmy parametry po kolei.

    • najpierw warstwa wejściowa, tutaj kanał,
    • potem powinniśmy zaznaczyć „Tylko zaznaczone obiekty”, tutaj mamy dokładnie wszystkie obiekty zaznaczone wiec nie robi to różnicy.
    • odległość, tu możemy podać jedną stałą odległość bufora, my użyjemy bardziej zaawansowanego podejscia i klikniemy w pole Edytuj…

    i wpisujemy :

    Już wyjaśniam dlaczego tak robimy. Możemy wymusić aby dla każdego obiektu dla którego będzie powstawał bufor, była to zmienna a nie stała. Tutaj jest to:

    dla przykładu DN = 100:

    • Potrzebujemy średnice wyrażoną w metrach zatem =100mm / 1000 = 0.1m
    • połowa średnicy czyli (/ 2) = 0.05m
    • dodajmy metr od krawędzi przewodu czyli 0.05m + 1 m
    • wynikowo dla DN=100 bufor będzie wynosił 1.05m

    Klikamy Ok

    Wróćmy do Bufora

    • Pole Odcinki określa stopień zaokrąglenia bufora na końcach(zostawmy 5).
    • Styl zakończenia „zaokrąglony” – chcemy mieć równo odsunięty bufor na końcach
    • Styl połączenia „zaokrąglony” – chcemy mieć równo odsunięty bufor na łączeniach
    • Limit fazy (uciosu) – nie mamy połączeń ostrych więc nas nie interesuje
    • Zaznaczamy Agreguj wyniki, spowoduje to powstanie jednego połączonego obiektu.

    Wybieramy Uruchom

    Otrzymaliśmy 1 tymczasowy bufor dla kanału.

    Tak samo skonfigurujemy to dla wody i otrzymujemy:

    Łączymy teraz warstwy, albo narzędziem albo kopiując obiekty z jednej warstwy do drugiej. Ja użyję złączenia:

    Klikamy … i zaznaczamy warstwy Bufor i Bufor, Klikamy Uruchom, Powstała nam warstwa Złączone którą przenosimy na samą górę, odznaczamy też niepotrzebne warstwy Bufor.

    używamy teraz Narzędzia Agreguj

    Da nam to warstwę zagregowaną

    Pozostaje tylko przyciąć do granic działki, narzędzie Przytnij:

    Wybieramy kolejno:

    • Warstwa wejsciowa : Zgrupowane obiekty
    • Warstwa nakładki : dzialki_temp
    • Zaznaczamy „Tylko zaznaczone obiekty” – bo chcemy tylko dla zaznaczonej działki wycięcie
    • I klikamy Uruchom…

    Po odznaczeniu niepotrzebnych warstw mamy warstwę Przycięte – nasz pas dla działki.

    Teraz spróbujmy przyciąć sieci:

    Przechodzimy na warstwę woda, wybieramy znowu Przytnij i tym razem:

    • Warstwa wejsciowa : woda
    • Zaznaczamy : Tylko zaznaczone obiekty
    • Warstwa nakładki: dzialki_temp
    • Zaznaczamy „Tylko zaznaczone obiekty” – bo chcemy tylko dla zaznaczonej działki wycięcie
    • I znowu Uruchom…

    Tak samo robimy dla warstwy kanal

    Odznaczamy warstwę woda i kanał, Odznaczamy też wszystkie obiekty zaznaczone. Mamy w projekcie 3 warstwy Przycięte, warstwę pasa i 2 warstwy domiarów odrębnie dla kanału i wody

    Pozostaje nam skonfigurować wyglad i etykiety i wynikowo otrzymamy:

    Czego się dzisiaj nauczyliśmy, co zrobiliśmy:

    • zaznaczyliśmy po lokalizacji warstwy liniowe
    • stworzyliśmy i zagregowaliśmy bufory poszczególnych warstw liniowych
    • docięliśmy bufory do wymaganego zakresu
    • wynikowo powstał zagregowany pas służebności ze zmienną długością pasa dla 2 odrębnych warstw.

    W przyszłych wpisach pokażę jak zautomatyzować to za pomocą Pythona.

    Jak zawsze zapraszam do dyskusji…

  • 3. Konfiguracja warstw QGIS

    Zacznijmy od otwarcia projektu z poprzedniego wpisu 2. Definiowanie warstw PostGIS / PostgreSQL.

    Jak w poprzednim wpisie, przejdźmy teraz do kącika nauki.

    Istnieją dwa podstawowe sposoby publikowania danych online przez serwery GIS:

    1. Web Map Service (WMS) – dane udostępniane są w postaci gotowych obrazów map (np. JPG, TIFF).
    2. Web Feature Service (WFS) – dane publikowane są jako obiekty wektorowe zawierające geometrię oraz, opcjonalnie, atrybuty opisowe.

    W Polsce powszechnie dostępne są usługi typu WMS. Niestety, praktycznie nie udostępnia się danych w formacie WFS – nawet w postaci samych geometrii.

    Wyjątek stanowią nieliczne warstwy, takie jak Dzielnice, Działki, Budynki, Adresy czy Cieki. My dodaliśmy do naszych zasobów dwie warstwy WFS: Dzielnice administracyjne Krakowa oraz Granice miasta.

    Skonfigurujmy sobie jakoś nasz projekt żeby wyglądał „po ludzku”. Wyłączamy warstwy test_* poprzez odznaczenie ich w legendzie. Potem dwukrotnym kliknięciem w legendzie na warstwę „Obserwatorium_Dzielnice:Dzielnice_administracyjne” otwieramy właściwości warstwy:

    Klasyczne właściwości warstwy wektorowej wyglądają jak okienko powyżej

    • Informacje zawierają dane o źródle warstwy, dostawcy, zasięgu i układzie współrzędnych warstwy (niekoniecznie tym samym co projekt)
    • Źródło zawiera szczegółowe dane źródła, można tam nadpisać podstawowy układ współrzędnych warstwy, aktualizować zasięgi, i utworzyć indeks przestrzenny – to zalecam przy każdym nowym pliku typu shp dodanym do projektu.
    • Styl – chyba najważniejszy element mapy. Tutaj nadajemy style wyświetlania obiektom warstwy (wygląd punktów, linii i poligonów)

    Nas głównie interesują style

    • Bez symbolu, czyli nie rysuj obiektu (nie przeszkadza to w renderowaniu tekstu na obiekcie – przykład poniżej), tego można użyć jeśli np potrzebujemy same opisy:
    • Pojedynczy symbol – kiedy mamy jedną spójną definicję wyglądu obiektu
    • Wartość unikalna – kiedy mamy pole na podstawie którego chcemy pogrupować obiekty i dla danej grupy wyświetlić styl, tutaj wybieramy kolejno:
      • Wartość która będzie używana do zdefiniowania unikalnych symboli
      • Następnie Symbol podstawowy
      • Klikamy klasyfikuj (wszystkie symbole powstaną jako wariacje kolorów symbolu podstawowego)
      • Oczywiście każdy symbol można zmodyfikować poprzez otwarcie właściwości („dwuklik” na kolorze symbolu)
      • Nie zapomnijmy o zastosowaniu na koniec edycji
    • Symbol Stopniowy – Jeśli posiadamy dane liczbowe możemy zastosować symbolikę stopniową, tutaj mamy pole Powierzchnia
    • Symbol oparty na regułach – Ciekawa opcja dająca możliwość definicji wielu symboli naraz i filtrowania obiektów (ten temat jest tak obszerny ,że wymaga dodatkowego wpisu i zostanie ujęty we wpisie dotyczącym tworzenia Atlasu w wydrukach)

    Ostatnim dzisiaj poruszanym tematem są Etykiety (temat naprawdę obszerny, wiele opcji i rozwiązań, zalecam testować) Mamy do wyboru :

    • Brak etykiet – chyba nie muszę tego wyjaśniać
    • Proste Etykiety – to już było wyżej zaprezentowane przy Stylach
    • Etykietowanie oparte na regułach – ten rodzaj etykietowania zasługuje na szczególną uwagę, albowiem w QGIS na jednej warstwie przy prostym etykietowaniu można zdefiniować tylko jeden rodzaj etykiety. Etykietowanie oparte na regułach rozszerza tą możliwość. Często wykorzystuje się ten rodzaj etykietowania np. przy sieciach, gdzie potrzebujemy wyświetlić więcej danych na 1 obiekcie, a nie chcemy duplikować warstw. Poniżej przykład 3 rodzajów etykiet na 1 obiekcie:

    PS. Na szczególną uwagę zasługuje tam zakładka Położenie->Kotwiczenie Etykiety we właściwościach etykiet Liniowych, dzięki której na warstwach liniowych możemy dokładnie zdefiniować, gdzie QGIS będzie starał się daną etykietę umieścić. Właśnie dzięki kotwiczeniu uzyskaliśmy taki wynik.

    Poniżej przykład konfiguracji naszych warstw z Projektu.

    I na tych 4 podstawowych zakładkach Właściwości warstwy zakończmy dzisiejszy wpis. Pozdrawiam i zapraszam do dyskusji.

  • 2. Definiowanie warstw PostGIS / PostgreSQL

    Dzisiaj spróbujemy zdefiniować warstwę PostGIS w PostgreSQL

    Można to zrobić na kilka sposobów:

    • z poziomu pgAdmina (od zera)
    • z poziomu QGIS (od zera)
    • poprzez import danych w QGIS

    Dzisiaj będziemy to robić z poziomu pgAdmina, w następnym wpisie pokaże jak stworzyć lub zaimportować warstwy w QGIS.

    Czym jest układ współrzędnych (układ odniesienia)?

    Każda warstwa dodawana do bazy danych powinna mieć zdefiniowany układ współrzędnych. Jest to tzw układ odniesienia w terenie. Bez tego nie zlokalizujemy się w terenie, nie przeprowadzimy pomiarów i nie porównamy danych.

    Układy odniesienia

    W skrócie… ziemia nie jest kulą, a raczej mniej lub bardziej dokładną elipsoidą (geoidą) (https://pl.wikipedia.org/wiki/Geoida). Z uwagi na fakt że geoida jest pofałdowana i nierówna i odbiega od ideału elipsoidy, nie da się wyprowadzić idealnego wzoru na wyliczenie lokalizacji punktu na geoidzie.

    Wyróżniamy układy współrzednych:

    • Geographic Coordinate Systems (GCS)
      • opisują położenie punktów na elipsoidzie(ziemi)
      • do nich zaliczamy min. WGS84 (EPSG:4326) – używany w GPS
      • współrzędne podawane są w stopniach, długości i szerokości geograficznej
      • w zależności od szerokości geograficznej występują różnice w realnych metrach (nie jest to system do pomiarów)
    • Projected Coordinate Systems (PCS)
      • powstają z rzutowania powierzchni ziemi na płaską mapę.
      • do nich zaliczamy min. Polskie układy współrzędnych :
        • PUWG 1992 (EPSG:2180) – dla całej Polski
        • PUWG 2000 (EPSG:2176-2179) – podział na 4 strefy w celu lepszego odwzorowania punktu na mapie
      • współrzędne podawane są w metrach (szczegółowe pomiary)

    Praca autorstwa Radvid – Praca własna, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=3769616

    Dla Krakowa i okolic wybieramy (EPSG:2178), dla całej Polski (EPSG:2180). Część danych państwowych jest dostarczana w EPSG:2180, lokalne dane Krakowskie są w większości dostarczane w EPSG:2178.

    Pomijając wszystko co napisałem powyżej QGIS sam przeprowadza reprojekcje danych do układu Projektu (u mnie standardowo zdefinowanego w Ustawieniach na EPSG:2178), ale wydajniej jest robić wszystko w 1 układzie.

    Definiowanie warstw z poziomu pgAdmina, psql (tekstowy client sql)

    Jak już mamy wybrany układ współrzędnych dla naszych warstw testowych (2180), to spróbujmy teraz zdefiniować 3 warstwy(punktową , liniową i poligonową). Zalogujmy się więc do naszego pgAdmina.

    Otwórzmy Query Tool (Alt – Shift – Q) lub naciśnijmy odpowiedni przycisk na prawo od Object Explorer

    Będziemy definiować proste warstwy każdego typu i tworzyć im indeksy geometryczne (przyspiesza to odnajdowanie obiektów w przestrzeni, a w niektórych przypadkach jest konieczne do przeprowadzania obliczeń)

    Nasze warstwy będą zawiarały:

    • ID – unikalny identyfikator
    • Nazwa – tekst
    • geom – geometria
    -- Dodajmy warstwę punktową i tworzymy index geometryczny
    CREATE TABLE qgis.test_punkty (id SERIAL PRIMARY KEY, nazwa TEXT, geom geometry(Point, 2180));
    CREATE INDEX test_punkty_geom_idx ON qgis.test_punkty USING GIST(geom);
    -- Dodajmy warstwę liniową i tworzymy index geometryczny
    CREATE TABLE qgis.test_linie (id SERIAL PRIMARY KEY, nazwa TEXT, geom geometry(LineString, 2180));
    CREATE INDEX test_linie_geom_idx ON qgis.test_linie USING GIST(geom);
    -- Dodajemy warstwę poligonową i tworzymy index geometryczny
    CREATE TABLE qgis.test_poligony (id SERIAL PRIMARY KEY, nazwa TEXT, geom geometry(Polygon, 2180));
    CREATE INDEX test_poligony_geom_idx ON qgis.test_poligony USING GIST(geom);
    -- Jesteśmy (przynajmniej ja jako postgres, więc nadajmy użytkownikowi końcowemu (qgis) stosowne uprawnienia od odczytu, aktualizacji i usuwania
    GRANT ALL ON qgis.test_punkty TO qgis;
    GRANT ALL ON qgis.test_linie TO qgis;
    GRANT ALL ON qgis.test_poligony TO qgis;
    

    FIX: Dodajmy jeszcze prawa do Sekwencji czyli w tym przypadku mechanizmowi nadawania kolejnych ID na warstwach. Jako, że tworzymy tabele jako użytkownik postgres to musimy użytkownikowi końcowemu, czyli qgis, nadać prawa.

    GRANT ALL ON SEQUENCE qgis.test_punkty_id_seq TO qgis;
    GRANT ALL ON SEQUENCE qgis.test_linie_id_seq TO qgis;
    GRANT ALL ON SEQUENCE qgis.test_poligony_id_seq TO qgis;
    

    Teraz czas na QGIS

    Sprawdźmy teraz w QGIS czy możemy to już edytować

    Utwórzmy nowy projekt, wybierzmy układ współrzędnych projektu (1)

    Żeby zlokalizować się wstępnie na mapie musimy mieć jakiś punkt odniesienia. Ja wziąłem ogólnodostępne dane dotyczące Krakowa w formie WFS ze strony Miejskiego Systemu Informacji Przestrzennej, https://msip.krakow.pl/dataset/2643

    Kopiujemy : https://msip.um.krakow.pl/arcgis/services/Obserwatorium/Dzielnice/MapServer/WFSServer , otwieramy QGIS -> menu Warstwa -> Dodaj warstwę WFS / OGC API…

    Potem dodajemy nowe połączenie… Ok żeby zatwierdzić i klikamy Połącz

    a potem zaznaczamy każdą kolejną warstwę (ten dostawca dostarcza 2 warstwy) i klikamy Dodaj na dole ekranu. Na koniec Zamknij.

    Przy nowym projekcie QGIS przeniesie nas od razu do dodanej warstwy, w przypadku istniejącego projektu trzeba prawy klawisz na danej warstwie na Liście warstw i Powiększ do warstw(y)

    Teraz w końcu możemy zacząć dodawać nasze prywatne warstwy:

    Połączenie konfigurowane było we wpisie 1. Instalacja PostgreSQL i PostGIS . Wciskamy połącz, podajemy użytkownika i haslo (qgis i nasze nadane hasło). rozszerzamy strzałeczką i pojawiają nam sie nasze 3 puste warstwy

    Dodajemy kolejno każdą z nich, Zaznaczając i Dodaj. i zamykamy. Ustawiamy sobie kolejność warstw na mapie aby warstwy poligonowe nie zasłaniały nam liniowych i punktowych (w panelu Warstwy) przeciągając odpowiednio.

    Zaznaczamy warstwę którą chcemy edytować, włączamy edycje warstwy (patrz pkt 1 na obrazku poniżej), a potem już możemy dodawać punkty (patrz pkt 2)

    Ja dodam kilka punktów, id punktu będzie się samo nadawało, nazwę podajemy wg uznania.

    Na koniec wyłączamy tryb edycji i zatwierdzamy zmiany, dopiero wtedy dane są zapisywane w PostgreSQL

    Tak samo dodajemy linie i poligony , zakańczając prawym klawiszem

    Skopiujmy jeszcze na próbę jeden obiekt z Dzielnic. Przechodzimy na warstwę Dzielnice, zaznaczamy narzędziem zaznaczania a potem kopiujemy, przechodzimy na naszą warstwę Poligony, oczywiście włączając tryb edycji i wklejamy na próbę.

    Jak wyłączymy edycję, zatwierdzimy zmiany i odznaczymy wszystkie obiekty uzyskujemy :

    Zatem udało się nam wkleić obiekt zewnętrzny do naszej warstwy testowej. I na tym zakończymy ten wpis.

    Podsumowanie:

    • Przebrnęliśmy przez nudną teorię dot. układów współrzędnych
    • Zdefiniowaliśmy próbne warstwy punktowe, liniowe, poligonowe, z poziomu pgAdmin
    • Utworzyliśmy nowy projekt testowy i ustawiliśmy jego układ współrzędnych
    • Dodaliśmy warstwy odniesienia (tutaj granice Krakowa) żeby móc przenieść się w granice naszego układu
    • Dodaliśmy do QGIS nasze warstwy, oraz testowe obiekty
    • Skopiowaliśmy obiekt z warstwy odniesienia

    Zapraszam do komentarzy

  • 1. Instalacja PostgreSQL i PostGIS

    Do instalacji modułu PostGIS mamy wiele możliwości:

    Ja wybrałem wersje https://www.enterprisedb.com/downloads/postgres-postgresql-downloads, wersja ta zawiera Stack Builder (który pomaga doinstalować moduły do PostgreSQL)

    Ściągamy zatem wersje Windows x86-64 i instalujemy.

    Wybieramy lokalizacje instalacji, hasło dla administacyjnego usera postgres-a (zapamietajmy je) i port (jak nie mamy więcej instancji postrgresa to zostawmy default). Potem baza zaczyna sie instalować.

    Po zakończeniu instalacji wybieramy aby uruchomiło nam Stack Buildera

    Stack Builder poprosi nas o wybranie instalacji Postgres którą chcemy „modyfikować”, wybieramy naszą.

    a potem doznaczamy co Stack Builder ma nam doinstalować (u nas PostGIS).

    następnie wybieramy download directory (u mnie default user dir), to jest folder do którego zostaną ściągniete pliki instalacyjne.

    Po naciśnięciu Next, czekamy (u mnie trwało to trochę zanim wogóle zaczęło się sciągać, proszę nie panikować).

    Następnie musimy przeprowadzić instalacje ściągniętych modułów

    Kolejno (I agree) o ile zgadzamy się z licencją …

    Next (wybór komponentów) i Next (wybór lokalizacji instalacji, default w folderze Postgres-a) w kolejnych krokach, potem Close w ostatnim.

    Teraz możemy wrócić do Stack Buildera i nacisnąć Finish

    No i mamy juz działającego PostgreSQL z zainstalowanym modułem postGIS.

    Wejdźmy teraz do pgAdmina (tutaj 4)

    wybieramy naszą bazę i podajemy do niej hasło (u mnie jest więcej baz, nas interesuje aktualnie PostgreSQL 17)

    Potem wybieramy żółte pole, to otworzy nam Query Tool , okienko gdzie będziemy sie porozumiewać z posgreSQL, wpisujemy

     CREATE EXTENSION postgis; 
    
    

    i klikamy w strzałkę powyżej. (Execute Script). To instaluje nam w bazie funkcje postGIS.

    Następnie możemy sprawdzić czy postGIS został poprawnie zainstalowany poprzez wpisanie i uruchomienie skryptu jak powyżej:

    SELECT PostGIS_Full_Version();
    
    

    Zwróci to nam Dane w Data Output poniżej.

    Ok, utwórzmy teraz nowego usera „qgis” , który będzie mogł grzebać tylko w swoim scheme (obszarze, schemacie), ale też będzię mógł korzystać z dobrodziejstw postgis-a, Najcześciej tworzy się schematy z tą samą nazwą co user, więc stworzymy usera i schemat „qgis”, i nadamy mu uprawnienia.

    Wykonujemy (Execute script [F5]) poniższe

    -- tworzymy usera qgis i nadajemy mu haslo
    CREATE USER qgis WITH PASSWORD 'qpass'; 
    -- tworzymy miejsce pracy usera
    CREATE SCHEMA AUTHORIZATION qgis; 
    -- zezwalamy zeby nasz nowy user mogl sie logowac do bazy
    GRANT CONNECT ON DATABASE postgres TO qgis; 
    -- zezwalamy zeby nasz nowy user mogl korzystac z publicznej schema, 
    -- tam sa min zainstalowane funkcje postgisa
    GRANT USAGE ON SCHEMA public TO qgis; 
    GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO qgis;
    GRANT ALL ON geometry_columns TO qgis;
    GRANT ALL ON spatial_ref_sys TO qgis;
    
    

    Teraz w teorii moglibyśmy się już zalogować z poziomu QGIS i sprawdzić czy serwer zezwala nam na połączenie.

    Wpisujemy nasze dane logowania

    • Nazwa : dowolna nazwa rozpoznawalna dla nas, nie zalecam spacji
    • Usługa : nie definiowalismy danych usługi, pozostawiamy puste
    • Host : u nas localhost (baza lokalna)
    • Port : o ile podczas instalacji nie zmienilismy to port bedzie default 5432
    • Baza danych : postgres (defaultowa nazwa)

    Potem klikamy Test Połączenia i podajemy naszego user/pass i klikamy OK

    Teraz mozemy kliknac OK, połączenie zostało zdefinowane a QGIS już je dodał do listy.

    Definowaniem tabel i warstw zajmiemy się w nastepnym wpisie.

    Co dzisiaj zrobilismy:

    • Zainstalowalismy na Windows 11 lokalny serwer PostgreSQL
    • Zainstalowaliśmy i aktywowaliśmy moduł postGIS
    • Stworzylismy usera i scheme dla naszego usera
    • Nadaliśmy mu uprawnienia żeby mógł się łączyć i korzystać z dobrodziejstw postGIS
    • Skonfigurowaliśmy połączenie z postgreSQL w QGIS

    Czego nie zrobiliśmy:

    • nic nie zrobiliśmy w kwestiach bezpieczenstwa, zakładam że będzie to lokalna testowa baza danych do nauki
    • zalecam tez wybranie innego niz 'qpass’ hasla 🙂

  • Wyszukiwanie i zaznaczanie obiektów na warstwach w QGIS z poziomu Pythona

    Na przykladzie danych z powiatu Krakowskiego wyszukamy i zaznaczymy obiekty w QGIS.

    Najpierw potrzebujemy danych startowych, możemy je znaleźć w https://epodgik.pl/ a dokladniej w https://wms02.epodgik.pl/walidator/uslugi.php?wfs#

    Kopiujemy adres WFS z Usługi pobierania i dodajemy w warstwach QGIS / WFS-OGC

    Dodajemy 2 przykładowe warstwy

    Eksportujemy je np. do gpkg.

    Wynikowo mamy wyeksportowane 2 warstwy, będziemy wyszukiwać w konsoli pythona. Reszte odznaczymy bo serwisy online są nieprzewidywalne, tj teraz są, za chwilę może ich nie być. Zapisałem też projekt bo QGIS też czasem potrafi się zwiesić bez powodu.

    Teraz przechodzimy do konsoli pythona w QGIS,

    Menu -> Wtyczki -> Konsola Pythona -> w otwartej konsoli klikamy ikonkę Pokaż Edytor.

    Konsola Pythona składa się z kilku obszarów:

    • ŻÓŁTY to Output Pythona czyli tu będziemy wypisywać wyniki jak i błędy naszego kodu
    • RÓŻOWY to input pojedynczych komend Pythona, tu możemy wpisać komendę która ma być uruchomiona po naciśnięciu Enter
    • ZIELONY (edytor otwieramy poprzez naciśniecie przycisku zaznaczonego na fioletowo) w tym edytorze możemy napisać już złożony skrypt, uruchamiamy go poprzez naciśniecie przycisku PLAY zaznaczonego na niebiesko

    Pełna dokumentacja QGIS Python dostępna jest na stronie PyQGIS Developer Cookbook — QGIS Documentation documentation. Ja z tego korzystam z doskoku, tj wyszukuje to czego aktualnie potrzebuje.

    Jest wiele sposobów na identyfikacje warstw w QGIS. Ja używam QgsProject (https://qgis.org/pyqgis/3.40/core/QgsProject.html) i QgsMapLayerStore.

    # to zwraca nam zainicjowaną klasę aktualnie 
    # otwartego projektu https://qgis.org/pyqgis/3.40/core/QgsProject.html
    qgsprj = QgsProject.instance() 
    # to zwraca nam klase
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayerStore.html
    qgsls = qgsprj.layerStore() 
    layers = qgsls.mapLayers() # lista niepowtarzalnych id warstw
    for layer in layers: # iterujemy listę niepowtarzalnych id warstw
    # potrzebujemy wyszukać specyficzną warstwę 
    # wiele warstw może mieć podobne nazwy, źródła etc 
    # musimy byc pewni ze wyszukalismy specyficzną warstwę
    # przyglądnijmy się danym w klasie QgsMapLayer i wróćmy do QgsProject
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayer.html
        print(f"layerid = {layer}")
        print(f"layersrc = {qgsprj.mapLayer(layer).source()}, layername = {qgsprj.mapLayer(layer).name()}")
    

    to zwróci nam coś w stylu

    layerid = ms_budynki_7ae65efe_c42b_4471_8841_540e71af475b
    layersrc =  pagingEnabled='default' preferCoordinatesForWfsT11='false' restrictToRequestBBOX='1' srsname='EPSG:2178' typename='ms:budynki' url='https://wms.powiat.krakow.pl:1518/iip/ows' version='auto', layername = ms:budynki
    layerid = ms_dzialki_ad325524_0958_4c2e_94e7_0947075a76b3
    layersrc =  pagingEnabled='default' preferCoordinatesForWfsT11='false' restrictToRequestBBOX='1' srsname='EPSG:2178' typename='ms:dzialki' url='https://wms.powiat.krakow.pl:1518/iip/ows' version='auto', layername = ms:dzialki
    layerid = msbudynki_66ce0e5c_421b_447e_8b4d_be6840885e5e
    layersrc = D:/projekt1/budynki.gpkg|layername=msbudynki, layername = budynki — msbudynki
    layerid = msdzialki_7cacff14_ce63_4c32_9191_3788eccf239d
    layersrc = D:/projekt1/dzialki.gpkg|layername=msdzialki, layername = dzialki — msdzialki
    

    nam chodzi tylko o nasze gpkg czyli layersrc in 'dzialki.gpkg’ i layerbname = 'dzialki – msdzialki’ oraz layersrc in 'budynki.gpkg’ and layername = 'budynki – msbudynki’, zmodyfikujmy nasz skrypt :

    #zmienna lyr budynki
    budynki = None
    #zmienna lyr dzialki
    dzialki = None
    # to zwraca nam zainicjowaną klasę aktualnie 
    # otwartego projektu https://qgis.org/pyqgis/3.40/core/QgsProject.html
    qgsprj = QgsProject.instance() 
    # to zwraca nam klase
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayerStore.html
    qgsls = qgsprj.layerStore() 
    layers = qgsls.mapLayers() # listę warstw (obie)
    for layer in layers: # iterujemy listę niepowtarzalych id warstwy
    # potrzebujemy wyszukać specyficzną warstwę 
    # wiele warstw może mieć podobne nazwy 
    # musimy byc pewni ze wyszukalismy specyficzną warstwę
    # przyglądnijmy się danym w klasie QgsMapLayer i wróćmyu do QgsProject
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayer.html
        print(f"layerid = {layer}")
        print(f"layersrc = {qgsprj.mapLayer(layer).source()}, layername = {qgsprj.mapLayer(layer).name()}")
    # jeżeli spelniaja nasze kryteria, przepisz zmienna budynki, dzialki
        if 'budynki.gpkg' in qgsprj.mapLayer(layer).source() and qgsprj.mapLayer(layer).name() == 'budynki — msbudynki':
            budynki = qgsprj.mapLayer(layer)
        if 'dzialki.gpkg' in qgsprj.mapLayer(layer).source() and qgsprj.mapLayer(layer).name() == 'dzialki — msdzialki':
            dzialki = qgsprj.mapLayer(layer)
    print(dzialki)
    print(budynki)
    

    Zwróci to nam 2 znalezione warstwy ,na których będziemy wyszukiwać, pełna specyfikacja : https://qgis.org/pyqgis/3.40/core/QgsMapLayer.html

    najpierw wyszukamy po danych tj. uzyjemy selectByExpression, przygladnijmy się danym które oferuje nam warstwa dzialki

    Wyszukajmy wszystkie dzialki które są w Giebułtowie i działka zaczyna się od 434, czyli expression musi brzmieć : NAZWA_OBREBU = 'Giebułtów’ AND NUMER_DZIALKI LIKE '434%’, bazując na qgisowej wersji zaznaczania będzię to ok 51 obiektów, ale sprawdźmy jak to będzie działać w Pythonie.

    dzialki.selectByExpression('NAZWA_OBREBU = \'Giebułtów\' AND NUMER_DZIALKI LIKE \'434%\'')
    

    Jest to najprostszy sposób zaznaczania. W QGIS oczywiscie mozemy pobrać features bez zaznaczania i mieć je w iteratorze, liście etc. do tego celu musimy użyć funkcji getFeatures i stworzyć QgsFeatureRequest np:

    features = dzialki.getFeatures(QgsFeatureRequest(QgsExpression('NAZWA_OBREBU = \'Giebułtów\' AND NUMER_DZIALKI LIKE \'434%\'')))
    flist = list(features)
    print(len(flist))
    

    Da nam to jak już mówiłem około 51 features selected. Powód dla którego przetwarzam to na listę jest prosty, iteratory można użyć tylko raz, a listę wiele razy. Możemy teraz zrobic liste feature id i wyselekcjonować recznie wybrane id.

    Dla przykładu przyjmijmy, że interesują nas działki mniejsze od 10 arów, zobaczymy ile takich znajdziemy. Iterujemy liste features i sprawdzamy czy powierzchnia geometrii danej feature jest mniejsza lub równa 1000m2 (patrz specyfikacja https://qgis.org/pyqgis/3.40/core/QgsFeature.html i https://qgis.org/pyqgis/3.40/core/QgsGeometry.html)

    fid = [] # pusta lista samych id
    flist2 = [] # lista samych wybranych features
    for f in flist:
        if f.geometry().area() <= 1000:
            fid.append(f.id())
            flist2.append(f)
    dzialki.selectByIds(fid)
    

    Wynikowo mamy wyselekcjonowane po Id wszystkie obiekty mniejsze lub równe 1000m2

    No a po co była nam 2 warstwa Budynki ? Zaznaczmy teraz wszyskie budynki znajdujące się na wyselekcjonowanych działkach. Aby tego dokonać i zrobić to wydajnie musimy wykonać kilka kroków.

    Po pierwsze żeby nie porównywać wszystkich obiektów na warstwie Budynki potrzebujemy pobrać tylko obiekty w zakresie features. Ja preferuje uzycie funkcji boundingBox ktora tworzy prostokąt z którego zostaną pobrane dane od providera (dostawcy warstwy tutaj ogr). Najczesciej providerzy mają zoptymalizowaną funkcję pobierania features z prostokątnych obszarów. Potem z wybranych juz features sprawdzamy czy pojedyncze features dotykają naszego obiektu geometrycznego. Moglibyśmy iterować wszystkie obiekty na warstwie budynki ale będzie to mało wydajne (czytaj bardzo wolne). Finalny kod.

    #zmienna lyr budynki
    budynki = None
    #zmienna lyr dzialki
    dzialki = None
    # to zwraca nam zainicjowaną klasę aktualnie 
    # otwartego projektu https://qgis.org/pyqgis/3.40/core/QgsProject.html
    qgsprj = QgsProject.instance() 
    # to zwraca nam klase
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayerStore.html
    qgsls = qgsprj.layerStore() 
    layers = qgsls.mapLayers() # listę warstw (obie)
    for layer in layers: # iterujemy listę niepowtarzalych id warstwy
    # potrzebujemy wyszukać specyficzną warstwę 
    # wiele warstw może mieć podobne nazwy 
    # musimy byc pewni ze wyszukalismy specyficzną warstwę
    # przyglądnijmy się danym w klasie QgsMapLayer i wróćmyu do QgsProject
    # https://qgis.org/pyqgis/3.40/core/QgsMapLayer.html
        print(f"layerid = {layer}")
        print(f"layersrc = {qgsprj.mapLayer(layer).source()}, layername = {qgsprj.mapLayer(layer).name()}")
    # jeżeli spelniaja nasze kryteria, przepisz zmienna budynki, dzialki
        if 'budynki.gpkg' in qgsprj.mapLayer(layer).source() and qgsprj.mapLayer(layer).name() == 'budynki — msbudynki':
            budynki = qgsprj.mapLayer(layer)
        if 'dzialki.gpkg' in qgsprj.mapLayer(layer).source() and qgsprj.mapLayer(layer).name() == 'dzialki — msdzialki':
            dzialki = qgsprj.mapLayer(layer)
    print(dzialki)
    #dzialki.selectByExpression('NAZWA_OBREBU = \'Giebułtów\' AND NUMER_DZIALKI LIKE \'434%\'')
    features = dzialki.getFeatures(QgsFeatureRequest(QgsExpression('NAZWA_OBREBU = \'Giebułtów\' AND NUMER_DZIALKI LIKE \'434%\'')))
    flist = list(features)
    print(len(flist))
    fid = []
    flist2 = []
    # latwiej nam bedzie selekcjonowac 1 geometria niz 51
    # wiec polaczymy to w jedna geom
    geom = None
    for f in flist:
        if f.geometry().area() &lt;= 1000:
            fid.append(f.id())
            flist2.append(f)
            # tu sie dzieje magia łączenia
            if geom is None:
                geom = f.geometry()
            else:
                geom = geom.combine(f.geometry())
    dzialki.selectByIds(fid)
    # teraz w geom mamy polaczona geometrie wszystkich dzialek
    # tworzymy boundingBox czyli prostokąt który zawiera całą geometrie geom
    geombb = geom.boundingBox()
    # no i pobieramu features z budynkow po boundingBox
    feat_budynki = budynki.getFeatures(QgsFeatureRequest().setFilterRect(geombb))
    fbudynki_id = []
    for f in feat_budynki:
        # teraz musimy sprawdzic czy rzeczywiscie geometrie sie przecinaja gdziekolwiek
        if f.geometry().intersects(geom):
            fbudynki_id.append(f.id())
    # no i finalnie zaznaczamy budynki na mapie
    budynki.selectByIds(fbudynki_id)
    

    i finalny wynik w QGIS

    32 features selected on layer budynki.

    Podsumowując :

    • dodalismy warstwy wfs
    • wyeksportowalismy je
    • wyszukalismy potrzebne warstwy z poziomu Pythona
    • wyszukalismy dane po expression z warstwy dzialki
    • wyfiltrowalismy tylko wyniki spelniajace kryteria wielkosci obiektu
    • i na koniec zaznaczylismy tymi obiektami, obiekty z warstwy budynki

    Zapraszam do kontaktu i komentowania, jestem też otwarty na konstruktywną krytykę 🙂