基本介紹
位置服務(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類
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接口,填上對應的bean類與ID類型,無需實現裏面的方法即可使用,先看代碼.
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是無序的也無大小限制.
如果大家有新發現,也可回帖,我會及時補充.