niedziela, 30 grudnia 2012

Klasyfikacja kulturowo-historyczna: kodowanie pojęć

W poprzednim artykule opisałem organizację wykazów do kodowania informacji o klasyfikacji kulturowo-historycznej. Dla przypomnienia: na ekranie aplikacji, albo na karcie AZP pokazywane są informacje o tym, z jakiego okresu dziejów (epoka kamienia, średniowiecze, ...), zdaniem archeologa, pochodzi badane stanowisko, jaka była jego funkcja (osada, cmentarzysko, ...), czy sposób i tradycję wykonania znalezionych przedmiotów (kultura pucharów lejkowatych, kultura łużycka, ...). Nazwy wykorzystywane do opisu są przechowywane w tabelach-wykazach: OKRESY_DZIEJOW, JEDNOSTKI, FUNKCJE. Kluczem podstawowym każdej krotki w tabeli wykazu jest ciąg alfanumeryczny kodujący informację, która jest zapisana w rekordzie. W przypadku wykazu funkcji kod ma następującą strukturę ABCCD, gdzie kolejne litery oznaczają jego segmenty. W tym przypadku obowiązkowe jest podanie tylko segmentu A, w którym określa się ogólną funkcję: np. obrzędową literą B, obronną literą O, gospodarczą G, itd. Zatem gdyby w opisie funkcji stanowiska podać kod składający się z litery G, oznaczałoby to tylko tyle, że pełniło ono w przeszłości funkcję gospodarczą. Kolejne segmenty kodu służą do zapisu bardziej szczegółowej informacji. Jeżeli kod S oznacza funkcję sepulkralną (związaną z grzebaniem zmarłych), to SL będzie oznaczało cmentarzysko ciałopalne, SL03 - cmentarzysko ciałopalne płaskie, a SL03J - cmentarzysko ciałopalne płaskie z grobami jamowymi, które to pojęcie jest przykładem najbardziej szczegółowej informacji w wykazie funkcji. Oczywiście sposób konstruowania kodu, opisany na przykładzie funkcji, został zastosowany w wykazie okresów i jednostek kulturowych. 
Jak z każdym kodowaniem informacji, z tym także wiążą się określone zyski i straty, których bilans powinien decydować o jego zastosowaniu do naszego projektu. Do oczywistych zalet oczywiście należy spójna i czytelna reprezentacja informacji w bazie danych. Zamiast w każdym rekordzie charakteryzującym funkcję stanowiska pisać cmentarzysko ciałopalne płaskie z grobami jamowymi można po prostu wstawić kod SL03J i liczyć na to, że będzie on zrozumiały dla odbiorcy. Po drugie znacząco może ułatwić wyszukiwanie informacji o stanowiskach przy pomocy operatora SQL LIKE. Listę stanowisk z funkcją gospodarczą można utworzyć stosując polecenie SELECT * FROM FAKTY WHERE FUNKCJA LIKE 'G%', które uzwględni kod G, GI, GP13 i inne, które pasują do podanego wzorca. Szybkość wykonania takiego polecenia nie jest najwyższa, ale można je zoptymalizować na przykład przez utworzenie odpowiedniego indeksu na kolumnie FUNKCJA. Stosowanie wzorców nie zawsze będzie miało pożądanych skutek, ponieważ np. polecenie SELECT * FROM FAKTY WHERE FUNKCJA LIKE '%04%' zwróci stanowiska z funkcją osada otoczona rowem/rowami (MS04) a także grób szkieletowy katakumbowy (SS04).
Stosowanie kodów wymusza też na autorach pewną ścisłość w charakteryzowaniu stanowisk. Można to postrzegać jako zaletę, gdyż podwyższa to wartość bazy jako źródła archeologicznego, a także jako wadę, gdyż procesy społeczne nie zawsze można wtłoczyć w zmatematyzowany schemat. Jeżeli wykaz można rozszerzać o nowe pojęcia, to w przypadku, gdy brakuje odpowiedniego, można je dodać do tabeli. Natomiast w przypadku, gdy uzupełnianie wykazu jest niemożliwe, to trzeba próbować wykorzystać już istniejące, co może budzić oczywisty sprzeciw wobec utraty informacji.

niedziela, 16 grudnia 2012

Klasyfikacja funkcjonalno-kulturowo-chronologiczna: wykazy

