Skip to content

Commit 43f3b18

Browse files
committed
- Add geo-shape documentation
- Clean code
1 parent f701dbe commit 43f3b18

File tree

7 files changed

+156
-32
lines changed

7 files changed

+156
-32
lines changed

docs/advanced_usage_examples.rst

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,91 @@ Ordering
927927
928928
http://localhost:8000/search/publishers/?ordering=location__48.85__2.30__km__plane
929929
930+
Geo-shape
931+
~~~~~~~~
932+
933+
**Setup**
934+
935+
In order to be able to do all geo-shape queries, you need a GeoShapeField with 'recursive' strategy.
936+
Details about spatial strategies here : https://www.elastic.co/guide/en/elasticsearch/reference/master/geo-shape.html#spatial-strategy
937+
938+
.. code-block:: python
939+
940+
# ...
941+
942+
@INDEX.doc_type
943+
class PublisherDocument(Document):
944+
945+
# ...
946+
947+
location_circle = fields.GeoShapeField(strategy='recursive',
948+
attr='location_circle_indexing')
949+
950+
# ...
951+
952+
class Publisher(models.Model):
953+
954+
# ...
955+
956+
@property
957+
def location_circle_indexing(self):
958+
"""
959+
Indexing circle geo_shape with 10km radius.
960+
Used in Elasticsearch indexing/tests of `geo_shape` native filter.
961+
"""
962+
return {
963+
'type': 'circle',
964+
'coordinates': [self.latitude, self.longitude],
965+
'radius': '10km',
966+
}
967+
968+
969+
You need to use GeoSpatialFilteringFilterBackend and set the LOOKUP_FILTER_GEO_SHAPE to the geo_spatial_filter_field. (This takes place in ViewSet)
970+
971+
.. code-block:: python
972+
973+
# ...
974+
class PublisherDocumentViewSet(DocumentViewSet):
975+
# ...
976+
filter_backends = [
977+
# ...
978+
GeoSpatialFilteringFilterBackend,
979+
# ...
980+
]
981+
# ...
982+
geo_spatial_filter_fields = {
983+
# ...
984+
'location_circle': {
985+
'lookups': [
986+
LOOKUP_FILTER_GEO_SHAPE,
987+
]
988+
},
989+
# ...
990+
}
991+
# ...
992+
993+
994+
**Supported shapes & queries**
995+
996+
With this setup, we can do several types of Geo-shape queries.
997+
998+
Supported and tested shapes types are : point, circle, envelope
999+
1000+
Pottentially supported but untested shapes are : multipoint and linestring
1001+
1002+
Supported and tested queries are : INTERSECTS, DISJOINT, WITHIN, CONTAINS
1003+
1004+
**Shape intersects**
1005+
1006+
Interesting queries are shape intersects : this gives you all documents whose shape intersects with the shape given in query. (Should be 2 with the actual test dataset)
1007+
1008+
.. code-block:: text
1009+
1010+
http://localhost:8000/search/publishers/?location_circle__geo_shape=49.119696,6.176355__radius,15km__relation,intersects__type,circle
1011+
1012+
This request give you all publishers having a location_circle intersecting with the one in the query.
1013+
1014+
9301015
Suggestions
9311016
-----------
9321017

examples/simple/books/management/commands/books_create_test_data.py

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

55
import factories
66

7-
87
DEFAULT_NUMBER_OF_ITEMS_TO_CREATE = 100
98

109

@@ -54,3 +53,16 @@ def handle(self, *args, **options):
5453
print("{} address objects created.".format(number))
5554
except Exception as err:
5655
raise CommandError(str(err))
56+
57+
try:
58+
points = [[50.691589, 3.174173], [49.076088, 6.222905], [48.983755, 6.019749]]
59+
for point in points:
60+
factories.PublisherFactory.create(
61+
**{
62+
'latitude': point[0],
63+
'longitude': point[1],
64+
}
65+
)
66+
print("{} publishers objects created.".format(len(points)))
67+
except Exception as err:
68+
raise CommandError(str(err))

examples/simple/books/models/publisher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def location_field_indexing(self):
5151
}
5252

