skip to Main Content

I’m trying to retrieve one specific object from an existing array inside the DB document, using wildcards $near and $geometry.

I have a model that looks like this:

const schema = new mongoose.Schema({
name: {...},
email: {...},
...
locations: [
    {
      name: String,
      address: String,
      mapurl: String,
      location: {
        type: {
          type: String,
          default: "Point",
        },
        coordinates: [
          {
            type: Number,
          },
        ],
      },
    },
  ],
}

Schema indexed like that:

schema.index({ "locations.location": "2dsphere" });

Document in DB looks like this:

{
"_id": ...,
"name": ...,
"email": ...,
"locations": [
    {
      "name": "name 1",
      "address": "address 1",
      "mapurl": "https://maps.google.com/SOME_MAP_URL_1",
      "location": {
        "type": "Point",
        "coordinates": [
          49.8370513,
          24.0311641
        ]
      },
      "_id": {
        "$oid": "..."
      }
    },
    {
      "name": "name 2",
      "address": "address 2",
      "mapurl": "https://maps.google.com/SOME_MAP_URL_2",
      "location": {
        "type": "Point",
        "coordinates": [
          49.83454210000001,
          24.0115613
        ]
      },
      "_id": {
        "$oid": "..."
      }
    },
    {
      "name": "name 3",
      "address": "address 3",
      "mapurl": "https://maps.google.com/SOME_MAP_URL_3",
      "location": {
        "type": "Point",
        "coordinates": [
          49.81605019999999,
          24.0016884
        ]
      },
      "_id": {
        "$oid": "..."
      }
    },
  ],
}

I’m trying to query DB for a specific Object in an Array and then get all info I need from that Object with .select():

const q = {
    _id: place._id,
    "locations.location": {
      $near: {
        $geometry: {
          type: "Point",
          coordinates: [49.837066, 24.031458]
        },
        $maxDistance: 100, // 100m
      },
    },
  };

  const storeCoords = await Place.find(q).select("locations.location");

As the result I’m expecting to get only one specific Object from Array that is near 100 meters from provided coordinates, but instead I’m getting the whole Array with all existing Object there, including the one I need.

Expecting result:

{
  "coords": [
    {
      "_id": "...",
      "locations": [
        {
          "location": {
            "type": "Point",
            "coordinates": [49.83454210000001, 24.0115613]
          }
        },
      ]
    }
  ]
}

Actual result:

{
  "coords": [
    {
      "_id": "...",
      "locations": [
        {
          "location": {
            "type": "Point",
            "coordinates": [49.8370513, 24.0311641]
          }
        },
        {
          "location": {
            "type": "Point",
            "coordinates": [49.83454210000001, 24.0115613]
          }
        },
        {
          "location": {
            "type": "Point",
            "coordinates": [49.81605019999999, 24.0016884]
          }
        }
      ]
    }
  ]
}

I might use that $near and $geometry in a wrong way or completly wrong wildcards.
Please, advise.

2

Answers


  1. Chosen as BEST ANSWER

    Okay, so eventually I've figured out the solution. Thanks @SAMRAT for suggestions.

    I've created "statics"/"class" method to the Model itself, that returns an aggregated data:

    Schema.statics.getLocation = function(id, coordinates) {
      return this.aggregate([
        {
          $geoNear:
            {
              near: {
                type: "Point",
                coordinates,
              },
              distanceField: "distance",
              includeLocs: "location",
              maxDistance: 100,
              spherical: true,
            },
        },
        {
          $match: {
            _id: id
          }
        },
        {
          $project: {
            location: 1,
          }
        },
      ]);
    };
    

    And with the help of this Model function/method I can simply call it inside my controller with required parametres as follows:

    const location = await Place.getLocation(id, coordinates);

    Final result returns what's expected.

    Besides that I've modified Model Schema to look like this:

    const schema = new mongoose.Schema({
    name: {...},
    email: {...},
    ...
    locations: [
        {
          location: {
            type: {
              type: String,
              default: "Point",
            },
            coordinates: [
              {
                type: Number,
              },
            ],
            name: String,
            address: String,
            mapurl: String,
          },
        },
      ],
    }
    

    So I can retrieve all other related information as name, mapurl etc.


  2. If you only want to get only one document then you should use findOne.
    But in your case, it will not give you the answer you are expecting because findOne will return the 1st document match to the condition from your DB which may not be the nearest one.

    In that case, you should use the ‘$geoNear’ operator.

    https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/

    Here is the aggregation that may help

         const aggregationQuery = [
      {
        $geoNear: {
          near: {
            type: "Point",
            coordinates: [
              22.333,
              23.444
            ]
          },
          distanceField: "distance",
          spherical: true,
          maxDistance: 100// 100m
          
        }
      },
      {
        $match: {
          _id: ObjectId("60e0ce8d42c1fd1657e3021a")
        }
      },
      {
        $unwind: "$locations"
      },
      {
        $addFields: {
          // Calculate search score based on your criteria
          searchScore: {
            $subtract: [
              100,
              "$distance"
            ]// Example: inverse distance for simplicity
            
          }
        }
      },
      {
        $match: {
          searchScore: {
            $gt: 0
          }// Apply filtering based on your search score criteria
          
        }
      },
      {
        $sort: {
          searchScore: -1// Sort by search score, highest first
          
        }
      },
      {
        $limit: 1// Limit to only the nearest location with the highest search score
        
      },
      {
        $project: {
          _id: 1,
          location: "$locations.location"
        }
      }
    ];
    
    const storeCoords = await Place.aggregate(aggregationQuery);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search