GeoHash编解码示例(c++版本)

 

GeoHash的原理讲的很多了,

GeoHash 核心原理解析 https://baijiahao.baidu.com/s?id=1684213349530757049&wfr=spider&for=pc

大多数实现都是用JAVA写的,今天给个c++版本:

考虑综合性能,可能使用7字符编码是一个合适的考虑:

static const 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' };

///
// 附加一个35比特,7字符的哈希;纬度17级,经度18级;
// 备注:从低到高,索引index为0-----34,每位都是等于0x1移动了 index位
// 位移操作默认1是32bit的,如要使用64bit需要强制声明1ULL。
string GeoHashStr7(double lon, double lat)
{
	uint64_t d = 0ULL;
	int n = 34;
	double lon_left = -180;
	double lon_right = 180;
	double lon_mid = 0; 
	double lat_left = -90;
	double lat_right =  90;
	double lat_mid;
	// 每个循环处理2个比特,
	for (int i = 0; i< 17; i++)
	{
		// 先处理经度
		lon_mid = (lon_left + lon_right) / 2;
		if (lon > lon_mid) // 右侧,1
		{
			lon_left = lon_mid;
			d = d | (1ULL << (34 - 2*i));
		}
		else  //  左侧0
		{
			 lon_right = lon_mid;
		}

		// 处理纬度,
		lat_mid = (lat_left + lat_right) / 2;
		if (lat > lat_mid)   // 右侧
		{
			lat_left = lat_mid ;
			d = d | (1ULL << (34 - 2 * i - 1));
		}
		else
		{
			lat_right = lat_mid;
		}
	}

	//剩下最低位留给经度
	lon_mid = (lon_left + lon_right) / 2;
	if (lon > lon_mid) // 右侧,1
	{
		d = d | 0x1;
	}
	else  //  左侧0
	{
	}

	// 开始转哈希字符
	assert(sizeof(digits) / sizeof(char) == 32);
	n = 7;  // 字符个数
	string temp = "";
	for (int i = 0; i < n; i++)
	{
		uint64_t t = d >> 5 * (n - 1 - i);
		
		t = t & 0X1FULL;
		cout << t << endl;
		assert(t < 32);
		char a = digits[t];
		temp += a;
	}
	std::cout << "经度范围:" << lon_left << "," << lon_right << ",纬度范围:" << lat_left << "," << lat_right << endl;
	std::cout << "宽-高(m):" << (lon_right - lon_left) / 0.00001 * 1.1 << ", " << (lat_right - lat_left) / 0.00001 << endl;
	std::cout << "7字符编码:"<<temp.c_str() << endl;
	return temp;


}

bool GeoHashDecode7(const string & str, double &lon, double &lat)
{
	static std::map<char, int> digitsMap;
	if (digitsMap.size() == 0)
	{
		for (int i = 0; i < 32; i++)
		{
			digitsMap.insert(make_pair(digits[i], i));
		}
	}

	// 拼接
	uint64_t d = 0ULL;
	int i = 0;
	for (int i = 0; i < 7; i++)
	{
		auto it = digitsMap.find(str[i]);
		if (it == digitsMap.end())
		{
			return false;  // 错误
		}
		uint64_t t = static_cast<uint64_t>(it->second) << (6 - i) * 5;
		d = d | t;
	}

	lon = 0;
	lat = 0;
	int n = 34;
	double lon_left = -180;
	double lon_right = 180;
	double lon_mid = 0;
	double lat_left = -90;
	double lat_right = 90;
	double lat_mid;

	// 每个循环处理2个比特,
	uint64_t t = 0UI64;
	for (int i = 0; i < 17; i++)
	{
		// 先处理经度
		t = d & (1ULL << (34 - 2 * i));
		lon_mid = (lon_left + lon_right) / 2;
		if (t) // 右侧,1
		{
			lon_left = lon_mid;
		}
		else  //  左侧0
		{
			lon_right = lon_mid;
		}

		// 处理纬度,
		t = d & (1ULL << (34 - 2 * i - 1));
		lat_mid = (lat_left + lat_right) / 2;
		if (t)   // 右侧
		{
			lat_left = lat_mid;
		}
		else
		{
			lat_right = lat_mid;
		}
	}
	// 处理最后一bit经度
	t = d & 1ULL;
	if (t) // 右侧,1
	{
		lon_left = lon_mid;
	}
	else  //  左侧0
	{
		lon_right = lon_mid;
	}
	
	// 此时此刻,锁定了编码所在的区域;
	std::cout << "经度范围:" << lon_left << "," << lon_right << ",纬度范围:" << lat_left << "," << lat_right << endl;
	lon = (lon_left + lon_right) / 2;
	lat = (lat_left + lat_right) / 2;

	std::cout << lon << ", " << lat << endl;
	return true;
}

