Scott Tiger Tech Blog

Blog technologiczny firmy Scott Tiger S.A.

OpenStreetMap – notatki z książki

Autor: Piotr Karpiuk o wtorek 30. Październik 2012

Poniżej notatki z kolejnej książki o OpenStreetMap, uzupełniające notatki z poprzedniej książki.

Wprowadzenie

Tworzenie mapy świata

Wiele miast jest już w OpenStreetMap zmapowanych do poziomu szczegółowości nieosiągalnego przez produkty Google’a, Yahoo czy Microsoftu. Co więcej, dane OSM są zwykle bardziej aktualne niż jakakolwiek mapa drukowana i wiele map dostępnych w sieci.

Niektóre obszary zostały w dużym stopniu zmapowane dzięki uprzejmości firm i rządów, np. Niderlandy (duńska firma AND), USA (baza danych TIGER), podobnie Kanada czy Niemcy.

Wady OSM: projekt nie doczekał się jeszcze łatwego sposobu pokazywania zmian w danych mapy w przystępnej formie, tak aby wolontariusze widzieli co zmieniło się na ich obszarze. Ponadto, wycofywanie zmian jest obecnie bardziej kłopotliwe niż np. w Wikipedii, i często wymaga uciążliwej ręcznej pracy.

Idea współtworzenia map przez społeczność znalazła uznanie również w komercyjnych projektach, np. firma TomTom produkująca narzędzia nawigacyjne, kupiła firmę TeleAtlas (drugiego na świecie co do wielkości dostawcę danych mapowych), której program Map Share pozwala użytkownikom w ograniczonym stopniu modyfikować dane mapy. Google w 2008 roku udostępniło edytor Map Maker.

Sztuka kartografii polega głównie na wyborze właściwego zbioru obiektów z bazy danych, i podjęciu decyzji jak one powinny być reprezentowane na mapie (kolor, styl, itp.).

Światowa społeczność GISowa składa się głównie z osób używających GISów profesjonalnie. Oprogramowanie GISowe jest potężne, ale trudne do opanowania przez laików, a ponadto często sporo kosztuje.

Choć OSM nie jest GISem, istnieje kilka mostów łączących te dwa światy. Przykładowo dane OSM mogą być przekonwertowane do shapefiles lub zaimportowane do bazy danych PostGIS – oba formaty są bardzo powszechne w świecie GIS.

OpenStreetMap w sieci

Główny cel OSM to zbieranie danych geograficznych i udostępnianie ich każdemu w surowej formie. Ponadto, projekt oferuje również kilka sposobów renderowania map na podstawie swoich danych.

W mapie na stronie projektu możesz w prawym górnym rogu wybrać warstwę data layer, po czym kliknąć dowolny obiekt na mapie aby poznać jego szczegóły.

Najlepszy sposób na wydrukowanie mapy to eksport wybranego prostokąta do formatu PDF i wydruk tego dokumentu PDF.

Na witrynie www.openstreetbugs.org znajduje się mapa, na której możesz kliknięciem oznaczać (i opisywać) wszystkie problemy wymagające poprawienia. Przy odpowiednio dużym powiększeniu pojawia się link „RSS feed”, który pozwala subskrybować kanał RSS gdzie będą pojawiać się informacje o zmianach w aktualnie oglądanym obszarze.

Serwis www.openrouteservice.org (tylko Europa) pozwala interaktywnie wyznaczyć optymalną drogę pomiędzy dwoma wybranymi punktami na mapie, z uwzględnieniem środka lokomocji (samochód, rower, na piechotę), a nawet wskazać obszary które mają być omijane. Inny serwis tego typu to YOURS.

Społeczność OSM

Jeśli chcesz tylko wczytać dane OSM lub przejrzeć Wiki, nie musisz się rejestrować do projektu. Jednak aby wrzucić własne dane, potrzebujesz loginu i hasła, a nawet oddzielnych kont na wiki i stronie projektu. Wiki używane jest m.in. do koordynacji lokalnych prac. Wiele obszarów geograficznych ma własnestrony, gdzie zapisywane są postępy prac w terenie i dyskutowane są napotkane problemy.

Pomocne mogą być witryny help.openstreetmap.org (gdzie można zadawać pytania) oraz forum.openstreetmap.org.

Obiekty – co mapujemy