Ostatnio sporo się działo w kwestii rozwoju QAZP2. Z radością donoszę, że wtyczka ma już zaimplementowane wszystkie funkcje, których wymagał program badawczy, w ramach którego aplikacja jest realizowana. Tym samym na podstawie zawartości bazy danych można wygenerować karty AZP zgodne ze specyfikacją Ministra Kultury i Dziedzictwa Narodowego. 

Jednym z najważniejszych elementów karty AZP jest określenie dla znalezionych śladów działalności człowieka  konotacji kulturowo-historycznej, czyli innymi słowy dokonanie klasyfikacji funkcjonalno-kulturowo-chronologicznej w oparciu o zebrane fakty, czyli na przykład fragmenty ceramiki, znalezione narzędzia i inne przedmioty, które archeologowi pozwalają określić wiek badanych osad, cementarzysk, itp. Aby przekaz był uniwersalny i czytelny dla każdego naukowca w Polsce do jego formułowania posługujemy się wykazami pojęć opracowanymi w Narodowym Instytucie Dziedzictwa. Taka standaryzacja ma niebagatelne znaczenie, gdyż czyni bazę danych uniwersalnym źródłem informacji nie tylko w ramach projektu, z którego pochodzi, ale dla wszystkich zainteresowanych analizą wydarzeń, które miały miejsce w przeszłości na terenach, z których pochodzą zebrane dane. 

Opracowano trzy zbiory zawierające pojęcia służące do określania chronologii (okresu dziejów, z których pochodzi obiekt osadniczy - na przykład epoka kamienia, epoka brązu, średniowiecze, nowożytność); jednostki kulturowej, która informuje, że dany przedmiot wykazuje podobieństwo w ramach pewnej grupy wskaźników; funkcji - określenie roli obiektu - na przykład cmentarzysko, osada, grodzisko, itd. Każdy z wykazów jest zapisywany w osobnej tabeli, gdyż ich struktura jest odmienna. Konstrukcja schematu bazy danych obsługiwanego przez QAZP2 wynika z tego, że ma ona dwa podstawowe zadania: 
  1. dostarczać informacje w celu ich wyświetlania,
  2. dostarczać informacje w celu ich przetwarzania i analizy.
Określenia każdej z właściwości (chornologii, jednostki kulturowej i funkcji) można dokonać na kilku poziomach szczegółowości. Na przykładzie chronologii wyróżnić można takie dwa: pierwszy poziom - epoka - na przykład epoka kamienia, epoka żelaza oraz drugi poziom - okres epoki - paleolit (czyli starsza epoka kamienia), II okres epoki brązu, Halsztad C (okres epoki żelaza). Użytkownik zgodnie ze swoimi umiejętnościami, wiedzą i możliwościami określa w ten sposób chronologię dla każdego znalezionego przedmiotu używając pojęć z pierwszego poziomu (mniej szczegółowo) albo drugiego (bardziej szczegółowo). Ma to znaczenie z uwagi na analizę danych, gdyż okres wpływów rzymskich różni się od okresu halsztackiego. Natomiast w przypadku wyświetlania, zwłaszcza w formie karty KEZA, która w dobie współczesnych technologii jest już tylko wyrazem zacofania polskich służb konserwatorskich, jest mniej znaczące. Stąd w każdej tabeli wykazów znajduje się pole skrót zawierajace wartość, która potem jest umieszczana na wydruku. Z kolei w kolumnie NAZWA zapisuje się wartości, które nie są pełną nazwą okresu chronologicznego, ale wyświetlona jest zrozumiała dla archeologa. Na przykład pisząc wartość HA C domyślamy się, że chodzi o Epokę żelaza - okres halsztacki C, z kolei nazwa Grupa mątewska wskazuje na neolityczną kulturę pucharów lejkowatych i to jest trzeci poziom dokładności w określeniu jednostki kulturowej. 
Taka konstrukcja jest wygodna w analizie danych, gdyż wszukując wszystkie ślady związane z okresem Neolitu nie trzeba znać ich szczegółowego oznaczenia, czyli zbędna jest wiedza, czy osada wiąże się z kulturą pucharów lejkowatych, kulturą amform kulistych, czy grupą radziejowską, gdyż wszystkie mieszczą się w zbiorze Neolit. Z kolei szukając faktów dotyczących grupy mątewskiej nie zajmujemy się kulturą ceramiki wstęgowej, ani pozostałymi grupami kultury pucharów lejkowatych.

ś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, 24 listopada 2012

Drukowanie rozbudowanej tabeli w Qt4

