Skip to content

Commit 5e0ebe4

Browse files
geo polygon; yet untested
1 parent 0b03e5b commit 5e0ebe4

File tree

6 files changed

+167
-11
lines changed

6 files changed

+167
-11
lines changed

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ Main features and highlights
4444
``isnull``, ``range``, ``in``, ``term`` and ``terms``
4545
is implemented.
4646
- :doc:`Geo-spatial filtering filter backend <advanced_usage_examples>` (the
47-
following filters implemented: ``geo_distance``).
47+
following filters implemented: ``geo_distance``, ``geo_polygon``).
4848
- :doc:`Geo-spatial ordering filter backend <advanced_usage_examples>` (the
4949
following filters implemented: ``geo_distance``).
5050
- :doc:`Faceted search filter backend <advanced_usage_examples>`.
5151
- :doc:`Suggester filter backend <advanced_usage_examples>`.
5252
- :doc:`Pagination (Page number and limit/offset pagination) <advanced_usage_examples>`.
53-
- Ids filter backend.
53+
- :doc:`Ids filter backend <advanced_usage_examples>`.
5454

5555
Installation
5656
============

advanced_usage_examples.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,20 @@ the example below, documents would be ordered first by field
623623
624624
http://127.0.0.1:8080/search/books/?search=title|lorem&ordering=-publication_date&ordering=price
625625
626+
Ids filter
627+
----------
628+
Filters documents that only have the provided ids.
629+
630+
.. code-block:: text
631+
632+
http://127.0.0.1:8000/api/articles/?ids=68|64|58
633+
634+
Or, alternatively:
635+
636+
.. code-block:: text
637+
638+
http://127.0.0.1:8000/api/articles/?ids=68&ids=64&ids=58
639+
626640
Faceted search
627641
--------------
628642

examples/simple/search_indexes/viewsets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
LOOKUP_FILTER_PREFIX,
55
LOOKUP_FILTER_WILDCARD,
66
LOOKUP_FILTER_GEO_DISTANCE,
7+
LOOKUP_FILTER_GEO_POLYGON,
78
LOOKUP_QUERY_IN,
89
LOOKUP_QUERY_GT,
910
LOOKUP_QUERY_GTE,
@@ -288,6 +289,7 @@ class PublisherDocumentViewSet(BaseDocumentViewSet):
288289
'location': {
289290
'lookups': [
290291
LOOKUP_FILTER_GEO_DISTANCE,
292+
LOOKUP_FILTER_GEO_POLYGON,
291293
],
292294
},
293295
}

src/django_elasticsearch_dsl_drf/constants.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
'FALSE_VALUES',
1717
'LOOKUP_FILTER_EXISTS',
1818
'LOOKUP_FILTER_GEO_DISTANCE',
19-
'LOOKUP_FILTER_GEO_DISTANCE_RANGE',
19+
'LOOKUP_FILTER_GEO_DISTANCE_FROM',
2020
'LOOKUP_FILTER_GEO_DISTANCE_GT',
2121
'LOOKUP_FILTER_GEO_DISTANCE_GTE',
22+
'LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_LOWER',
23+
'LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_UPPER',
2224
'LOOKUP_FILTER_GEO_DISTANCE_LT',
2325
'LOOKUP_FILTER_GEO_DISTANCE_LTE',
24-
'LOOKUP_FILTER_GEO_DISTANCE_FROM',
26+
'LOOKUP_FILTER_GEO_DISTANCE_RANGE',
2527
'LOOKUP_FILTER_GEO_DISTANCE_TO',
26-
'LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_UPPER',
27-
'LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_LOWER',
2828
'LOOKUP_FILTER_GEO_POLYGON',
2929
'LOOKUP_FILTER_PREFIX',
3030
'LOOKUP_FILTER_RANGE',
@@ -39,6 +39,8 @@
3939
'LOOKUP_QUERY_STARTSWITH',
4040
'NUMBER_LOOKUP_FILTERS',
4141
'SEARCH_QUERY_PARAM',
42+
'SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE',
43+
'SEPARATOR_LOOKUP_COMPLEX_VALUE',
4244
'SEPARATOR_LOOKUP_FILTER',
4345
'SEPARATOR_LOOKUP_VALUE',
4446
'STRING_LOOKUP_FILTERS',
@@ -95,6 +97,11 @@
9597
# lookups.
9698
SEPARATOR_LOOKUP_VALUE = '|'
9799

