mangoBD地理位置索引JAVA实战
来源:互联网 发布:unity3d 角色模型下载 编辑:程序博客网 时间:2024/05/17 03:35
在现在的移动互联网应用中,LBS功能几乎是每个APP的标配。LBS功能的实现方式也有很多种,Mysql有相应的计算函数,但是Mysql实现此功能需要经过较多的计算,如果数量很大,对于查询性能是个极大的考验。不过对于一开始就使用Mysql的项目来说,需要增加LBS功能就能平和地过渡。
mongoDB有个重要的特性就是支持二维空间索引,利用mongoDB我们极其容易实现LBS功能。例如,我们有个需求,需要查找在我当前位置附近的学校,学校按距离从近到远的顺序排序,并能准确地获取当前位置到学校的距离。这样的需求,使用mongoDB的空间索引结合特殊的查询方法很容易实现。
MongoDB二维空间索引的数据存储及JAVA实现
建立MongDB二位空间索引就要在文档中有相应的存储坐标信息的字段。建立空间索引的key可以使用array或内嵌文档存储,但是前两个elements必须存储固定的一对空间位置数值。如
{ loc : [ 50 , 30 ] }
{ loc : { x : 50 , y : 30 } }
{ loc : { foo : 50 , y : 30 } }
{ loc : { lat : 40.739037, long: 73.992964 } }
我们采用第一种数组的方式。
先来看看我们插入文档数据的java代码:
/** * 根据索引名称获取索引 * @param dbName 数据库名 * @param collectionName 集合名 * @param indexName 索引名字 * @return */ @Override public DBObject getIndexByName(String dbName, String collectionName,String indexName) { List<DBObject> indexList=this.getIndexInfos(dbName, collectionName); if(null!=indexList){ for(DBObject o:indexList){ String name=(String) o.get("name"); if(StringUtils.isNotBlank(name)&&name.equals(indexName)){ return o; } } } return null; }/** * 向指定的数据库中添加给定的keys和相应的values,并插入文档的地理位置信息 * @param dbName 数据库名 * @param collectionName 集合名 * @param keys 集合中域的keys * @param values 集合中域中的值 * @param index_name_2d 2d索引的名称 * @param lon 经度 * @param lat 纬度 * @return */ @Override public boolean insert(String dbName, String collectionName, String[] keys, Object[] values,String index_name_2d, double lon, double lat) { DB db=null; DBCollection dbCollection=null; WriteResult result=null; String resultString=null; if(null!=keys && null!=values){ if(keys.length!=values.length){ return false; } db=this.mongoClient.getDB(dbName); dbCollection=db.getCollection(collectionName); BasicDBObject insertObject=new BasicDBObject(); for(int i=0;i<keys.length;i++){//构建添加条件 insertObject.put(keys[i], values[i]); } //设置地址位置信息索引字段 if(StringUtils.isNotBlank(index_name_2d)){ BasicDBObject index_2d = new BasicDBObject(); index_2d.put(index_name_2d, "2d"); index_2d.put("background", true); //没有2d索引 则创建 if(null==this.getIndexByName(dbName, collectionName, index_name_2d)){ dbCollection.ensureIndex(index_2d, index_name_2d, false); } } //地理位置信息 insertObject.put( index_name_2d, new Double[]{lon,lat} ); try { result=dbCollection.insert(insertObject); resultString=result.getError(); } catch (Exception e) { e.printStackTrace(); }finally { if(null!=db){ db.requestDone();//请求结束后关闭db } } return null==resultString?false:true; } return false; }
简单说明一下思路,上述代码主要的思路是在MongoDB中新建文档数据,而且文档中一个名为loc的字段专门存储位置信息即地理位置坐标;然后给集合中的loc字段建立2d地理空间索引。
因为后续我们需要使用mongoDB的GeoNear命令来做地理空间查询的功能,所以一个文档中只能有一个位置信息存储的字段,多于一个则会报错。
接下来,我们写测试类,并在mongoDB中添加一点数据:
@Test public void testInsert(){ MongoDBDao dao= MongoDBDaoImpl.getInstance(); System.out.println("-----------测试mongoDB的crdu操作------------"); //新增 System.out.println("-----------插入-----------"); String[] keys=new String[]{"name","address","city","students"}; Object[] values=new Object[]{"中山大学","广州地海珠区新港西路135号","广州",new Integer(101)}; dao.insert("test", "school",keys, values,"loc",113.305314,23.102723); values=new Object[]{"华南农业大学","广州天河区五山街五山路483号华南农业大学三角市","广州",new Integer(32342433)}; dao.insert("test", "school",keys, values,"loc",113.359105,23.161023); values=new Object[]{"四川大学","四川成都市人民南路三段17号","成都",new Integer(643432)}; dao.insert("test", "school",keys, values,"loc",104.072946,30.647093); values=new Object[]{"重庆大学","重庆市沙坪坝区沙正街174号","重庆",new Integer(5673834)}; dao.insert("test", "school",keys, values,"loc",106.474815,29.570351); values=new Object[]{"清华大学","北京市海淀区清华大学","北京",new Integer(938383)}; dao.insert("test", "school",keys, values,"loc",116.332557,40.009417); System.out.println("#####插入数据后,集合中得数据:"); ArrayList<DBObject> list=dao.find("test", "school", null, null, -1); for(DBObject o:list){ System.out.println(o); } } @Test public void testGetIndexes(){ MongoDBDao dao= MongoDBDaoImpl.getInstance(); System.out.println("-----------测试mongoDB的crdu操作------------"); List<DBObject> list=dao.getIndexInfos("test", "mapinfo"); System.out.println(dao.getIndexInfos("test", "mapinfo")); for(DBObject o:list){ String indexName=(String) o.get("name"); System.out.println("indexName:"+indexName); } System.out.println("loc_2d index:"+dao.getIndexByName("test", "mapinfo", "loc_2d")); }
上面的测试代码显示,我们在mongDB中存储一些学校的信息,并记录了每个学校的坐标信息。
Junit执行之后的控制台的输出如下:
-----------测试mongoDB的crdu操作-----------------------插入-----------#####插入数据后,集合中得数据:{ "_id" : { "$oid" : "570f54d609a80a3f4c8718b3"} , "name" : "中山大学" , "address" : "广州地海珠区新港西路135号" , "city" : "广州" , "students" : 101 , "loc" : [ 113.305314 , 23.102723]}{ "_id" : { "$oid" : "570f54d609a80a3f4c8718b4"} , "name" : "华南农业大学" , "address" : "广州天河区五山街五山路483号华南农业大学三角市" , "city" : "广州" , "students" : 32342433 , "loc" : [ 113.359105 , 23.161023]}{ "_id" : { "$oid" : "570f54d609a80a3f4c8718b5"} , "name" : "四川大学" , "address" : "四川成都市人民南路三段17号" , "city" : "成都" , "students" : 643432 , "loc" : [ 104.072946 , 30.647093]}{ "_id" : { "$oid" : "570f54d609a80a3f4c8718b6"} , "name" : "重庆大学" , "address" : "重庆市沙坪坝区沙正街174号" , "city" : "重庆" , "students" : 5673834 , "loc" : [ 106.474815 , 29.570351]}{ "_id" : { "$oid" : "570f54d609a80a3f4c8718b7"} , "name" : "清华大学" , "address" : "北京市海淀区清华大学" , "city" : "北京" , "students" : 938383 , "loc" : [ 116.332557 , 40.009417]}
可以看到,每个文档数据中有这样的位置信息字段”loc” : [ 116.332557 , 40.009417]
使用MongoDB的GeoNear命令来实现地理位置搜索功能
GeoNear命令,是基于db的command,而不是基于collection的find,也就是需要通过runcommand执行,具体语法如下
db.runCommand({ geoNear : “school” , near : [113.366498,23.127249], num : 10 } )
这个结果是根据距离排序而且有距离的记录,但是距离是经纬度的差值,MongoDB 1.8以后提供了Spherical Model,用distanceMultiplier指定地球半径来得到实际的公里或者米的距离,记得加上spherical:true,命令变为:
db.runCommand({ geoNear : “school” , near : [113.366498,23.127249], distanceMultiplier: 6378137, num : 10, spherical:true } )
那么查出的结果中距离的单位就是米!
这个num参数是取得记录的条数,适合做列表翻页用,但是地图上我们很难说只取多少个点,而是取多大范围,那么maxDistance参数正好适合,也就是多少范围的;我上面采用的地球半径是米(地球的半径是6378137米),那么这里查询我统一采用米来计算,命令变为:
db.runCommand({ geoNear : “school” , near : [113.366498,23.127249], distanceMultiplier: 6378137, maxDistance:2500/6378137 ,spherical:true} )
也就是查询2500米范围内的点;
其他参数还有个Query,用于联合查询,结果完整的命令如下:
db.runCommand({ geoNear : “school” , near : [113.366498,23.127249], distanceMultiplier: 6378137, maxDistance:2500/6378137,num : 10, spherical:true , query:{city:”广州”}} )
整条命令的含义:我要搜索在坐标(113.366498,23.127249)附近2500米范围内最近的学校,最多只能查出10所学校,而且学校所在的城市是在广州。
实现geoNear 搜索的java代码如下:
/** * 使用geoNear查询附近地理空间的数据 * @param dbName 数据库名 * @param collectionName 集合名 * @param locationField 位置信息域的名字 * @param centerLon 中心点的经度 * @param centerLat 中心 * @param keys 其他查询条件的keys * @param values 其他查询条件的values * @param limit 查询限制条数大小 * @param maxDistance 最大距离 * @return */ @Override public CommandResult geoNear(String dbName, String collectionName, String locationField, double centerLon, double centerLat, String[] keys, Object[] values, int limit, Long maxDistance) { DB db=null; DBCursor dbCursor=null; try { db=this.mongoClient.getDB(dbName); //构建查询条件 BasicDBObject queryObj=new BasicDBObject(); if(null!=keys && null!=values && keys.length==values.length){ for(int i=0;i<keys.length;i++){ queryObj.put(keys[i], values[i]); } } BasicDBObject myCmd = new BasicDBObject(); myCmd.append("geoNear", collectionName);//集合名 double[] loc = {centerLon,centerLat}; myCmd.append("near", loc); /** * geoNear默认结果是根据距离排序有距离的记录,但是距离是经纬度的差值, * MongoDB 1.8以后提供了Spherical Model,用distanceMultiplier指定地球半径来得到实际的公里或者米的距离, * 记得加上spherical:true */ myCmd.append("spherical", true); myCmd.append("distanceMultiplier", 6378137); //地球的半径,单位米 myCmd.append("maxDistance", (double)maxDistance / 6378137 ); //指定maxDistance米范围内 myCmd.append("query", queryObj);//非地理位置域的查询条件 myCmd.append("limit", limit); CommandResult myResults = db.command(myCmd); return myResults; } catch (Exception e) { e.printStackTrace(); }finally { if(null!=dbCursor){ dbCursor.close(); } if(null!=db){ db.requestDone(); } } return null; }
接下来,就是我们的测试代码了
@Test public void testGeoNear(){ MongoDBDao dao= MongoDBDaoImpl.getInstance(); System.out.println("-----------测试mongoDB的crdu操作------------"); String[] keys={"city"}; String[] values={"广州"}; CommandResult c=dao.geoNear("test", "school", "loc", 113.366498,23.127249, keys, values, 10, 1000000l); System.out.println("mongodb geoNear命令执行的结果:"+c); if(c.ok()){//查询成功 //获取数据 Collection<DBObject> resultList=(Collection<DBObject>) c.get("results"); for(DBObject o:resultList){ System.out.println("------------------------------------------"); DBObject school=(DBObject) o.get("obj"); System.out.println("学校名称:"+school.get("name")); System.out.println("城市:"+school.get("city")); System.out.println("地址:"+school.get("address")); System.out.println("学校人数:"+school.get("students")); System.out.println("距离:"+o.get("dis")+"米"); } } }
执行测试测结果如下:
-----------测试mongoDB的crdu操作------------mongodb geoNear命令执行的结果:{ "serverUsed" : "192.168.244.100:27017" , "waitedMS" : 0 , "results" : [ { "dis" : 3835.107409650452 , "obj" : { "_id" : { "$oid" : "570f54d609a80a3f4c8718b4"} , "name" : "华南农业大学" , "address" : "广州天河区五山街五山路483号华南农业大学三角市" , "city" : "广州" , "students" : 32342433 , "loc" : [ 113.359105 , 23.161023]}} , { "dis" : 6833.3044097593565 , "obj" : { "_id" : { "$oid" : "570f54d609a80a3f4c8718b3"} , "name" : "中山大学" , "address" : "广州地海珠区新港西路135号" , "city" : "广州" , "students" : 101 , "loc" : [ 113.305314 , 23.102723]}}] , "stats" : { "nscanned" : 20 , "objectsLoaded" : 8 , "avgDistance" : 5334.205909704904 , "maxDistance" : 6833.3044097593565 , "time" : 1} , "ok" : 1.0}------------------------------------------学校名称:华南农业大学城市:广州地址:广州天河区五山街五山路483号华南农业大学三角市学校人数:32342433距离:3835.107409650452米------------------------------------------学校名称:中山大学城市:广州地址:广州地海珠区新港西路135号学校人数:101距离:6833.3044097593565米
测试结果显示,学校按与当前位置的距离由近及远的排序,而且能计算出到学校的距离,完美地实现了我们的需求。
由此可见,使用mongoDB的二位空间索引的功能,是很容易实现最常规又最实用的LBS需求的,搜索效率也是非常高。
- mangoBD地理位置索引JAVA实战
- mongodb的地理位置索引
- mongodb的地理位置索引
- Mongodb 地理位置索引
- mongodb的地理位置索引
- Mongo地理位置索引原理
- mongoDB的地理位置索引
- MongoDB 地理位置索引
- MongoDB地理位置索引
- mongodb地理位置索引实现原理
- Java 获取地理位置
- 索引实战
- 索引实战
- 图解 MongoDB 地理位置索引的实现原理
- 大型的LBS应用地理位置索引技术
- 图解 MongoDB 地理位置索引的实现原理
- 图解 MongoDB 地理位置索引的实现原理
- MongoDB 地理位置索引的实现原理
- 对四大浏览器内核的了解
- 【Android UI】自定义圆形SeekBar和自定义Dialog的结合使用
- 线程中断 thread.interrupt()的用法
- HTML参考手册
- java异常处理,目前还是不懂,问题先留在这里,以后再解决
- mangoBD地理位置索引JAVA实战
- 多态和虚函数
- JVM调优总结 -Xms -Xmx -Xmn -Xss
- 逻辑精简?解决Integer空串的问题
- 信号量、互斥体和自旋锁
- iOS CocoaPods之 Pods 制作(新版方式)
- 安凯AK3918E加载mtk7601驱动不能ifconfig wlan0 down
- java垃圾回收机制
- 封装CLLocationManager定位获取经纬度