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.
Advertisement
Answer
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.