基本介绍
位置服务(LBS)解决的主要问题是当前位置周围某个范围内的人或场所.
在传统的解决方案,开发人员需要根据复杂的几何运算与大量的SQL语句进行查找,这无疑加大的开发人员的开发难度.
现在我们需要更为方便的解决方案,MongoDB为我们完美解决此类LBS问题.此篇文章也主要使用SpringData,将spring与MongoDB进行整合.
二维地图
MongoDB目前支持二维的地图查询,查询区域包括圆形与矩形,距离单位包括MILES,KILOMETERS,NEUTRAL,下面的示例演示距离单位为NEUTRAL,而实际生产应用中则会用到MILES与KILOMETERS.
MongoDB示例
首先定义一个位置集合,给定a,b,c,d节点.
> 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索引
db . location . ensureIndex ( { position : "2d" } )
现在我们可以进行简单的GEO查询
查询point(0,0),半径0.7附近的点
> db . location . find ( { position : { $near : [ 0 , 0 ] , $maxDistance : 0.7 } } )
{ "_id" : "A" , "position" : [ 0.1 , - 0.1 ] }
查询point(0,0),半径0.75附近的点
> 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]区域附近的点
> 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类
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来实现
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类
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接口,填上对应的bean类与ID类型,无需实现里面的方法即可使用,先看代码.
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是无序的也无大小限制.
如果大家有新发现,也可回帖,我会及时补充.