В случае того, что вы пытаетесь сделать здесь, гораздо лучшим вариантом будет на самом деле использовать стадию агрегации конвейера $geoNear
для определения «ближайших» совпадений в пределах ваших ограничений. Примечательно, что ваши критерии фактически запрашивают $geoWithin
для радиуса 7 миль в соответствии с примененной математикой. Так что это действительно лучше выразить, используя $geoNear
, и его опции фактически позволяют вам делать то, что вы хотите.
User.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [76.7786787, 30.7352527]
},
"spherical": true,
"distanceField": "distance",
"distanceMultiplier": 0.000621371,
"maxDistance": 7 * 1609.34,
"includeLocs": "location"
}},
{ "$addFields": {
"parking_space": {
"$filter": {
"input": "$parking_space",
"cond": {
"$eq": ["$location", "$$this.location"]
}
}
}
}}
],function(err,park_places) {
// rest of your code.
})
Это даст результат, который выглядит следующим образом:
{
"_id" : "5ae04fd45f104a5980cf7e0e",
"name" : "Rehan",
"email" : "rehan@gmail.com",
"status" : true,
"created_at" : "2018-04-25T09:52:20.266Z",
"parking_space" : [
{
"_id" : "5ae06d4d5f104a5980cf7e52",
"parking_name" : "my space 2",
"restriction" : "no",
"hourly_rate" : "6",
"location" : {
"type" : "Point",
"coordinates" : [
76.7786787,
30.7352527
]
}
}
],
"distance" : 0,
"location" : {
"type" : "Point",
"coordinates" : [
76.7786787,
30.7352527
]
}
}
{
"_id" : "5ae2f8148d51db4937b9df02",
"name" : "nellima",
"email" : "neel@gmail.com",
"status" : true,
"created_at" : "2018-04-27T10:14:44.598Z",
"parking_space" : [
{
"_id" : "5ae2f89d8d51db4937b9df04",
"parking_name" : "my space 3",
"restriction" : "no",
"hourly_rate" : "60",
"location" : {
"type" : "Point",
"coordinates" : [
76.7786787,
30.7352527
]
}
}
],
"distance" : 0,
"location" : {
"type" : "Point",
"coordinates" : [
76.7786787,
30.7352527
]
}
}
Мы используем два этапа здесь в конвейере агрегации, поэтому, чтобы объяснить, что на самом деле делает каждый:
Сначала $geoNear
выполняет запрос, учитывая расположение, указанное здесь в формате GeoJSON, для сравнения в опции "near"
, и это, конечно, основное ограничение. Параметр "spherical"
обычно требуется для индексов "2dsphere"
, и это тип индекса, который вы действительно хотите использовать для своих данных. "distanceField"
является другим обязательным аргументом, и он указывает имя свойства, которое фактически будет записывать расстояние от запрашиваемой точки для местоположения, с которым сопоставлен «документ».
Другие опции - это части, которые делают эту работу для того, что вы хотите сделать здесь. Во-первых, есть "distanceMultiplier"
, который на самом деле здесь «необязательный», поскольку он просто определяет значение, которое будет выводиться в свойстве, указанном "distanceField"
. Используемое здесь значение будет корректировать метров , возвращаемых как «расстояние», в миль , на что вы обычно смотрите. На самом деле это никак не влияет на остальные параметры, но поскольку "distanceField"
является обязательным, мы хотим показать «ожидаемое» числовое значение.
Следующий параметр - это другой основной «фильтр», имитирующий ваше утверждение $geoWithin
. Опция "maxDistance"
устанавливает верхний предел того, «насколько далеко» может быть сопоставленное местоположение. В этом случае мы даем 7
для миль, где мы умножаем на 1609.34
, то есть сколько метров находится в миле . Обратите внимание, что "distanceMultiplier"
не влияет на этот номер, поэтому любое "преобразование" должно быть выполнено и здесь.
Последний вариант здесь - "includeLocs"
, который на самом деле является наиболее важным параметром здесь, кроме ограничения по расстоянию. Это фактическая часть, которая сообщает нам «данные местоположения», которые фактически использовались для «ближайшего совпадения» из массива местоположений, содержащихся в документе. Здесь определено, конечно, свойство, которое будет использоваться для хранения этих данных в документах, возвращаемых с этой стадии конвейера. Вы можете увидеть дополнительное свойство "location"
, добавленное к каждому документу, отражающее это.
Таким образом, на этом этапе конвейера фактически были идентифицированы совпадающие данные "location"
, но это фактически не идентифицирует, какой элемент массива был фактически сопоставлен явно. Таким образом, чтобы на самом деле вернуть информацию для конкретного члена массива, мы можем использовать $filter
для сравнения.
Операция, конечно, представляет собой простое сравнение «согласованного местоположения» с фактическими данными «местоположения» каждого члена массива. Поскольку совпадение будет один , вы можете поочередно использовать такие вещи, как $indexOfArray
и $arrayElemAt
для сравнения и извлечения только «результат, но $filter
- это, как правило, самая понятная операция, которую легко понять.
Весь смысл ограничения радиуса может быть продемонстрирован несколькими короткими изменениями условий. Так что, если мы немного отодвинем локацию:
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [76.7786787, 30.6352527] // <-- different location
},
"spherical": true,
"distanceField": "distance",
"distanceMultiplier": 0.000621371,
"maxDistance": 7 * 1609.34,
"includeLocs": "location"
}},
Это все еще в пределах радиуса , как указано в выходных данных, как указано для "distanceField"
:
"distance" : 6.917030204982402,
Но если вы измените это radius , чтобы оно было меньше указанного числа:
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [76.7786787, 30.6352527] // <-- different location
},
"spherical": true,
"distanceField": "distance",
"distanceMultiplier": 0.000621371,
"maxDistance": 6.91 * 1609.34, // <--- smaller radius
"includeLocs": "location"
}},
Тогда запрос не вернет ни один из документов, представленных в вопросе. Таким образом, вы можете видеть, как этот параметр управляет теми же границами, что и в запросе $geoWithin
, и, конечно, теперь мы можем идентифицировать соответствующий поддокумент.
Несколько матчей
В качестве заключительного замечания по теме мы можем увидеть, как опция "includeLocs"
может использоваться для идентификации соответствующей записи для местоположения в массиве родительского документа. Хотя здесь это должно соответствовать сценарию использования, четкое ограничение заключается в совпадении mutliple местоположений в пределах диапазона.
Таким образом, «множественные» совпадения просто выходят за рамки $geoNear
или других геопространственных операций с MongoDB. Альтернативным случаем будет вместо $unwind
содержимое массива после начальной $geoNear
и затем стадия $geoWithin
для «фильтрации» этих несколько совпадений:
User.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [76.7786787, 30.7352527]
},
"spherical": true,
"distanceField": "distance",
"distanceMultiplier": 0.000621371,
"maxDistance": 7 * 1609.34,
}},
{ "$unwind": "$parking_space" },
{ "$match": {
"parking_space.location": {
"$geoWithin": {
"$centerSphere": [
[76.7786787, 30.7352527], 7 / 3963.2
]
}
}
}}
],function(err,park_places) {
// rest of your code.
})
Вероятно, лучше использовать стадию $geoNear
здесь, и мы действительно просто делаем то же самое, не нуждаясь в опции "includeLocs"
. Однако, если вы действительно хотите этого, то нет ничего плохого в том, чтобы просто использовать $geoWithin
по обе стороны от $unwind
stage:
User.aggregate([
{ "$match": {
"parking_space.location": {
"$geoWithin": {
"$centerSphere": [
[76.7786787, 30.7352527], 7 / 3963.2
]
}
}
}}
{ "$unwind": "$parking_space" },
{ "$match": {
"parking_space.location": {
"$geoWithin": {
"$centerSphere": [
[76.7786787, 30.7352527], 7 / 3963.2
]
}
}
}}
],function(err,park_places) {
// rest of your code.
})
Причина этого в том, что пока $geoWithin
работает наиболее "оптимально" , когда он может фактически использовать геопространственный индекс, определенный в коллекции, на самом деле делает * не требуется индекс для возврата результатов.
Следовательно, в любом случае после того, как «начальный запрос» возвращает «документ», который содержит хотя бы одно совпадение для условия, мы просто $unwind
содержимое массива и затем примените те же самые ограничения снова, чтобы отфильтровать те записи массива, теперь как документы. Если вы хотите, чтобы «массив» был возвращен, то вы всегда можете $group
и $push
вернуть элементы обратно в форму массива.
В отличие от этого $geoNear
ступень конвейера должна использоваться в качестве самой первой ступени конвейера только . Это единственное место, где он может использовать индекс, и, следовательно, его невозможно использовать на более поздних этапах. Но, конечно, информация о «ближайшем расстоянии», вероятно, полезна для вас, и поэтому ее стоит включить в результаты и условия запроса.