0

I am working on a Java/Mongodb system - where each user has their own lat/lng coordinates

I have made an api query to user collection -- and I want to start filtering/sorting through the users.

I want to complete this sort process -- it works fine for options 2-5 -- but I need to work out how to create an aggregate I think in mongo (which I am new at).

a user record looks like this - where I have placed the coordinates in a locations object, the other filters are mostly in the root - like createdAt, lastLoginAt ...

enter image description here

How do I start creating the required aggregate using the current code base -- so I can sort a user that is logged in -- get their coordinates - and sort against those with the viewed list.

using the springframework imports

import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

@CrossOrigin
@GetMapping("/api/users")
@ResponseBody
public BasicDBObject listUsers (@RequestHeader("Authorization") String bearerToken, @RequestParam("sort_by") String sort_by, @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size) {

    String email = tokenHandler.getEmailFromBearerToken(bearerToken);

    //get logged in user
    Query q = new Query();
    q.addCriteria(Criteria.where("email").is(email));
    List<Person> lp = MongoApp.mongoOps().find(q, Person.class);
    Person loggedInUser = null;
    if (!lp.isEmpty()) {
        for (Person person : lp) {
            loggedInUser = person;
        }
    }


    String field = "";//createdAt
    switch (sort_by) {
        case "1":
            System.out.println("Distance");
            break;
        case "2":
            System.out.println("Last online");
            field = "lastLoginAt";
            break;
        case "3":
            System.out.println("Latest joined");
            field = "createdAt";
            break;
        case "4":
            System.out.println("Newest photo");
            field = "newestImageAt";
            break;
        case "5":
            System.out.println("Recently Updated");
            field = "updatedAt";
            break;
        default:
            System.out.println("default");
            field = "createdAt";
    }

    Query query = new Query();

    long totalUsercount = MongoApp.mongoOps().count(query, Person.class);

    //only show isActive users
    query.addCriteria(Criteria.where("isActive").is(true));

    //dont show yourself - loggedin user excluded from list
    query.addCriteria(Criteria.where("id").ne(loggedInUser.getId()));

    query.with(Sort.by(Sort.Direction.DESC, field));

    Pageable pageable = PageRequest.of(page, size);
    List<Person> totalPeople = new ArrayList<>();
    totalPeople = queryPeopleAddImages(totalPeople, query);
    long totalCount = totalPeople.size();
    query.with(pageable);

    // List People
    List<Person> people = new ArrayList<>();
    people = queryPeopleAddImages(people, query);

    BasicDBObject obj = userListResponse(loggedInUser, people, totalCount, totalUsercount);

    return obj;
}

I've added some research links here - which I think is on track to complete this functionality

Research

How to write MongoDb Aggregation for this Criteria query

AggregationOperation match = Aggregation.match(Criteria.where("careGiverId").is(careTakerId).and("careType").is(careType));
 AggregationOperation count = Aggregation.count().as("count");
 Aggregation agg = Aggregation.newAggregation(match, count);
 mongoOperations.aggregate(agg, CareLogBean.class);

Spring MongoDB query sorting

AggregationOperation match = Aggregation.match(matching criteria);
AggregationOperation group = Aggregation.group("fieldname");
AggregationOperation sort = Aggregation.sort(Sort.Direction.ASC, "fieldname");
Aggregation aggregation = Aggregation.newAggregation(Aggregation.unwind("fieldname"),match,group,sort);

MongoDB print distance between two points

 db.new_stores.aggregate([
    { "$geoNear": {
        "near": {
            "type": "Point",
            "coordinates": [ -81.093699, 32.074673 ]
        }, 
        "maxDistance": 500 * 1609,
        "key" : "myLocation",
        "spherical": true,
        "distanceField": "distance",
        "distanceMultiplier": 0.000621371
    }}
]).pretty()

"This allows you to specify "distanceField" which will produce another field in the output documents containing the distance from the queried point. You can also use "distanceMultiplier" to apply any conversion to the output distance as required ( i.e meters to miles, and noting that all GeoJSON distances are returned in meters )

There is also the geoNear command with similar options, but it of course does not return a cursor as output.

