Pokazywanie postów oznaczonych etykietą qgsvectorlayer. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą qgsvectorlayer. Pokaż wszystkie posty

niedziela, 24 lutego 2013

Tymczasowa warstwa wektorowa w QGIS

Ważnym elementem filtrowania stanowisk według wybranych kryteriów jest ich wyświetlenie na mapie, choćby po to, by przeanalizować ich rozproszenie w przestrzeni. W QAZP2 jest używana tymczasowa warstwa, gdzie QgsVectorDataProvider jest zaimplementowany w ten sposób, że pobiera obiekty prosto z pamięci, a nie z pliku SHP, albo przestrzennej bazy danych.

Tworzenie tymczasowej warstwy wektorowej razem z zawartością jest dość proste i składa się z następujących kroków:
  1. Utworzenie pustej warstwy wektorowej przez wywołanie konstruktora klasy w następujący sposób: QgsVectorLayer(typGeometryczny, nazwa, 'memory'), gdzie wartość typGeometryczny powinna mieć na przykład wartość 'Point', albo 'Polygon', czy 'Line'. 
  2. Rozpoczęcie edycji przez wywołanie metody QgsVectorLayer.startEditing().
  3. Określenie atrybutów warstwy przez podanie ich listy. Każdy z nich jest reprezentowany przez klasę QgsField. W warstwie tymczasowej można używać tylko następujące typy pól: QVariant.String, QVariant.Double oraz QVariant.Int
  4. Dodanie obiektów QgsFeature, które mają być wyświetlane na warstwie.
  5. Zatwierdzenie edycji.
Funkcja qgsop.tempWarstwa() jest przykładem implementacji powyższego algorytmu.

środa, 12 grudnia 2012

QGIS: struktury danych

Jednym z warunków sprawnego posługiwania się API programu QGIS jest znajomość jego specyficznych struktur danych, które właśnie chciałbym przybliżyć w tym artykule. Na jego potrzeby będę wyróżniał dwie kategorie danych: przestrzenne - współrzędne kartograficzne określające położenie obiektu przy pomocy punktów, linii, poligonów i innych oraz tekstowe - odnoszące się do dowolnych właściwości obiektu. Ten podział ma zastosowanie bez względu na to, w jakim kontenerze dane są zapisywane - pliku SHP, przestrzennej bazie danych, itp.
Każdy obiekt wyświetlany na mapie w QGIS można uzyskać odwołując się do odpowiedniej warstwy reprezentowanej klasą QgsVectorLayer. Można to zrobić na kilka sposobów - używając wewnętrznego identyfikatora, który do każdego obiektów przypisuje QGIS, iterując po obiektach i wybierając te, które spełniają nasze kryteria (jeżeli na warstwie znajduje się ich naprawdę dużo, to ta metoda może być nieefektywana), albo pobierając listę uprzednio zaznaczonych (np. przez użytkownika programu). W każdym z wymienionych przypadków każdy obiekt jest reprezentowany klasą QgsFeature. Dla każdego z nich można pobrać mapę właściwości, gdzie kluczem są liczby oznaczające kolejność przypisanej do niego właściwości. Aby uzyskać nazwy cech, trzeba w pierwszej kolejności z warstwy pobrać informację o źródle danych przy pomocy metody dataProvider(), a następnie na jej wyniku (czyli obiekcie klasy QgsVectorDataProvider) wykonać metodę fieldNameMap(). Właściwości tekstowe obiektu można modyfikować bezpośrednio - stosując metodę setAttributeMap(), która spowoduje, że stare wartości zostaną zastąpione nowymi, albo funkcją changeAttributeValue do której trzeba podać numer modyfikowanej właściwości, identyfikator obiektu (featureId) oraz nową wartość. 
Drugą ważną metodą klasy QgsFeature jest geometry(), która zwraca referencję do danych przestrzennych. Tydzień temu opisywałem jak przekształcać współrzędne przy pomocy API QGIS i wymienione tam funkcje jako parametry przyjmują wartość typu QgsGeometry. Ale to nie jedyne zastosowanie. Zasadniczo wszystkie operacje na danych przestrzennych można wykonać przy jego pomocy. Ponadto statyczne metody umożliwiają na przykład konwersję danych przestrzennych w postaci tekstowej (WKT) na binarną (WKB).

niedziela, 28 października 2012

Kilka uwag o Python DBAPI

Nowa wersja QAZP2 jest już dostępna bezpośrednio albo z repozytorium wtyczek. Działa już edytor faktów kulturowych, to znaczy do każdego stanowiska można przyporządkować określenia jego przynależności chronologiczno-kulturowej.

Wymagania

