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.

niedziela, 17 lutego 2013

Golang : obsługa bazy Spatialite

W QAZP2 pojawiła się nowa funkcja, moim zdaniem jedna z ważniejszych, czyli filtrowanie danych. Póki co można ją stosować tylko do stanowisk i polega na wybieraniu z listy stanowisk tych, które spełniają wybrane kryteria. A więc w pierwszym kroku wyszukujemy stanowiska znajdujące się na przykład na pewnym obszarze, a następnie spośród nich stanowiska, które mają dużą wartość dla badań nad przeszłością i wobec których istnieje zagrożenie zniszczenia. To oczywiście tylko przykład, bo praktycznie stanowiska można filtrować w oparciu o większość atrybutów edytowanych w programie. A prezentację sposobu stosowania tego mechanizmu można obejrzeć na krótkim filmie.

To by było na tyle, jeżeli chodzi o ostanie wydarzenia, bo dzisiaj chciałbym dokończyć temat z poprzedniego tygodnia, czyli wykorzystanie języka Go do realizacji projektu QAZP2. Wtedy omówiłem kilka interesujących właściwości Go, które odróżniają go od dobrze znanych C/C++ a także popularnych języków obiektowych, na przykład Javy. Go (Golang) charakteryzuje się bogatą biblioteką standardową, która umożliwia realizację podstawowych zadań, bez konieczności instalacji dodatkowych zależności. W pozostałych przypadkach można się posłużyć intuicyjnym i wygodnym mechanizmem zarządzania nimi. Na przykład do pobrania sterownika do bazy SQLite/Spatialite. Istnieje kilka alternatywnym projektów, w ramach których jest on rozwijany, ja zdecydowałem się na Go-Sqlite3. Jak już pisałem do jego instalacji wystarczy wykonać polecenie
go get github.com/mattn/go-sqlite3
, które po pierwsze pobierze kod źródłowy utrzymywany w repozytorium GIT i po drugie skompiluje go, jeżeli spełnione są wszystkie zależności. To jest proste nie tylko w teorii, z tym że w normalnej postaci sterownika nie można używać do baz przestrzennych. Wynika to z tego, że przed wykonaniem jakiegokolwiek polecenia SQL, które dotyczy danych geometrycznych należy załadować rozszerzenie spatialite, na przykład stosując polecenie (na linuksie) select load_extension('libspatialite.so.3'). Do tego, by ono zadziałało trzeba wcześniej zmodyfikować kod źródłowy sterownika w ten sposób, by w chwili "łączenia się" z bazą umożliwiał ładowanie rozszerzeń, która to możliwość jest domyślnie zablokowana.
Dowolny program może korzystać z bazy SQLite przy pomocy biblioteki funkcji, których używa się na przykład do otwierania bazy, wykonywania poleceń SQL i pobierania wyników. Wśród nich jest także funkcja sqlite3_enable_load_extension, która służy (jak sama nazwa wskazuje ...) do włączenia możliwości ładowania rozszerzeń. Trzeba ją wykonać po otworzeniu pliku z bazą danych. A więc w pliku sqlite3 , wewnątrz funkcji Open, przed deklaracją zwracającą wskaźnik do nowego połączenia z bazą należy wstawić następujące polecenie: 
C.sqlite3_enable_load_extension(db, C.int(1)). I to wszystko. Po otworzeniu bazy zostanie wywołana ww. funkcja, gdzie parametrami są wskaźnik do bazy i liczba 1 oznaczająca True. Po nawiązaniu połączenia wystarczy wykonać polecenie SQL, które spowoduje załadowanie rozszerzenia Spatialite i od tej chwili można przetwarzać dane geometryczne zapisane w przestrzennej bazie danych.

sobota, 9 lutego 2013

Golang : import danych AZP

Jednym z ważniejszych aspektów związanych z projektem QAZP było przygotowanie narzędzia, które (w miarę) bezboleśnie pozwoli na "przerzucenie" danych utworzonych w programie AZPMAX do relacyjnej bazy SQL. Oczywiście pierwszą decyzją był wybór języka programowania, który z wiadomych względów padł na Jave, choćby z tego powodu, że mam sporo doświadczeń związanych z tworzeniem narzędzi obsługujących zarówno DBF-y jak i bazy danych. Niestety musiałem z tego zrezygnować, bo mimo wcześniejszych pozytywnych wyników, tym razem sterownik do SQLite odmówił współpracy z danymi przestrzennymi. Przy próbie inicjalizacji rozszerzenia Spatialite JVM przestawała działać. W związku z tym zdecydowałem się stworzyć narzędzie do importu w języku Golang, który od kilku lat jest rozwijany przez dobrze znaną firmę Google. Ten wybór też ma uzasadnienie, biorąc pod uwagę, że:
  1. mam już pewne doświadczenie związane z tworzeniem oprogramowania w Go,
  2. dobra obsługa baz sql, w tym Sqlite.