Obiekty rysowane na mapach OSM to tzw. map features. Każdy obiekt ma pozycję geograficzną, obszar i typ. Obiekty w OSM są reprezentowane przez wierzchołek (ang. node) lub linię (ang. way), w połączeniu z zestawem etykiet. Propozycje nowych etykiet są publikowane i dyskutowane na stronie Proposed Features i tam można nad nimi głosować. Strona Map Features zawiera zestaw zaakceptowanych etykiet.

Tworzenie i używanie map

OpenStreetMap na stronach WWW

Mapy Google są już standardem w sieci WWW. W żargonie OSM, ten rodzaj interaktywnej, skalowalnej mapki nosi nazwę slippy map.

Byłoby wspaniale gdyby istniały mapy każdego zakątka globu, w dowolnym odwzorowaniu i w dowolnej rozdzielczości. To jest teoretycznie możliwe dla danych OSM, jako że są to dane wektorowe. W praktyce jednak proces renderowania map – czyli konwersji surowych danych w obraz mapy – wymaga dużej mocy obliczeniowej (dla przyzwoitej wielkości mapy trwa to ładnych kilka sekund – wystarczająco długo aby nie dało się takiej usługi udostępnić wielu użytkownikom naraz). Dlatego też, mapy na użytek prezentacji w przeglądarkach są zwykle przygotowywane zawczasu i prezentowane jako obrazki rastrowe (ang. raster images), tj. bitmapy zaprojektowane do wyświetlania danych z określoną rozdzielczością.

Kafelki na użytek OSM są tworzone w odwzorowaniu sferycznym Mercatora (ang. spherical Mercator projection) i pokrywają Ziemię między 85 stopni szerokości geograficznej północnej a 85 stopni szerokości geograficznej południowej, co pozwala stworzyć idealnie kwadratową mapę świata. Takie odwzorowanie znacząco zniekształca mapę w okolicach biegunów, ale jest to niewielka cena w porównaniu z zaletami. Rozmiar pojedynczego kafelka wynosi 256×256 pikseli. Na poziomie przybliżenia 0 cały świat mieści się na jednym kafelku, każdy kolejny poziom mnoży liczbę kafelków razy 4 (na poziomie 2 jest 16 kafelków), najwyższy poziom to 18 (dla renderera Mapnik). Średni rozmiar kafelka to ok. 2 KB.

Jeśli zechcesz wyliczyć sobie współrzędne kafelka (x, y) dla danej pozycji GPS (lat, lon) i danego poziomu przybliżenia (z), można użyć zaprezentowanych na wiki procedur (w różnych językach programowania).

Aby wyświetlić mapę w przeglądarce, konieczna jest współpraca trzech komponentów: renderer (ang. renderer engine) tworzy bitmapowe kafelki w oparciu o wektorowe dane OSM, serwer kafelków (ang. tile server) udostępnia je przeglądarce, a kod JavaScriptu w przeglądarce ładuje je i łączy w spójną mapę na ekranie.

Na danych OSM mogą pracować różne renderery – najpopularniejszym z obecnie stosowanych jest darmowy Mapnik, ale można też użyć otwartoźródłowych pakietów takich jak UMN Mapserver, jak i narzędzi komercyjnych.

Serwer kafelków Mapnik (ang. Mapnik Tile Server)

Serwer kafelków Mapnik jest oparty na bazie danych PostGIS, na bieżąco aktualizowanej najnowszymi danymi OSM. Renderer używa danych bazy i kilku dodatkowych zewnętrznych zasobów, aby wyprodukować kafelki. Dla serwera tile.openstreetmap.org klient może pobrać kafelek o współrzędnych x,y na poziomie przybliżenia z podając URL http://tile.openstreetmap.org/z/x/y.png.

Wyprodukowanie z danych OSM kafelków dla całego świata trwa kilka dni. Ogólnie nie jest dobrym pomysłem produkowanie wszystkich kafelków dla całej planety, ponieważ relatywnie niewielki ich podzbiór będzie w rzeczywistości oglądany przez użytkowników; większość serwerów generuje pełne pokrycie kafelkami dla poziomów przybliżenia od 0 do 8 (czasem 10), a wszystkie pozostałe produkuje na żądanie. Serwer kafelków Mapnik używa modułu Apache o nazwie mod_tile i programu o nazwie renderd aby zapewnić, że każdy brakujący kafelek zostanie wygenerowany gdy będzie potrzebny.

