基本介绍
位置服务(LBS)解决的主要问题是当前位置周围某个范围内的人或场所.
在传统的解决方案,开发人员需要根据复杂的几何运算与大量的SQL语句进行查找,这无疑加大的开发人员的开发难度.
现在我们需要更为方便的解决方案,MongoDB为我们完美解决此类LBS问题.此篇文章也主要使用SpringData,将spring与MongoDB进行整合.
二维地图
MongoDB目前支持二维的地图查询,查询区域包括圆形与矩形,距离单位包括MILES,KILOMETERS,NEUTRAL,下面的示例演示距离单位为NEUTRAL,而实际生产应用中则会用到MILES与KILOMETERS.
MongoDB示例
首先定义一个位置集合,给定a,b,c,d节点.
1 2 3 4 5 6 7 8 |
> db.createCollection("location") { "ok" : 1 } > db.location.save( {_id: "A", position: [0.1, -0.1]} ) > db.location.save( {_id: "B", position: [1.0, 1.0]} ) > db.location.save( {_id: "C", position: [0.5, 0.5]} ) > db.location.save( {_id: "D", position: [-0.5, -0.5]} ) |
接着指定location索引
1 |
db.location.ensureIndex( {position: "2d"} ) |
现在我们可以进行简单的GEO查询
查询point(0,0),半径0.7附近的点
1 2 3 4 |
> db.location.find( {position: { $near: [0,0], $maxDistance: 0.7 } } ) { "_id" : "A", "position" : [ 0.1, -0.1 ] } |
查询point(0,0),半径0.75附近的点
1 2 3 4 5 6 |
> db.location.find( {position: { $near: [0,0], $maxDistance: 0.75 } } ) { "_id" : "A", "position" : [ 0.1, -0.1 ] } { "_id" : "C", "position" : [ 0.5, 0.5 ] } { "_id" : "D", "position" : [ -0.5, -0.5 ] } |
我们可以看到半径不一样,查询出的点也不一样,因为c点坐标为[0.5,0.5],c至圆点的距离根据勾股定理可得出Math.sqrt(0.25 +0.25) ≈ 0.707,所以最大距离0.7时查找不到你要的点.
查询[0.25, 0.25], [1.0,1.0]区域附近的点
1 2 3 4 5 |
> db.location.find( {position: { $within: { $box: [ [0.25, 0.25], [1.0,1.0] ] } } } ) { "_id" : "C", "position" : [ 0.5, 0.5 ] } { "_id" : "B", "position" : [ 1, 1 ] } |
Spring Data示例
spring data为我们封装了mongoDB访问接口与实现,我们可以像使用hibernateTemplate一样使用mongoTemplate.
首先我们需要像hibernate一样定义pojo类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "location") public class Location { @Id private String id; private double[] position; /** getter setter hashcode equals toString ... */ } |
定义Dao,我们先使用最简单的mongoTemplate来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.Box; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; @Repository public class LocationDao { @Autowired MongoTemplate mongoTemplate; public List<Location> findCircleNear(Point point, double maxDistance) { return mongoTemplate.find( new Query(Criteria.where("position").near(point).maxDistance(maxDistance)), Location.class); } public List<Location> findBoxNear(Point lowerLeft, Point upperRight) { return mongoTemplate.find( new Query(Criteria.where("position").within(new Box(lowerLeft, upperRight))), Location.class); } } |
最后我们写一个test类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import java.util.Collection; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/applicationContext.xml", "classpath:/application-mongo.xml" }) public class MongoDBTest { @Autowired LocationDao locationDao; @Autowired MongoTemplate template; @Before public void setUp() { // 等同db.location.ensureIndex( {position: "2d"} ) template.indexOps(Location.class).ensureIndex(new GeospatialIndex("position")); // 初始化数据 template.save(new Location("A", 0.1, -0.1)); template.save(new Location("B", 1, 1)); template.save(new Location("C", 0.5, 0.5)); template.save(new Location("D", -0.5, -0.5)); } @Test public void findCircleNearTest() { List<Location> locations = locationDao.findCircleNear(new Point(0, 0), 0.7); print(locations); System.err.println("-----------------------"); locations = locationDao.findCircleNear(new Point(0, 0), 0.75); print(locations); } @Test public void findBoxNearTest() { List<Location> locations = locationDao.findBoxNear(new Point(0.2, 0.2), new Point(1, 1)); print(locations); } public static void print(Collection<Location> locations) { for (Location location : locations) { System.err.println(location); } } } |
大家可以看到运行结果与我们直接在mongoDB上的一样.
MongoRepository
MongoRepository提供了对MongoTemplate的封装与实现,只需要继承MongoRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import org.springframework.data.mongodb.core.geo.Box; import org.springframework.data.mongodb.core.geo.Distance; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.repository.MongoRepository; public interface LocationRepository extends MongoRepository<Location, String> { List<Location> findByPositionNear(Point p, Distance d); List<Location> findByPositionWithin(Box b); } |
然后在test类中引用此类即可,MongoRepository实现了最基本的增删改查的功能,要想增加额外的查询方法,可以按照以下规则定义接口的方法.
自定义查询方法,格式为findBy+字段名+方法名,方法传进的参数即字段的值,此外还支持分页查询,通过传进一个Pageable对象会返回Page集合.
原理相信大家也很清楚,即aop,细节就不说拉.
小提示
near与within方法区别,near方法查询后会对结果集对distance进行排序且有大小限制,而within是无序的也无大小限制.
如果大家有新发现,也可回帖,我会及时补充.
写得清晰简洁,对未接触过入门已经足够了,比如我!
这写的就是垃圾!单位呢!
第二栏已经说明示例单位是NEUTRAL~