而我自己用的是经纬度分别编码为2字节,15比特,同时需要在显示时候编码为6字节:

/*
 * LinkUtils.h
 *
 *  Created on: 2021年4月25日
 *      Author: 390017268@qq.com
 */

#ifndef LINK4A_LINKUTILS_H_
#define LINK4A_LINKUTILS_H_
#include <stdint.h>
#include <string>
using namespace std;

class LinkUtils {
public:
    LinkUtils();
    virtual ~LinkUtils();

    // 浮点数与二进制哈希互转
    static uint32_t GeoHash(double lonlat, double left, double right, int level);
    static double GeoHash2LonLat(uint32_t d, double left, double right, int level);

    // 字符串相关
    static std::string GeoHashEncode(uint32_t & d, int level);
    static int GeoHashDecode(const string & str, uint32_t & d);
    static double GeoStrDecodeLontitude(const string & str);
    static double GeoStrDecodeLatitude(const string & str);

    //  二进制哈希与2字节互转
    static void GeoHashEncode2Bytes(uint32_t  d, uint8_t & d1, uint8_t & d2);
    static void GeoHashDecode2Byte(uint32_t & d, uint8_t  d1, uint8_t  d2);

    static void GeoHashEncode2Bytes(double  lon, double lat, uint8_t & d1, uint8_t & d2, uint8_t & d3, uint8_t & d4);
    static void GeoHashDecode2Byte(double  &lon, double &lat, uint8_t  d1, uint8_t  d2, uint8_t  d3, uint8_t  d4);
};

#endif /* LINK4A_LINKUTILS_H_ */

CPP文件: 

/*
 * LinkUtils.cpp
 *
 *  Created on: 2021年4月25日
 *      Author: 39001
 */

#include "LinkUtils.h"
#include <assert.h>
#include <map>

static const 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' };


LinkUtils::LinkUtils() {
    // TODO Auto-generated constructor stub

}

LinkUtils::~LinkUtils() {
    // TODO Auto-generated destructor stub
}

//--------------------------------------------------------------
// 精度比较好的是纬度使用17比特,经度使用18比特,一共35比特,7个字符编码;
// 混合编码经度使用奇数位,这样就比维度多一位;
// 这里主要考虑分别编码
// 经纬度单独编码,二分法计算编码,将经纬度转为二进制
// index表示编码级别15,或10,
// d = GeoHash(39.9232, -90, 90, 15);
// d = GeoHash(116.389550, -180, 180, 15);
uint32_t LinkUtils::GeoHash(double lonlat, double left, double right, int level)
{
    uint32_t d = 0;
    double mid = 0;
    while (true)
    {
        mid = (left + right) / 2;
        if (lonlat > mid)
        {
            d = d | 0x01;
            left = mid;
            //std::cout << "1  ";
        }
        else
        {
            // 该位为0
            right = mid;
            //std::cout << "0  ";
        }

        level --;
        if (level > 0)
        {
            d = d << 1;
            continue;
        }
        else
            break;
    }

    return d;
}