Różne serwery kafelków wdrażają różne strategie aktualizowania kafelków, w zależności od częstotliwości żądań, częstotliwości aktualizacji surowych danych, a także od poziomu przybliżenia. Możliwe jest analizowanie plików aktualizacji surowych danych, aby ustalić które kafelki wymagają przegenerowania – narzędzie osm2pgsql potrafi wygenerować listę zmodyfikowanych kafelków. Niektóre serwery regularnie przegenerowują wszystkie kafelki w cache’u niezależnie od tego czy coś się na nich zmieniło.

Oprócz stale modyfikowanych surowych danych OSM, serwer kafelków Mapnik używa także wstępnie przetworzonego zbioru danych linii brzegowej aktualizowanego na podstawie danych OSM raz na tydzień lub raz na dwa tygodnie. Jest to potrzebne aby wyznaczyć zwarte obszary wody, co wymaga dużej ilości mocy obliczeniowej i nie może być wykonywane na bieżąco. Stąd też, w odróżnieniu od innego rodzaju zmian, zmiany w linii brzegowej pojawiają się na mapach z dużo większym opóźnieniem.

Kafelki mogą być także dostępne na dowolnych poddomenach (a.tile.openstreetmap.org, b.tile.openstreetmap.org i tak dalej). To jest sztuczka używana na większości serwerów kafelków, która pozwala przyspieszyć jednoczesne ładowanie dużej ilości obrazków w przeglądarce (przeglądarka ma ograniczenie na liczbę możliwych połączeń z jednym serwerem, a poddomeny pozwalają obejść to ograniczenie bo każda z nich z punktu widzenia przeglądarki to osobny host).

Serwer bazujący na module mod_tile można zmusić do przegenerowania kafelka po dodaniu do URLa sufiksu /dirty. Można także otrzymać status każdego kafelka podając sufiks /status, aby dowiedzieć się czy kafelek został przegenerowany od czasu ostatniej aktualizacji bazy danych surowych.

Po stronie przeglądarki

Jeśli na swojej witrynie używasz biblioteki Google Maps, to można łatwo podmienić kilka linijek kodu JavaScript, aby zmusić bibliotekę do wyświetlania danych z serwera kafelków OSM (jako że używa tego samego układu współrzędnych). Tym niemniej, zdecydowana większość użytkowników danych OpenStreetMap do prezentacji mapy w przeglądarce używa otwartoźródłowej biblioteki JavaScript o nazwie OpenLayers. Co więcej, za jej pomocą można renderować nie tylko kafelki Mapnika, ale również mapy z serwerów Google, Yahoo, Binga, czy WMS (te ostatnie są popularne w kręgach osób związanych z systemami GIS). Biblioteka ma również kilka dodatkowych możliwości, jak np. rysowanie figur wektorowych czy obsługa formatu KML.

Ściąganie danych

Plik planety zawiera jedynie ostatnie wersje obiektów. Jeżeli z jakiegoś powodu ktoś koniecznie chce mieć w pliku planety historię każdego obiektu, niech ściąga (rzadziej przygotowywaną) wersję z historią (tzw. Full History Planet lub History Dump).

Łatki z dziennymi zmianami mają wielkość rzędu 0.5% pliku planety, zmiany godzinne rzadko przekraczają 0.1%.

Rysowanie map: renderer Mapnik

Mapnik to napisany przez Artema Pavlenko, niezależny od OSM program, który przetwarza surowe dane geograficzne w obrazek z mapą. Mapnik nie może bezpośrednio pracować na plikach OSM (w odróżnieniu od nieużywanego już Osmarendera) i wymaga najpierw umieszczenia danych OSM w bazie danych PostGIS. Poza tym Mapnik może pracować na plikach ESRI shapefile, OGR/GDAL i innych źródłach danych.

Zasadniczo Mapnik generuje pliki PNG. Może również generować pliki SVG, ale zdecydowanie nie jest to rozwiązanie lepsze niż użycie Osmarender do tego celu. Ponieważ Mapnik jest napisany w C++, wygenerowanie pojedynczego kafelka zajmuje mu jedynie ułamki sekundy (czas ten zależy od poziomu przybliżenia i ilości szczegółów na mapie). Mapnik udostępnia API w Pythonie.