if you have more than one 2dsphere, you should specify a "key"."


I have this working on the frontend to calculate distance in km

Calculate distance between two latitude-longitude points? (Haversine formula)

using the ‘Haversine’ formula

function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}

28th March 2023 current research

I am working on a mongobdb application - using Java springboot.

Its a user website -- where members will log in and be able to search/filter/sort for other members.

I've started building an api with basic queries --

I need to complete part of the api to allow you to sort via distance. Each user has lat/lng details -- I believe its a case of building an aggregate query.

Aggerate distance in a query that sorts via distance between a logged in user and user lists using mongodb/java springboot


  • Handle the distance sorting -- aggregate query part.
  • I want to clean the codebase up - as there are separate apis made to do similar/same searches but for specific filters -- getUsers, getFavs, getBlocks, getViewedMe -
  • Also make pagination options optional -(or have a part of the code in its own function to isolate it with optional pagination handling - aka reduce repeated code effort)- so I could acquire ALL user data and not chunks of it for analysis - again similar searches/same but scalable - aka provide just 5 of the latest results, provide paginated data with or with sorting/filtering.
  • Be able to add multiple types of filters -- with AND and OR operations -- image only, blonde hair, blue eyes, ethnicities

-- so you can see here -- that the other cases work - because they are actual fields --- lastLoginAt, createdAt ----- etc.. -- whereas distance is like a virtual field that needs to be made -- I think with the aggregate

String field = "";//createdAt
switch (sort_by) {
case "1":
System.out.println("Distance");
break;
case "2":
System.out.println("Last online");
field = "lastLoginAt";
break;
case "3":
System.out.println("Latest joined");
field = "createdAt";
break;
case "4":
System.out.println("Newest photo");
field = "newestImageAt";
break;
case "5":
System.out.println("Recently Updated");
field = "updatedAt";
break;
default:
System.out.println("default");
field = "createdAt";
}

Query query = new Query();

long totalUsercount = MongoApp.mongoOps().count(query, Person.class);
System.out.println("totalUsercount "+ totalUsercount);

///default filters (only active users, no admins, no self)
query = addDefaultFilters(query, loggedInUser);
///default filters

//country filter
//query.addCriteria(Criteria.where("country").is("AR"));

query.with(Sort.by(Sort.Direction.DESC, field));

"MongoDB Compass terminal which is great to test out queries and aggregations"

enter image description here

Added here documentations for indexes in mongodb and the 2dsphere index type.

2dshpere: https://www.mongodb.com/docs/manual/core/2dsphere/#:~:text=A%202dsphere%20index%20supports%20queries,geospatial%20queries%2C%20see%20Geospatial%20Queries. Indexes: https://www.mongodb.com/docs/manual/indexes/

The 2dsphere index is required for MongoDB's $geoNear aggregation stage to work correctly with geospatial data. This is because $geoNear uses the 2dsphere index to efficiently perform geospatial queries

there is already a default filter in place

public Query addDefaultFilters(Query query, Person loggedInUser) {

///default filters
//only show isActive users
query.addCriteria(Criteria.where("isActive").is(true));

//dont show admins
query.addCriteria(Criteria.where("role").ne("ROLE_ADMIN"));

//dont show yourself - loggedin user excluded from list
query.addCriteria(Criteria.where("id").ne(loggedInUser.getId()));
///default filters

return query;
}

--

Query query = new Query();

long totalUsercount = MongoApp.mongoOps().count(query, Person.class);
System.out.println("totalUsercount "+ totalUsercount);

///default filters (only active users, no admins, no self)
query = addDefaultFilters(query, loggedInUser);
///default filters

//country filter
//query.addCriteria(Criteria.where("country").is("AR"));

query.with(Sort.by(Sort.Direction.DESC, field));

Pageable pageable = PageRequest.of(page, size);
List totalPeople = new ArrayList();
totalPeople = queryPeopleAddImages(totalPeople, query);
long totalCount = totalPeople.size();
System.out.println("xxxxxxxxxxxxxtotalCount "+totalCount);
query.with(pageable);

// List People
//List people = MongoApp.mongoOps().findAll(Person.class);
List people = new ArrayList();
people = queryPeopleAddImages(people, query);

