/**
* GeoHash算法
*
* 可以到 http://geohash.co/ 进行geohash编码,以确定自己代码是否写错
*
* @Description GeoHash字符串编码越长,表示的范围越小,位置也越精确。
* 因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。
* @author xrj
* @date 2020/4/15
*/
@Slf4j
public class GeoHash {
private static final double MINLAT = -90;
private static final double MAXLAT = 90;
private static final double MINLNG = -180;
private static final double MAXLNG = 180;
private 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' };
//定义编码映射关系
private final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
//初始化编码映射内容
static {
int i = 0;
for (char c : digits)
lookup.put(c, i++);
}
/**
* 范围最小
*/
private static final int SCOPE_MIX=6;
private static final int MULTIPLE=5;
/**
* 范围最小是3 1.2km
* 范围一般是2 39.1km
* 范围最大是1 5000km
* current_numbits=数字*5
* current_numbits=SCOPE_MIX*MULTIPLE
*/
private int current_numbits ;
//最小单位
private double minLat;
private double minLng;
/**
* 私有化构造器
*/
private GeoHash(){
}
/**
* 模糊匹配
* 必须赋值经纬度单独编码长度,否则报错
* @param numbits 整数范围1-3;包含1和3
* 范围最小是3 1.2km
* 范围一般是2 39.1km
* 范围最大是1 5000km
*/
private GeoHash(Integer numbits){
if(numbits!=null && numbits>0 && numbits<4){
this.current_numbits=numbits*MULTIPLE;
setMinLatLng();
}else{
throw new BaseException(Constant.BUSINESS,ResultEnum.GEOHASH_FAILED);
}
}
/**
* 得到了最小单位,那么周边区域的经纬度也可以计算得到了。比如说左边区域的经度肯定是自身经度减去最小经度单位。
* 纬度也可以通过加减,得到上下的纬度值,最终周围8个单位也可以计算得到。
*/
private void setMinLatLng() {
minLat = MAXLAT - MINLAT;
for (int i = 0; i < current_numbits; i++) {
minLat /= 2.0;
}
minLng = MAXLNG - MINLNG;
for (int i = 0; i < current_numbits; i++) {
minLng /= 2.0;
}
}
/**
* 企业,项目,消纳站得到geoHash参数
* @param lat 纬度
* @param lon 经度
* @return
*/
public static String getGeoHashStr(Double lat, Double lon){
validatorParam(lon,lat);
return new GeoHash(SCOPE_MIX).encode(lat,lon);
}
/**
* 得到模糊查询的geohash字符串
* 范围最小是3 1.2km
* 范围一般是2 39.1km
* 范围最大是1 5000km
* @param lat 纬度
* @param lon 经度
* @return
*/
public static String getGeoHashByLikeStr(Integer scope,Double lat, Double lon){
validatorParam(lon,lat);
return new GeoHash(scope).encode(lat,lon);
}
/**
* 得到附近八个点
* 范围最小是3 1.2km
* 范围一般是2 39.1km
* 范围最大是1 5000km
* @param lat 纬度
* @param lon 经度
* @return
*/
public static ArrayList<String> getArroundGeoHashByLikeStr(Integer scope,Double lat, Double lon){
validatorParam(lon,lat);
return new GeoHash(scope).getArroundGeoHash(lat,lon);
}
/**
* 对geoHashStr接码得到经纬度
* @param geoHashStr
* @return
*/
public static double[] getDecodeByGeoHashStr(String geoHashStr){
return new GeoHash(SCOPE_MIX).decode(geoHashStr);
}
private static void validatorParam(Double lon,Double lat){
if(lon == null || lon==0 || lat==null || lat==0){
//抛异常
}
}
/**
* 得到GeoHash字符串
* @param lat 纬度
* @param lon 经度
* @return
*/
private String encode(double lat, double lon) {
BitSet latbits = getBits(lat, MINLAT, MAXLAT);
BitSet lonbits = getBits(lon, MINLNG, MAXLNG);
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < current_numbits; i++) {
buffer.append( (lonbits.get(i))?'1':'0');
buffer.append( (latbits.get(i))?'1':'0');
}
String code = base32(Long.parseLong(buffer.toString(), 2));
log.info( "encode lat = " + lat + " lng = " + lon + " code = " + code);
return code;
}
/**
* 根据经纬度和范围,获取对应的二进制
* @param lat
* @param floor
* @param ceiling
* @return
*/
private BitSet getBits(double lat, double floor, double ceiling) {
BitSet buffer = new BitSet(current_numbits);
for (int i = 0; i < current_numbits; i++) {
double mid = (floor + ceiling) / 2;
if (lat >= mid) {
buffer.set(i);
floor = mid;
} else {
ceiling = mid;
}
}
return buffer;
}
/**
* 将经纬度合并后的二进制进行指定的32位编码
* @param i
* @return
*/
private 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));
}
/**
* 对编码后的字符串解码得到经纬度
* @param geohash
* @return
*/
private 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();
//偶数位,经度
int j =0;
for (int i=0; i< current_numbits*2;i+=2) {
boolean isSet = false;
if ( i < buffer.length() )
isSet = buffer.charAt(i) == '1';
lonset.set(j++, isSet);
}
//奇数位,纬度
j=0;
for (int i=1; i< current_numbits*2;i+=2) {
boolean isSet = false;
if ( i < buffer.length() )
isSet = buffer.charAt(i) == '1';
latset.set(j++, isSet);
}
double lon = decode(lonset, -180, 180);
double lat = decode(latset, -90, 90);
return new double[] {lat, lon};
}
/**
* 根据二进制和范围解码
* @param bs
* @param floor
* @param ceiling
* @return
*/
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;
}
/**
* 得到附近八个点
* @param lat
* @param lon
* @return
*/
private ArrayList<String> getArroundGeoHash(double lat, double lon){
log.info("getArroundGeoHash lat = " + lat + " lng = " + lon);
ArrayList<String> list = new ArrayList<>();
double uplat = lat + minLat;
double downLat = lat - minLat;
double leftlng = lon - minLng;
double rightLng = lon + minLng;
String leftUp = encode(uplat, leftlng);
list.add(leftUp);
String leftMid = encode(lat, leftlng);
list.add(leftMid);
String leftDown = encode(downLat, leftlng);
list.add(leftDown);
String midUp = encode(uplat, lon);
list.add(midUp);
String midMid = encode(lat, lon);
list.add(midMid);
String midDown = encode(downLat, lon);
list.add(midDown);
String rightUp = encode(uplat, rightLng);
list.add(rightUp);
String rightMid = encode(lat, rightLng);
list.add(rightMid);
String rightDown = encode(downLat, rightLng);
list.add(rightDown);
log.info("getArroundGeoHash list = " + list.toString());
return list;
}
public static void main(String[] args) throws Exception{
//得到建筑物当前矩形点位
String s = getGeoHashStr(40.222012, 116.248283);
System.out.println("字符串:"+s);
//得到范围比较大的点位
// System.out.println(getGeoHashByLikeStr(3,40.222012, 116.248283));
// //查询最近的点
// getArroundGeoHashByLikeStr(SCOPE_MIX,40.222012, 116.248283);
// getArroundGeoHashByLikeStr(5,40.222012, 116.248283);
// //得到经纬度
double[] geo = getDecodeByGeoHashStr(s);
System.out.println(geo[0]+" "+geo[1]);
}
}

还没有评论,来说两句吧...