查找附近网点geohash算法及实现 (Java版本号)

时间:2023-03-08 21:07:16
查找附近网点geohash算法及实现 (Java版本号)

參考文档:

http://blog.csdn.net/wangxiafghj/article/details/9014363geohash  算法原理及实现方式

http://blog.charlee.li/geohash-intro/  geohash:用字符串实现附近地点搜索

http://blog.sina.com.cn/s/blog_7c05385f0101eofb.html    查找附近点--Geohash方案讨论

http://www.wubiao.info/372        查找附近的xxx 球面距离以及Geohash方案探讨

http://en.wikipedia.org/wiki/Haversine_formula       Haversine formula球面距离公式

http://www.codecodex.com/wiki/Calculate_Distance_Between_Two_Points_on_a_Globe   球面距离公式代码实现

http://developer.baidu.com/map/jsdemo.htm#a6_1   球面距离公式验证  

http://www.wubiao.info/470     Mysql or Mongodb LBS高速实现方案

geohash有下面几个特点:

首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同一时候应用索引 (比如MySQL 4之前的版本号,Google App Engine的数据层等),利用geohash。仅仅需在一列上应用索引就可以。

其次,geohash表示的并非一个点。而是一个矩形区域。比方编码wx4g0ec19,它表示的是一个矩形区域。 使用者能够公布地址编码,既能表明自己位于北海公园附近。又不至于暴露自己的精确坐标,有助于隐私保护。

第三,编码的前缀能够表示更大的区域。比如wx4g0ec1。它的前缀wx4g0e表示包括编码wx4g0ec1在内的更大范围。 这个特性能够用于附近地点搜索。首先依据用户当前坐标计算geohash(比如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),就可以查询附近的全部地点。

Geohash比直接用经纬度的高效非常多。

Geohash算法实现(Java版本号)

package com.DistTest;
import java.util.BitSet;
import java.util.HashMap; public class Geohash { private static int numbits = 6 * 5;
final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
static {
int i = 0;
for (char c : digits)
lookup.put(c, i++);
} public double[] decode(String geohash) {
StringBuilder buffer = new StringBuilder();
for (char c : geohash.toCharArray()) { int i = lookup.get(c) + 32;
buffer.append( Integer.toString(i, 2).substring(1) );
} BitSet lonset = new BitSet();
BitSet latset = new BitSet(); //even bits
int j =0;
for (int i=0; i< numbits*2;i+=2) {
boolean isSet = false;
if ( i < buffer.length() )
isSet = buffer.charAt(i) == '1';
lonset.set(j++, isSet);
} //odd bits
j=0;
for (int i=1; i< numbits*2;i+=2) {
boolean isSet = false;
if ( i < buffer.length() )
isSet = buffer.charAt(i) == '1';
latset.set(j++, isSet);
}
//中国地理坐标:东经73°至东经135°,北纬4°至北纬53°
double lon = decode(lonset, 70, 140);
double lat = decode(latset, 0, 60); return new double[] {lat, lon};
} private double decode(BitSet bs, double floor, double ceiling) {
double mid = 0;
for (int i=0; i<bs.length(); i++) {
mid = (floor + ceiling) / 2;
if (bs.get(i))
floor = mid;
else
ceiling = mid;
}
return mid;
} public String encode(double lat, double lon) {
BitSet latbits = getBits(lat, 0, 60);
BitSet lonbits = getBits(lon, 70, 140);
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < numbits; i++) {
buffer.append( (lonbits.get(i))?'1':'0');
buffer.append( (latbits.get(i))?'1':'0');
}
return base32(Long.parseLong(buffer.toString(), 2));
} private BitSet getBits(double lat, double floor, double ceiling) {
BitSet buffer = new BitSet(numbits);
for (int i = 0; i < numbits; i++) {
double mid = (floor + ceiling) / 2;
if (lat >= mid) {
buffer.set(i);
floor = mid;
} else {
ceiling = mid;
}
}
return buffer;
} public static String base32(long i) {
char[] buf = new char[65];
int charPos = 64;
boolean negative = (i < 0);
if (!negative)
i = -i;
while (i <= -32) {
buf[charPos--] = digits[(int) (-(i % 32))];
i /= 32;
}
buf[charPos] = digits[(int) (-i)]; if (negative)
buf[--charPos] = '-';
return new String(buf, charPos, (65 - charPos));
} }