Jednym z ważniejszych założeń, których staram się trzymać rozwijając program jest jego "bezobsługowość", czyli uwolnienie użytkownika od obowiązku dbania o zależności w postaci dodatkowych modułów, spoza biblioteki standardowej i w tym sterowników do bazy danych. Obecnie QAZP2 umożliwia korzystanie z bazy SQLite albo PostgreSQL, z tej prostej przyczyny, że tylko spośród tych dwóch można wybierać w QGIS-ie. Moduł do obsługi jest dostępny w bibliotece standardowej Pythona, natomiast instalator QuantumGIS, gdy jest uruchamiany pod kontrolą MS Windows, domyślnie dodaje sterownik psycopg do łączenia się z bazą PostgreSQL. W przypadku użytkowników Linuksa problem jest trochę bardziej skomplikowany, ale w takiej sytuacji zakładam, że potrafią oni korzystać z menadżera pakietów i zainstalować wymagane biblioteki.
Wszystko to wydaje się trochę zagmatwane, więc w skrócie sytuację można opisać następująco: dwa rodzaje bazy danych SQLite/Spatialite i PostgreSQL/Postgis i sterowniki sqlite3 oraz psycopg, które implementują wzorzec opisany w PEP 249.

Pobieranie informacji o połączeniu

Pierwszy z problemów, które w związku z tym trzeba było rozwiązać to określenie, z jakiej bazy korzysta użytkownik. Jak już wspominałem QAZP ma być "bezobsługowe", czyli między innymi automatycznie rozwiązywać problemy konfiguracji, np. takie jak wybór bazy, z której pobrać dane. W obecnej wersji obsługiwane są trzy warstwy wektorowe: Miejsca, czyli zbiór punktów charakterystycznych na mapie; Trasy - drogi pokonane w trakcie badań archeologicznych; Stanowiska - ślady dawnego osadnictwa. Każda z nich w QGIS jest reprezentowana przez obiekt klasy QgsVectorLayer. Z tego kolei można uzyskać informacje o źródle pochodzenia warstwy, którym może być plik SHP, usługa WFS, czy właśnie relacyjna baza danych. Wywołując metodę dataProvider() uzyskuje się obiekt klasy QgsVectorDataProvider, którego metoda dataSourceUri() zwraca ciąg reprezentujący połączenie. Może to być ścieżka do pliku, albo lokalizacja bazy z parametrami - nazwą użytkownika, hasłem, itp. Żeby uniknąć ręcznego parsowania takiej informacji można wykorzystać klasę QgsDataSourceURI, której metody host(), port(), database() i inne pozwalają pobrać potrzebne informacje do nawiązania połączenia z bazą danych. To może się wydawać skomplikowane, ale w rzeczywistości jest dość banalne i całą operację łączenia można załatwić w kilku liniach kodu, tak jak to jest zrobione w metodzie zrodla.getPolaczenie2(). Jak zwykle w takich przypadkach większość czasu zajmuje znalezienie rozwiązania problemu, a nie jego implementacja ;).

Parametry poleceń SQL

Wspomniana metoda w zależności od nazwy bazy łączy się z wykorzystaniem modułu sqlite3 albo psycopg. I tutaj z mojej perspektywy rozpoczyna się najgorsze - otóż instrukja PEP 249 dopuszcza kilka różnych sposobów podawania parametrów polecenia sql. W przypadku sqlite przykładowe zapytanie mogłoby mieć postać (1) select * from tabela where a=? and b=? albo (2) select * from tabela where a=:pa and b=:pb gdy posługujemy się parametrami nazwanymi. Natomiast używając psycopg polecenie (1) trzeba zapisać jako select * from tabela where a=%s, a (2) w postaci select * from tabela where a=%(pa)s and b=%(pb)s. Każdy z tych formatów jest poprawny z uwagi na PEP 249, ale z jakiegoś powodu autorzy sqlite3 zdecydowali się na jeden, a psycopg na drugi. Są dwa rozwiązania tego problemu:
  1. Każdorazowe badanie rodzaju bazy i na tej podstawie konstruowanie poleceń z odpowiednio zaznaczonymi parametrami.
  2. Skonstruowanie adaptera, który by "opakował" oryginalne połączenie sqlite3 albo psycopg i umożliwiał jednorodny sposób wykonywania poleceń.
Zdecydowałem się na drugą opcję i napisałem dwie klasy - Polaczenie i Polecenie. Pierwsza opakowuje połączenie sqlite3 albo psycopg i w metodzie prep(sql), znając rodzaj bazy dostosowuje polecenie SQL do jej wymagań. Jako format wejściowy zdecydowałem się na ten używany w sqlite3, gdyż wydaje mi się bardziej czytelny. Jeżeli połączenie dotyczy bazy PostgreSQL, to za pomocą wyrażenia regularnego :([0-9a-zA-Z_]+) i metody re.subn parametry są zamieniane na format z psycopg.

Nie jest komfortowym rozwiązaniem, gdy do napisania uniwersalnego kodu trzeba tworzyć dodatkowe klasy tak jak w powyższym przypadku. Choćby z tego powodu bardziej podoba mi się rozwiązanie JDBC albo choćby z Go. Tu można postawić zarzut, że przecież istnieje coś takiego jak ORM i cały mój wywód nie ma sensu. Postaram się na niego odpowiedzieć wkrótce.