====== Laboratorium 7 - województwa, powiaty, gminy, miejscowości ====== Plik {{:po:admin-units.csv}} zawiera informacje o jednostkach administracyjnych w Polsce wyodrębnione z mapy [[https://www.openstreetmap.org/#map=6/52.018/19.137|Open Street Map (OSM)]]. Dane są zapisane w formacie CSV, czyli wartości poszczególnych pól oddzielone są przecinkami. Każdemu rekordowi odpowiada jeden wiersz pliku. Naszym celem będzie wczytanie zawartości pliku do pamięci (listy) i implementacja różnych funkcji do selekcji elementów. ===== Opis pól ===== Plik csv można otworzyć w OpenOffice. Poniżej opisy pol *id -- numer rekordu w bazie danych (long), nie chcemy tego przechowywać *parent -- numer rekordu jednostki nadrzędnej (np. województwo dla powiatu). Zamiast numeru chcemy użyć referencji. Województwa nie mają jednostki nadrzędnej (pusty tekst) *name -- nazwa jednostki (string) *admin_level -- określa typ jednostki (int). Specyfikacja jest [[http://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative| tu (na końcu tabelki)]] *population -- liczba mieszkańców (double), czasem może być pusta *area -- powierzchnia (double), zawsze podana *density -- gęstość zaludnienia (double), czasem pusta *x1,y1,x2,y2,x3,y3,x4,y4,x11,y11 - 10 współrzędnych double. Długość i szerokość geograficzna pięciu punktów wyznaczających prostokąt, w którym zawarta jest jednostka //BoundingBox//. Pięciu - ostatnie dwie współrzędne są powtórzeniem pierwszej, ponieważ są to współrzędne linii łamanej. Tych danych jest za dużo, wystarczyłyby 4 współrzędne i tylu użyjemy. ===== Struktura klas ===== Struktura klas podana jest na rysunku {{ :po:admin-units.png?700 |Admin units}} Zadeklaruj wymagane klasy ''AdminUnitList'', ''AdminUnit'' i ''BoundingBox'' ===== Czytanie z pliku ===== Sprawdź, czy potrafisz przeczytać zawartość pliku za pomocą napisanej poprzednio klasy ''CSVReader'': *wczytaj rekord (wiersz) *pobierz zawartość wybranych pól i wydrukuj *przerwij odczyt po 100 rekordach, bo nie ma sensu drukować na ekranie wszystkiego ===== Klasa BoundingBox v.1 ===== Na razie zadeklaruj niezbyt funkcjonalną klasę ''BoundingBox''. W pierwszej wersji nie będzie przetwarzana public class BoundingBox { double xmin; double ymin; double xmax; double ymax; } ===== Klasa AdminUnit ===== Zadeklaruj klasę ''AdminUnit''. public class AdminUnit { String name; int adminLevel; double population; double area; double density; AdminUnit parent; BoundingBox bbox = new BoundingBox(); } Zauważ, że w odróżnieniu od zawartości pliku: *nie ma tu id *''parent'' nie jest liczbą ''long'' ale referencją. Podczas czytania jakoś to będzie trzeba przekonwertować. ===== toString() ==== Dodaj metodę toString() wypisującą podstawowe informacje (nazwa, typ jednostki, powierzchnia, itp.) ===== Klasa AdminUnitList ===== Klasa przechowuje listę obiektów typu ''AdminUnit''. ==== Zadeklaruj klasę ==== public class AdminUnitList { List units = new ArrayList<>(); } ==== Odczyt z pliku v.1 ==== Dodaj metodę ''public void read(String filename)''. /** * Czyta rekordy pliku i dodaje do listy * @param filename nazwa pliku */ public void read(String filename) { } Wewnątrz *użyj klasy ''CSVReader'' *czytaj kolejne rekordy (wiersze pliku) *po przeczytaniu utwórz obiekt AdminUnit i wypełnij jego atrybuty wartościami (na razie pomijając ''parent'') *dodaj do listy ==== Warto coś wypisać ==== Napisz trzy metody klasy ''AdmiUnitList'' /** * Wypisuje zawartość korzystając z AdminUnit.toString() * @param out */ void list(PrintStream out){ } /** * Wypisuje co najwyżej limit elementów począwszy od elementu o indeksie offset * @param out - strumień wyjsciowy * @param offset - od którego elementu rozpocząć wypisywanie * @param limit - ile (maksymalnie) elementów wypisać */ void list(PrintStream out,int offset, int limit ){ } /** * Zwraca nową listę zawierającą te obiekty AdminUnit, których nazwa pasuje do wzorca * @param pattern - wzorzec dla nazwy * @param regex - jeśli regex=true, użyj finkcji String matches(); jeśli false użyj funkcji contains() * @return podzbiór elementów, których nazwy spełniają kryterium wyboru */ AdminUnitList selectByName(String pattern, boolean regex){ AdminUnitList ret = new AdminUnitList(); // przeiteruj po zawartości units // jeżeli nazwa jednostki pasuje do wzorca dodaj do ret return ret; } Raczej podczas testowania używaj ''void list(PrintStream out,int offset, int limit )''. Regex (wyrażenie regularne) specyfikuje zaawansowane kryteria wyszukiwania [[http://www.vogella.com/tutorials/JavaRegularExpressions/article.html]]. Funkcja ''contains()'' sprawdza zawieranie się łańcuchów. W poniższych przykładach zawsze rezultatem będzie ''true'' System.out.println("województwo małopolskie".matches(".*małop.*")); System.out.println("województwo małopolskie".matches("^wojew.*")); System.out.println("województwo pomorskie".matches(".*skie")); System.out.println("województwo małopolskie".contains("małop")); **TODO** Wypisz wybrane jednostki wywołując ''selectByName()'' oraz ''list()''. Projekt interfejsu w stylu //lista zwraca listę obiektów spełniających kryteria// będziemy dalej rozwijać. To jest całkiem wydajne. Lista jest tablicą referencji (8-bajtowych wartości). Obiekty nie są kopiowane, więc listy nie zużywają dużo pamięci w porównaniu do obecnie dostępnych zasobów. ==== Odczyt z pliku v.2 ==== Przetwórzmy prawidłowo informacje o ''parent'' jednostce nadrzędnej. W pliku są zapisywane wartości ''long'', które chcemy zamienić na referencje. A więc potrzebne jest odwzorowanie //(mapa)// przypisująca ''Long'' id -> ''AdminUnit''. Potrzebne jest zapewne także odwzorowanie ''AdminUnit'' -> ''Long'' parentid. *Zbieraj te informacje podczas czytania rekordów *Po zakończeniu czytania ustaw odpowiednie referencje Niektóre rekordy (województwa) nie mają jednostki nadrzędnej. Przypisz im ''parent=null'' **Uwaga mapa ma służyć wyłącznie do odczytu. Nie ma być atrybutem klasy** ==== Brakujące wartości ==== Plik wejściowy powstał z przetwarzania danych mapy. Granice jednostek były zawsze określone, stąd znana jest powierzchnia oraz można wyznaczyć ''BoundingBox''. Niestety, liczba ludności nie zawsze jest podana. Może być jednak podana w jednostce nadrzędnej (parent) lub jej jednostce nadrzędnej. Napisz prywatną funkcję ''AdminUnitList'' o nazwie ''fixMissingValues()'' która: *dla każdego obiektu ''AdminUnit'', z brakującymi danymi ''population'' oraz ''density'' *przyjmie estymację ''density = parent.density'' *obliczy ''population = area*density'' Możesz dodać funkcję ''fixMissingValues()'' do klasy ''AdminUnits''. Alternatywnie, możesz napisać funkcję ''fixMissingValues(AdminUnit au)'' w ''AdminUnitList''. **Problem** :!:. W jednostce ''parent'' też może brakować wartości. Chyba funkcja ''AdminUnit.fixMissingValues()'' (lub ''fixMissingValues(AdminUnit au)'') powninna być rekurencyjna? ===== Jednostki potomne - odczyt z pliku v.3===== Dodaj w klasie ''AdminUnit'' zaznaczoną na diagramie relację ''children''. Jest to lista jednostek składowych (np. województwo składa się z powiatów, powiat z gmin.) Czyli deklaracja klasy powinna wyglądać następujaco: public class AdminUnit { String name; int adminLevel; double population; double area; double density; AdminUnit parent; BoundingBox bbox = new BoundingBox(); List children; // ... metody ... } Znowu konieczne będzie odwzorowanie (mapa) uzupełniana podczas odczytu lub budowana na podstawie wcześniej zdefiniowanych odwzorowań. Prawdopodobnie będzie potrzebne coś w rodzaju: Map> parentid2childid = new HashMap<>(); lub Map> parentid2child = new HashMap<>(); Druga wersja ma pewną zaletę - można od razu przypisać wartość do ''children'' w ''AdminUnit''.