Aplikacja GIS – Spring, GDAL i MongoDB

MongoDB

Ok, od strony bazy to banał, już przerabiany. Zapytanie dla zbioru punktów wygląda tak samo jak dla pojedynczego.

Jedyny problem jest taki, że część gmin ma pole geometry typu „Polygon”, a część typu „MultiPolygon”. Różnica polega oczywiście na tym, że część gmin ma spójny obszar, a część nie.  Spring tego nie lubi, bo wbudowana konwersja typów będzie się wykrzaczać na coś takiego. Rozwiązaniem jest zamiana wszystkich Polygonów na MultiPolygon, z jednym Polygonem wewnątrz.

doc = db.gminy.find({'geometry.type':'Polygon'}).forEach(function(doc){
   set=doc;
set.geometry.type='MultiPolygon';
set.geometry.coordinates= [doc.geometry.coordinates];
db.gminy.update({'_id':set._id}, {$set: set})
});

Teraz problem, który może wystąpić, ale nie musi. Przy przenoszeniu bazy z nowszej wersji MongoDB (3.2), na starszą (2.6), występuje niekompatybilność indeksów. W tym wypadku należy skasować indeks, a następnie utworzyć go w niższej wersji.

db.gminy.ensureIndex({geometry: '2dsphere', "2dsphereIndexVersion": 2 })

Zakładam, że mamy dwie bazy, jedną pełną, drugą uproszczoną. W pierwszej będziemy przeszukiwać dane, w drugiej wyświetlać wyniki.

Teraz słowo, jak wygląda obsługa tego od strony Javy

@Getter
@Document(collection = "gminy")
public class Gmina {
    protected GminaProperties properties;
}
@Value
public class GminaProperties {
    @Field("jpt_nazwa_")
    private String name;
    @Field("jpt_kod_je")
    private Integer id;
}
@Value
@Document(collection = "gminy_simply")
@EqualsAndHashCode(callSuper=true)
public class GminaSimply extends Gmina {
    private String type;
    private GeoJsonMultiPolygon geometry;
}
public interface GminaRepository extends MongoRepository<Gmina, String> {
    @Query("{'geometry':{$geoIntersects:{$geometry:?0}}}")
    List<GminaSimply> findWithin(GeoJson point);
}
public interface GminaSimplyRepository extends MongoRepository<GminaSimply, String> {
    @Query("{'geometry':{$geoIntersects:{$geometry:?0}}}")
    List<GminaSimply> findWithin(GeoJson point);
}

Mamy łącznie trzy POJOGmina, GminaSimply i GminaProperties, odpowiadające danym w bazie MongoDB. Adnotacje @Field i @Document umożliwiają wskazanie nazw innych, niż te używane w bazie danych. Adnotacje @Getter i @Value pochodzą z Lombok. Lombok na ich postawie generuje gettery, settery i inny śmieciowy kod w czasie kompilacji.

Ciekawszą sprawą są interfejsy dziedziczące po MongoRepository. W czasie uruchamiania, na ich podstawie jest tworzona implementacja, którą można wstrzyknąć gdzieś indziej. Adnotacja @Query umożliwia wyspecyfikowanie kwerend, które zostaną skompilowane w trakcie uruchamiania do kodu Javy. Jak widać, kod Mongo jest identyczny, jedynie przekazanie argumentów z metody odbywa się poprzez ‚?’ z numerem argumentu.
Można też użyć tradycyjnego podejścia, czyli wstrzyknąć sobie gdzieś interfejs MongoTemplate i na jego postawie generować zapytania.

Użycie interfejsu w kontrolerze

@RequestMapping("/ajax")
@RestController
public class AjaxController {
    @Autowired
    GminaSimplyRepository gminaSimplyRepository;
    @Autowired
    GminaRepository gminaRepository;
    @RequestMapping("/all")
    public Collection<GminaSimply> getAll(){
        return gminaSimplyRepository.findAll();
    }
    @RequestMapping("/where")
    public Collection<Gmina> getWhere(double x, double y){
        return gminaRepository.findWithin(new GeoJsonPoint(x, y));
    }

}

Także strzelamy na /ajax/where?x=lat&y=lon i w zwrotce dostajemy identyfikator i nazwę gminy. Strzelając na /ajax/all, dostajemy gminy w uproszczonej formie, gotowe do wyświetlenia w Leaflet. Nieźle, jak na kilka linijek kodu? Oczywiście, wszystko wymaga delikatnego podrasowania, ale o tym nieco dalej.

Sama konfiguracja bazy w Spring Boot ogranicza się do wpisania adresu

spring.data.mongodb.uri=mongodb://user:password@host/database

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *