Find centroid of the coordinates stored in GeoJson Mongodb - javascript

I have recently started working on GeoJson in Mongodb.
I have collection named Restaurants which stores the coordinates of the restaurants in GeoJson POINT object.
Is there a mongodb way of finding the centroid of the collection?
Schema of the Restaurants is as given below:
"use strict";
const mongoose = require("mongoose");
const restaurantSchema = new mongoose.Schema({
name: {
type: String,
},
geoCoordinates: {
type: {
type: String,
enum: ["Point"],
},
coordinates: {
type: [Number],
},
},
createdTimestamp: {
type: Date,
default: Date.now,
},
lastUpdatedTimestamp: {
type: Date,
default: Date.now,
},
});
const Restaurants = mongoose.model("Restaurants", restaurantSchema, "Restaurants");
module.exports = {
Restaurants,
};
Thank you,
KK

One option is to $group and calculate the $avg:
db.collection.aggregate([
{$group: {
_id: 0,
lon: {$push: {$arrayElemAt: ["$location.coordinates", 0]}},
lat: {$push: {$arrayElemAt: ["$location.coordinates", 1]}}
}},
{$project: {
_id: 0,
centroid: [{$avg: "$lon"}, {$avg: "$lat"}]
}}
])
See how it works on the playground example

Related

How to populate a nested path using aggregate?

I have been trying to find the averageSum and averageRating, but I cannot get it done because I do not know how to populate using aggregate or if there is a work around. I have heard of $lookup, but I am not sure how to do it, also it tells me something about atlas tier does not do it. Is there a another way around to this? Can I populate then aggregate or can I find the averageSum and averageRating at the end using another method? Please help me
here is how my schema looks:
const favoriteSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
favoriteSellers: [
//create array of object id, make sure they are unique for user not to add multiple sellers
{
type: mongoose.Schema.Types.ObjectId,
ref: "Seller",
unique: true,
},
],
});
and here is my Seller schema:
const sellerSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
business: businessSchema,
sellerType: [String],
reviews: [
{
by: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
title: {
type: String,
},
message: {
type: String,
},
rating: Number,
imagesUri: [String],
timestamp: {
type: Date,
default: Date.now,
},
},
],
...
});
So I have an array of favorite sellers, I want to populate the sellers, then populate the reviews.by and user paths, and then do the calculation for the average sum and do the average rating. If possible please help me. What are my options here? Just do it outside on the expressjs route logic?
Here is my aggregate:
aggregatePipeline.push({
$match: { user: req.user._id },
});
//****** Here is where I want to populate before start the rest **********
then continue to following code because the fields(paths) are not populated so it averageSum will be 0 at all times.
aggregatePipeline.push({
$addFields: {
ratingSum: {
$reduce: {
initialValue: 0,
input: "$favoriteSellers.reviews",
in: { $sum: ["$$value", "$$this.rating"] },
},
},
},
});
//get average of rating ex. seller1 has a 4.5 averageRating field
aggregatePipeline.push({
$addFields: {
averageRating: {
$cond: [
{ $eq: [{ $size: "favoriteSellers.reviews" }, 0] }, //if it does not have any reviews, then we will just send 0
0, //set it to 0
{
$divide: ["$ratingSum", { $size: "$reviews" }], //else we can divide to get average Rating
},
],
},
},
});
let favList = await Favorite.aggregate(aggregatePipeline).exec();
When I retrieve my code, the array looks like:
[
{
_id: new ObjectId("62a7ce9550094eafc7a61233"),
user: new ObjectId("6287e4e61df773752aadc286"),
favoriteSellers: [ new ObjectId("6293210asdce81d9f2ae1685") ],
}
]
Here is a sample on how I want it to look:
(so each seller should have a field of average rating like and averageSum)
_id: 'favorite_id.....'
user: 'my id',
favoriteSellers:[
{
_id: 'kjskjhajkhsjk',
averageRating: 4.6
reviews:[.....],
...
},
{
_id: 'id______hsjk',
averageRating: 2.6
reviews:[.....],
...
},
{
_id: 'kjid______khsjk....',
averageRating: 3.6
reviews:[.....],
...
}
]

Populating in Mongodb Aggregating

