using System;
using System.Collections.Generic;
using System.Text;
namespace NewLife.Data;
/// <summary>经纬坐标的一维编码表示</summary>
/// <remarks>
/// 文档 https://newlifex.com/core/geo_hash
///
/// 一维编码表示一个矩形区域,前缀表示更大区域,例如北京wx4fbzdvs80包含在wx4fbzdvs里面。
/// 这个特性可以用于附近地点搜索。
/// GeoHash编码位数及距离关系:
/// 1位,+-2500km;
/// 2位,+-630km;
/// 3位,+-78km;
/// 4位,+-20km;
/// 5位,+-2.4km;
/// 6位,+-610m;
/// 7位,+-76m;
/// 8位,+-19m;
/// 9位,+-2m;
/// </remarks>
public static class GeoHash
{
#region 属性
private static readonly Int32[] BITS = [16, 8, 4, 2, 1];
private const String _base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
private static readonly Dictionary<Char, Int32> _decode = new();
#endregion
#region 构造
static GeoHash()
{
for (var i = 0; i < _base32.Length; i++)
{
_decode[_base32[i]] = i;
}
}
#endregion
#region 方法
/// <summary>编码坐标点为GeoHash字符串</summary>
/// <param name="longitude">经度</param>
/// <param name="latitude">纬度</param>
/// <param name="charCount">字符个数。默认9位字符编码,精度2米</param>
/// <returns></returns>
public static String Encode(Double longitude, Double latitude, Int32 charCount = 9)
{
Double[] longitudeRange = [-180, 180];
Double[] latitudeRange = [-90, 90];
var isEvenBit = true;
UInt64 bits = 0;
var len = charCount * 5;
for (var i = 0; i < len; i++)
{
bits <<= 1;
// 轮流占用信息位
var value = isEvenBit ? longitude : latitude;
var rang = isEvenBit ? longitudeRange : latitudeRange;
var mid = (rang[0] + rang[1]) / 2;
if (value >= mid)
{
bits |= 0x1;
rang[0] = mid;
}
else
{
rang[1] = mid;
}
isEvenBit = !isEvenBit;
}
bits <<= (64 - len);
// base32编码
var sb = new StringBuilder();
for (var i = 0; i < charCount; i++)
{
var pointer = (Int32)((bits & 0xf800000000000000L) >> 59);
sb.Append(_base32[pointer]);
bits <<= 5;
}
return sb.ToString();
}
/// <summary>解码GeoHash字符串为坐标点</summary>
/// <param name="geohash"></param>
/// <returns></returns>
public static (Double Longitude, Double Latitude) Decode(String geohash)
{
Double[] latitudeRange = [-90, 90];
Double[] longitudeRange = [-180, 180];
var isEvenBit = true;
for (var i = 0; i < geohash.Length; i++)
{
var ch = _decode[geohash[i]];
for (var j = 0; j < 5; j++)
{
// 轮流解码信息位
var rang = isEvenBit ? longitudeRange : latitudeRange;
var mid = (rang[0] + rang[1]) / 2;
if ((ch & BITS[j]) != 0)
rang[0] = mid;
else
rang[1] = mid;
isEvenBit = !isEvenBit;
}
}
var longitude = (longitudeRange[0] + longitudeRange[1]) / 2;
var latitude = (latitudeRange[0] + latitudeRange[1]) / 2;
return (longitude, latitude);
}
#endregion
}
|