Table of Contents

Laboratorium 8 - województwa, powiaty, gminy, miejscowości (kontynuacja)

Klasa BoundingBox v.2

Napisz docelową klasę BoundingBox. Jest to prostokąt otaczający jednostkę. Do zdefiniowania prostokąta wystarczą dwa przeciwległe punkty, zamiast pięciu.

Zaprojektuj BoundingBox tak, aby miał dwa stany:

Sam(a) zadecyduj, jak je odróżnić, np. stosując specjalne wartości Double.NaN albo dodając flagę pusty/niepusty.

public class BoundingBox {
    double xmin = ???;
    double ymin = ???;
    double xmax = ???;
    double ymax = ???;
 
    /**
     * Powiększa BB tak, aby zawierał punkt (x,y)
     * Jeżeli był wcześniej pusty - wówczas ma zawierać wyłącznie ten punkt  
     * @param x - współrzędna x
     * @param y - współrzędna y
     */
    void addPoint(double x, double y){
 
    }
 
    /**
     * Sprawdza, czy BB zawiera punkt (x,y)
     * @param x
     * @param y
     * @return
     */
    boolean contains(double x, double y){
        return false;
    }
 
    /**
     * Sprawdza czy dany BB zawiera bb 
     * @param bb
     * @return
     */
    boolean contains(BoundingBox bb){
        return false;
    }
 
    /**
     * Sprawdza, czy dany BB przecina się z bb
     * @param bb
     * @return
     */
    boolean intersects(BoundingBox bb){
        return false;
    }
    /**
     * Powiększa rozmiary tak, aby zawierał bb oraz poprzednią wersję this
     * Jeżeli był pusty - po wykonaniu operacji ma być równy bb
     * @param bb
     * @return
     */
    BoundingBox add(BoundingBox bb){
        return this;
    }
    /**
     * Sprawdza czy BB jest pusty
     * @return
     */
    boolean isEmpty(){
        return true;
    }
 
    /**
     * Sprawdza czy 
     * 1) typem o jest BoundingBox 
     * 2) this jest równy bb
     * @return
     */
    boolean equals(Object o){
        return true;
    }
 
    /**
     * Oblicza i zwraca współrzędną x środka
     * @return if !isEmpty() współrzędna x środka else wyrzuca wyjątek
     * (sam dobierz typ)
     */
    double getCenterX(){
        throw new RuntimeException("Not implemented");
    }
    /**
     * Oblicza i zwraca współrzędną y środka
     * @return if !isEmpty() współrzędna y środka else wyrzuca wyjątek
     * (sam dobierz typ)
     */
    double getCenterY(){
        throw new RuntimeException("Not implemented");
    }
 
    /**
     * Oblicza odległość pomiędzy środkami this bounding box oraz bbx
     * @param bbx prostokąt, do którego liczona jest odległość
     * @return if !isEmpty odległość, else wyrzuca wyjątek lub zwraca maksymalną możliwą wartość double
     * Ze względu na to, że są to współrzędne geograficzne, zamiast odległości użyj wzoru haversine
     * (ang. haversine formula)
     *
     * Gotowy kod można znaleźć w Internecie...
     */
    double distanceTo(BoundingBox bbx){
        throw new RuntimeException("Not implemented");
    }
 
}

Odczyt z pliku v.4

Opcjonalnie: Gdyby ktoś chciał zobaczyć BoundingBox na mapie...

Podczas czytania rekordów wydrukuj

System.out.printf(Locale.US,"LINESTRING(%f %f, %f %f, %f %f, %f %f,%f %f)", 
reader.getDouble("x1"),
reader.getDouble("y1"),
reader.getDouble("x2"),
reader.getDouble("y2"),
reader.getDouble("x3"),
reader.getDouble("y3"),
reader.getDouble("x4"),
reader.getDouble("y4"),
reader.getDouble("x5"),
reader.getDouble("y5")
);

Np. dla Krakowa będzie to

LINESTRING(19.7922353426102 49.9676667885393,19.7922353426102 50.1261382575516,20.2173454438402 50.1261382575516,20.2173454438402 49.9676667885393,19.7922353426102 49.9676667885393)

Skopiuj wynik i wklej na stronie