Typowe użycie Mapnika wymaga zainstalowania bazy danych PostgreSQL i rozszerzenia PostGIS, które wzbogaca bazę o operacje geograficzne i typy takie jak punkty, linie i wielokąty – pozwala to np. wyliczyć punkt przecięcia linii lub sprawdzić czy dany punkt leży wewnątrz wielokąta. Dodatkowo, PostGIS pozwala tworzyć stosowne indeksy, dzięki czemu taka baza może być silnikiem aplikacji GISowych. PostGIS zajmuje się również konwersją danych geograficznych między różnymi odwzorowaniami kartograficznymi.

Po zainstalowaniu bazy należy zaimportować do niej plik OSM planety za pomocą programu osm2pgsql (jeśli chcemy później aktualizować dane, ważne jest użycie opcji --slim). Domyślnie, importowane są dane w odwzorowaniu Merkatora, dzięki czemu nie są potrzebne dodatkowe przekształcenia przy generowaniu kafelków. PostGIS na określenie odwzorowania kartograficznego używa pojęcia spatial reference system (SRS), przy czym każdy SRS ma swój identyfikator – odwzorowanie Merkatora jest znane jako SRS 3857.

Plik stylu Mapnika (ang. style file) to plik XML, który wymienia wszystkie źródła danych (bazy danych, pliki shapefile itd.) z których Mapnik ma składać mapę, a także wszystkie reguły i definicje wyglądu mówiące jakie dane wyciągać (na ogół jest to SQL-owe polecenie SELECT) i jak je rysować. Standardowy plik stylu ma ok. 8 tys. wierszy i używa encji zdefiniowanych w innych plikach zależnych.

Aby móc generować kafelki, oprócz bazy PostGIS i pliku stylu potrzebne są pliki definiujące linie brzegowe (ang. coastline files) – ściągany z Internetu i aktualizowany co kilka tygodni – oraz pliki ikonek (w SVNie).

Narzędzie Cascadenic upraszcza edycję pliku stylu Mapnika stosując łatwiejszą w użyciu składnię naśladującą znane ze świata WWW kaskadowe arkusze stylów (CSS) – tak utworzone pliki muszą być potem przekonwertowane na format zrozumiały przez Mapnika.

Hakowanie OSM

Tworzenie oprogramowania OSM

Obecnie wszystkie dane OSM są trzymane w jednej dużej, centralnej bazie danych. Prawdopodobnie nie będzie tak zawsze, ponieważ zbiór danych ciągle się rozrasta, podobnie jak przybywa wolontariuszy a więc i zapytań. W przyszłości trzeba będzie rozłożyć obciążenie na kilka maszyn, np. po jednej maszynie na każdy kontynent. Aby odciążyć centralny serwer, osoby chcące jedynie odczytywać dane zachęcane są do tworzenia serwerów lustrzanych na własny użytek – opracowana została odpowiednia procedura replikacji danych pozwalająca utrzymać precyzję synchronizacji rzędu kilku minut. Póki co nie ma żadnych publicznych, oficjalnych, dostępnych dla wszystkich serwerów lustrzanych.

Większość narzędzi do przetwarzania XML radzi sobie jedynie z małymi plikami, przez co nie nadaje się do danych OSM. W szczególności nie należy używać parserów XML typu DOM (wczytują całe drzewo do pamięci), tylko SAX (przetwarzają elementy XML po kolei generując zdarzenia).

Kod źródłowy większości narzędzi OSM znajduje się na serwerze SVN: svn.openstreetmap.org, z wyjątkiem kodu źródłowego strony głównej OSM (Ruby), który znajduje się w repozytorium Git (git.openstreetmap.org). Repozytoria mogą być czytane przez każdego. Dostęp do zapisu wymaga kontaktu z administratorem. Zanim wolontariusz wprowadzi jakieś zmiany do SVNa, powinien je przedyskutować na stosownych grupach dyskusyjnych; dla większych zmian będzie zachęcany do utworzenia gałęzi.

API

API OSM to dla bazy danych OSM okno na świat. Dla Internautów ma bezpośredniego dostępu do danych przez SQL, a jedynie przez API. Wszystkie zlecenia modyfikujące bazę wymagają uwierzytelniania, przy czym dostępne są dwa metody: HTTP Basic Auth oraz OAuth. Obecna wersja API 0.6 wymaga aby przed każdą modyfikacją otwierać zestaw zmian (ang. changeset), a jego ID musi być przekazywany w późniejszych zleceniach modyfikacji.