Tydzień temu pisałem o drukowaniu dokumentów w Pythonie stosując metody biblioteki Qt4. Wszystko w kontekście tworzenia Kart Ewidencji Zabytków Archeologicznych (KEZA), które wypełnia się przede wszystkim na okoliczność prowadzenia badań w ramach projektu Archeologicznego Zdjęcia Polski.
Przypatrując się wzorcowej karcie KEZA można zauważyć, że faktycznie jest to bardzo rozbudowana tabela ze scalonymi komórkami. Eksperyment przeprowadzony przy pomocy Excela wykazał, że podobny efekt można osiągnąć stosując tabelę z 60 kolumnami i 35 wierszami równej wysokości, która wypełnia arkusz A4.

 Wstawianie tabeli

To okazało się szczęśliwym zrządzeniem losu, ponieważ dzięki temu i wykorzystaniu klas QTextDocument i QTextCursor nie musiałem implementować własnoręcznie funkcji rysującej tabelę. Zamiast tego mogłem wykorzystać metodę insertTable(wiersze, kolumny, format) klasy QTextCursor. Znaczenie dwóch pierwszych parametrów powinno być oczywiste. W pierwszym podaje się liczbę wierszy, w drugim liczbę kolumn. W przypadku tworzenia karty KEZA istotny jest trzeci, za pomocą którego określa się właściwości tabeli, takie jak obramowanie (border-style), odległość między obramowaniem a zawartością komórki (cellpadding). Te i inne parametry określa się w obiekcie klasy QTextTableFormat, podawanym jako trzeci argument metody insertTable. Po wykonaniu zwraca ona obiekt klasy QTextTable.

Przykład

Kod programu Pythona rozpoczynając od inicjalizacji klasy QTextDocument do wstawienia nowej tabeli mógłby wyglądać następująco:

doc = QTextDocument()
cur = QTextCursor(doc)
fmt = QTextTableFormat()
fmt.setCellPadding(1)
fmt.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
keza = cur.insertTable(35,60,fmt)

W sześciu liniach została utworzona duża tabela, zawierająca ponad 2100 komórek. Teraz kwestią pozostaje ich scalenia w taki sposób, żeby po wszystkim przypominała wzór karty KEZA. Do połączenia grupy komórek można zastosować metodę mergeCells(y, x, wysokosc, szerokosc) klasy QTextTable, gdzie argumenty y oraz x  oznaczają lokalizację pierwszej komórki w grupie łączonych (licząc od lewego górnego narożnika), a wysokość i szerokość to odpowiednio liczba komórek w pionie i w poziomie, które mają być scalone. Chcąc połączyć 6 komórek w górnym lewym rogu tabeli o wymiarach 3 x 2 trzeba wykonać polecenie keza.mergeCells(0,0,2,3). W przypadku generowania karty KEZA takich operacji jest zbyt dużo, aby każdą z osobna zapisywać jako wywołanie powyższej funkcji, dlatego zamiast tego stosuję prosty schemat w postaci pliku CSV, który zawiera definicję każdej docelowej komórki.

Formatowanie komórek

Na koniec pozostaje problem, jak odwołać się do wybranej komórki i wstawić do niej zawartość. Za pomocą metody cellAt(y,x) klasy QTextTable uzyskuje się referencję do komórki o podanych współrzędnych, którą reprezentuje klasa QTextTableCell. Metodą firstCursorPosition() można ustawić w niej kursor (dla przypomnienia reprezentuje go klasa QTextCursor) a następnie użyć go do wstawienia np. tekstu przy pomocy insertText(tekst). To nie wszystko każdą z komórek można formatować. Do tego potrzeba uzyskać informacje o jej bieżącym wyglądzie przy pomocy metody format() klasy QTextTableCell, a następnie stosując odpowiednie metody zmienić formatowanie. Na przykład metoda setBackground(Qt.yellow) zmieni tło komórki z białego na żółty. Na koniec należy to formatowanie jawnie zaplikować do komórki wywołując metodę setFormat. Bez tego zmiana tła będzie bezskuteczna.

Kontynuacja przykładu

keza.mergeCells(0,0,2,3)
grupa = keza.cellAt(0,0) # pobiera scaloną przed chwilą grupę komórek
gf = grupa.format() # formatowanie komórek
gf.setFont(QFont('Times',8,QFont.DemiBold))
grupa.setFormat(gf) # jawne wprowadzenie formatowania
grupa.firstCursorPosition().insertText('QAZP2') # zawartość komórki

drukarka = QPrinter()
doc.print_(drukarka) # wydrukowanie dokumentu na urządzeniu.

