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