Search code examples
pythonelasticsearchflaskgeolocation

How to get closest geo points in Elasticsearch with Python (Flask)


I am following the official tutorial of Elasticsearch using Python with Flask. I implemented the full text matching search, but I wanted to extend the functionality to find nearest locations. I found again from the official documentation geo-distance query. I tried the query, but it doesn't work on my side. Here is my creation of the Search object:

from pprint import pprint

from elasticsearch import Elasticsearch
from decouple import config
from managers.home_manager import HomeManager
from schemas.response.home_response import HomeResponseSchema, PinSchema

mapping = {
    "mappings": {
        "properties": {"pin": {"properties": {"location": {"type": "geo_point"}}}}
    }
}


class Search:
    def __init__(self) -> None:
        self.es = Elasticsearch(
            api_key=config("ELASTIC_API_KEY"), cloud_id=config("ELASTIC_CLOUD_ID")
        )
        client_info = self.es.info()
        print("Connected to Elasticsearch!")
        pprint(client_info.body)

    def create_index(self):
        self.es.indices.delete(index="real_estate_homes", ignore_unavailable=True)
        self.es.indices.create(index="real_estate_homes", body=mapping)

    def insert_document(self, document):
        return self.es.index(index="real_estate_homes", body=document)

    def insert_documents(self, documents):
        operations = []
        for document in documents:
            operations.append({"index": {"_index": "real_estate_homes"}})
            operations.append(document)
        return self.es.bulk(operations=operations)

    def reindex_homes(self):
        self.create_index()
        homes = HomeManager.select_all_homes()
        pins = []
        for home in homes:
            pin = {
                "location": {"lat": float(home.latitude), "lon": float(home.longitude)}
            }
            pins.append(pin)
        
        return self.insert_documents(pins)

    def search(self, **query_args):
        return self.es.search(index="real_estate_homes", **query_args)


es = Search()

I want to mention that I tried to dump json files using PinSchema object.

Here is the code with the queiries:

from flask_restful import Resource

from managers.home_manager import HomeManager
from search import es
from schemas.response.home_response import HomeResponseSchema


class ElasticResource(Resource):
    def get(self, home_id):
        print(home_id)
        home = HomeManager.select_home_by_id(home_id)
        geo_query = es.search(
            body={
                "query": {
                    "bool": {
                        "must": {"match_all": {}},
                        "filter": {
                            "geo_distance": {
                                "distance": "2000km",
                                "pin.location": {"lat": 43, "lon": 27},
                            }
                        },
                    }
                }
            }
        )
        print(geo_query)
        result = es.search(
            query={
                "bool": {
                    "must": {"match": {"city": {"query": home.city}}},
                    "must_not": {"match": {"id": {"query": home.id}}},
                }
            }
        )
        print(result["hits"]["hits"])
        if len(result["hits"]["hits"]) == 0:
            return "No results."
        suggested_homes = [
            suggested_home["_source"] for suggested_home in result["hits"]["hits"]
        ]
        resp_schema = HomeResponseSchema()
        return resp_schema.dump(suggested_homes, many=True), 200

Can anybody help me to find the problem and receive matches?

I tried to find solutions in StackOverflow, but they didn't worked too.


Solution

  • I took a closer look at the data which is sent to Elasticsearch. I can see that the construction of the dictionary pin is wrong:

     def reindex_homes(self):
        self.create_index()
        homes = HomeManager.select_all_homes()
        pins = []
        for home in homes:
            pin = {
                "pin": {"location": {"lat": float(home.latitude), "lon": float(home.longitude)}}
            }
            pins.append(pin)
        
        return self.insert_documents(pins)