Reputation: 646
I have a MongoDB collection containing documents with geographic coordinates stored as GeoJSON objects. Each document represents an item with two locations (location_1 and location_2). Here's an example document structure:
[
{
"_id": ObjectId("62c43a4128c8b27fda436536"),
"item": "item1",
"location_1": {
"type": "Point",
"coordinates": [76.22191167727965, 10.929396489878417]
},
"location_2": {
"type": "Point",
"coordinates": [76.2249520851109, 10.97594496161423]
}
},
{
"_id": ObjectId("62c43a4128c8b27fda436537"),
"item": "item2",
"location_1": {
"type": "Point",
"coordinates": [50.22191167727965, 10.929396489878417]
},
"location_2": {
"type": "Point",
"coordinates": [10.2249520851109, 10.97594496161423]
}
},
// Additional documents...
]
I want to calculate the distance in kilometers between location_1 and location_2 for each items using the MongoDB aggregation pipeline.
I know that MongoDB has a $geoNear aggregation stage for geospatial queries, but I'm not sure how to use it to calculate distances between two points within the same document.
Can someone provide an example of how to achieve this using MongoDB aggregation pipeline?
Thank you!
Upvotes: 2
Views: 161
Reputation: 59622
I would suggest to use the Haversine formula.
db.collection.aggregate([
{
$set: {
distance: {
$let: {
vars: {
dlon: { $degreesToRadians: { $subtract: [{ $first: "$location_1.coordinates" }, { $first: "$location_2.coordinates" }] } },
dlat: { $degreesToRadians: { $subtract: [{ $last: "$location_1.coordinates" }, { $last: "$location_2.coordinates" }] } },
lat1: { $degreesToRadians: { $last: "$location_1.coordinates" } },
lat2: { $degreesToRadians: { $last: "$location_2.coordinates" } }
},
in: {
// Haversine formula: sin²(dLat / 2) + sin²(dLon / 2) * cos(lat1) * cos(lat2);
$add: [
{ $pow: [{ $sin: { $divide: ["$$dlat", 2] } }, 2] },
{ $multiply: [{ $pow: [{ $sin: { $divide: ["$$dlon", 2] } }, 2] }, { $cos: "$$lat1" }, { $cos: "$$lat2" }] }
]
}
}
}
}
},
{
$set: {
distance: {
// Distance in Meters given by "6378.1 * 1000"
$multiply: [6378.1, 1000, 2, { $asin: { $sqrt: "$distance" } }]
}
}
}
])
According to this sample $geoNear
uses the same formula with an earth radius of 6378.1 km
db.collection.insertOne(
{
location: {
type: "Point",
coordinates: [-0.123507, 51.5083228]
}
}
)
const coordinate = [-0.0649729793707321, 51.50160291888072]
db.collection.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: coordinate },
distanceField: "distance_geoNear",
includeLocs: "location",
spherical: true
}
},
{
$set: {
distance: {
$let: {
vars: {
dlon: { $degreesToRadians: { $subtract: [{ $arrayElemAt: ["$location.coordinates", 0] }, coordinate[0]] } },
dlat: { $degreesToRadians: { $subtract: [{ $arrayElemAt: ["$location.coordinates", 1] }, coordinate[1]] } },
lat1: { $degreesToRadians: { $arrayElemAt: ["$location.coordinates", 1] } },
lat2: { $degreesToRadians: coordinate[1] }
},
in: {
// Haversine formula: sin²(dLat / 2) + sin²(dLon / 2) * cos(lat1) * cos(lat2);
$add: [
{ $pow: [{ $sin: { $divide: ["$$dlat", 2] } }, 2] },
{ $multiply: [{ $pow: [{ $sin: { $divide: ["$$dlon", 2] } }, 2] }, { $cos: "$$lat1" }, { $cos: "$$lat2" }] }
]
}
}
}
}
},
{
$set: {
distance_haversine: {
// Distance in Meters given by "6378.1 * 1000"
$multiply: [6378.1, 1000, 2, { $asin: { $sqrt: "$distance" } }]
}
}
}
])
{
distance_geoNear: 4124.233475368821,
distance_haversine: 4124.233475368682
}
Upvotes: 1