Сначала сгенерируйте почасовые значения даты. Затем рассчитайте расстояния для этих часов и пользователей.
function haversine() {
var radians = Array.prototype.map.call(arguments, function (deg) { return deg / 180.0 * Math.PI; });
var lat1 = radians[0], lon1 = radians[1], lat2 = radians[2], lon2 = radians[3];
if (!lat1 || !lon1)
return 0;
var R = 6372.8 * 1000; // meters
var dLat = lat2 - lat1;
var dLon = lon2 - lon1;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.asin(Math.sqrt(a));
return R * c;
}
var preLon, preLat, preUser, preHour
db.collection.aggregate([
{ $addFields: { DateParts: { $dateToParts: { date: "$Date" } } } },
{
$addFields: {
Date_Hour: {
$dateFromParts: {
year: "$DateParts.year",
month: "$DateParts.month",
day: "$DateParts.day",
hour: "$DateParts.hour"
}
}
}
},
{ $sort: { Username: 1, Date: 1 } }
]).forEach(function (doc) {
if ( preUser != doc.Username || preHour != doc.Date_Hour.getTime() ) {
// maybe other conditions for reset should be added
preLon = null;
preLat = null;
preUser = null;
preHour = null;
}
distance = haversine(preLat, preLon, doc.Location.coordinates[1], doc.Location.coordinates[0])
preLon = doc.Location.coordinates[0];
preLat = doc.Location.coordinates[1];
preUser = doc.Username;
preHour = doc.Date_Hour.getTime();
db.collection.updateOne(
{ _id: doc._id },
{ $set: { distance: distance, Date_Hour: doc.Date_Hour } }
)
})
После этого вы можете суммировать расстояния по пользователю и часам:
db.collection.aggregate([
{
$group: {
_id: { Username: "$Username", hour: "$Date_Hour" },
distance: { $sum: "$distance" }
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$_id"] } } },
{ $unset: "_id" }
])
Результат выборки:
{
"distance" : 111226.34257109408,
"Username" : "abc",
"hour" : ISODate("2016-06-16T21:00:00.000+0200")
}
{
"distance" : 111226.34257109408,
"Username" : "abc",
"hour" : ISODate("2016-06-16T11:00:00.000+0200")
}
{
"distance" : 8108.472378934691,
"Username" : "xyz",
"hour" : ISODate("2016-06-16T11:00:00.000+0200")
}
Другой подход (без функции JavaScript) будет следующим:
db.collection.aggregate([
{ $addFields: { DateParts: { $dateToParts: { date: "$Date" } } } },
// Create hourly value
{
$addFields: {
Date_Hour: {
$dateFromParts: {
year: "$DateParts.year",
month: "$DateParts.month",
day: "$DateParts.day",
hour: "$DateParts.hour"
}
}
}
},
// Sort documents in order to get proper track
{ $sort: { Username: 1, Date: 1 } },
// Group by user and hour
{
$group: {
_id: {Username: "$Username", Date_Hour: "$Date_Hour"},
Locations: { $push: "$Location" }
}
},
// Put "Location" & "Location+1" into array in order to get a line
{
$addFields: {
distance: {
$map: {
input: { $range: [0, { $size: "$Locations" }] },
in: {
$cond: {
if: { $eq: ["$$this", 0] },
then: null,
else: [
{ $arrayElemAt: [{ $arrayElemAt: ["$Locations.coordinates", { $subtract: ["$$this", 1] }] }, 0] },
{ $arrayElemAt: [{ $arrayElemAt: ["$Locations.coordinates", { $subtract: ["$$this", 1] }] }, 1] },
{ $arrayElemAt: [{ $arrayElemAt: ["$Locations.coordinates", "$$this"] }, 0] },
{ $arrayElemAt: [{ $arrayElemAt: ["$Locations.coordinates", "$$this"] }, 1] }
]
}
}
}
}
}
},
// Calculate the haversine
{
$set: {
distance: {
$map: {
input: "$distance",
in: {
$let: {
vars: {
dlon: { $degreesToRadians: { $subtract: [{ $arrayElemAt: ["$$this", 0] }, { $arrayElemAt: ["$$this", 2] }] } },
dlat: { $degreesToRadians: { $subtract: [{ $arrayElemAt: ["$$this", 1] }, { $arrayElemAt: ["$$this", 3] }] } },
lat1: { $degreesToRadians: { $arrayElemAt: ["$$this", 1] } },
lat2: { $degreesToRadians: { $arrayElemAt: ["$$this", 3] } }
},
in: {
// 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" }] }
]
}
}
}
}
}
}
},
// Calculate the distance (in meters, given by "6372.8 * 1000")
{
$set: {
distance: {
$map: {
input: "$distance",
in: { $multiply: [6372.8, 1000, 2, { $asin: { $sqrt: "$$this" } }] }
}
}
}
},
// Sum up distance parts
{ $set: { distance: { $sum: "$distance" } } },
// Remove unused fields
{ $unset: "Locations" }
])
См. Пн go детская площадка
Вы можете взглянуть на $ geoNear . Это обеспечивает расстояние автоматически на distanceField
. Однако вы не можете указать прямоугольник (то есть $box
) в качестве фильтра, вы должны указать точку плюс maxDistance
.