niedziela, 18 listopada 2012

Programowanie w Qt4: drukowanie tabel

Obecnie na "tapecie" mam problem generowania Kart Ewidencji Zabytków Archeologicznych (w skrócie KEZA), która jest formą archiwizowania informacji pochodzących z badań archeologicznych. Jej koncepcja wiąże się z początkami programu Archeologicznego Zdjęcia Polski, którego celem jest inwentaryzacja śladów osadnictwa w celu zapewnienia im ochrony konserwatorskiej. Jej wzór zmieniał się z biegiem lat, przy czym najważniejszym założeniem było to, by wszystkie informacje mieściły się na jednej kartce A4, co miało uzasadnienie w czasach "analogowych" metod archwizowania danych (to znaczy polegających na maszynowym wypełnianiu kart i wkładaniu do teczek czy segregatorów). Ponieważ do sprawozdania z badań archeologicznych muszą zawsze być dostarczone wydrukowane karty KEZA, to oczywiste jest, że QAZP2 musi także udostępniać taką funkcjonalność.
W najprostszej postaci wszystko czego potrzebujemy do drukowania przy pomocy Qt4 to dwie klasy: QPainter oraz QPrinter. Przykład ich zastosowania może wyglądać następująco:
drukarka = QPrinter()
drukarka.setOutputFormat(QPrinter.PdfFormat) # rodzaj drukarki
drukarka.setOutputFileName("nowy_plik.pdf")
painter = QPainter()
painter.begin(drukarka)
painter.drawText(10, 10, 'Test') # wydrukowanie tekstu
drukarka.newPage() # wysuniecie pierwszej strony
painter.drawText(10, 10, 'Test 2') # tekst na drugiej stronie
painter.end()

W w takiej formie drukowanie nie wiąże się z żadną filozofią: klasa QPrinter reprezentuje uniwersalny interfejs urządzenia, który umożliwia drukowanie do pliku PDF, na kartkach papieru i inne. Z kolei QPainter wykorzystując ten kanał wspomaga programistę w pisaniu na "urządzeniu" tekstu, rysowaniu figur geometrycznych albo drukowaniu obrazków.
Ponieważ karta KEZA to faktycznie tabela, to powższy kod byłby znacząco bardziej skomplikowany i zwiększający prawodopodobnieństwo wystąpienia błędów. Aby procedura drukowania była trochę bardziej abstrakcyjna, to QAZP2 zamiast klasy QPainter została wykorzystana inna - QTextDocument, która jak sama nazwa wskazuje jest używana do reprezentowania dokumentów. Te mogą być tworzone przez użytkownika, który wpisuje zdania w polu tekstowym, albo automatycznie przy pomocy klasy QTextCursor, która służy do określania miejsca, w którym bieżący dokument jest edytowany i wstawiania do niego różnych obiektów - ciągów znaków, tabel, obrazków, których kształ można określać za pomocą znaczników HTML. Gdy dokument jest już gotowy wystarczy wywołać funkcję print_(drukarka) klasy QTextDocument do wysłania go na urządzenie wskazane w parametrze drukarka. W tej metodzie zawartość dokumentu jest tłumaczona na ciąg poleceń takich jak w przykładzie powyżej.
To podejście w odróżnieniu od "niskopoziomowego" ma tą zaletę, że pozwala się skupić na zawartości dokumentu i jego wyglądzie. To w jaki sposób zostanie ona przełożona na papier albo plik PDF pozostaje w gestii klasy QTextDocument. Jak zostało ono wykorzystane w przypadku generowania karty KEZA opiszę następnym razem.

sobota, 10 listopada 2012

Przepis na PostGIS



Podstawą działania QAZP2 jest przestrzenna baza danych. Informacje o śladach działalności człowieka, a także o prowadzonych badaniach archeologicznych łączy się z przestrzenią po przez dodanie do nich współrzędnych geograficznych. W przypadku archeologii jest to kluczowa informacja, gdyż umożliwia analizę danych w kontekście krajobrazu i środowiska, które prawdopodobnie miały niebagatelny wpływ na decyzje podejmowane przez dawnych osadników.


Zanim przejdę do omówienia schematu bazy danych, do którego dostosowany jest QAZP2, w kilku zdaniach opiszę przygotowywanie systemu PostgreSQL do pracy z bazami przestrzennymi. Nie zamierzam szczegółowo omawiać poszczególnych poleceń - lepiej ode mnie robi to bogata dokumentacja. Zgodnie z tytułem - to ma być przepis, który szybko pozwoli upiec ciastko ;). Będę pisał z perspektywy użytkownika Linuksa, a dokładnie Debiana i zakładam, że czytelnicy posiadają podstawową wiedzę na temat posługiwania się tym systemem.