Kraków i Wieliczka

MULTILINESTRING((19.7922353426102 49.9676667885393,19.7922353426102 50.1261382575516,20.2173454438402 50.1261382575516,20.2173454438402 49.9676667885393,19.7922353426102 49.9676667885393),
(20.0027443903341 49.9709019769598,20.0027443903341 50.004649838968,20.0889354062256 50.004649838968,20.0889354062256 49.9709019769598,20.0027443903341 49.9709019769598))

Oczywiście możesz też wypisać odpowiedni tekst na podstawie współrzędnych w BoundingBox, a nawet dodać metodę, która to robi do klasy. Metoda np. może nazywać się getWKT() - WKT (Well Known Text) to format reprezentacji wektorów https://en.wikipedia.org/wiki/Well-known_text.

Szukamy sąsiadów dla AdminUnit

Zdefiniujmy wpierw pojęcie sąsiada. W typowym przypadku sąsiadem danej jednostki ma być jednostka

Miejscowości mogą być oddalone i ich granice nie będą się pokrywały. Stąd kryterium pokrywania nie wystarczy. Przyjmiemy więc, że sąsiadujące miejscowości położone są w odległości mniejszej niż pewien konfigurowalny parametr maxdistance, np. równy 15 km. Odległości mają być liczone od środków BoundingBox

Napisz funkcję klasy AdminUnitList

   /**
     * Zwraca listę jednostek sąsiadujących z jendostką unit na tym samym poziomie hierarchii admin_level. 
     * Czyli sąsiadami wojweództw są województwa, powiatów - powiaty, gmin - gminy, miejscowości - inne miejscowości
     * @param unit - jednostka, której sąsiedzi mają być wyznaczeni
     * @param maxdistance - parametr stosowany wyłącznie dla miejscowości, maksymalny promień odległości od środka unit, 
     *                    w którym mają sie znaleźć punkty środkowe BoundingBox sąsiadów
     * @return lista wypełniona sąsiadami
     */
    AdminUnitList getNeighbors(AdminUnit unit, double maxdistance){
        throw new RuntimeException("Not implemented");
    }

Zademonstruj w kodzie (np. testach), że jesteś w stanie:

Oblicz czas wyszukiwania sąsiadów

        double t1 = System.nanoTime()/1e6;
        // wywołanie funkcji
        double t2 = System.nanoTime()/1e6;
        System.out.printf(Locale.US,"t2-t1=%f\n",t2-t1);

Usprawnienie algorytmu - punkty dodatkowe

Najprostsza metoda realizacji algorytmu wyszukiwania sąsiadów polega na iteracji przez wszystkie elementy listy i sprawdzenie, czy są sąsiadami (zgodnie ze zdefiniowanymi kryteriami). Gdybyśmy chcieli wyznaczyć sąsiadów dla wszystkich obiektów AdminUnit złożoność obliczeniowa będzie $O(n^2)$. Biorąc pod uwagę, że n~15000, da to 22500000 testów sąsiedztwa, czyli raczej dużo…

Dysponujemy jednak hierarchią obiektów (parent→children) - potencjalnie drzewem z nieistniejącym korzeniem, którego potomkami są województwa, dalej powiaty i gminy. Jeżeli z niej skorzystamy - pozwoli to nam zapewne przyspieszyć obliczenia.

Uwaga - nie musimy tworzyć nieistniejącego korzenia. Wystarczy poprawnie zainicjować algorytm przeszukiwania.

Zaimplementuj algorytm wyszukiwania podobny do dla R-tree opisany w Wikipedii (sekcja Search).

  1. Dla województwa, powiatu lub gminy użyj zdefiniowanego BoundingBox.
  2. Dla miejscowości - po prostu rozszerz środek o maxdistance w każdym kierunku (ale zastosuj kryterium odległości).
  3. Porównaj czasy wykonania dla wersji liniowej i wykorzystującej drzewo parent→children.
  4. :!: Warto przetestować sąsiadów dla gmin położonych na granicy województw, np. gmina Brzeszcze: https://geoportal360.pl/12/oswiecimski/brzeszcze-121302/ lub https://geoportal360.pl/24/bielski/wilamowice-240209/