I want to get a boolean value eg. true or false if a location coordinate is within the given radius is the $geoWithin query or $geoNear pipeline in mongoose aggregate function. If I user $geoNear in the mongoose aggregate pipeline then it only returns the filtered result. So far I have done the following,
The Model:
import * as mongoose from 'mongoose';
import { User } from 'src/user/user.model';
export const BlipSchema = new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
media: [
new mongoose.Schema(
{
fileName: String,
mediaType: {
type: String,
enum: ['image', 'video'],
},
},
{
toJSON: {
transform: function (doc, ret) {
delete ret._id;
},
},
},
),
],
likesCount: {
type: Number,
default: 0,
},
commentsCount: {
type: Number,
default: 0,
},
isLiked: Boolean,
status: {
type: String,
enum: ['public', 'private'],
default: 'public',
},
location: {
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: [Number],
address: String,
description: String,
},
lat: Number,
lng: Number,
address: String,
city: {
type: String,
default: '',
},
createdAt: {
type: Date,
default: Date.now,
},
},
{
toJSON: {
transform: function (doc, ret) {
ret.id = ret._id;
delete ret._id;
delete ret.location;
delete ret.__v;
},
},
toObject: { virtuals: true },
},
);
export interface Media {
fileName: string;
mediaType: string;
}
export interface Location {
type: string;
coordinates: [number];
address: string;
description: string;
}
export interface Blip {
user: User;
media: [Media];
likesCount: number;
commentsCount: number;
isLiked: boolean;
status: string;
lat: number;
lng: number;
address: string;
city: string;
createdAt: string;
}
The function in my controller:
async getLocationBlips(user: User, query: any) {
const aggrea = [];
aggrea.push(
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'user',
},
},
{
$unwind: '$user',
},
{ $set: { 'user.id': '$user._id' } },
{
$group: {
_id: { lat: '$lat', lng: '$lng' },
lat: { $first: '$lat' },
lng: { $first: '$lng' },
isViewable: { $first: false },
totalBlips: { $sum: 1 },
blips: {
$push: {
id: '$_id',
media: '$media',
user: '$user',
likesCount: '$likesCount',
commentsCount: '$commentsCount',
isLiked: '$isLiked',
status: '$status',
address: '$address',
city: '$city',
createdAt: '$createdAt',
},
},
},
},
{ $unset: 'blips.media._id' },
{ $unset: 'blips.user._id' },
{ $unset: 'blips.user.__v' },
{
$project: {
_id: 0,
lat: 1,
lng: 1,
totalBlips: 1,
isViewable: 1,
blips: 1,
},
},
{ $sort: { totalBlips: -1 } },
);
if (query.page !== undefined && query.limit !== undefined) {
const page = query.page * 1;
const limit = query.limit * 1;
const skip = (page - 1) * limit;
aggrea.push({ $skip: skip }, { $limit: parseInt(query.limit, 10) });
}
const blipLocations = await this.blipModel.aggregate(aggrea);
const total = blipLocations.length;
let viewableBlips = [];
if (query.lat && query.lng) {
const radius = query.distance / 3963.2;
viewableBlips = await this.blipModel.find({
location: {
$geoWithin: {
$centerSphere: [[query.lng, query.lat], radius],
},
},
});
}
await Promise.all(
blipLocations.map(async (blpL) => {
await Promise.all(
blpL.blips.map(async (blp) => {
const like = await this.likeModel.findOne({
user: user.id,
blip: blp.id,
});
if (like) {
blp.isLiked = true;
} else {
blp.isLiked = false;
}
if (query.lat && query.lng) {
viewableBlips.forEach((vBlp) => {
if (vBlp._id.toString() === blp.id.toString()) {
console.log(vBlp.id);
blpL.isViewable = true;
}
});
}
}),
);
}),
);
return {
status: true,
message: 'Data fetched successfully',
results: total,
blipLocations,
};
}
In the above snippet, I have a field called isViewable. Right now I am updating this field in runtime. But I want to update this field in the aggregation pipeline. Is there any way to check if the location coordinate is within the provided $geoWithin or $geoNear from the aggregation pipeline? Thanks.
CodePudding user response:
If you're fine with using $geoNear
(you need the 2dsphere
index & it must be the first stage in the pipeline as noted in docs) you could add a distance field and then another field which will output a boolean based on it, like so:
this.blipModel.aggregate([
{
$geoNear: {
key: 'location',
near: {
type: 'Point',
coordinates: [query.lng, query.lat]
},
distanceField: 'distance',
spherical: true
}
},
{
$addFields: {
isViewable: {
$cond: {
if: {$lte: ["$distance", query.distance]},
then: true,
else: false
}
}
}
}
]);
And optionally unset the distance field in another stage if you don't need it.