|
32 | 32 | LOOKUP_FILTER_GEO_DISTANCE, |
33 | 33 | LOOKUP_FILTER_GEO_POLYGON, |
34 | 34 | LOOKUP_FILTER_GEO_BOUNDING_BOX, |
| 35 | + LOOKUP_FILTER_GEO_SHAPE, |
35 | 36 | SEPARATOR_LOOKUP_COMPLEX_VALUE, |
36 | 37 | SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE, |
37 | 38 | ) |
@@ -340,6 +341,112 @@ def get_geo_bounding_box_params(cls, value, field): |
340 | 341 |
|
341 | 342 | return params |
342 | 343 |
|
| 344 | + @classmethod |
| 345 | + def get_geo_shape_params(cls, value, field): |
| 346 | + """Get params for `geo_shape` query. |
| 347 | +
|
| 348 | + Example: |
| 349 | +
|
| 350 | + /search/publishers/?location__geo_shape=48.9864453,6.37977 |
| 351 | + __relation,intersects |
| 352 | + __type,circle |
| 353 | + __radius,20km |
| 354 | +
|
| 355 | + Example: |
| 356 | +
|
| 357 | + /search/publishers/?location__geo_shape=48.906254,6.378593 |
| 358 | + __48.985850,6.479359 |
| 359 | + __relation,within |
| 360 | + __type,envelope |
| 361 | +
|
| 362 | + Elasticsearch: |
| 363 | +
|
| 364 | + { |
| 365 | + "query": { |
| 366 | + "bool" : { |
| 367 | + "must" : { |
| 368 | + "match_all" : {} |
| 369 | + }, |
| 370 | + "filter" : { |
| 371 | + "geo_shape" : { |
| 372 | + "location" : { |
| 373 | + "shape": { |
| 374 | + "type": "circle", |
| 375 | + "coordinates": [48.9864453, 6.37977], |
| 376 | + "radius": "20km" |
| 377 | + }, |
| 378 | + "relation": "intersects" |
| 379 | + } |
| 380 | + } |
| 381 | + } |
| 382 | + } |
| 383 | + } |
| 384 | + } |
| 385 | +
|
| 386 | + :param value: |
| 387 | + :param field: |
| 388 | + :type value: str |
| 389 | + :type field: |
| 390 | + :return: Params to be used in `geo_shape` query. |
| 391 | + :rtype: dict |
| 392 | + """ |
| 393 | + __values = cls.split_lookup_complex_value(value) |
| 394 | + __len_values = len(__values) |
| 395 | + |
| 396 | + if not __len_values: |
| 397 | + return {} |
| 398 | + |
| 399 | + __coordinates = [] |
| 400 | + __options = {} |
| 401 | + |
| 402 | + # Parse coordinates (can be x points) |
| 403 | + for value in __values: |
| 404 | + __lat_lon = value.split( |
| 405 | + SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE |
| 406 | + ) |
| 407 | + if len(__lat_lon) >= 2: |
| 408 | + try: |
| 409 | + __point = [ |
| 410 | + float(__lat_lon[0]), |
| 411 | + float(__lat_lon[1]), |
| 412 | + ] |
| 413 | + __coordinates.append(list(__point)) |
| 414 | + except ValueError: |
| 415 | + if SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE in value: |
| 416 | + __opt_name_val = value.split( |
| 417 | + SEPARATOR_LOOKUP_COMPLEX_MULTIPLE_VALUE |
| 418 | + ) |
| 419 | + if len(__opt_name_val) >= 2: |
| 420 | + if __opt_name_val[0] in ('relation', |
| 421 | + 'type', |
| 422 | + 'radius'): |
| 423 | + __options.update( |
| 424 | + { |
| 425 | + __opt_name_val[0]: __opt_name_val[1] |
| 426 | + } |
| 427 | + ) |
| 428 | + |
| 429 | + __type = __options.pop('type', None) |
| 430 | + __relation = __options.pop('relation', None) |
| 431 | + if not __coordinates or not __type or not __relation: |
| 432 | + return {} |
| 433 | + |
| 434 | + params = { |
| 435 | + field: { |
| 436 | + 'shape': { |
| 437 | + 'type': __type, |
| 438 | + 'coordinates': __coordinates if len(__coordinates) > 1 else __coordinates[0], |
| 439 | + }, |
| 440 | + 'relation': __relation, |
| 441 | + } |
| 442 | + } |
| 443 | + radius = __options.pop('radius', None) |
| 444 | + if radius: |
| 445 | + params[field]['shape'].update({'radius': radius}) |
| 446 | + params.update(__options) |
| 447 | + |
| 448 | + return params |
| 449 | + |
343 | 450 | @classmethod |
344 | 451 | def apply_query_geo_distance(cls, queryset, options, value): |
345 | 452 | """Apply `geo_distance` query. |
@@ -400,6 +507,26 @@ def apply_query_geo_bounding_box(cls, queryset, options, value): |
400 | 507 | ) |
401 | 508 | ) |
402 | 509 |
|
| 510 | + @classmethod |
| 511 | + def apply_query_geo_shape(cls, queryset, options, value): |
| 512 | + """Apply `geo_shape` query. |
| 513 | +
|
| 514 | + :param queryset: Original queryset. |
| 515 | + :param options: Filter options. |
| 516 | + :param value: value to filter on. |
| 517 | + :type queryset: elasticsearch_dsl.search.Search |
| 518 | + :type options: dict |
| 519 | + :type value: str |
| 520 | + :return: Modified queryset. |
| 521 | + :rtype: elasticsearch_dsl.search.Search |
| 522 | + """ |
| 523 | + return queryset.query( |
| 524 | + Q( |
| 525 | + 'geo_shape', |
| 526 | + **cls.get_geo_shape_params(value, options['field']) |
| 527 | + ) |
| 528 | + ) |
| 529 | + |
403 | 530 | def get_filter_query_params(self, request, view): |
404 | 531 | """Get query params to be filtered on. |
405 | 532 |
|
@@ -491,4 +618,12 @@ def filter_queryset(self, request, queryset, view): |
491 | 618 | value |
492 | 619 | ) |
493 | 620 |
|
| 621 | + # `geo_shape` query lookup |
| 622 | + elif options['lookup'] == LOOKUP_FILTER_GEO_SHAPE: |
| 623 | + queryset = self.apply_query_geo_shape( |
| 624 | + queryset, |
| 625 | + options, |
| 626 | + value |
| 627 | + ) |
| 628 | + |
494 | 629 | return queryset |
0 commit comments