MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Suporte geoespacial no MongoDB

1. Visão geral


Neste tutorial, exploraremos o suporte geoespacial no MongoDB.

Discutiremos como armazenar dados geoespaciais, indexação geográfica e pesquisa geoespacial. Também usaremos várias consultas de pesquisa geoespacial como perto , geoDentro , e geoIntersects .

2. Armazenando dados geoespaciais


Primeiro, vamos ver como armazenar dados geoespaciais no MongoDB.

MongoDB suporta vários GeoJSON tipos para armazenar dados geoespaciais. Ao longo de nossos exemplos, usaremos principalmente o Point e Polígono tipos.

2.1. Ponto


Este é o GeoJSON mais básico e comum tipo e é usado para representar um ponto específico na grade .

Aqui, temos um objeto simples, em nossos lugares coleção, que tem o campo local como um Ponto :
{
  "name": "Big Ben",
  "location": {
    "coordinates": [-0.1268194, 51.5007292],
    "type": "Point"
  }
}

Observe que o valor da longitude vem primeiro, depois a latitude.

2.2. Polígono


Polígono é um pouco mais complexo GeoJSON tipo.

Podemos usar Polygon para definir uma área com suas fronteiras externas e também furos internos, se necessário.

Vamos ver outro objeto que tem sua localização definida como um Polígono :
{
  "name": "Hyde Park",
  "location": {
    "coordinates": [
      [
        [-0.159381, 51.513126],
        [-0.189615, 51.509928],
        [-0.187373, 51.502442],
        [-0.153019, 51.503464],
        [-0.159381, 51.513126]
      ]
    ],
    "type": "Polygon"
  }
}

Neste exemplo, definimos uma matriz de pontos que representam limites externos. Também temos que fechar o limite para que o último ponto seja igual ao primeiro ponto.

Observe que precisamos definir os pontos dos limites externos no sentido anti-horário e os limites do furo no sentido horário.

Além desses tipos, também existem muitos outros tipos, como LineString, Multiponto, MultiPolygon, MultiLineString, e GeometriaCollection.

3. Indexação geoespacial


Para realizar consultas de pesquisa nos dados geoespaciais que armazenamos, precisamos criar um índice geoespacial em nossa localização campo.

Basicamente temos duas opções:2d e 2dsphere .

Mas primeiro, vamos definir nossa coleção de lugares :
MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("myMongoDb");
collection = db.getCollection("places");

3.1. 2d Índice geoespacial


O 2d index nos permite realizar consultas de pesquisa que funcionam com base em cálculos de plano 2d.

Podemos criar um 2d índice no local campo em nosso aplicativo Java da seguinte forma:
collection.createIndex(Indexes.geo2d("location"));

Claro, podemos fazer o mesmo no mongo Concha:
db.places.createIndex({location:"2d"})

3.2. 2dsphere Índice geoespacial


A 2dsfera index suporta consultas que funcionam com base em cálculos de esfera.

Da mesma forma, podemos criar um 2dsphere index em Java usando os mesmos Indexes classe como acima:
collection.createIndex(Indexes.geo2dsphere("location"));

Ou no mongo Concha:
db.places.createIndex({location:"2dsphere"})

4. Pesquisando usando consultas geoespaciais


Agora, para a parte empolgante, vamos procurar objetos com base em sua localização usando consultas geoespaciais.

4.1. Consulta próxima


Vamos começar com perto. Podemos usar o perto consulta para pesquisar lugares dentro de uma determinada distância.

O próximo consulta funciona com ambos 2d e 2dsphere índices.

No próximo exemplo, buscaremos lugares que estejam a menos de 1 km e a mais de 10 metros da posição especificada:
@Test
public void givenNearbyLocation_whenSearchNearby_thenFound() {
    Point currentLoc = new Point(new Position(-0.126821, 51.495885));
 
    FindIterable<Document> result = collection.find(
      Filters.near("location", currentLoc, 1000.0, 10.0));

    assertNotNull(result.first());
    assertEquals("Big Ben", result.first().get("name"));
}

E a consulta correspondente no mongo Concha:
db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-0.126821, 51.495885]
      },
      $maxDistance: 1000,
      $minDistance: 10
    }
  }
})

Observe que os resultados são classificados do mais próximo para o mais distante.

Da mesma forma, se usarmos um local muito distante, não encontraremos lugares próximos:
@Test
public void givenFarLocation_whenSearchNearby_thenNotFound() {
    Point currentLoc = new Point(new Position(-0.5243333, 51.4700223));
 
    FindIterable<Document> result = collection.find(
      Filters.near("location", currentLoc, 5000.0, 10.0));

    assertNull(result.first());
}

Também temos o nearSphere método, que age exatamente como near, exceto que calcula a distância usando geometria esférica.

