A simple application of this 2dsphere index is seen below. Given the "locations" collection with the given "typical" document shape:
/* 0 */
{
"_id" : ObjectId("51887218c0aa488a0394002f"),
"_class" : "com.icfi.mongo.data.model.Location",
"city" : "Wheeling",
"state" : "WV",
"coords" : [40.071472, -80.6868],
"timeZone" : -5,
"zipCode" : "26003",
"dstObserved" : true
}
I will apply a 2dsphere index on the "coords" (short for coordinates) element. This element is an array of double precision numbers representing the longitude and latitude (in that order) of a given city. The command is below:
db.locations.ensureIndex( { "coords" : "2dsphere" } )
Next we can run query using a geo-spatial operator, like $near. The $near command syntax is seen below:db.collection<collection>.find( { <location field=""> :
{ $near :
{ $geometry :
{ type : "Point" ,
coordinates : [ <longitude> , <latitude> ] } },
$maxDistance : <distance in="" meters="">
} } )
My actual command to search for cities near Wheeling, WV 26003 is:
db.locations.find({ 'coords' : { $near : { $geometry : { type : 'Point' ,coordinates : [ 40.071472 , -80.6868 ] } }, $maxDistance : 10000} })
This search returns documents that are within a circular distance of 10000 meters for the given Long/Lat coordinates.To make this more friendly in Java, I added the utility to convert miles to meters; I don't use meters much.
public class GeoUtils {
public static final double MILES_METERS_DIVISOR = 0.00062137;
public static double milesToMeters(double miles) {
return miles / GeoUtils.MILES_METERS_DIVISOR;
}
}
A JUnit test is seen below. First I get the location object from which I want to harvest the coordinates (point). Then I call the LocationService method to find the nearest cities. I have also include the Location model class and the Spring Data LocationRepository class.
@Test
public void testNearMiles() {
log.info("<<<<<<<<<<<<<<<<< testNearMiles >>>>>>>>>>>>>>>>>>>>");
List<Location> locations = locationService.findByCityAndState(
"Wheeling", "WV");
assertNotNull("locations[0] was null.", locations.get(0));
assertEquals("City was not correct.", "Wheeling", locations.get(0)
.getCity());
assertEquals("State was not correct.", "WV", locations.get(0)
.getState());
assertEquals("ZipCode was not correct.", "26003", locations.get(0)
.getZipCode());
List<Location> locales = this.locationService.findNear(
locations.get(0), 5);
for (Location locale : locales) {
log.info(locale.toString());
}
assertEquals("City was not correct.","Yorkville",locales.get(2).getCity());
assertEquals("City was not correct.","Glen Dale",locales.get(14).getCity());
}
Location service class:
...
@Override
public List<Location> findNear(double lon, double lat, double distance) {
return this.locationRepository.findByGeoNear(lon, lat, distance);
}
@Override
public List<Location> findNear(Location location, double distanceInMiles) {
return this.findNear(location.getLongitude(), location.getLatitude(),
GeoUtils.milesToMeters(distanceInMiles));
}
...
In the LocationRepository class I have the following annotated method:
...
@Query("{ 'coords' : { $near : { $geometry : { type : 'Point' ,coordinates : [ ?0 , ?1 ] } }, $maxDistance : ?2} }")
List<Location> findByGeoNear(double lon, double lat, double distance);
Since I am using Spring Data, I can add a new method to my LocationService like so:@Override
public GeoResults<Location> findNearPoint(Location location,
double distanceInMiles) {
Point point = new Point(location.getLongitude(), location.getLatitude());
NearQuery query = NearQuery.near(point).maxDistance(
new Distance(distanceInMiles, Metrics.MILES));
GeoResults<Location> results = this.mongoOps.geoNear(query,
Location.class);
return results;
}
This new method uses a new Point class and the Distance class that handles the miles appropriately. This approach is considered a "GEO Near" query. It finds the locations near the given point and calculates the actual distance from the original point to each resultant location. Results are returned in a parameterized GeoResults object.
No comments:
Post a Comment