Na minus trzeba zaliczyć brak obsługi DBF-ów, ale to póki co rozwiązałem kopiując dane z plików CSV, a do tego formatu można łatwo wyeksportować DBF, posługując się na przykład arkuszem kalkulacyjnym.
Golang / Go ma kilka interesujących właściwości, które przyniosły mu już dużo rozgłosu i powodują, że język dość szybko zyskuje na popularności. Jest statycznie typowany, ale określenie typu zmiennej można przerzucić na kompilator. Wydaje mi się, że nie można go uznać za niskopoziomowy, bo nie umożliwia na przykład wykorzystywanie assemblera, natomiast różnorakie porównania wykazują, że w wielu przypadkach szybkością dorównuje programom pisanym w C / C++. Nad tymi dwoma ma przewagę (przec co niektórych uznawaną za wadę) automatycznego zarządzania pamięcią (garbage collector). Programy można tworzyć w modelu strukturalnym - do reperentowania danych używa się list, map, i struktur bardzo podobnych do tych stosowanych w C / C++, albo takim, który oferuje abstrakcję i polimorfizm (ale bez dziedziczenia) znany z paradygmatu programowania obiektowego. Realizuje się to przy pomocy tak zwanych interfejsów, które nieco przypominają konstrukcje o tej samej nazwie, które stosuje się w Javie. W Golang interfejs jest pewnym kontraktem, który umożliwia tworzenie polimorficznych obiektów. Natomiast inny jest sposób ich stosowania. O ile w Javie, jawnie trzeba podać, że klasa jest implementacją, o tyle w przypadku Golang jest to automatycznie rozpoznawane przez kompilator. Każdy typ danych, dla którego zaimplementowano funkcje wskazane w definicji interfejsu staje się automatycznie jego implementacją. W narzędziu do importu, o którym mowa został zdefiniowany interfejs Tabela z jedną metodą Params, która zwraca tablicę wartości dowolnego typu. Każda struktura, która implementuje interfejs może być zastosowana w metodzie dodaj(ps *sql.Stmt, t Tabela, spr bool), która wykonując metodę Params pobiera wartości ze struktury w takiej kolejności, które powinny być zastosowane jako parametry polecenia SQL reprezentowanego przez inny interfejs Stmt zdefiniowany w bibliotece standardowej Golang.
W tym krótkim opisie trudno wskazać wszystkie właściwości Go, ja skupiłem się tylko na tych, które były istotne z uwagi na problem, który musiałem rozwiązać. W następnym poście opisałem, jak "zmusić" sterownik Go do obsługi danych geometrycznych zapisanych w przestrzennej bazie danych.

niedziela, 3 lutego 2013

Implementacja nowych sygnałów Qt w Pythonie

Dzisiaj będzie krótko, w ramach uzupełnienia do poprzedniego artykułu. Opisałem w nim krótko koncepcję obsługi zdarzeń w Qt4, natomiast teraz chciałbym to uzupełnić o definiowanie nowych sygnałów dla widgetów tworzonych w Pythonie.
Zasada jest dość prosta, bo opiera się na stosowaniu metody pyqtSignal(args, name=nazwa_sygnalu), której zadaniem jest utworzenie nowego sygnału.

Przykład (tworzenie nowego sygnału)

from PyQt4.QtCore import pyqtSignal
from PyQt4.QtGui import QWidget, QPushButton

class NowyWidget(QWidget):
    
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        btn = QPushButton('Kliknij')
        btn.clicked.connect(self.btnClick)

    widgetSygnal = pyqtSignal(str, name='widgetSygnal')

    def btnClick(self, ch):
        self.widgetSygnal.emit('Witaj')

def nowySlot(wiadomosc):
    print wiadomosc

def main():
    wgt = NowyWidget()
    wgt.widgetSygnal.connect(nowySlot)

Ze zdarzeniem QPushButton.clicked został połączony slot w postaci metody btnKlik zdefiniowany w klasie NowyWidget. Gdy użytkownik kliknie w przycisk, zostanie ona uruchomiona, a następnie wyemitowany nowy sygnał widgetSygnal, który został utworzony przy pomocy wspomnianej funkcji pyqtSignal. Zgodnie z jej wywołaniem slot, który podłączymy do sygnału powinien przyjmować jeden argument typu str, tak jak metoda nowySlot, która wyświetla otrzymaną wiadomość na ekranie.