4.2. Dentro da consulta


Em seguida, exploraremos o geoWithin inquerir.

O geoWithin consulta nos permite pesquisar lugares que existem totalmente dentro de uma determinada Geometria , como um círculo, caixa ou polígono. Isso também funciona com 2d e 2dsphere índices.

Neste exemplo, estamos procurando lugares que existem em um raio de 5 km da posição central fornecida:
@Test
public void givenNearbyLocation_whenSearchWithinCircleSphere_thenFound() {
    double distanceInRad = 5.0 / 6371;
 
    FindIterable<Document> result = collection.find(
      Filters.geoWithinCenterSphere("location", -0.1435083, 51.4990956, distanceInRad));

    assertNotNull(result.first());
    assertEquals("Big Ben", result.first().get("name"));
}

Observe que precisamos transformar a distância de km para radiano (basta dividir pelo raio da Terra).

E a consulta resultante:
db.places.find({
  location: {
    $geoWithin: {
      $centerSphere: [
        [-0.1435083, 51.4990956],
        0.0007848061528802386
      ]
    }
  }
})

Em seguida, pesquisaremos todos os lugares que existem dentro de uma “caixa” retangular. Precisamos definir a caixa por sua posição inferior esquerda e posição superior direita:
@Test
public void givenNearbyLocation_whenSearchWithinBox_thenFound() {
    double lowerLeftX = -0.1427638;
    double lowerLeftY = 51.4991288;
    double upperRightX = -0.1256209;
    double upperRightY = 51.5030272;

    FindIterable<Document> result = collection.find(
      Filters.geoWithinBox("location", lowerLeftX, lowerLeftY, upperRightX, upperRightY));

    assertNotNull(result.first());
    assertEquals("Big Ben", result.first().get("name"));
}

Aqui está a consulta correspondente no mongo Concha:
db.places.find({
  location: {
    $geoWithin: {
      $box: [
        [-0.1427638, 51.4991288],
        [-0.1256209, 51.5030272]
      ]
    }
  }
})

Por fim, se a área que queremos pesquisar não for um retângulo ou um círculo, podemos usar um polígono para definir uma área mais específica :
@Test
public void givenNearbyLocation_whenSearchWithinPolygon_thenFound() {
    ArrayList<List<Double>> points = new ArrayList<List<Double>>();
    points.add(Arrays.asList(-0.1439, 51.4952));
    points.add(Arrays.asList(-0.1121, 51.4989));
    points.add(Arrays.asList(-0.13, 51.5163));
    points.add(Arrays.asList(-0.1439, 51.4952));
 
    FindIterable<Document> result = collection.find(
      Filters.geoWithinPolygon("location", points));

    assertNotNull(result.first());
    assertEquals("Big Ben", result.first().get("name"));
}

E aqui está a consulta correspondente:
db.places.find({
  location: {
    $geoWithin: {
      $polygon: [
        [-0.1439, 51.4952],
        [-0.1121, 51.4989],
        [-0.13, 51.5163],
        [-0.1439, 51.4952]
      ]
    }
  }
})

Definimos apenas um polígono com seus limites externos, mas também podemos adicionar furos a ele. Cada buraco será uma Lista de Ponto s:
geoWithinPolygon("location", points, hole1, hole2, ...)

4.3. Consulta de interseção


Finalmente, vamos ver os geoIntersects inquerir.

Os geoIntersects consulta encontra objetos que pelo menos se cruzam com uma determinada Geometria. Em comparação, geoWithin encontra objetos que existem totalmente dentro de uma determinada Geometria .

Esta consulta funciona com o 2dsphere índice apenas.

Vamos ver isso na prática, com um exemplo de busca de qualquer lugar que cruze com um Polígono :
@Test
public void givenNearbyLocation_whenSearchUsingIntersect_thenFound() {
    ArrayList<Position> positions = new ArrayList<Position>();
    positions.add(new Position(-0.1439, 51.4952));
    positions.add(new Position(-0.1346, 51.4978));
    positions.add(new Position(-0.2177, 51.5135));
    positions.add(new Position(-0.1439, 51.4952));
    Polygon geometry = new Polygon(positions);
 
    FindIterable<Document> result = collection.find(
      Filters.geoIntersects("location", geometry));

    assertNotNull(result.first());
    assertEquals("Hyde Park", result.first().get("name"));
}

A consulta resultante:
db.places.find({
  location:{
    $geoIntersects:{
      $geometry:{
        type:"Polygon",
          coordinates:[
          [
            [-0.1439, 51.4952],
            [-0.1346, 51.4978],
            [-0.2177, 51.5135],
            [-0.1439, 51.4952]
          ]
        ]
      }
    }
  }
})