在平面坐标系下实现GeoHash算法 JAVA
程序员文章站
2022-04-13 22:39:12
...
在平面坐标系下实现GeoHash算法 JAVA
GeoHash算法的思想就不赘述了,网上看到的算法都是针对经纬度坐标的,而在项目中可能遇到平面坐标及独立坐标系,因此用算法思想写了实现,对平面坐标XY进行编码。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.BitSet;
/**
* GeoHash计算工具类
*
* @author: yx
* @Date: 2020/3/18
* @Description:用于在坐标非经纬度时生成GeoHash值
*/
public class GeoHashXY {
private double MIN_Y;
private double MAX_Y ;
private double MIN_X ;
private double MAX_X ;
private double minExtentLength;//用于计算单独编码长度即Geohash的长度
private int level;//GeoHash长度
private int numbitY;//经纬度单独编码长度,即进行对分的次数
private int numbitX;
private double minY;//XY在该level下的最小差距
private double minX;
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'};
//定义编码映射关系
final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
//初始化编码映射内容
static {
int i = 0;
for (char c : digits) {
lookup.put(c, i++);
}
}
/**
*
* @params
* @return
* @Desription GeoHash构造函数,设置该对象最大边界的范围
*/
public GeoHashXY(double max_y,double min_y,double max_x,double min_x) {
this.MAX_Y=max_y;
this.MIN_Y=min_y;
this.MAX_X=max_x;
this.MIN_X=min_x;
this.level=6;
setSuitableExtent();
setNumbitsByLevel();
setMinXY();
}
/**
*
* @params
* @return
* @Desription GeoHash构造函数,设置该对象最大边界的范围并指定生成的GeoHash字符串的长度
*/
public GeoHashXY(double max_y,double min_y,double max_x,double min_x,int level) {
this.MAX_Y=max_y;
this.MIN_Y=min_y;
this.MAX_X=max_x;
this.MIN_X=min_x;
this.level=level;
setSuitableExtent();
setNumbitsByLevel();
setMinXY();
}
/**
*
* @params
* @return
* @Desription GeoHash构造函数,设置该对象最大边界的范围并指定最大误差
*/
public GeoHashXY(double max_y,double min_y,double max_x,double min_x,double minExtentLength) {
this.MAX_Y=max_y;
this.MIN_Y=min_y;
this.MAX_X=max_x;
this.MIN_X=min_x;
this.minExtentLength=minExtentLength;
setSuitableExtent();
setNumbitsByMinExtentLength();
setMinXY();
}
/**
*
* @params
* @return
* @Desription 返回设置的最大矩形范围,[MAX_Y,MIN_Y,MAX_X,MIN_X]
*/
public double[] getMaxExtent(){
double[] extent=new double[]{MAX_Y,MIN_Y,MAX_X,MIN_X};
return extent;
}
public int getLevel(){
return this.level;
}
public void setLevel(int level){
this.level=level;
setNumbitsByLevel();
setMinXY();
}
public double getMinExtentLength(){
return this.minExtentLength;
}
public void setMinExtentLength(double minExtentLength){
this.minExtentLength=minExtentLength;
setNumbitsByMinExtentLength();
setMinXY();
}
/**
*
* @params
* @return
* @Desription 先指定最大误差,再进行GeoHash编码
*/
public String encode(double Y, double X,double minExtentLength) {
this.minExtentLength=minExtentLength;
setNumbitsByMinExtentLength();
setMinXY();
return encode(Y,X);
}
/**
*
* @params
* @return
* @Desription 先指定GeoHash的长度,再进行GeoHash编码
*/
public String encode(double Y, double X,int level) {
this.level=level;
setNumbitsByLevel();
setMinXY();
return encode(Y,X);
}
/**
*
* @params
* @return
* @Desription 根据初始化的参数进行GeoHash编码
*/
public String encode(double Y, double X) {
BitSet Ybits = getBits(Y,numbitY, MIN_Y, MAX_Y);
BitSet Xbits = getBits(X,numbitX, MIN_X, MAX_X);
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < numbitY; i++) {
buffer.append((Xbits.get(i)) ? '1' : '0');
buffer.append((Ybits.get(i)) ? '1' : '0');
}
if(numbitX!=numbitY){
buffer.append((Xbits.get(numbitX-1)) ? '1' : '0');
}
String code = base32(Long.parseLong(buffer.toString(), 2));
return code;
}
/**
*
* @params
* @return
* @Desription 计算点所在矩形范围GeoHash值及周围8个矩形范围的GeoHash值
*/
public ArrayList<String> getArroundGeoHashList(double Y, double X) {
ArrayList<String> list = new ArrayList<>();
double upY = Y + minY;
double downLat = Y - minY;
double leftlng = X - minX;
double rightLng = X + minX;
String leftUp = encode(upY, leftlng);
list.add(leftUp);
String leftMid = encode(Y, leftlng);
list.add(leftMid);
String leftDown = encode(downLat, leftlng);
list.add(leftDown);
String midUp = encode(upY, X);
list.add(midUp);
String midMid = encode(Y, X);
list.add(midMid);
String midDown = encode(downLat, X);
list.add(midDown);
String rightUp = encode(upY, rightLng);
list.add(rightUp);
String rightMid = encode(Y, rightLng);
list.add(rightMid);
String rightDown = encode(downLat, rightLng);
list.add(rightDown);
//Log.i("okunu", "getArroundGeoHash list = " + list.toString());
return list;
}
//限制maxx,minx的差与maxy,miny的差相距大小,即如果传入的最大范围如果是长矩形,则两边都用最大的长,转为正方形
private void setSuitableExtent(){
double differenceY = MAX_Y - MIN_Y;
double differenceX = MAX_X - MIN_X;
if(differenceX>differenceY){
if(differenceX/differenceY>=5){
double midY=(MAX_Y+MIN_Y)/2.0;
MAX_Y=midY+differenceX/2.0;
MIN_Y=midY-differenceX/2.0;
}
}else {
if(differenceY/differenceX>=5){
double midX=(MAX_X+MIN_X)/2.0;
MAX_X=midX+differenceY/2.0;
MIN_X=midX-differenceY/2.0;
}
}
}
//根据经纬度和范围,获取对应的二进制
private BitSet getBits(double Y,int numbits, double floor, double ceiling) {
BitSet buffer = new BitSet(numbits);
for (int i = 0; i < numbits; i++) {
double mid = (floor + ceiling) / 2;
if (Y >= mid) {
buffer.set(i);
floor = mid;
} else {
ceiling = mid;
}
}
return buffer;
}
//将经纬度合并后的二进制进行指定的32位编码
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));
}
//根据传入的最小范围长度计算numbits
private void setNumbitsByMinExtentLength() {
double differenceY = MAX_Y - MIN_Y;
int Yi=0;
while(differenceY>=minExtentLength)
{
differenceY /=2.0;
Yi++;
}
double differenceX = MAX_X - MIN_X;
int Xi=0;
while(differenceX>=minExtentLength)
{
differenceX /=2.0;
Xi++;
}
if(Yi>=Xi) {
if (Yi <= 2) {
level=1;
numbitY = 2;
numbitX = 3;
} else if (Yi <= 5) {
level=2;
numbitY = 5;
numbitX = 5;
} else if (Yi <= 7) {
level=3;
numbitY = 7;
numbitX = 8;
} else if (Yi <= 10) {
level=4;
numbitY = 10;
numbitX = 10;
} else if (Yi <= 12) {
level=5;
numbitY = 12;
numbitX = 13;
} else if (Yi <= 15) {
level=6;
numbitY = 15;
numbitX = 15;
} else if (Yi <= 17) {
level=7;
numbitY = 17;
numbitX = 18;
} else {
level=8;
numbitY = 20;
numbitX = 20;
}
}else {
if (Xi <= 3) {
level=1;
numbitY = 2;
numbitX = 3;
} else if (Xi <= 5) {
level=2;
numbitY = 5;
numbitX = 5;
} else if (Xi <= 8) {
level=3;
numbitY = 7;
numbitX = 8;
} else if (Xi <= 10) {
level=4;
numbitY = 10;
numbitX = 10;
} else if (Xi <= 13) {
level=5;
numbitY = 12;
numbitX = 13;
} else if (Xi <= 15) {
level=6;
numbitY = 15;
numbitX = 15;
} else if (Xi <= 18) {
level=7;
numbitY = 17;
numbitX = 18;
} else {
level=8;
numbitY = 20;
numbitX = 20;
}
}
}
//根据传入的GeoHash长度计算numbits
private void setNumbitsByLevel(){
switch (level){
case 1:
numbitY = 2;
numbitX = 3;
break;
case 2:
numbitY = 5;
numbitX = 5;
break;
case 3:
numbitY = 7;
numbitX = 8;
break;
case 4:
numbitY = 10;
numbitX = 10;
break;
case 5:
numbitY = 12;
numbitX = 13;
break;
case 6:
numbitY = 15;
numbitX = 15;
break;
case 7:
numbitY = 17;
numbitX = 18;
break;
case 8:
numbitY = 20;
numbitX = 20;
break;
}
}
private void setMinXY() {
minY = MAX_Y - MIN_Y;
for (int i = 0; i < numbitY; i++) {
minY /= 2.0;
}
minX = MAX_X - MIN_X;
for (int i = 0; i < numbitX; i++) {
minX /= 2.0;
}
}
//根据二进制和范围解码
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 double[] decode(String geohash) {
this.level=geohash.length();
setNumbitsByLevel();
setMinXY();
StringBuilder buffer = new StringBuilder();
for (char c : geohash.toCharArray()) {
int i = lookup.get(c) + 32;
buffer.append(Integer.toString(i, 2).substring(1));
}
BitSet Xset = new BitSet();
BitSet Yset = new BitSet();
//偶数位,经度
int j = 0;
for (int i = 0; i < numbitX * 2; i += 2) {
boolean isSet = false;
if (i < buffer.length()) {
isSet = buffer.charAt(i) == '1';
}
Xset.set(j++, isSet);
}
//奇数位,纬度
j = 0;
for (int i = 1; i < numbitY * 2; i += 2) {
boolean isSet = false;
if (i < buffer.length()) {
isSet = buffer.charAt(i) == '1';
}
Yset.set(j++, isSet);
}
double X = decode(Xset, MIN_X, MAX_X);
double Y = decode(Yset, MIN_Y, MAX_Y);
return new double[]{Y, X};
}
}