球面距离公式:

package com.DistTest;
public class Test{
private static final double EARTH_RADIUS = 6371000;//赤道半径(单位m) /**
* 转化为弧度(rad)
* */
private static double rad(double d)
{
return d * Math.PI / 180.0;
}
/**
* 基于googleMap中的算法得到两经纬度之间的距离,计算精度与谷歌地图的距离精度差点儿相同。相差范围在0.2米下面
* @param lon1 第一点的精度
* @param lat1 第一点的纬度
* @param lon2 第二点的精度
* @param lat3 第二点的纬度
* @return 返回的距离,单位m
* */
public static double GetDistance(double lon1,double lat1,double lon2, double lat2)
{
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a = radLat1 - radLat2;
double b = rad(lon1) - rad(lon2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2)+Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
s = s * EARTH_RADIUS;
s = Math.round(s * 10000) / 10000;
return s;
} public static void main(String []args){
double lon1=109.0145193757;
double lat1=34.236080797698;
double lon2=108.9644583556;
double lat2=34.286439088548;
double dist;
String geocode; dist=Test.GetDistance(lon1, lat1, lon2, lat2);
System.out.println("两点相距:" + dist + " 米"); Geohash geohash = new Geohash();
geocode=geohash.encode(lat1, lon1);
System.out.println("当前位置编码:" + geocode); geocode=geohash.encode(lat2, lon2);
System.out.println("远方位置编码:" + geocode); }
//wqj7j37sfu03h2xb2q97
/*
永相逢超市
108.83457500177
34.256981052624
wqj6us6cmkj5bbfj6qdg
s6q08ubhhuq7
*/
}

附近网点距离排序

package com.DistTest;

import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement; public class sqlTest { public static void main(String[] args) throws Exception {
Connection conn = null;
String sql;
String url = "jdbc:mysql://132.97.**.**/test?"
+ "user=***&password=****&useUnicode=true&characterEncoding=UTF8"; try {
Class.forName("com.mysql.jdbc.Driver");// 动态载入mysql驱动
// System.out.println("成功载入MySQL驱动程序");
// 一个Connection代表一个数据库连接
conn = DriverManager.getConnection(url);
// Statement里面带有非常多方法,比方executeUpdate能够实现插入,更新和删除等
Statement stmt = conn.createStatement();
sql = "select * from retailersinfotable limit 1,10";
ResultSet rs = stmt.executeQuery(sql);// executeQuery会返回结果的集合,否则返回空值
double lon1=109.0145193757;
double lat1=34.236080797698;
System.out.println("当前位置:");
int i=0;
String[][] array = new String[10][3];
while (rs.next()){
//从数据库取出地理坐标
double lon2=Double.parseDouble(rs.getString("Longitude"));
double lat2=Double.parseDouble(rs.getString("Latitude")); //依据地理坐标,生成geohash编码
Geohash geohash = new Geohash();
String geocode=geohash.encode(lat2, lon2).substring(0, 9); //计算两点间的距离
int dist=(int) Test.GetDistance(lon1, lat1, lon2, lat2); array[i][0]=String.valueOf(i);
array[i][1]=geocode;
array[i][2]=Integer.toString(dist); i++; // System.out.println(lon2+"---"+lat2+"---"+geocode+"---"+dist);
} array=sqlTest.getOrder(array); //二维数组排序
sqlTest.showArray(array); //打印数组 } catch (SQLException e) {
System.out.println("MySQL操作错误");
e.printStackTrace();
} finally {
conn.close();
} }
/*
* 二维数组排序,比較array[][2]的值。返回二维数组
* */
public static String[][] getOrder(String[][] array){
for (int j = 0; j < array.length ; j++) {
for (int bb = 0; bb < array.length - 1; bb++) {
String[] ss;
int a1=Integer.valueOf(array[bb][2]); //转化成int型比較大小
int a2=Integer.valueOf(array[bb+1][2]);
if (a1>a2) {
ss = array[bb];
array[bb] = array[bb + 1];
array[bb + 1] = ss; }
}
}
return array;
} /*打印数组*/
public static void showArray(String[][] array){
for(int a=0;a<array.length;a++){
for(int j=0;j<array[0].length;j++)
System.out.print(array[a][j]+" ");
System.out.println();
}
} }