Pokazywanie postów oznaczonych etykietą qgis api. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą qgis api. 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).

wtorek, 4 grudnia 2012

Transformacje współrzędnych w QGIS

Nanosząc nowe obiekty w QGIS posługujemy się pewnym systemem odniesienia. Umożliwia on określenie dla dowolnego punktu jego rzeczywistego położenia na powierzchni kuli ziemskiej za pomocą współrzędnych prostokątnych płaskich, które oznaczają w jakiej odległości znajduje się od początku układu. Istnieją globalne systemy odniesienia - obecnie stosowanym standardem na przykład w odbiornikach GPS jest układ WGS84 (GRS80), z kolei w Polsce dla ma mało skalowych (czyli bardziej dokładnych) stosuje się układ 1992. Niekiedy zachodzi konieczność przekształcenia współrzędnych z jednego układu w drugi i zazwyczaj robi się to, by z lokalnego odwzorowania otrzymać globalne, albo w drugą stronę. W QAZP2 mamy do czynienia z drugą sytuacją, gdy odczytując dane z odbiornika GPS zapisane w globalnym układzie WGS84 trzeba je przekształcić na lokalny system 1992.
Zadanie to można wykonać na dwa sposoby:
  1. Przy pomocy funkcji przestrzennej bazy danych: funkcja ST_transform(wspolrzedne, srid) (Postgis).
  2. Wykorzystując metody z API QGIS.
Funkcja ST_transform przyjmuje dwa parametry: współrzędne oraz srid - identyfikator  systemu odniesienia. Przy czym oryginalny układ odniesienia dla danych które się przekształca musi być znany. Identyfikator układu WGS84 jest równy 4326,  natomiast układu 1992: 2180. Jeżeli oryginalne współrzędne będą zapisane w pierwszym z wynienionych i przekształcamy je do drugiego, to oczywiście będzie prawdziwa następująca relacja: wspolrzedne = ST_transform(ST_transform(wspolrzedne,2180),4326). Pierwsze wywołanie funkcji przekształca je do układu 1992, drugie do układu WGS84 i ta wartość powinna być równa początkowej. Funkcję ST_transform można stosować we wszytkich poleceniach DML oraz DQL do przestrzennej bazy danych.

Podobną operację można wykonać przy pomocy metod z interfejsu QGIS. W tym celu posługujemy się klasą QgsCoordinateReferenceSystem, która służy do reprezentowania wybranego układu odniesienia. Jej obiekt możemy uzyskać na dwa sposoby: ze wskazanej warstwy wektorowej QgsVectorLayer przy pomocy metody crs(), albo konstruując nowy, gdzie w parametrze podaje się identyfikator układu, np. 4326. Klasa QgsGeometry, która w QGIS reprezentuje współrzędne geograficzne posiada metodę transform, za pomocą której dokonuje się przekształcenia. W jej parametrze podaje się obiekt klasy QgsCoordinateTransform tworzony przez podanie oryginalnego i docelowego systemu odniesienia. Przykład wykorzystania zastosowania tego mechanizmu znajduje się w metodzie lib.qgsop.dodaj

sobota, 3 listopada 2012

QGIS API: zastępowanie domyślnego formularza

W QuantumGIS, każdy obiekt geograficzny reprezentowany klasą QgsFeature charakteryzuje się zbiorem właściwości (atrybutów). W przypadku punktów kopiowanych przez QAZP2 do relacyjnej bazy są to na przykład nazwa punktu (nadawana przez użytkownika), rodzaj badań (lotnicze, powierzchniowe, itp.), czas rejestracji i inne. Jednym ze sposobów wprowadzania obiektów jest wskazanie myszką ich lokalizacji, a w przypadku dwuwymiarowych (np. poligonów) dodatkowo ich kształtu. W rezultacie zostaje wyświetlone okno dialogowe, w którym określa się atrybuty, o których mowa była wcześniej. W domyślnej postaci składa się ono z pól tekstowych, w których użytkownik wprowadza odpowiednie wartości. I tak się dzieje bez względu na to, czy kolumna bazy danych zawiera dane tekstowe, czy liczbowe, logiczne, albo na przykład są ograniczone do pewnego zakresu wartości (tzw. dziedziny). To zachowanie można zmienić modyfikując właściwości warstwy, nad którą pracujemy, jednak trzeba to robić dla każdego projektu z osobna. W tym artykule chciałbym przedstawić sposób na zastępowanie domyślnego okna dialogowe takim, które jest dostosowane do naszych potrzeb bez udziału użytkownika.

Przed rozpoczęciem pisania funkcji w Pythonie konieczne jest utworzenie naszego formularza. QGIS wymaga przygotowania go w postaci pliku XML z rozszerzeniem *.ui, który można wygenerować w programie QtDesigner, który jest dostarczany z biblioteką Qt. Można wtedy umieścić w oknie dialogowym różne komponenty takie jak listy rozwijane (QCombobox), pola liczbowe (QSpinBox), czy do wprowadzania wartości logicznych (QCheckbox). Trzeba przy tym pamiętać, żeby każdy z komponentów miał taką samą nazwę jak kolumna w tabeli, której odpowiada. Przykładem takiego formularza jest ten stosowany w QAZP2 do wprowadzania punktów. Jego zawartość można obejrzeć otwierając plik w dowolnym edytorze tekstu, albo w QtDesignerze.

Kluczową kwestią dla rozwiązania tego problemu zastąpienia domyślnego okna dialogowego jest uchwycenie chwili, w której interesująca nas warstwa wektorowa zostaje wczytana w programie QGIS. Aby to zrobić trzeba się odwołać do obiektu klasy qgis.core.QgsMapLayerRegistry, który można uzyskać wywołując statyczną metodę instance(). Za każdym razem, gdy użytkownik otwiera nową warstwę, emitowany jest syngnał layerWasAdded, na który musimy zareagować podejmując odpowiednią akcję. To działanie będzie reprezentowała funkcja dodajUi(warstwa) w następującej postaci:

def dodajUi(warstwa):
    if warstwa.name() == 'miejsca':
        warstwa.setEditForm(abspath(__file__+'/../forms/miejsca.ui'))


Przez parametr warstwa przekazywana jest referencja do obiektu klasy QgsVectorLayer, której wczytanie wyemitowało sygnał layerWasAdded. Jeżeli warstwa nazywa się 'miejsca', to trzeba wywołać metodę setEditForm, w której podajemy bezpośrednią ścieżkę pliku *.ui wygenerwanego w QtDesignerze. Wartość __file__ dla dowolnego modułu Pythona zwaraca jego lokalizację w systemie plików. W tym przypadku katalog 'forms', w którym znajduje się plik 'miejsca.ui' jest zapisany na tym samym poziomie co katalog z modułem, który zawiera funkcję dodajUi. Przykład implementacji funkcji dodajUi można znaleźć w module qazp.py.

Aby wskazać, jaka akcja ma być wykonana po emisji sygnału layerWasAdded, trzeba go połączyć z funkcją dodajUi, co w bibliotece Qt realizuje się wywołując sekwencję QgsMapLayerRegistry.instance().layerWasAdded.connect(dodajUi). Ta operacja jest wykonywana w chwili inicjalizacji wtyczki, która zgodnie z wymaganiami QGIS następuje w funkcji initGui w momencie uruchamiania programu albo po pierwszej instalacji nowej wtyczki. Od tej chwili, gdy użytkownik będzie próbował dodać kolejny punkt na mapie, zamiast domyślnego okna dialogowego zostanie mu wyświetlone to, które zostało zdefiniowane w pliku miejsca.ui.

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.