myarik / django-rest-elasticsearch

Elasticsearch support for Django REST Framework
Other
192 stars 38 forks source link

feature request - geospatial query support #36

Open knackjax opened 6 years ago

knackjax commented 6 years ago

can we get geo-spatial queries support? bounding box would be useful for us displaying data on a map.

I imagine having something similar to this library

https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/blob/master/advanced_usage_examples.rst#geo-spatial-features

will take a look if i have time as well for a PR...

knackjax commented 6 years ago

I added this in as a subclass implementation, please let me know if you want a PR.

rest_framework_elasticsearch_ext.es_filters_ext


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

from elasticsearch_dsl import Q

from rest_framework_elasticsearch.es_filters import BaseEsFilterBackend

GEO_BOUNDING_BOX_PARAM = 'location__geo_bounding_box'

class ElasticGeoBoundingBoxFilter(BaseEsFilterBackend):
    geo_bounding_box_param = GEO_BOUNDING_BOX_PARAM

    def get_geo_bounding_box_params(self, request, view):
        """
        Geo bounding box are set by a ?location__geo_bounding_box=... query parameter,
        and may be comma and/or whitespace delimited.
        """
        s_fields = view.get_es_geo_bounding_box_fields()
        if not s_fields:
            return {}

        values = request.query_params.get(self.geo_bounding_box_param, '').split('|')

        if len(values) < 2:
            return {}

        top_left_points = {}
        bottom_right_points = {}
        options = {}

        # Top left
        lat_lon = values[0].split(
            ','
        )
        if len(lat_lon) >= 2:
            top_left_points.update({
                'lat': float(lat_lon[0]),
                'lon': float(lat_lon[1]),
            })

        # Bottom right
        lat_lon = values[1].split(
            ','
        )
        if len(lat_lon) >= 2:
            bottom_right_points.update({
                'lat': float(lat_lon[0]),
                'lon': float(lat_lon[1]),
            })

        # Options
        for value in values[2:]:
            if ':' in value:
                opt_name_val = value.split(
                    ':'
                )
                if len(opt_name_val) >= 2:
                    if opt_name_val[0] in ('_name', 'validation_method', 'type'):
                        options.update(
                            {
                                opt_name_val[0]: opt_name_val[1]
                            }
                        )

        if not top_left_points or not bottom_right_points:
            return {}

        params = {}
        for s_field in s_fields:
            param = {
                s_field.name: {
                    'top_left': top_left_points,
                    'bottom_right': bottom_right_points,
                }
            }
            params.update(param)
        params.update(options)
        return params

    def filter_search(self, request, search, view):
        s_params = self.get_geo_bounding_box_params(request, view)

        if not s_params:
            return search

        print(s_params)

        q = Q('geo_bounding_box', **s_params)
        search = search.query(q)
        return search

rest_framework_elasticsearch_ext.es_filters_ext


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

from rest_framework_elasticsearch import es_views

from rest_framework_elasticsearch.es_mixins import ListElasticMixin
from rest_framework_elasticsearch.es_pagination import ElasticLimitOffsetPagination

class ElasticAPIViewExt(es_views.ElasticAPIView):
    """Elasticsearch base API view class."""
    def get_es_geo_bounding_box_fields(self):
        """
        Return field or fields used for search.
        The return value must be an iterable.
        """
        return getattr(self, 'es_geo_bounding_box_fields', None)

class ListElasticAPIViewExt(ListElasticMixin, ElasticAPIViewExt):
    """Concrete view for listing a queryset."""
    es_pagination_class = ElasticLimitOffsetPagination

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
dmvass commented 6 years ago

Hello @knackjax, thanks for your proposal. Can you please create PR with all related tests and documentary updates?

knackjax commented 5 years ago

I haven't had time to create tests/docs, but I have a branch if you like to look https://github.com/knackjax/django-rest-elasticsearch/tree/gis_queries

josemlp91 commented 4 years ago

@myarik I think, this issue can be closed yet.