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 ...
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);
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.
- 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"
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);
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");
}