Агрегация по георасстоянию
Мульти-корзинная агрегация, работающая с полями geo_point и концептуально очень похожая на агрегацию диапазона. Пользователь может определить точку начала отсчета и набор корзин диапазонов расстояний. Агрегация вычисляет расстояние каждого значения документа от начальной точки и определяет корзину, к которой оно относится, на основе диапазонов (документ принадлежит корзине, если расстояние между документом и начальной точкой попадает в диапазон расстояний корзины).
resp = client.indices.create(
index="museums",
mappings={
"properties": {
"location": {
"type": "geo_point"
}
}
},
)
print(resp)
resp1 = client.bulk(
index="museums",
refresh=True,
operations=[
{
"index": {
"_id": 1
}
},
{
"location": "POINT (4.912350 52.374081)",
"name": "NEMO Science Museum"
},
{
"index": {
"_id": 2
}
},
{
"location": "POINT (4.901618 52.369219)",
"name": "Museum Het Rembrandthuis"
},
{
"index": {
"_id": 3
}
},
{
"location": "POINT (4.914722 52.371667)",
"name": "Nederlands Scheepvaartmuseum"
},
{
"index": {
"_id": 4
}
},
{
"location": "POINT (4.405200 51.222900)",
"name": "Letterenhuis"
},
{
"index": {
"_id": 5
}
},
{
"location": "POINT (2.336389 48.861111)",
"name": "Musée du Louvre"
},
{
"index": {
"_id": 6
}
},
{
"location": "POINT (2.327000 48.860000)",
"name": "Musée d'Orsay"
}
],
)
print(resp1)
resp2 = client.search(
index="museums",
size="0",
aggs={
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{
"to": 100000
},
{
"from": 100000,
"to": 300000
},
{
"from": 300000
}
]
}
}
},
)
print(resp2) response = client.indices.create(
index: 'museums',
body: {
mappings: {
properties: {
location: {
type: 'geo_point'
}
}
}
}
)
puts response
response = client.bulk(
index: 'museums',
refresh: true,
body: [
{
index: {
_id: 1
}
},
{
location: 'POINT (4.912350 52.374081)',
name: 'NEMO Science Museum'
},
{
index: {
_id: 2
}
},
{
location: 'POINT (4.901618 52.369219)',
name: 'Museum Het Rembrandthuis'
},
{
index: {
_id: 3
}
},
{
location: 'POINT (4.914722 52.371667)',
name: 'Nederlands Scheepvaartmuseum'
},
{
index: {
_id: 4
}
},
{
location: 'POINT (4.405200 51.222900)',
name: 'Letterenhuis'
},
{
index: {
_id: 5
}
},
{
location: 'POINT (2.336389 48.861111)',
name: 'Musée du Louvre'
},
{
index: {
_id: 6
}
},
{
location: 'POINT (2.327000 48.860000)',
name: "Musée d'Orsay"
}
]
)
puts response
response = client.search(
index: 'museums',
size: 0,
body: {
aggregations: {
rings_around_amsterdam: {
geo_distance: {
field: 'location',
origin: 'POINT (4.894 52.3760)',
ranges: [
{
to: 100_000
},
{
from: 100_000,
to: 300_000
},
{
from: 300_000
}
]
}
}
}
}
)
puts response const response = await client.indices.create({
index: "museums",
mappings: {
properties: {
location: {
type: "geo_point",
},
},
},
});
console.log(response);
const response1 = await client.bulk({
index: "museums",
refresh: "true",
operations: [
{
index: {
_id: 1,
},
},
{
location: "POINT (4.912350 52.374081)",
name: "NEMO Science Museum",
},
{
index: {
_id: 2,
},
},
{
location: "POINT (4.901618 52.369219)",
name: "Museum Het Rembrandthuis",
},
{
index: {
_id: 3,
},
},
{
location: "POINT (4.914722 52.371667)",
name: "Nederlands Scheepvaartmuseum",
},
{
index: {
_id: 4,
},
},
{
location: "POINT (4.405200 51.222900)",
name: "Letterenhuis",
},
{
index: {
_id: 5,
},
},
{
location: "POINT (2.336389 48.861111)",
name: "Musée du Louvre",
},
{
index: {
_id: 6,
},
},
{
location: "POINT (2.327000 48.860000)",
name: "Musée d'Orsay",
},
],
});
console.log(response1);
const response2 = await client.search({
index: "museums",
size: 0,
aggs: {
rings_around_amsterdam: {
geo_distance: {
field: "location",
origin: "POINT (4.894 52.3760)",
ranges: [
{
to: 100000,
},
{
from: 100000,
to: 300000,
},
{
from: 300000,
},
],
},
},
},
});
console.log(response2); PUT /museums
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "POINT (4.912350 52.374081)", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "POINT (4.901618 52.369219)", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "POINT (4.914722 52.371667)", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "POINT (4.405200 51.222900)", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "POINT (2.336389 48.861111)", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "POINT (2.327000 48.860000)", "name": "Musée d'Orsay"}
POST /museums/_search?size=0
{
"aggs": {
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{ "to": 100000 },
{ "from": 100000, "to": 300000 },
{ "from": 300000 }
]
}
}
}
} Ответ:
{
...
"aggregations": {
"rings_around_amsterdam": {
"buckets": [
{
"key": "*-100000.0",
"from": 0.0,
"to": 100000.0,
"doc_count": 3
},
{
"key": "100000.0-300000.0",
"from": 100000.0,
"to": 300000.0,
"doc_count": 1
},
{
"key": "300000.0-*",
"from": 300000.0,
"doc_count": 2
}
]
}
}
} Указанное поле должно быть типа geo_point (который может быть установлен только явно в отображениях). И оно также может содержать массив полей типа geo_point, в этом случае все они будут учтены во время агрегирования. Точка начала отсчета может принимать все форматы, поддерживаемые типом geo_point:
- Объектный формат:
{ "lat" : 52.3760, "lon" : 4.894 }— это наиболее безопасный формат, так как он наиболее явно указывает значенияlatиlon - Строковый формат:
"52.3760, 4.894"— где первое число — этоlat, а второе —lon - Массивный формат:
[4.894, 52.3760]— который основан на стандарте GeoJSON, где первое число — этоlon, а второе —lat
По умолчанию единицей измерения расстояния является m (метры), но она также может принимать значения: mi (мили), in (дюймы), yd (ярды), km (километры), cm (сантиметры), mm (миллиметры).
resp = client.search(
index="museums",
size="0",
aggs={
"rings": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"unit": "km",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 300
},
{
"from": 300
}
]
}
}
},
)
print(resp) response = client.search(
index: 'museums',
size: 0,
body: {
aggregations: {
rings: {
geo_distance: {
field: 'location',
origin: 'POINT (4.894 52.3760)',
unit: 'km',
ranges: [
{
to: 100
},
{
from: 100,
to: 300
},
{
from: 300
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "museums",
size: 0,
aggs: {
rings: {
geo_distance: {
field: "location",
origin: "POINT (4.894 52.3760)",
unit: "km",
ranges: [
{
to: 100,
},
{
from: 100,
to: 300,
},
{
from: 300,
},
],
},
},
},
});
console.log(response); POST /museums/_search?size=0
{
"aggs": {
"rings": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"unit": "km",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 300 },
{ "from": 300 }
]
}
}
}
} | Расстояния будут вычисляться в километрах |
Существует два режима вычисления расстояния: arc (по умолчанию) и plane. Расчет по методу arc является наиболее точным. Метод plane — самый быстрый, но наименее точный. Рассмотрите использование plane, когда область поиска «узкая» и охватывает небольшие географические области (~5 км). plane вернет более высокие погрешности при поиске по очень обширным областям (например, поиск по всему континенту). Тип вычисления расстояния можно задать с помощью параметра distance_type:
resp = client.search(
index="museums",
size="0",
aggs={
"rings": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"unit": "km",
"distance_type": "plane",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 300
},
{
"from": 300
}
]
}
}
},
)
print(resp) response = client.search(
index: 'museums',
size: 0,
body: {
aggregations: {
rings: {
geo_distance: {
field: 'location',
origin: 'POINT (4.894 52.3760)',
unit: 'km',
distance_type: 'plane',
ranges: [
{
to: 100
},
{
from: 100,
to: 300
},
{
from: 300
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "museums",
size: 0,
aggs: {
rings: {
geo_distance: {
field: "location",
origin: "POINT (4.894 52.3760)",
unit: "km",
distance_type: "plane",
ranges: [
{
to: 100,
},
{
from: 100,
to: 300,
},
{
from: 300,
},
],
},
},
},
});
console.log(response); POST /museums/_search?size=0
{
"aggs": {
"rings": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"unit": "km",
"distance_type": "plane",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 300 },
{ "from": 300 }
]
}
}
}
} Ключевой ответ
Установка флага keyed в значение true позволит ассоциировать уникальный строковый ключ с каждой корзиной и возвращать диапазоны как хеш, а не как массив:
resp = client.search(
index="museums",
size="0",
aggs={
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{
"to": 100000
},
{
"from": 100000,
"to": 300000
},
{
"from": 300000
}
],
"keyed": True
}
}
},
)
print(resp) response = client.search(
index: 'museums',
size: 0,
body: {
aggregations: {
rings_around_amsterdam: {
geo_distance: {
field: 'location',
origin: 'POINT (4.894 52.3760)',
ranges: [
{
to: 100_000
},
{
from: 100_000,
to: 300_000
},
{
from: 300_000
}
],
keyed: true
}
}
}
}
)
puts response const response = await client.search({
index: "museums",
size: 0,
aggs: {
rings_around_amsterdam: {
geo_distance: {
field: "location",
origin: "POINT (4.894 52.3760)",
ranges: [
{
to: 100000,
},
{
from: 100000,
to: 300000,
},
{
from: 300000,
},
],
keyed: true,
},
},
},
});
console.log(response); POST /museums/_search?size=0
{
"aggs": {
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{ "to": 100000 },
{ "from": 100000, "to": 300000 },
{ "from": 300000 }
],
"keyed": true
}
}
}
} Ответ:
{
...
"aggregations": {
"rings_around_amsterdam": {
"buckets": {
"*-100000.0": {
"from": 0.0,
"to": 100000.0,
"doc_count": 3
},
"100000.0-300000.0": {
"from": 100000.0,
"to": 300000.0,
"doc_count": 1
},
"300000.0-*": {
"from": 300000.0,
"doc_count": 2
}
}
}
}
} Также возможно настроить ключ для каждого диапазона:
resp = client.search(
index="museums",
size="0",
aggs={
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{
"to": 100000,
"key": "first_ring"
},
{
"from": 100000,
"to": 300000,
"key": "second_ring"
},
{
"from": 300000,
"key": "third_ring"
}
],
"keyed": True
}
}
},
)
print(resp) response = client.search(
index: 'museums',
size: 0,
body: {
aggregations: {
rings_around_amsterdam: {
geo_distance: {
field: 'location',
origin: 'POINT (4.894 52.3760)',
ranges: [
{
to: 100_000,
key: 'first_ring'
},
{
from: 100_000,
to: 300_000,
key: 'second_ring'
},
{
from: 300_000,
key: 'third_ring'
}
],
keyed: true
}
}
}
}
)
puts response const response = await client.search({
index: "museums",
size: 0,
aggs: {
rings_around_amsterdam: {
geo_distance: {
field: "location",
origin: "POINT (4.894 52.3760)",
ranges: [
{
to: 100000,
key: "first_ring",
},
{
from: 100000,
to: 300000,
key: "second_ring",
},
{
from: 300000,
key: "third_ring",
},
],
keyed: true,
},
},
},
});
console.log(response); POST /museums/_search?size=0
{
"aggs": {
"rings_around_amsterdam": {
"geo_distance": {
"field": "location",
"origin": "POINT (4.894 52.3760)",
"ranges": [
{ "to": 100000, "key": "first_ring" },
{ "from": 100000, "to": 300000, "key": "second_ring" },
{ "from": 300000, "key": "third_ring" }
],
"keyed": true
}
}
}
} Ответ:
{
...
"aggregations": {
"rings_around_amsterdam": {
"buckets": {
"first_ring": {
"from": 0.0,
"to": 100000.0,
"doc_count": 3
},
"second_ring": {
"from": 100000.0,
"to": 300000.0,
"doc_count": 1
},
"third_ring": {
"from": 300000.0,
"doc_count": 2
}
}
}
}
}
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-aggregations-bucket-geodistance-aggregation.html