最近給andorid做背景查詢資料功能,有一個需求是模仿微信的查找附近人功能。 資料庫中存儲每個使用者的經緯度資訊及使用者資訊,通過目前使用者傳遞過來的經緯度查詢這個使用者半徑N公裡以内的使用者資訊。
資料庫表結構
表名 | Mobile_User |
---|---|
mu_id | 自增,主鍵 |
mu_u_id | 使用者表的ID 外鍵 |
mu_longitud | 精度 |
mu_latitude | 緯度 |
(還有其他的一些資訊,這裡就列舉4個字段足矣)
首先需要一個方法,是把傳遞過來的經緯度按照半徑N公裡擴散,找出距離中心經緯度N公裡的上下左右經緯度值。效果如圖
随手畫的 勿噴
以中心生成經緯度時 正上方和正下方的精度是不變的,隻有緯度變化。 生成左右時道理一樣,隻有精度變化,緯度是不變的。
是以隻需要生成上下的緯度,左右的精度就可以了。
參考了網上的文章,http://digdeeply.info/archives/06152067.html 這篇文章是用PHP實作的經緯度查詢。修改成java的 代碼如下
public static double[] getAround(double lat, double lon, int raidus) {
Double latitude = lat;
Double longitude = lon;
Double degree = (24901 * 1609) / 360.0;
double raidusMile = raidus;
Double dpmLat = 1 / degree;
Double radiusLat = dpmLat * raidusMile;
Double minLat = latitude - radiusLat;
Double maxLat = latitude + radiusLat;
Double mpdLng = degree * Math.cos(latitude * (Math.PI / 180));
Double dpmLng = 1 / mpdLng;
Double radiusLng = dpmLng * raidusMile;
Double minLng = longitude - radiusLng;
Double maxLng = longitude + radiusLng;
return new double[] { minLat, minLng, maxLat, maxLng };
}
這樣四周的經緯度都已經生成了。
下一步是查詢資料庫中和四周經緯度比對的資料。 如果資料量很大的話會很耗時間,而且會很消耗流量。是以需要用到分頁查詢
代碼如下
@RequestMapping(params = "doGetQuserNearList")
@ResponseBody
public JSONObject doGetQuserNearList( HttpServletRequest request) {
JSONObject jsonObject=new JSONObject();
String km=request.getParameter("km");//搜尋範圍 這裡以米為機關
String useridString=request.getParameter("userid");
String precision=request.getParameter("precision");
String latitude=request.getParameter("latitude");
String rows=request.getParameter("rows");
String page=request.getParameter("page");
try {
if("".equals(useridString)||useridString==null){
jsonObject.put("status", "0");
jsonObject.put("msg", "使用者id不能為空");
return jsonObject;
}
Session session= memberService.getSession();
Query query=null;
org.hibernate.SQLQuery sqlQuery= session.createSQLQuery("select * from q_member where latitude <> 0 "
+ "and _precision > :left_lat "
+ "and _precision < :right_lat "
+ "and latitude > :down_lon "
+ "and latitude < :top_lon "
+ "and id <> :userid "
+" order by ACOS(SIN((:lat * 3.1415) / 180 ) * SIN((latitude * 3.1415) / 180 )+COS((:lat * 3.1415) / 180 )*COS((latitude * 3.1415) / 180 )*COS((:lon * 3.1415) / 180 - (_precision * 3.1415) / 180 ) ) * 6380 asc limit :start,:size");
double[] lalon=Location.getAround(Double.valueOf(latitude), Double.valueOf(precision), Integer.valueOf(km));
sqlQuery.setParameter("left_lat", lalon[1]);
sqlQuery.setParameter("right_lat", lalon[3]);
sqlQuery.setParameter("down_lon", lalon[0]);
sqlQuery.setParameter("top_lon", lalon[2]);
sqlQuery.setParameter("lat", latitude);
sqlQuery.setParameter("lon", precision);
sqlQuery.setParameter("start", Integer.valueOf(page)-1);
sqlQuery.setParameter("size", Integer.valueOf(rows));
sqlQuery.setParameter("userid", useridString);
java.util.List<MemberEntity> memberEntities=sqlQuery.addEntity(MemberEntity.class).list();
//循環計算相對距離
for(MemberEntity memberEntity:memberEntities){
memberEntity.setDistance(Location.distance(Double.valueOf(latitude), Double.valueOf(precision), Double.valueOf(memberEntity.getLatitude()), Double.valueOf(memberEntity.getPrecision())));
}
jsonObject.put("status", "1");
jsonObject.put("msg", memberEntities);
} catch (Exception e) {
e.printStackTrace();
message = "擷取失敗";
jsonObject.put("status", "0");
jsonObject.put("msg", message);
throw new BusinessException(e.getMessage());
}
return jsonObject;
}
我用的是Hibernate架構, sql語句計算了每條資料和中心經緯度的距離并且以最近進行排序。 sql語句是根據下面的方法演變而來
方法是計算兩個經緯度之間的直線距離。
private static double distance(double centerLon, double centerLat, double targetLon, double targetLat) {
double jl_jd = 102834.74258026089786013677476285;// 每經度機關米;
double jl_wd = 111712.69150641055729984301412873;// 每緯度機關米;
double b = Math.abs((centerLat - targetLat) * jl_jd);
double a = Math.abs((centerLon - targetLon) * jl_wd);
return Math.sqrt((a * a + b * b));
}
這樣既實作了分頁處理,又實作了每條資料的經緯度與中心經緯度的直線距離(以米為機關)。