背景:在大数据数仓建设的过程中,有时会遇到经纬度类型的数据信息,但在进行关联分析和数仓建设的时候用经纬度去关联,难免不够便捷,于是我们可以开发UDF使用地理经纬度信息哈希编码的方案进行开发,非常有效
地理哈希算法(Geohash) 采用方案
适合场景:在需要可变精度表示位置,且便于进行范围查询和空间索引的场景中表现出色。例如在基于位置的服务中,用户可能在不同场景下需要不同精度的位置信息(如粗略定位城市或精确到街道),Geohash 可以通过调整字符串长度满足这一需求,同时其前缀匹配特性利于快速查询附近位置。
不适合场景:如果对边界连续性要求极高,Geohash 在边界处的精度和连续性问题可能带来困扰,例如在需要精确计算边界距离或进行严格的空间拓扑分析时。
某个格点温度,后面想粗略看周围格点的温度变化情况,另外编码后可解析经纬度信息,无需额外的经纬度字段数据,编码可逆
根据接入层经纬度范围,囊括山东的经度维度范围,有效编码解码范围区间
经度【110,130】 纬度【30,45】
(123.4196,44.2848)->xt7z16sv 八位地理编码
public class GeohashUtils {// 每个Geohash字符对应的二进制位数private static final int BITS = 5;// Base32编码表,用于将二进制转换为字符表示private static final String BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";// 经度范围的最小值private static final double MIN_LON = 110;// 经度范围的最大值private static final double MAX_LON = 130;// 纬度范围的最小值private static final double MIN_LAT = 30;// 纬度范围的最大值private static final double MAX_LAT = 45;// 编码方法,将经纬度编码为Geohash字符串public static String encode(double lat, double lon, int precision) {// 初始化纬度范围double[] latRange = {MIN_LAT, MAX_LAT};// 初始化经度范围double[] lonRange = {MIN_LON, MAX_LON};// 用于存储最终的Geohash字符串StringBuilder geohash = new StringBuilder();// 用于临时存储二进制位StringBuilder bits = new StringBuilder();// 用于交替处理经度和纬度,初始为true表示先处理经度boolean isEven = true;// 当前处理的二进制位索引int bit = 0;// 用于累积二进制位以转换为Base32字符int ch = 0;// 循环直到生成的Geohash字符串达到指定精度while (geohash.length() < precision) {double mid;if (isEven) {// 计算经度范围的中间值mid = (lonRange[0] + lonRange[1]) / 2;if (lon > mid) {// 如果当前经度大于中间值,设置相应的二进制位ch |= (1 << (BITS - 1 - bit));// 更新经度范围的下限lonRange[0] = mid;} else {// 如果当前经度小于等于中间值,更新经度范围的上限lonRange[1] = mid;}} else {// 计算纬度范围的中间值mid = (latRange[0] + latRange[1]) / 2;if (lat > mid) {// 如果当前纬度大于中间值,设置相应的二进制位ch |= (1 << (BITS - 1 - bit));// 更新纬度范围的下限latRange[0] = mid;} else {// 如果当前纬度小于等于中间值,更新纬度范围的上限latRange[1] = mid;}}// 切换处理经度或纬度isEven =!isEven;if (bit < BITS - 1) {// 如果还未处理完一个字符的所有二进制位,增加位索引bit++;} else {// 当处理完一个字符的所有二进制位,将累积的二进制值转换为Base32字符并添加到Geohash字符串中geohash.append(BASE32.charAt(ch));// 重置位索引和累积值,准备下一个字符的处理bit = 0;ch = 0;}}// 返回生成的Geohash字符串return geohash.toString();}// 解码方法,将Geohash字符串解码为经纬度public static double[] decode(String geohash) {// 初始化纬度范围double[] latRange = {MIN_LAT, MAX_LAT};// 初始化经度范围double[] lonRange = {MIN_LON, MAX_LON};// 用于交替处理经度和纬度,初始为true表示先处理经度boolean isEven = true;// 遍历Geohash字符串的每个字符for (int i = 0; i < geohash.length(); i++) {// 获取当前字符在Base32编码表中的索引,即对应的整数值int cd = BASE32.indexOf(geohash.charAt(i));// 对每个字符的5个二进制位进行处理for (int j = BITS - 1; j >= 0; j--) {int mask = 1 << j;if (isEven) {if ((cd & mask) != 0) {// 如果当前二进制位为1,更新经度范围的下限lonRange[0] = (lonRange[0] + lonRange[1]) / 2;} else {// 如果当前二进制位为0,更新经度范围的上限lonRange[1] = (lonRange[0] + lonRange[1]) / 2;}} else {if ((cd & mask) != 0) {// 如果当前二进制位为1,更新纬度范围的下限latRange[0] = (latRange[0] + latRange[1]) / 2;} else {// 如果当前二进制位为0,更新纬度范围的上限latRange[1] = (latRange[0] + latRange[1]) / 2;}}// 切换处理经度或纬度isEven =!isEven;}}// 计算并返回解码后的纬度double lat = (latRange[0] + latRange[1]) / 2;// 计算并返回解码后的经度double lon = (lonRange[0] + lonRange[1]) / 2;return new double[]{lat, lon};}// 主方法,用于测试编码和解码功能public static void main(String[] args) {double lat = 35;double lon = 120;int precision = 6;// 调用编码方法生成Geohash字符串String geohash = encode(lat, lon, precision);System.out.println("Geohash: " + geohash);// 调用解码方法将Geohash字符串解码回经纬度double[] decoded = decode(geohash);System.out.println("Decoded Latitude: " + decoded[0]);System.out.println("Decoded Longitude: " + decoded[1]);}
}
功能总结
-
编码 (
encode
方法):- 从给定的经纬度范围开始,通过二分法逐步缩小范围,并根据经纬度与范围中间值的比较结果,确定二进制位的值。
- 每
BITS
个二进制位组合成一个Base32编码的字符,添加到Geohash字符串中。 - 重复上述过程,直到生成的Geohash字符串达到指定的精度。
-
解码 (
decode
方法):- 从给定的Geohash字符串的每个字符中提取二进制位。
- 根据二进制位的值,通过二分法逐步缩小经纬度范围。
- 最终计算并返回解码后的经纬度。
-
main
方法:- 提供了一个简单的测试示例,对给定的经纬度进行编码,并将生成的Geohash字符串解码回经纬度,输出结果用于验证编码和解码的正确性。