enter image description here


enter image description here

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here


another java example enter image description here enter image description here


10th April 2023

there was another solution given to me that may solve these issues -please evaluate.


To handle the distance sorting using MongoDB aggregation query, you can use the following steps:

Create a GeoJSON object for the user's location, using the coordinates stored in the user's document.

Use the $geoNear stage in the aggregation pipeline to find other users who are within a certain radius of the user's location. You can specify the maximum distance and the field that contains the other users' location information.

Use the $sort stage to sort the results based on the distance from the user's location.

Here's an example code snippet that demonstrates how you can implement this in Java using Spring Boot and the MongoDB Java driver:


import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.GeoNearAggregation;
import org.springframework.data.mongodb.core.aggregation.SortOperation;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

GeoJsonPoint userLocation = new GeoJsonPoint(user.getLongitude(), user.getLatitude());
double maxDistance = 10000.0; // 10km

GeoNearAggregation geoNearAggregation = new GeoNearAggregation(
    near(userLocation).spherical(true).maxDistance(maxDistance),
    "distance"
);

SortOperation sortOperation = sort(Sort.by("distance"));

TypedAggregation[User] aggregation = newAggregation(User.class, geoNearAggregation, sortOperation);
AggregationResults[User] results = mongoTemplate.aggregate(aggregation, User.class);
List[User] nearbyUsers = results.getMappedResults();

To clean up the codebase and reduce repeated code effort, you can create a generic search function that takes in filter parameters and pagination options as parameters. You can use the Query class to build up the filter criteria and the PageRequest class to handle pagination.

Here's an example code snippet that demonstrates how you can implement this:


public Page[User] searchUsers(String keyword, List[Criteria] filters, Sort sort, int page, int size) {
    Query query = new Query();

    // Add the keyword search criteria
    if (keyword != null) {
        Criteria keywordCriteria = new Criteria()
            .orOperator(
                Criteria.where("firstName").regex(keyword, "i"),
                Criteria.where("lastName").regex(keyword, "i")
            );
        query.addCriteria(keywordCriteria);
    }

    // Add the filters
    if (filters != null) {
        Criteria filterCriteria = new Criteria().andOperator(filters.toArray(new Criteria[0]));
        query.addCriteria(filterCriteria);
    }

    // Add the pagination options
    PageRequest pageRequest = PageRequest.of(page, size, sort);

    query.with(pageRequest);

    return mongoTemplate.find(query, User.class, "users");
}
The Old County
  • 89
  • 13
  • 59
  • 129

1 Answers1

0

I have worked with MongoDB, not with springboot but as i understand you issue is you want to do sorting/filtering in aggregation with different fields. Right?

For sorting, you can use:

collectionName.aggregate([
  {
    $match: {
     //... use the filter from the req body, just make sure that which fields you are filtering are introduced/used in aggregation.
    }
  },
  {
    $sort :{
     //... use the sorting option from the req body that will help you with custom fields, just make sure that which fields you are sorting are introduced/used before this portion of aggregation.
    }
  }
])

Here is the reference link for sorting.

  • but it needs to use the Java Springboot framework as I started -- and sort by distance which is a virtual field that needs to be tabulated from the lat/lng of the users against the logged in persons lat/lng – The Old County Apr 01 '23 at 19:47
  • Doesn't matter if it's Java Springboot or javascript MongoDB aggregation is the same for all. And Yes, sorting will also work in virtual fields. use sorting once you declare the virtual field. You can also use `$near` for location near by lat/long. Here is the [link](https://www.mongodb.com/docs/manual/reference/operator/query/near/) – Vaibhav Tamakuwala Apr 16 '23 at 08:57
  • It did matter to be awarded the bounty for this problem. – The Old County Apr 17 '23 at 09:20
  • Can you send me your aggregation query with 2-3 sample data? so I can test it on my side. – Vaibhav Tamakuwala Apr 17 '23 at 15:03
  • I've added the usercontroller here - https://jsfiddle.net/0jxvoybt/ - but I had to comment out an attempt as a class isnt defined. – The Old County Apr 18 '23 at 03:41