GeoHash是一种将二维地理坐标(经纬度)转换为一维字符串的技术,通过递归划分地球表面区域实现高效索引。

GeoHash是一种将二维地理坐标(经纬度)转换为一维字符串的技术,通过递归划分地球表面区域实现高效索引。

基本原理

GeoHash将经纬度范围划分为矩形区域,每个区域分配相同编码。编码通过二进制序列表示经纬度位置,通过奇偶位交替记录纬度与经度信息,最终转换为Base32编码的字符串。 ‌

核心特点

  • 空间划分‌:通过经纬度二分法递归分割地球表面,精度由划分次数决定(如12次划分生成12位编码)。
  • 编码规则‌:纬度用奇数位记录,经度用偶数位记录,合并后形成Base32编码字符串。 
  • 应用场景‌:用于地理位置索引、隐私保护及高效查询,例如Elasticsearch等系统采用GeoHash优化地理数据检索。 

示例(以北京为例)

假设北京某点经纬度为(116.389550, 39.928167),通过GeoHash算法可生成类似"WX4ER"的编码,该编码代表北京周边区域。

/**
 *
 */
function getGeoHash(longitude, latitude, length) {
	if (!longitude && !latitude) {
		console.error('请传入经纬度')
	}
	if (typeof longitude == "string") {
		longitude = Number(longitude)
	}
	if (typeof latitude == "string") {
		latitude = Number(latitude)
	}
	// 获取base32编码
	const Base32 = function(str) {
		let num = str
		if (typeof num == "string") {
			num = Number(str)
		}
		if (num >= 0 && num <= 9) {
			return num.toString()
		} else if (num >= 10 && num <= 16) {
			return String.fromCharCode(98 + str - 10)
		} else if (num >= 17 && num <= 18) {
			return String.fromCharCode(106 + str - 17)
		} else if (num >= 19 && num <= 20) {
			return String.fromCharCode(109 + str - 19)
		} else if (num >= 21 && num <= 31) {
			return String.fromCharCode(112 + str - 21)
		}
	}
	let lngExtent = [-180, 180]
	let latExtent = [-90, 90]
	let accuracy = length || 12 // 精度
	let times = (accuracy * 5) / 2
	let result = []
	let lngList = [] // 经度的二进制结果
	let latList = [] // 纬度的二进制结果
	let lngAverage = 0
	let latAverage = 0
	let lngLeftRange = []
	let lngRightRange = []
	let latLeftRange = []
	let latRightRange = []
	for (let i = 0; i < times; i++) {
		lngAverage = eval(lngExtent.join("+")) / lngExtent.length
		latAverage = eval(latExtent.join("+")) / latExtent.length
		lngLeftRange = [lngExtent[0], lngAverage]
		lngRightRange = [lngAverage, lngExtent[1]]
		latLeftRange = [latExtent[0], latAverage]
		latRightRange = [latAverage, latExtent[1]]
		if (longitude >= lngLeftRange[0] && longitude < lngLeftRange[1]) {
			lngList.push(0)
			lngExtent = lngLeftRange
		} else {
			lngList.push(1)
			lngExtent = lngRightRange
		}
		if (latitude >= latLeftRange[0] && latitude < latLeftRange[1]) {
			latList.push(0)
			latExtent = latLeftRange
		} else {
			latList.push(1)
			latExtent = latRightRange
		}
	}
	let lngNum = 0
	let latNum = 0
	// 合并:偶数位放经度,奇数位放纬度
	for (let j = 0; j < times * 2; j++) {
		if (j % 2 == 0) {
			result.push(lngList[lngNum])
			lngNum++
		} else {
			result.push(latList[latNum])
			latNum++
		}
	}
	// 转为十进制,再转base32编码
	let geohash = ''
	let k = 0
	for (let k = 0; k < result.length; k = k + 5) {
		let arr = result.slice(k, k + 5)
		let sum = parseInt(arr.join(''), 2)
		let base32 = Base32(sum)
		geohash += base32
	}
	return geohash
}

下面是一个用 JavaScript 实现的 GeoHash 解码函数,它可以将 GeoHash 字符串转换为对应的经纬度坐标。

这个函数的工作原理是:

  1. 使用 GeoHash 标准的 Base32 字符集进行解码
  2. 交替处理经度和纬度(偶数位处理经度,奇数位处理纬度)
  3. 对每个字符解析为 5 位二进制数,逐步缩小经纬度范围
  4. 最终取经纬度区间的中点作为解码结果
  5. 同时计算出该 GeoHash 对应的位置精度误差
/**
 * 解码GeoHash字符串,返回经纬度的最小值和最大值
 * @param {string} geohash - GeoHash字符串
 * @returns {{lng_min: number, lng_max: number, lng_center: number, lng_error: number, lat_min: number, lat_max: number, lat_center: number, lat_error: number}} - 经纬度的最小值、最大值、中心点、中心点偏差
 */
function decodeGeoHash(geohash) {
    // GeoHash 编码使用的 Base32 字符集
    const base32 = '0123456789bcdefghjkmnpqrstuvwxyz';

    let isEven = true;
    let latitude = [-90, 90];
    let longitude = [-180, 180];

    // 遍历 GeoHash 中的每个字符
    for (let i = 0; i < geohash.length; i++) {
        const c = geohash[i];
        // 找到字符在 base32 中的索引
        let index = base32.indexOf(c);
        if (index === -1) {
            throw new Error('Invalid GeoHash character: ' + c);
        }

        // 解析 5 位二进制
        for (let j = 4; j >= 0; j--) {
            const bit = (index >> j) & 1;

            if (isEven) {
                // 偶数位处理经度
                const mid = (longitude[0] + longitude[1]) / 2;
                if (bit === 1) {
                    longitude[0] = mid;
                } else {
                    longitude[1] = mid;
                }
            } else {
                // 奇数位处理纬度
                const mid = (latitude[0] + latitude[1]) / 2;
                if (bit === 1) {
                    latitude[0] = mid;
                } else {
                    latitude[1] = mid;
                }
            }
            isEven = !isEven;
        }
    }

    return {
        lng_min: longitude[0],
        lng_max: longitude[1],
        lng_center: (latitude[0] + latitude[1]) / 2,
        lng_error: (longitude[1] - longitude[0]) / 2,
        lat_min: latitude[0],
        lat_max: latitude[1],
        lat_center: (latitude[0] + latitude[1]) / 2,
        lat_error: (latitude[1] - latitude[0]) / 2
    };
}

 

posted on 2025-08-04 16:14  骑着母猪去打猎  阅读(76)  评论(0)    收藏  举报