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 comonear,geoWithin egeoIntersects.
2. Armazenamento de dados geoespaciais
Primeiro, vamos ver como armazenar dados geoespaciais no MongoDB.
MongoDB supports multiple GeoJSON types to store geospatial data. Ao longo de nossos exemplos, usaremos principalmente os tiposPointePolygon.
2.1. Point
Este é o tipoGeoJSON mais básico e comum eit’s used to represent one specific point on the grid.
Aqui, temos um objeto simples, em nossa coleçãoplaces, que tem o campolocation comoPoint:
{
"name": "Big Ben",
"location": {
"coordinates": [-0.1268194, 51.5007292],
"type": "Point"
}
}
Observe que o valor da longitude vem primeiro, depois a latitude.
2.2. Polygon
Polygon é um pouco mais complexo do tipoGeoJSON.
We can use Polygon to define an area with its exterior borderse também furos internos, se necessário.
Vamos ver outro objeto que tem sua localização definida comoPolygon:
{
"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.
Observe que precisamos definir os pontos limites externos na direção anti-horária e os limites dos orifícios no sentido horário.
Além desses tipos, existem também muitos outros tipos, comoLineString,MultiPoint,MultiPolygon,MultiLineString, eGeometryCollection.
3. Indexação Geoespacial
Para realizar consultas de pesquisa nos dados geoespaciais que armazenamos, precisamos criar um índice geoespacial em nosso campolocation.
Basicamente, temos duas opções:2d e2dsphere.
Mas, primeiro, vamos definir nossos lugares collection:
MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("myMongoDb");
collection = db.getCollection("places");
3.1. 2d Índice geoespacial
O índice2d nos permite realizar consultas de pesquisa que funcionam com base em cálculos de plano 2d.
Podemos criar um índice2d no campolocation em nosso aplicativo Java da seguinte maneira:
collection.createIndex(Indexes.geo2d("location"));
Claro, podemos fazer o mesmo no shellmongo:
db.places.createIndex({location:"2d"})
3.2. 2dsphere Índice geoespacial
O índice2dsphere suporta consultas que funcionam com base em cálculos de esfera.
Da mesma forma, podemos criar um índice2dsphere em Java usando a mesma classeIndexes acima:
collection.createIndex(Indexes.geo2dsphere("location"));
Ou no shellmongo:
db.places.createIndex({location:"2dsphere"})
4. Pesquisando usando consultas geoespaciais
Agora, para a parte emocionante, vamos pesquisar objetos com base em sua localização usando consultas geoespaciais.
4.1. Consulta próxima
Vamos começar comnear. We can use the near query to search for places within a given distance.
A consultanear funciona com os índices2d e2dsphere.
No próximo exemplo, vamos pesquisar lugares que estão a menos de 1 km e mais de 10 metros de distância da posição fornecida:
@Test
public void givenNearbyLocation_whenSearchNearby_thenFound() {
Point currentLoc = new Point(new Position(-0.126821, 51.495885));
FindIterable 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 shellmongo:
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 ao mais distante.
Da mesma forma, se usarmos um local muito distante, não encontraremos nenhum lugar próximo:
@Test
public void givenFarLocation_whenSearchNearby_thenNotFound() {
Point currentLoc = new Point(new Position(-0.5243333, 51.4700223));
FindIterable result = collection.find(
Filters.near("location", currentLoc, 5000.0, 10.0));
assertNull(result.first());
}
Também temos o métodonearSphere, que atua exatamente comonear,, exceto que calcula a distância usando geometria esférica.
4.2. Dentro da Consulta
A seguir, vamos explorar a consultageoWithin.
The geoWithin query enables us to search for places that fully exist within a given Geometry, como um círculo, caixa ou polígono. Isso também funciona com os índices2de2dsphere.
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 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 em radianos (apenas divida pelo raio da Terra).
E a consulta resultante:
db.places.find({
location: {
$geoWithin: {
$centerSphere: [
[-0.1435083, 51.4990956],
0.0007848061528802386
]
}
}
})
A seguir, pesquisaremos todos os lugares que existem dentro de uma “caixa” retângulo. 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 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 shellmongo:
db.places.find({
location: {
$geoWithin: {
$box: [
[-0.1427638, 51.4991288],
[-0.1256209, 51.5030272]
]
}
}
})
Finalmente,if the area we want to search within isn’t a rectangle or a circle, we can use a polygon to define a more specific area:
@Test
public void givenNearbyLocation_whenSearchWithinPolygon_thenFound() {
ArrayList> points = new ArrayList>();
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 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. Cada buraco será umList dePoints:
geoWithinPolygon("location", points, hole1, hole2, ...)
4.3. Consulta de intersecção
Finalmente, vamos dar uma olhada na consultageoIntersects.
The geoIntersects query finds objects that at least intersect with a given Geometry. Por comparação,geoWithin encontra objetos que existem totalmente em um determinadoGeometry.
Esta consulta funciona apenas com o índicethe 2dsphere.
Vamos ver isso na prática, com um exemplo de como procurar qualquer lugar que cruze com umPolygon:
@Test
public void givenNearbyLocation_whenSearchUsingIntersect_thenFound() {
ArrayList positions = new ArrayList();
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 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]
]
]
}
}
}
})
5. Conclusão
Neste artigo, aprendemos como armazenar dados geoespaciais no MongoDB e observamos a diferença entre os índices geoespaciais2de2dsphere. Também aprendemos como pesquisar no MongoDB usando consultas geoespaciais.
Como de costume, o código-fonte completo dos exemplos está disponívelover on GitHub.