1. Instalacja i konfiguracja PostgreSQL


# apt-get install postgresql postgis

To polecenie chyba nie wymaga komentarza. Po kilku albo kilkunastu minutach oczekiwania (w zależności od prędkości połączenia) aktualna wersja oprogramowania - czyli system PostgreSQL i jego rozszerzenie Postgis powinno znaleźć się na dysku i zostać zainstalowane i uruchomione.

# cd /etc/postgresql/{wersja}/main/
# cp pg_hba.conf pg_hba.bak
# echo "local all all password" >> /etc/postgresql/{wersja}/main/pg_hba.conf
# /etc/init.d/postgresql restart

W domyślnej konfiguracji z bazą może się połączyć użytkownik root albo specjalnie tworzony do tego celu postgresql. A więc przed nawiązaniem połączenia z bazą i wykonaniem polecenia SELECT, UPDATE, CREATE itp. należy zalogować się jako root albo postgresql (ten drugi ma zdefiniowane domyślne hasło postgresql). Osobiście wolę inne podejście, w którym użytkownika i hasło podaje się w chwili łączenia z bazą danych. Ma ono dodatkowe uzasadnienie: w sytuacji, w której będziemy próbowali się połączyć w QGIS z bazą jako inny użytkownik, na przykład milosz, w domyślnej konfiguracji Postgre odrzuci nasze rządanie. Dlatego do pliku pg_hba.conf dodajemy następujący wiersz local all all password (trzecia linia). Żeby zabezpieczyć się przed uszkodzeniem tego pliku, wcześniej (druga linia) należy wykonać jego kopię zapasową. Ta operacja wymaga zrestartowania systemu, co czyni polecenie w lini czwartej.


To jest najprostsza konfiguracja, jaką można sobie wyobrazić, ale w zupełności wystarcza do lokalnego testowania i korzystania z systemu PostgreSQL.

# createuser -U postgresql -W -d -P tester

Na koniec tworzymy nowego użytkownika o nazwie tester, który będzie uprawniony do tworzenia nowych baz danych (przełącznik -d). Przełącznik -P oznacza, że w chwili tworzenia użytkownika zostaniemy poproszeni o nadanie mu hasła. Przełączniki -U i -W są standardowe dla każdego polecenia PostgreSQL. Pierwszym wskazujemy tego użytkownika, jako który łączymy się do bazy w celu utworzenia nowego - w tym przypadku tym użytkownikiem jest wspominany już postgresql; użycie drugiego spowoduje, że przed dodaniem nowego, trzeba będzie podać hasło użytkownika postgresql.

2. Tworzenie bazy PostGIS

# createdb -O tester -h localhost -p 5432 -U tester -W azp2
# createlang plpgsql -U tester -W azp2

Kiedy nowy użytkownik jest już dodany, czas na utworzenie bazy danych. W pierwszym poleceniu tworzymy nową bazę o nazwie azp2, której właścicielem będzie tester (przełącznik -O). Po wykonaniu drugiej komendy w bazie azp2 będzie można tworzyć procedury w języku PL/SQL. Przełączniki -W i -U mają takie samo znaczenie, jak przy dodawaniu nowego użytkownika.

# cd /usr/share/postgresql/{wersja_postgre}/contrib/postgis-{wersja_postgis}
# psql -f postgis.sql -U tester -W azp2
# psql -f spatial_ref_sys.sql -U tester -W azp2

Na koniec do bazy azp2 trzeba dodać procedury, których używamy np. do dodawania współrzędnych, wyszukiwania na ich podstawie, itd., które są zaimplementowane w języku PL/SQL, o którym pisałem powyżej. Polecenia, które dodają wspomniane procedury znajdują się w katalogu utworzonym w chwili instalacji Postgis. Przechodzimy do niego w pierwszej lini, a następnie wywołujemy narzędzie psql podając jako parametr plik postgis.sql, który zawiera definicje procedur SQL. Do pracy z przestrzenną bazą danych przydatne będzie także dodanie definicji systemów odniesienia, które są używane do konwertowania współrzędnych. To także robimy za pomocą polecenia psql podając jako parametr plik spatial_ref_sys.sql.
I tyle. Baza przestrzenna jest gotowa do pracy. Od tej chwili można już tworzyć tabele ze współrzędnymi geograficznymi. Ale o tym innym razem.






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.