100+
# Lookup filter value complex separator. To be used with geo-spatial features.
101+
SEPARATOR_LOOKUP_COMPLEX_VALUE = ':'
102+
103+
SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE = ','
104+
98105
# Search query param
99106
SEARCH_QUERY_PARAM = 'q'
100107

@@ -299,9 +306,8 @@
299306
# Geo Polygon Query
300307
#
301308
# A query allowing to include hits that only fall within a polygon of points.
302-
# Here is an example:
309+
# Example:
303310
#
304-
# GET /_search
305311
# {
306312
# "query": {
307313
# "bool" : {
@@ -324,11 +330,12 @@
324330
# }
325331
#
326332
# Query options:
327-
#
328333
# - _name: Optional name field to identify the filter
329334
# - validation_method: Set to IGNORE_MALFORMED to accept geo points with
330335
# invalid latitude or longitude, COERCE to try and infer correct latitude or
331336
# longitude, or STRICT (default is STRICT).
337+
# Example: http://localhost:8000
338+
# /api/articles/?location__geo_polygon=40,-70|30,-80|20,-90
332339
LOOKUP_FILTER_GEO_POLYGON = 'geo_polygon'
333340

334341
# ****************************************************************************

src/django_elasticsearch_dsl_drf/filter_backends/filtering/geo_spatial.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
LOOKUP_FILTER_GEO_DISTANCE_TO,
3939
LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_UPPER,
4040
LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_LOWER,
41+
LOOKUP_FILTER_GEO_POLYGON,
42+
SEPARATOR_LOOKUP_COMPLEX_VALUE,
43+
SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE,
4144
)
4245
from ..mixins import FilterBackendMixin
4346

@@ -145,6 +148,94 @@ def get_geo_distance_params(cls, value, field):
145148

146149
return params
147150

