Geolocalización con elasticsearch, el tipo geo_point

Elasticsearch

ES tiene dos formas de representar geolocalizaciones. Para ello dispone de los siguientes dos tipos de representación de datos.

  • geo_point type. Utilizado para representar puntos, utilizando coordenadas latitud y longitud.

    Con este tipo de representación podemos buscar todos los puntos que existen cerca de uno dado, calcular distancias entre puntos, ordenar por distancias, encontrar todos los puntos dentro de un circulo o polígono etc

    El orden de las coordenadas depende del formato elegido al insertar el documento.

    Si es un string con coordenadas el orden es lat,lon
    Si es un array con coordenadas el orden es [lon,lat]. Éste último orden es el elegido en el estandar de GeoJSON.

  • geo_shape type. Utilizado para representar formas o polígonos definidos utilizando GeoJSON.

    Con este tipo de representación podemos saber si dos polígonos se superponen, si un contiene al otro, etc.

geo_point type

Como hemos visto representan coordenadas geográficas. Por lo tanto tendremos dos puntos, la latitud y la longitud.

mapping

Debemos indicar el mapping de un tipo geo_point. El mapping dinámico no funciona con este tipo de dato.

Ejemplo

# Arrancamos ES (vamos a utilizar docker)
$ docker run -d --name es-geo-server -p 9200:9200 -p 9300:9300 elasticsearch:2.1.1 -Des.node.name="Puntos de Interes"

# Verificamos su correcto funcionamiento
$ curl localhost:9200?pretty
{
  "name" : "Puntos de Interes",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

# Creamos el indice y el mapping
curl -XPUT http://localhost:9200/mis_puntos_de_interes?pretty -d '{
    "mappings": {
        "cine": {
            "properties": {
                "nombre": {"type": "string"},
                "direccion": {"type": "string"},
                "coordenadas": {"type": "geo_point"}
            }
        }
    }
}'

# Insertar o actualizar puntos de interes
curl -XPUT http://localhost:9200/mis_puntos_de_interes/cine/1 -d '{
    "nombre" : "Universo",
    "direccion" : "Calle del universo, madrid, 28400"
    "coordenadas" :  "40.394609,-3.7740232"
}'

curl -XPUT http://localhost:9200/mis_puntos_de_interes/cine/2 -d '{
    "nombre" : "Los estrenos",
    "direccion" : "Calle del estreno, madrid, 28400"
    "coordenadas" :  "40.435469, -3.6871448"
}'

Tipos de búsquedas o filtros que podemos hacer con geo_point

Por defecto ningún resultado devuelto por estos filtros es cacheado.

geo_bounding_box

Es el filtro más simple y con el que obtendremos mejores resultados (en tiempo de respuesta). Devuelve todos los documentos que se encuentran dentro de un rectángulo, definido por sus coordenadas geográficas.

curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "coordenadas": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

y deberíamos obtener solamente 1 documento

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "mis_puntos_de_interes",
      "_type" : "cine",
      "_id" : "2",
      "_score" : 1.0,
      "_source":{
                "nombre" : "Los estrenos",
                "direccion" : "Calle del estreno, madrid, 28400",
                "coordenadas" :  "40.435469, -3.6871448"
        }
    } ]
  }
}

Podemos mejorar aún más los resultados (hablando de tiempo de respuesta) obtenidos al aplicar este tipo de filtro geo_bounding_box (tambíen para geo_distance). Todo lo que tenemos que hacer es cambiar el mapping del campo donde están las coordenadas para decirle a elasticsearch que indexe las coordenadas en campos separados.

Ahora tendremos 2 nuevos campos con la latitud y la longitud

  • coordenadas_v2.lat
  • coordenadas_v2.lon.

De esta forma estos dos nuevos campos los tenemos también en el índice invertido.

Finalmente cuando ejecutemos el filtro le indicaremos que tiene que utilizar los nuevos dos campos indexados. Entonces el filtro se aplica como una simple operación de rangos. En estos casos y cuando trabajemos con grandes volúmenes de datos mejoraremos sustancialmente el rendimiento obtenido.

El mapping en este caso cambiaría y sería

# Creamos el indice y el mapping
curl -XPUT http://localhost:9200/mis_puntos_de_interes?pretty -d '{
    "mappings": {
        "cine": {
            "properties": {
                "nombre": {"type": "string"},
                "direccion": {"type": "string"},
                "coordenadas": {"type": "geo_point"}
                "coordenadas_v2": {
                    "type": "geo_point",
                    "lat_lon": true
                }
            }
        }
    }
}'

NOTA. No es necesario mantener los dos campos de coordenadas. Es suficiente con mantener solo uno de ellos. El más útil es el mapeado con lat_lon: true.

Ahora tendremos 2 tipos de búsquedas.

  • Una de ellas es la búsqueda por defecto que se haría en memoria (type: memory, en este caso no sería necesario indicar el tipo) , y
  • la otra sería la búsqueda por el índice invertido (type: indexed)
# En memoria
#
curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "type": "memory",
                    "coordenadas_v2": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

# En el índice invertido (mejor rendimiento)
#
curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "type": "indexed",
                    "coordenadas_v2": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

geo_distance

A partir de una coordenada y una distancia encuentra todos los documentos que se encuentran dentro de ese radio.

Para optimizar y mejorar los tiempos de respuesta, elasticsearch primero localiza todos los documentos que se encuentran dentro del rectángulo que engloba el círculo de búsqueda, ya que este tipo de filtros es el menos costoso (geo_bounding_box). Una vez tiene los documentos candidatos, aplica el filtro según el radio proporcionado.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance": {
          "distance": "8643m",
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}'

Este filtro puede devolver los documentos encontrados ordenados por distancia. El calculo de la distancia entre dos puntos se puede personalizar cuando aplicamos el filtro.

Este calculo de distancia también se utiliza para saber qué documentos se encuentran dentro del radio de búsqueda.

Tenemos 3 tipos de algoritmos disponibles, configurados mediante la propiedad distance_type.

  • distance_type: sloppy_arc. Es el utilizado por defecto. Es más rápido que el arc pero algo menos preciso.

  • distance_type: plane. Es bastante rápido pero menos preciso. La precisión empeora para los puntos más cercanos a los polos.

  • distance_type: arc. El más lento y el más preciso.

Por tanto, para aumentar la precisión

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance": {
          "distance": "8643m",
          "distance_type": "arc", 
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}

geo_distance_range

Basado en una coordenada y a dos distancias, encuentra los documentos que se encuentran dentro del rango o donut indicado.

Sigue los mismos principios del filtro geo_distance.

Por ejemplo, dada una coordenada busca todos los documentos que se encuentran dentro del rango de 8642 metros hasta 8643 metros.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance_range": {
          "gte": "8642m",
          "lt": "8643m",
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}
'

geo_polygon

Es el filtro más costoso. Busca todos los puntos que se encuentran dentro de un poligono.

Si necesitamos utilizar este tipo de filtros es mejor que utilicemos el tipo geo_shape.

Ordenar por distancia

La ordenación siempre se realizará respecto a un punto.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search
{
  "query": {
    "filtered": {
      "filter": {
        "geo_bounding_box": {
          "type": "indexed",
          "coordenadas_v2": {
            "top_left": {
              "lat": 50,
              "lon": -5
            },
            "bottom_right": {
              "lat": 0,
              "lon": 0
            }
          }
        }
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

# 
curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search -d '
{
  "query": {
    "match_all": {}
  }, 
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}'

# 
curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search -d '
{
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}'
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s