Inne API i serwisy webowe

OSM Extended API (XAPI) jest zmodyfikowaną formą standardowego API. Działa na własnej kopii pełnej bazy danych, aktualnej z dokładnością do paru minut. XAPI w porównaniu ze zwykłym API pozwala na wykonywanie bardziej skomplikowanych zapytań, i może zwracać wyniki zawierające więcej danych. URL żądania XAPI zaczyna się od http://www.informationfreeway.org/api/0.6/ i ukrywa się za nim wiele serwerów – przekierowanie następuje do tego, który w danej chwili wydaje się być najlepszym dla konkretnego zapytania. Serwer XAPI jest zaimplementowany w mało popularnym języku „M” (dawniej MUMPS) i używa bazy danych „GT.M”. Źródła serwera są publicznie dostępne, ale instrukcje instalacji są dość skąpe.

W szczególności jednym z możliwych zapytań jest zapytanie o historyczne wersje obiektu dopasowujące historyczne wersje obiektów zależnych – ten rodzaj zapytań nie da się realizować za pomocą zwykłego API. Tak więc zapytanie http://www.informationfreeway.org/api/0.6/way/ID/VERSION/full zwróci wskazaną drogę w podanej wersji wraz ze wszystkimi wierzchołkami drogi, przy czym wersja każdego wierzchołka będzie taka, jaka była w chwili, gdy bieżącą wersją drogi było VERSION.

XAPI może także pamiętać spersonalizowaną listę obserwowanych obiektów (ang. watchlist) i informować użytkownika poprzez RSS o zmianach w tych obiektach.

Nominatim to silnik geokodowania dla OSM. Używa on bazy danych PostGIS, ale używa nietypowej, charakterystycznej dla siebie struktury bazy.

Zapytanie o ulicę Kolektorską zwracające XML może wyglądać tak:

     http://nominatim.openstreetmap.org/search?format=xml&accept-languatge=pl&q=Kolektorska&polygon=1&addressdetails=1

Serwer ROMA (Read-Only Map API) jest skryptem CGI używającym bazy danych PostGIS wypełnionej danymi OSM, i wspiera tylko żądania pobierania obiektów z zadanego prostokąta, ale jest w tym wyspecjalizowany i robi to szybko, bo korzysta z indeksów PostGIS.

Serwer TRAPI (Tiles Read-Only API), zaimplementowany w Perlu, przechowuje dane OSM w systemie plików i zajmuje niewiele miejsca na dysku (ok. 5 razy więcej niż skompresowany plik planety). Serwer TRAPI również wspiera zapytania o obiekty zadanego prostokąta, ale zawsze zwraca dane dla kompletnego kafelka na poziomach 11-14.

Portal GeoNames to niezależna od OSM kolekcja dostępnych dla każdego danych geograficznych, z dostępem przez przeglądarkę WWW i usługę webową (ang. web service), a pliki bazy każdy może sobie ściągnąć. GeoNames zbiera informacje o lokalizacjach krajów, regionów i miast, a także o kodach pocztowych i hierarchii administracyjnej. Co prawda w bazie przechowywane są tylko punkty (a nie wielokąty), ale dane są wartościowe ze względu na nacisk kładziony na klasyfikację i hierarchię, które to aspekty nie zawsze bywają spójne w OSM.

Osmosis – narzędzie uniwersalne

Osmosis to uniwersalny program do filtrowania i konwersji danych OSM, napisany w Javie. Program nie wczytuje wszystkich danych do pamięci, lecz przetwarza je po kawałku, dzięki czemu jest w stanie obrobić dowolnie duże zbiory danych.

Zadania (ang. tasks) są połączone strumieniami danych (ang. data streams). Zasadniczo wszystkie strumienie danych produkowane przez zadanie są umieszczane na stosie w odpowiedniej kolejności, a zadanie konsumujące strumienie pobiera je ze stosu. Istnieją dwa główne rodzaje strumieni danych: strumienie encji (ang. entity streams, ES) przenoszące obiekty OSM, oraz strumienie zmian (ang. change streams, CS) przenoszące informacje o zmianach w tych obiektach. Obydwa strumienie mogą przenosić kilka wersji tego samego obiektu OSM. Strumień CS jest nazywany strumieniem zmian delta (ang. delta change stream) jeśli zawiera najmniejszy zbiór informacji pozwalających zmienić stan obiektu ze stanu A do stanu B, podczas gdy strumień zmian replikacji (ang. replication change stream lub history change stream) zawiera również wszystkie stany pośrednie.