151+
@classmethod
152+
def get_geo_polygon_params(cls, value, field):
153+
"""Get params for `geo_polygon` query.
154+
155+
Example:
156+
157+
/api/articles/?location__geo_polygon=40,-70|30,-80|20,-90
158+
159+
Example:
160+
161+
/api/articles/?location__geo_polygon=40,-70|30,-80|20,-90
162+
|_name:myname|validation_method:IGNORE_MALFORMED
163+
164+
Elasticsearch:
165+
166+
{
167+
"query": {
168+
"bool" : {
169+
"must" : {
170+
"match_all" : {}
171+
},
172+
"filter" : {
173+
"geo_polygon" : {
174+
"person.location" : {
175+
"points" : [
176+
{"lat" : 40, "lon" : -70},
177+
{"lat" : 30, "lon" : -80},
178+
{"lat" : 20, "lon" : -90}
179+
]
180+
}
181+
}
182+
}
183+
}
184+
}
185+
}
186+
187+
:param value:
188+
:param field:
189+
:type value: str
190+
:type field:
191+
:return: Params to be used in `geo_distance` query.
192+
:rtype: dict
193+
"""
194+
__values = cls.split_lookup_value(value)
195+
__len_values = len(__values)
196+
197+
if not __len_values:
198+
return {}
199+
200+
__points = []
201+
__options = {}
202+
203+
for __value in __values:
204+
if SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE in __value:
205+
__lat_lon = __value.split(
206+
SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE
207+
)
208+
if len(__lat_lon) >= 2:
209+
__points.append(
210+
{
211+
'lat': float(__lat_lon[0]),
212+
'lon': float(__lat_lon[1]),
213+
}
214+
)
215+
216+
elif SEPARATOR_LOOKUP_COMPLEX_VALUE in __value:
217+
__opt_name_val = __value.split(
218+
SEPARATOR_LOOKUP_COMPLEX_VALUE
219+
)
220+
if len(__opt_name_val) >= 2:
221+
if __opt_name_val[0] in ('_name', 'validation_method'):
222+
__options.update(
223+
{
224+
__opt_name_val[0]: __opt_name_val[1]
225+
}
226+
)
227+
228+
if __points:
229+
params = {
230+
field: {
231+
'points': __points
232+
}
233+
}
234+
params.update(__options)
235+
236+
return params
237+
return {}
238+
148239
@classmethod
149240
def get_range_params(cls, value, field):
150241
"""Get params for `range` query.
@@ -345,6 +436,26 @@ def apply_query_geo_distance_lte(cls, queryset, options, value):
345436
**cls.get_gte_lte_params(value, 'lte', options['field'])
346437
)
347438

439+
@classmethod
440+
def apply_query_geo_polygon(cls, queryset, options, value):
441+
"""Apply `geo_polygon` query.
442+
443+
:param queryset: Original queryset.
444+
:param options: Filter options.
445+
:param value: value to filter on.
446+
:type queryset: elasticsearch_dsl.search.Search
447+
:type options: dict
448+
:type value: str
449+
:return: Modified queryset.
450+
:rtype: elasticsearch_dsl.search.Search
451+
"""
452+
return queryset.query(
453+
Q(
454+
'geo_polygon',
455+
**cls.get_geo_polygon_params(value, options['field'])
456+
)
457+
)
458+
348459
def get_filter_query_params(self, request, view):
349460
"""Get query params to be filtered on.
350461
@@ -462,7 +573,7 @@ def filter_queryset(self, request, queryset, view):
462573
)
463574

464575
# `geo_distance_range` `lte` query lookup
465-
elif options['lookup'] == (
576+
elif options['lookup'] in (
466577
LOOKUP_FILTER_GEO_DISTANCE_LTE,
467578
LOOKUP_FILTER_GEO_DISTANCE_INCLUDE_UPPER
468579
):
@@ -472,4 +583,12 @@ def filter_queryset(self, request, queryset, view):
472583
value
473584
)
474585

586+
# `geo_polygon` query lookup
587+
elif options['lookup'] == LOOKUP_FILTER_GEO_POLYGON:
588+
queryset = self.apply_query_geo_polygon(
589+
queryset,
590+
options,
591+
value
592+
)
593+
475594
return queryset

src/django_elasticsearch_dsl_drf/filter_backends/mixins.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from ..constants import (
66
SEPARATOR_LOOKUP_VALUE,
7-
SEPARATOR_LOOKUP_FILTER
7+
SEPARATOR_LOOKUP_FILTER,
8+
SEPARATOR_LOOKUP_COMPLEX_VALUE,
89
)
910

1011
__title__ = 'django_elasticsearch_dsl_drf.filter_backends.mixins'
@@ -42,3 +43,16 @@ def split_lookup_filter(cls, value, maxsplit=-1):
4243
:rtype: list
4344
"""
4445
return value.split(SEPARATOR_LOOKUP_FILTER, maxsplit)
46+
47+
@classmethod
48+
def split_lookup_complex_value(cls, value, maxsplit=-1):
49+
"""Split lookup complex value.
50+
51+
:param value: Value to split.
52+
:param maxsplit: The `maxsplit` option of `string.split`.
53+
:type value: str
54+
:type maxsplit: int
55+
:return: Lookup filter split into a list.
56+
:rtype: list
57+
"""
58+
return value.split(SEPARATOR_LOOKUP_COMPLEX_VALUE, maxsplit)

0 commit comments

Comments
 (0)