5353
@property
54-
def location_shape_indexing(self):
54+
def location_point_indexing(self):
5555
"""
5656
Indexing point geo_shape.
5757
Used in Elasticsearch indexing/tests of `geo_shape` native filter.

examples/simple/search_indexes/documents/publisher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ class PublisherDocument(Document):
7878
# Location
7979
location = fields.GeoPointField(attr='location_field_indexing')
8080

81-
location_shape = fields.GeoShapeField(strategy='recursive',
82-
attr='location_shape_indexing')
81+
# Geo-shape fields
82+
location_point = fields.GeoShapeField(strategy='recursive',
83+
attr='location_point_indexing')
8384
location_circle = fields.GeoShapeField(strategy='recursive',
8485
attr='location_circle_indexing')
8586

examples/simple/search_indexes/viewsets/publisher.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ class PublisherDocumentViewSet(DocumentViewSet):
7474
],
7575
},
7676
'location_2': 'location',
77-
'location_shape': {
77+
'location_point': {
78+
'lookups': [
79+
LOOKUP_FILTER_GEO_SHAPE,
80+
]
81+
},
82+
'location_circle': {
7883
'lookups': [
7984
LOOKUP_FILTER_GEO_SHAPE,
8085
]

src/django_elasticsearch_dsl_drf/filter_backends/filtering/geo_spatial.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,16 +426,18 @@ def get_geo_shape_params(cls, value, field):
426426
}
427427
)
428428

429-
if not __coordinates:
429+
__type = __options.pop('type', None)
430+
__relation = __options.pop('relation', None)
431+
if not __coordinates or not __type or not __relation:
430432
return {}
431433

432434
params = {
433435
field: {
434436
'shape': {
435-
'type': __options.pop('type'),
437+
'type': __type,
436438
'coordinates': __coordinates if len(__coordinates) > 1 else __coordinates[0],
437439
},
438-
'relation': __options.pop('relation'),
440+
'relation': __relation,
439441
}
440442
}
441443
radius = __options.pop('radius', None)

src/django_elasticsearch_dsl_drf/tests/test_filtering_geo_spatial.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ def test_field_filter_geo_bounding_box_fail_test(self):
393393
)
394394

395395
@pytest.mark.webtest
396-
def _test_field_filter_geo_shape(self, points, count, gs_coordinates, gs_relation, gs_type, gs_extra=None):
396+
def _test_field_filter_geo_shape(self, points, count, gs_data):
397397
"""Private helper test field filter geo-shape.
398398
399399
For testing use
@@ -404,47 +404,57 @@ def _test_field_filter_geo_shape(self, points, count, gs_coordinates, gs_relatio
404404
405405
Examples:
406406
407-
http://localhost:8000/api/articles/
408-
?location_shape__geo_shape=49.344809,6.643982
407+
http://localhost:8000/search/publishers/
408+
?location_point__geo_shape=49.344809,6.643982
409409
__48.643798,5.630493
410410
__relation,within
411411
__type,envelope
412412
413-
http://localhost:8000/api/articles/
414-
?location_shape__geo_shape=49.119696,6.176355
413+
http://localhost:8000/search/publishers/
414+
?location_point__geo_shape=49.119696,6.176355
415415
__radius,10km
416416
__relation,within
417417
__type,circle
418418
419+
http://localhost:8000/search/publishers/
420+
?location_circle__geo_shape=49.119696,6.176355
421+
__radius,15km
422+
__relation,intersects
423+
__type,circle
424+
419425
:param points:
420426
:param count:
421-
:type points:
422-
:type count:
427+
:param gs_data: contains geo_shape query data (type, relation, radius etc)
428+
:type points: list
429+
:type count: int
430+
:type gs_data: dict
423431
:return:
424432
:rtype:
425433
"""
426434
self.authenticate()
427435

436+
gs_coordinates = gs_data.get('coordinates')
428437
__params = ','.join(gs_coordinates[0])
429438
for coord in gs_coordinates[1:]:
430439
__params = '{}{}{}'.format(__params, SEPARATOR_LOOKUP_COMPLEX_VALUE, ','.join(coord))
431440

432441
__params = '{}{sep}relation,{}{sep}type,{}'.format(
433442
__params,
434-
gs_relation,
435-
gs_type,
443+
gs_data.get('relation'),
444+
gs_data.get('type'),
436445
sep=SEPARATOR_LOOKUP_COMPLEX_VALUE
437446
)
438447

448+
gs_extra = gs_data.get('extra')
439449
if gs_extra:
440450
__params = '{}{}{}'.format(__params, SEPARATOR_LOOKUP_COMPLEX_VALUE, gs_extra)
441451

442-
url = self.base_publisher_url[:] + '?{}={}'.format(
443-
'location_shape__geo_shape',
444-
__params
452+
url = self.base_publisher_url[:] + '?{field}{}={}'.format(
453+
'__geo_shape',
454+
__params,
455+
field=gs_data.get('field')
445456
)
446457

447-
print(url)
448458
publishers = []
449459
for __lat, __lon in points:
450460
publishers.append(
@@ -481,9 +491,12 @@ def test_field_filter_geo_shape_envelope(self):
481491
return self._test_field_filter_geo_shape(
482492
points=points,
483493
count=2,
484-
gs_coordinates=[['49.344809', '6.643982'], ['48.643798', '5.630493']],
485-
gs_relation='within',
486-
gs_type='envelope',
494+
gs_data={
495+
'field': 'location_point',
496+
'coordinates': [['49.344809', '6.643982'], ['48.643798', '5.630493']],
497+
'relation': 'within',
498+
'type': 'envelope',
499+
}
487500
)
488501

489502
@pytest.mark.webtest
@@ -502,10 +515,13 @@ def test_field_filter_geo_shape_circle(self):
502515
return self._test_field_filter_geo_shape(
503516
points=points,
504517
count=2,
505-
gs_coordinates=[['49.119696', '6.176355']],
506-
gs_relation='within',
507-
gs_type='circle',
508-
gs_extra='radius,10km',
518+
gs_data={
519+
'field': 'location_point',
520+
'coordinates': [['49.119696', '6.176355']],
521+
'relation': 'within',
522+
'type': 'circle',
523+
'extra': 'radius,10km',
524+
}
509525
)
510526

511527
@pytest.mark.webtest
@@ -524,10 +540,13 @@ def test_field_filter_geo_shape_circle_intersects(self):
524540
return self._test_field_filter_geo_shape(
525541
points=points,
526542
count=1,
527-
gs_coordinates=[['48.5728929', '7.8109768']],
528-
gs_relation='intersects',
529-
gs_type='circle',
530-
gs_extra='radius,10km',
543+
gs_data={
544+
'field': 'location_circle',
545+
'coordinates': [['48.5728929', '7.8109768']],
546+
'relation': 'intersects',
547+
'type': 'circle',
548+
'extra': 'radius,10km',
549+
}
531550
)
532551

533552

0 commit comments

Comments
 (0)