Oprócz strumieni istnieje również pojęcie zbioru danych (ang. dataset, DS). Zbiór danych nie jest dostępny w formie strumienia, lecz mamy do niego dostęp swobodny (ang. random access) – zwykle zbiory są odzwierciedleniem bazy danych.

Odczyt i zapis baz danych

Dla MySQL i PostgreSQL, Osmosis wspiera schemat APIDB, który jest używany przez serwer API i przechowuje historyczne wersje wszystkich obiektów. Podczas odczytu bazy APIDB, Osmosis pilnuje aby wyciągać te wersje obiektów, które były bieżące gdy został uruchomiony. To oznacza że wynik eksportu danych jest poprawny nawet jeśli w trakcie trwania eksportu dane bazy były modyfikowane.

Innym rodzajem schematu jest schemat Simple, używający PostGIS. To jest hybryda oryginalnego APIDB i schematu generowanego przez osm2pgsql. Dla każdego obiektu generowane są tu wartości geometryczne, a Osmosis może wykonywać pewne operacje geometryczne, jak np. wyciąganie danych dla zadanego prostokąta.

Pliki zmian

Wielokrotne aplikowanie tego samego pliku zmian (ogólnie: zmian już wprowadzonych do bazy) jest operacją nieszkodliwą. Ponieważ procesy generowania pliku planety i plików zmian nie są synchronizowane, warto zacząć aplikować zmiany począwszy od dnia sprzed wygenerowania pliku planety.

Osmosis może być używany do ciągłego aktualizowania lokalnej bazy danych z serwera OSM. Program ustala jakie pliki zmian pojawiły się na serwerze od poprzedniego uruchomienia, po czym je ściąga i aplikuje. Do regularnego uruchamiania Osmosis można użyć systemowego narzędzia cron.

Inne możliwości

Zadania --re i --ri tworzą tekstowy raport o wczytanych danych. Opcje --lp i --lpc powodują, że Osmosis podczas pracy w regularnych odstępach wyświetla postęp prac. Opcje --b i --bc tworzą bufor danych pomiędzy dwoma zadaniami. Może to przyspieszyć wykonywanie programu na wieloprocesorowych maszynach, ponieważ bez bufora dane mogą być tylko przetwarzane sekwencyjnie nawet jeśli dostępnych jest wiele procesorów.

Uruchamianie własnego serwera OSM

Upewnij się, że PostgreSQL ma wystarczająco dużo miejsca na dysku dla wszystkich danych. Szacując z grubsza, potrzebujesz 15 do 20 razy więcej miejsca niż skompresowany plik planety. Import może trwać długo – plik planety ok. 2 dni na nowoczesnym sprzęcie.

Jeśli stawiasz własny serwer kafelków, aktualizacja danych bazy może zużywać na tyle dużo zasobów, że szybkość jednoczesnego generowania kafelków może spaść zauważalnie.

Możesz wygenerować wstępnie kafelki dla dużego obszaru pomocniczym programem render_list. Warto to zrobić, bo wygenerowanie kafelków na niższym poziomie często wymaga znaczącej ilości zasobów i czasu.

Kolejny problem to przegenerowywanie kafelków zmodyfikowanych po aktualizacji danych planety. Możesz regularnie przegenerowywać stare kafelki lub po prostu usuwać je z cache’a. Inna opcja to użycie Osmosis do ustalenia kafelków wymagających aktualizacji w wyniku aktualizacji danych, co można zrobić na kilka sposobów: skrypt Rubego expire.rb, osm2pgsql lub program render_expired z pakietu renderd.

Literatura

OpenStreetMap. Using and Enhancing the Free Map of the World, F.Ramm, J.Topf, S.Chilton, UIT Cambridge, 2011.

Share and Enjoy:
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Śledzik
  • Blip
  • Blogger.com
  • Gadu-Gadu Live
  • LinkedIn
  • MySpace
  • Wykop

Zostaw komentarz

XHTML: Możesz użyć następujących tagów: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>