sobota, 20 października 2012

Kontekst

Zanim zagłębię się w problemy technologiczne, chciałbym krótko nakreślić ich kontekst, czyli powód dla którego jest rozwijany program QAZP2. Historia rozpoczyna się w 2010 roku, kiedy w związku z rozpoczęciem projektu naukowego w Instytucie Prahistorii UAM, finansowanego ze środków Narodowego Instytutu Dziedzictwa, pojawiła się potrzeba ewidecjonowania wyników badań w relacyjnej bazie danych.  Ważnym składnikiem zapisywanych w niej informacji jest tak zwany komponent przestrzenny, czyli współrzędne geograficzne. W ten sposób wyniki badań mogą być analizowane w kontekście środowiska i krajobrazu, co ma się przyczyniać do lepszego wyjaśniania decyzji podejmowanych przez ludzi w przeszłości.
Jako kontener do przechowywania danych został wybrany system PostgreSQL wyposażony w rozszerzenie PostGIS, które to umożliwia zapisywanie informacji przestrzennych.
Biorąc pod uwagę marny stan finansów polskiej nauki i instytucji zainteresowanych wykorzystywaniem takich informacji do ochrony zabytków w Polsce podjęto decyzję, że wszystkie narzędzia powinny być bezpłatne i tym samym obniżyć próg wejścia. Dlatego do gromadzenia danych jest używany wspomniany PostgreSQL+PostGIS, a do ich wyświetlania program QuantumGIS (QGIS). Ta aplikacja umożliwia proste wprowadzanie danych przestrzennych i tekstowych, jednak ten mechanizm jest niewystarczający do wprowadzania szczegółowych informacji pochodzących z propspekcji terenowej. Rozbudowany formularz do wprowadzenia danych jest realizowany właśnie jako QAZP2.
Zgodnie z aktualnymi założeniami ma umożliwiać wprowadzanie informacji o miejscach, czyli punktach rejestrowanych za pomocą odbiornika GPS; trasach - czyli zapisu drogi, która została pokonana na ziemii albo w powietrzu w czasie wykonywania zdjęć lotniczych; stanowiskach - czyli odnalezionych śladach działalności człowieka - np. osadach, obozowiskach, osadach itp. Oprócz "świeżych" danych w bazie mają się znaleźć także archiwalne pochodzące z badań AZP prowadzonych na przestrzenii lat, które mniej więcej od ostatniej dekady XX wieku są zapisywane w postaci elektronicznej. Ważne w tym wszystkim jest niedestrukcyjny charakter badań, czyli to, że nie prowadzą one do zniszczenia śladów osdanictwa w trakcie wykopalisk, a główną przesłanką do ich prowadzenia, oprócz wyjaśniania przeszłości, jest ich efektywna ochrona przed zniszczeniem.
W obecnej wersji 0.6, która jest dostępna od Poniedziałku do bazy danych można już wprowadzać wszystkie zakładane typy informacji, czyli miejsca, trasy i stanowiska. 

sobota, 13 października 2012

Początek

Przed chwilą zatwierdziłem ostatnie zmiany w QAZP2, czyli nowej wtyczki do QuantumGIS, której zadaniem jest wspomaganie użytkowników w zarządzaniu informacjami zbieranymi w trakcie nieinwazyjnych badań archeologicznych. I pomyślałem, że być może warto opisać doświadczenia z realizacji tego projektu, przede wszystkim po to, by nie umknęły mej pamięci, ale też ze względu na to, że problematyka tworzenia rozszerzeń do QuantumGIS jest w polskojęzycznym internecie rzadko omawiana.
W związku z tym tworząc ten dziennik chciałbym poruszyć następujące kwestie:
  1. Wykorzystywanie API QuantumGIS.
  2. Tworzenie aplikacji z zastosowaniem biblioteki Qt.
  3. Zarządzanie danymi archeologicznymi i ich przetwarzanie. 
Na tą chwilę przychodzą mi do głowy tylko te trzy punkty, ale w każdej chwili lista może ulec rozszerzeniu. Będę starał się pisać na tyle regularnie, na ile pozwolą mi na to obowiązki zawodowe i czasami przemijające twórcze natchnienie ;). W ramach wstępu w kolejnym poście opiszę krótko projek, w ramach którego narodziła się idea utworzenia rozszerzenia QAZP(2)