I just asked a related question here:
Mongoose/Mongodb Aggregate - group and average multiple fields
I'm trying to use Model.aggregate() to find the average rating of all posts by date and then by some author's subdocument like country.name or gender. Having trouble with this though. I know for the first stage I just need to use $match for the date and I think I need to use $lookup to "populate" the author field but not sure how to implement this.
This works for finding an average rating for all posts by date:
Post.aggregate([
{ $group: { _id: "$date", avgRating: { $avg: '$rating' }}}
]).
then(function (res) {
console.log(res);
})
And this is basically what I want to do but it doesn't work:
Post.aggregate([
{$match: {"date": today}},
{$group: {_id: {"country": "$author.country.name"}, avgRating: {$avg: "$rating"}}}
]).then(function(res) {
console.log(res)
})
User model:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
birthday: {
type: Date,
required: true,
},
gender:{
type: String,
required: true
},
country:{
name: {
type: String,
required: true
},
flag: {
type: String,
// default: "/images/flags/US.png"
}
},
avatar: AvatarSchema,
displayName: String,
bio: String,
coverColor: {
type: String,
default: "#343a40"
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post"
}
],
comments: [
{
type: Schema.Types.ObjectId,
ref: "Comment"
}
],
postedToday: {
type: Boolean,
default: false
},
todaysPost: {
type: String
}
})
You can populate an aggregation after you fetched the data from the MongoDB. Your `Query will look a bit like this:
modelName.aggregate([{
$unwind: ''//if Needed
}, {
$group: {
_id: {"country":"$author.country.name"},
avgRating: {
$avg: '$rating'
}
}])
.exec(function(err, transactions) {
// ERRORHANDLING
// CallsBacks
modelName.populate(columnName, {path: '_id'}, function(err, populatedModel) {
// Your populated columnName inside TaleName
});
});

Create array of array Schema using mongoose for NodeJS

I want to create a DB Schema to store the data as below
{
name : "xyz",
admin : "admin",
expense : [
jan: [{expenseObject},{expenseObject}],
feb: [[{expenseObject},{expenseObject}]
]
}
Expense Object
var expenseSchema = new Schema({
particular : String,
date : {type : Date, default: Date.now},
paid_by : String,
amount : Number
});
Can someone help me create a schema for the same.
Any suggestions for a better Schema for the same concept are welcome.
You can use Sub Docs
var parentSchema = new Schema({
name: { type: String },
admin: { type: String },
expense: [expenseSchema]
});
Or, if you need the expenseObjects to be stored in a seperate collection you can use refs, where Expense would be the name of another model
var parentSchema = new Schema({
name: { type: String },
admin: { type: String },
expense: [{ type: Schema.Types.ObjectId, ref: 'Expense' }],
});
var expenseSchema = new Schema({
particular : String,
date : {type : Date, default: Date.now},
paid_by : String,
amount : Number
});
// your schema
var mySchema = new Schema({
name : {type: String, trim: true},
admin : {type: String, trim: true},
expense: [expenseSchema]
});
--- UPDATE:
With this update now expense is an array of expenseSchema without any categorisation of month. Then if you want to get all expenses in a particular month you can simply do an aggregation like this:
db.users.aggregate(
[
// this match is for search the user
{ $match: { name: "<ADMIN NAME>"} },
// this unwind all expenses of the user selected before
{ $unwind: "$expense" },
// this project the month number with the expense
{
$project: {
expense: 1,
month: {$month: '$expense.date'}
}
},
// this search all the expenses in a particular month (of the user selected before)
{ $match: { month: 8 } },
// this is optional, it's for group the result by _id of the user
//(es {_id:.., expenses: [{}, {}, ...]}. Otherwise the result is a list of expense
{
$group: {
_id:"$month",
expenses: { $addToSet: "$expense"}
}
}
]);

node.js api with array's

I'm pretty new to node.js and javascript. I'm builded and api and out of the sudden the data model changed and now i'm kinda lost.
This is my code:
var mongoose = require('mongoose');
//create schema
var MedicSchema = new mongoose.Schema({
nombre:{
type: String,
required: true
},
especialidad:{
type: String,
required: true
},
ranking:{
type: Number,
},
direccion: [{
direccion: {type: String, required: true},
telefono: {type: String},
horario:{type: String}
}],
foto:{
type: String,
}
});
MedicSchema.find({})
.populate('direccion')
.exec(function (err, medic) {
console.log(medic.direccion); // This should have your information now
});
//export model
module.exports = MedicSchema;
I'm able to send data to the direccion array... but when i call the api; i get this:
{
"_id": "557839b36bcdd00e09173e36",
"nombre": "Sra. Hong",
"especialidad": "general",
"__v": 0,
"direccion": [
{
"_id": "55a44392b2e774572b957a8e"
},
{
"_id": "55a44392b2e774572b957a8d"
}
]
}
i can't find a way to call the details of the array.
edit: i posted it on bitbucket https://bitbucket.org/weput/api-server
You need to .populate your "direccion" array. For example:
MedicSchema.find({})
.populate('direccion')
.exec(function (err, medic) {
console.log(medic.direccion); // This should have your information now
});

Geospatial Box query - Mongoose, Node.js MongoDB - returns no results

I am attempting to run a Geo Box query using Mongoose however do not get any results. I have built out a simplified test case:
var mongoose = require('mongoose');
// Schema definition
var locationSchema = mongoose.Schema({
userid: { type : [Number], required: true},
loc: {
'type': {
type: String,
required: true,
enum: ['Point', 'LineString', 'Polygon'],
default: 'Point'
},
coordinates: []
},
tags: { type : [String], index: true, required: true },
create_date : {type: Date, "default": Date.now()}
});
locationSchema.index({ 'loc.coordinates': '2dsphere' });
var Location = mongoose.model('Location', locationSchema);
mongoose.connect('mongodb://localhost/test');
var chkin = new Location({
userid: 1,
loc: { type: 'Point', coordinates: [-122.424088, 37.529876] },
tags:"foo"
});
chkin.save(function (err, locations) {
console.log('SAVE err: ' + err)
console.log('SAVE locations: ' + locations)
console.log('');
var query = Location.find(
{ "coordinates" :
{ "$geoWithin" :
// <bottom left coordinates> - <upper right coordinates> - long,lat
{ "$box" :[[-122.610168,37.598167], [-122.288818,37.845833]] }
}
}, function (err, docs) {
console.log('FIND err: '+ err)
console.log('FIND locations: '+docs)
});
});
Console output:
SAVE err: null
SAVE locations: { __v: 0,
_id: 53cc7a3ea44a9bc70634fdc6,
create_date: Sun Jul 20 2014 19:26:06 GMT-0700 (PDT),
tags: [ 'foo' ],
loc: { coordinates: [ -122.424088, 37.529876 ], type: 'Point' },
userid: [ 1 ] }
FIND err: null
FIND locations:
Can anyone advise where my error is?
There are several problems in what you are coding up here. Best to look at a whole working listing and break that down:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var locationSchema = new Schema({
userid: { type: Number, required: true },
loc: {
type: {
type: "String",
required: true,
enum: ['Point', 'LineString', 'Polygon'],
default: 'Point'
},
coordinates: [Number]
},
tags: [{ type: String, index: true, required: true }],
create_date: { type: Date, default: Date.now }
});
locationSchema.index({ 'loc': '2dsphere' });
var Location = mongoose.model( 'Location', locationSchema );
mongoose.connect( 'mongodb://localhost/geotest' );
async.series(
[
function(callback) {
Location.remove(function(err) {
if (err) throw err;
callback();
});
},
function(callback) {
var loc = new Location({
userid: 1,
loc: {
type: 'Point',
coordinates: [ -122.4481201171875, 37.71370177998719 ]
},
tags: "foo"
});
loc.save(function(err,loc) {
if (err) throw err;
console.log( "Location: %s", JSON.stringify( loc, undefined, 4 ) );
callback();
});
},
function(callback) {
var query = Location.find({
"loc": {
"$geoWithin": {
"$geometry": {
"type": "Polygon",
"coordinates": [[
[ -122.610168, 37.598167 ],
[ -122.288818, 37.598167 ],
[ -122.288818, 37.845833 ],
[ -122.610168, 37.845833 ],
[ -122.610168, 37.598167 ]
]]
}
}
}
});
query.exec(function(err,docs) {
if (err) throw err;
console.log( "Docs: %s", JSON.stringify( docs, undefined, 4 ) );
callback();
});
}
]
);
Not directly related but seemingly that you did not mean to do is how you defined some elements in your schema like this:
userid: { type: [Number], required: true },
That defines the field as an "Array", and it would seem you really don't mean to in this case. If you did then it really makes more sense to write like this:
userid: [{ type: Number, required: true }],
But for the sake of this listing it is just shown as a plain field. The "tags" field makes sense as an array and has been defined that way.
The next thing to look at is the index definition. This may have been a result of trying things that did not work, but generally if you are defining a GeoJSON structure under "loc" in your documents, then that is the field you need to index on. Which possibly is related to the next part.
locationSchema.index({ 'loc': '2dsphere' });
When you are querying on GeoJSON formatted objects, helper operator such as $box cannot be used:
..."The $box operator returns documents based on grid coordinates and does not query for GeoJSON shapes."
So when you are actually using GeoJSON then you must specify as a "Polygon" or "MultiPolygon" type of bounds in order to match GeoJSON objects within those bounds. At any rate, the field supplied to $geoWithin has to be the field that holds the index. You where supplying "coordinates" which was not a valid top level field. Depending on the index field you are on "loc" or "loc.coordinates":
var query = Location.find({
"loc": {
"$geoWithin": {
"$geometry": {
"type": "Polygon",
"coordinates": [[
[ -122.610168, 37.598167 ],
[ -122.288818, 37.598167 ],
[ -122.288818, 37.845833 ],
[ -122.610168, 37.845833 ],
[ -122.610168, 37.598167 ]
]]
}
}
}
});
Finally, the "Point" you were looking for lies outside of the "box" you were specifying.
Here is the "box" you were searching with and the "Point" shown outside of the "box":
The listing shown above corrects all of these things and supplies a "Point" and "Polygon" forms with will actually meet the conditions. Try working with sites such as gejsonlint.com and others to check the validity of the data you are working with.

Categories

Resources