// 将二进制编码转为double
// 反向的转换一定会有精度损失,与之前就不一样了
//
//
double LinkUtils::GeoHash2LonLat(uint32_t d, double left, double right, int level)
{
    double mid = 0;
    for (int i=0; i<level; i++)
    {
        mid = (left + right) / 2;
        uint32_t mask = 1 << (level - i - 1);
        if (d & mask)  // 1,计算右侧
        {
            left = mid;
        }
        else   // 计算左侧
        {
            right = mid;
        }

    }

    // 最后缩小到区域,再计算一次,取中间
    mid = (left + right) / 2;

    //cout << "确定范围:" << left << ", " << right << ", 精度" << right -left <<endl;
    return mid;
}
//-------------------------------------------------------



// 32位哈希编码转字符
string LinkUtils::GeoHashEncode(uint32_t & d, int level)
{
    assert(sizeof(digits) / sizeof(char) == 32);
    int n = (level + 4) / 5;
    string temp = "";
    for (int i = 0; i < n; i++)
    {
        uint32_t t = d >> 5 * (n - 1 -i);
        t = t & 0x1f;
        assert(t < 32);
        char a = digits[t];
        temp += a;
    }
   // std::cout << temp.c_str() << endl;
    return temp;
}


// 从单独的经纬度字符串解码为二进制哈希
 int LinkUtils::GeoHashDecode(const string & str, uint32_t & d)
{
    static std::map<char, int> digitsMap;
    size_t n = str.length();

    if (digitsMap.size() == 0) // 初始化
    {
        for (int i = 0; i < (sizeof(digits) / sizeof(char)); i++)
        {
            digitsMap.insert(make_pair(digits[i], i));
        }

        // 遍历一遍
        /*for (auto &it : digitsMap)
        {
            std::cout << it.first << it.second << endl;
        }*/
    }

    d = 0;
    for (int i=0; i< n; i++)
    {
        auto it = digitsMap.find(str[i]);
        if (it == digitsMap.end())
        {
            return 0;
        }
        uint32_t t = it->second << (n - 1 - i) * 5;
        d = d | t;
    }
    return n * 5;
}
//
//  单独的经纬度字符编码直接转为double
double LinkUtils::GeoStrDecodeLontitude(const string & str)
{
    uint32_t  lon;
    GeoHashDecode(str, lon);
    return GeoHash2LonLat(lon, -180, 180, 15);
}

double LinkUtils::GeoStrDecodeLatitude(const string & str)
{
    uint32_t  lat;
    GeoHashDecode(str, lat);

    return GeoHash2LonLat(lat, -90, 90, 15);
}

//-------------------------------------------------------
// 另外
// 10位编码转为2个char
// d1是低8位,d2是高7 位
 void LinkUtils::GeoHashEncode2Bytes(uint32_t d, uint8_t & d1, uint8_t & d2)
 {
     d1 = d & 0xff;
     d2 = (d >> 8) & 0xff;
 }

void LinkUtils::GeoHashDecode2Byte(uint32_t & d, uint8_t  d1, uint8_t  d2)
 {
     d = 0;
     d = d1 | ((uint32_t)d2 << 8);
 }

void LinkUtils::GeoHashEncode2Bytes(double  lon, double lat, uint8_t & d1, uint8_t & d2, uint8_t & d3, uint8_t & d4)
{
    uint32_t temp = GeoHash(lat, -90, 90, 15);
    GeoHashEncode2Bytes(temp, d1, d2);

    temp= GeoHash(lon, -180, 180, 15);
    GeoHashEncode2Bytes(temp, d3, d4);
}

void LinkUtils::GeoHashDecode2Byte(double  &lon, double &lat, uint8_t  d1, uint8_t  d2, uint8_t  d3, uint8_t  d4)
{
	uint32_t temp;

	GeoHashDecode2Byte(temp, d1, d2);
	lat = GeoHash2LonLat(temp, -90, 90, 15);

	GeoHashDecode2Byte(temp, d3, d4);
	lon = GeoHash2LonLat(temp, -180, 180, 15);

}

希望对大家有用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值