GeoHash算法与用法

浅浅的花香味﹌ 2023-02-10 05:12 123阅读 0赞
  1. /**
  2. * GeoHash算法
  3. *
  4. * 可以到 http://geohash.co/ 进行geohash编码,以确定自己代码是否写错
  5. *
  6. * @Description GeoHash字符串编码越长,表示的范围越小,位置也越精确。
  7. * 因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。
  8. * @author xrj
  9. * @date 2020/4/15
  10. */
  11. @Slf4j
  12. public class GeoHash {
  13. private static final double MINLAT = -90;
  14. private static final double MAXLAT = 90;
  15. private static final double MINLNG = -180;
  16. private static final double MAXLNG = 180;
  17. private final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
  18. '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
  19. 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
  20. //定义编码映射关系
  21. private final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
  22. //初始化编码映射内容
  23. static {
  24. int i = 0;
  25. for (char c : digits)
  26. lookup.put(c, i++);
  27. }
  28. /**
  29. * 范围最小
  30. */
  31. private static final int SCOPE_MIX=6;
  32. private static final int MULTIPLE=5;
  33. /**
  34. * 范围最小是3 1.2km
  35. * 范围一般是2 39.1km
  36. * 范围最大是1 5000km
  37. * current_numbits=数字*5
  38. * current_numbits=SCOPE_MIX*MULTIPLE
  39. */
  40. private int current_numbits ;
  41. //最小单位
  42. private double minLat;
  43. private double minLng;
  44. /**
  45. * 私有化构造器
  46. */
  47. private GeoHash(){
  48. }
  49. /**
  50. * 模糊匹配
  51. * 必须赋值经纬度单独编码长度,否则报错
  52. * @param numbits 整数范围1-3;包含1和3
  53. * 范围最小是3 1.2km
  54. * 范围一般是2 39.1km
  55. * 范围最大是1 5000km
  56. */
  57. private GeoHash(Integer numbits){
  58. if(numbits!=null && numbits>0 && numbits<4){
  59. this.current_numbits=numbits*MULTIPLE;
  60. setMinLatLng();
  61. }else{
  62. throw new BaseException(Constant.BUSINESS,ResultEnum.GEOHASH_FAILED);
  63. }
  64. }
  65. /**
  66. * 得到了最小单位,那么周边区域的经纬度也可以计算得到了。比如说左边区域的经度肯定是自身经度减去最小经度单位。
  67. * 纬度也可以通过加减,得到上下的纬度值,最终周围8个单位也可以计算得到。
  68. */
  69. private void setMinLatLng() {
  70. minLat = MAXLAT - MINLAT;
  71. for (int i = 0; i < current_numbits; i++) {
  72. minLat /= 2.0;
  73. }
  74. minLng = MAXLNG - MINLNG;
  75. for (int i = 0; i < current_numbits; i++) {
  76. minLng /= 2.0;
  77. }
  78. }
  79. /**
  80. * 企业,项目,消纳站得到geoHash参数
  81. * @param lat 纬度
  82. * @param lon 经度
  83. * @return
  84. */
  85. public static String getGeoHashStr(Double lat, Double lon){
  86. validatorParam(lon,lat);
  87. return new GeoHash(SCOPE_MIX).encode(lat,lon);
  88. }
  89. /**
  90. * 得到模糊查询的geohash字符串
  91. * 范围最小是3 1.2km
  92. * 范围一般是2 39.1km
  93. * 范围最大是1 5000km
  94. * @param lat 纬度
  95. * @param lon 经度
  96. * @return
  97. */
  98. public static String getGeoHashByLikeStr(Integer scope,Double lat, Double lon){
  99. validatorParam(lon,lat);
  100. return new GeoHash(scope).encode(lat,lon);
  101. }
  102. /**
  103. * 得到附近八个点
  104. * 范围最小是3 1.2km
  105. * 范围一般是2 39.1km
  106. * 范围最大是1 5000km
  107. * @param lat 纬度
  108. * @param lon 经度
  109. * @return
  110. */
  111. public static ArrayList<String> getArroundGeoHashByLikeStr(Integer scope,Double lat, Double lon){
  112. validatorParam(lon,lat);
  113. return new GeoHash(scope).getArroundGeoHash(lat,lon);
  114. }
  115. /**
  116. * 对geoHashStr接码得到经纬度
  117. * @param geoHashStr
  118. * @return
  119. */
  120. public static double[] getDecodeByGeoHashStr(String geoHashStr){
  121. return new GeoHash(SCOPE_MIX).decode(geoHashStr);
  122. }
  123. private static void validatorParam(Double lon,Double lat){
  124. if(lon == null || lon==0 || lat==null || lat==0){
  125. //抛异常
  126. }
  127. }
  128. /**
  129. * 得到GeoHash字符串
  130. * @param lat 纬度
  131. * @param lon 经度
  132. * @return
  133. */
  134. private String encode(double lat, double lon) {
  135. BitSet latbits = getBits(lat, MINLAT, MAXLAT);
  136. BitSet lonbits = getBits(lon, MINLNG, MAXLNG);
  137. StringBuilder buffer = new StringBuilder();
  138. for (int i = 0; i < current_numbits; i++) {
  139. buffer.append( (lonbits.get(i))?'1':'0');
  140. buffer.append( (latbits.get(i))?'1':'0');
  141. }
  142. String code = base32(Long.parseLong(buffer.toString(), 2));
  143. log.info( "encode lat = " + lat + " lng = " + lon + " code = " + code);
  144. return code;
  145. }
  146. /**
  147. * 根据经纬度和范围,获取对应的二进制
  148. * @param lat
  149. * @param floor
  150. * @param ceiling
  151. * @return
  152. */
  153. private BitSet getBits(double lat, double floor, double ceiling) {
  154. BitSet buffer = new BitSet(current_numbits);
  155. for (int i = 0; i < current_numbits; i++) {
  156. double mid = (floor + ceiling) / 2;
  157. if (lat >= mid) {
  158. buffer.set(i);
  159. floor = mid;
  160. } else {
  161. ceiling = mid;
  162. }
  163. }
  164. return buffer;
  165. }
  166. /**
  167. * 将经纬度合并后的二进制进行指定的32位编码
  168. * @param i
  169. * @return
  170. */
  171. private String base32(long i) {
  172. char[] buf = new char[65];
  173. int charPos = 64;
  174. boolean negative = (i < 0);
  175. if (!negative){
  176. i = -i;
  177. }
  178. while (i <= -32) {
  179. buf[charPos--] = digits[(int) (-(i % 32))];
  180. i /= 32;
  181. }
  182. buf[charPos] = digits[(int) (-i)];
  183. if (negative){
  184. buf[--charPos] = '-';
  185. }
  186. return new String(buf, charPos, (65 - charPos));
  187. }
  188. /**
  189. * 对编码后的字符串解码得到经纬度
  190. * @param geohash
  191. * @return
  192. */
  193. private double[] decode(String geohash) {
  194. StringBuilder buffer = new StringBuilder();
  195. for (char c : geohash.toCharArray()) {
  196. int i = lookup.get(c) + 32;
  197. buffer.append( Integer.toString(i, 2).substring(1) );
  198. }
  199. BitSet lonset = new BitSet();
  200. BitSet latset = new BitSet();
  201. //偶数位,经度
  202. int j =0;
  203. for (int i=0; i< current_numbits*2;i+=2) {
  204. boolean isSet = false;
  205. if ( i < buffer.length() )
  206. isSet = buffer.charAt(i) == '1';
  207. lonset.set(j++, isSet);
  208. }
  209. //奇数位,纬度
  210. j=0;
  211. for (int i=1; i< current_numbits*2;i+=2) {
  212. boolean isSet = false;
  213. if ( i < buffer.length() )
  214. isSet = buffer.charAt(i) == '1';
  215. latset.set(j++, isSet);
  216. }
  217. double lon = decode(lonset, -180, 180);
  218. double lat = decode(latset, -90, 90);
  219. return new double[] {lat, lon};
  220. }
  221. /**
  222. * 根据二进制和范围解码
  223. * @param bs
  224. * @param floor
  225. * @param ceiling
  226. * @return
  227. */
  228. private double decode(BitSet bs, double floor, double ceiling) {
  229. double mid = 0;
  230. for (int i=0; i<bs.length(); i++) {
  231. mid = (floor + ceiling) / 2;
  232. if (bs.get(i))
  233. floor = mid;
  234. else
  235. ceiling = mid;
  236. }
  237. return mid;
  238. }
  239. /**
  240. * 得到附近八个点
  241. * @param lat
  242. * @param lon
  243. * @return
  244. */
  245. private ArrayList<String> getArroundGeoHash(double lat, double lon){
  246. log.info("getArroundGeoHash lat = " + lat + " lng = " + lon);
  247. ArrayList<String> list = new ArrayList<>();
  248. double uplat = lat + minLat;
  249. double downLat = lat - minLat;
  250. double leftlng = lon - minLng;
  251. double rightLng = lon + minLng;
  252. String leftUp = encode(uplat, leftlng);
  253. list.add(leftUp);
  254. String leftMid = encode(lat, leftlng);
  255. list.add(leftMid);
  256. String leftDown = encode(downLat, leftlng);
  257. list.add(leftDown);
  258. String midUp = encode(uplat, lon);
  259. list.add(midUp);
  260. String midMid = encode(lat, lon);
  261. list.add(midMid);
  262. String midDown = encode(downLat, lon);
  263. list.add(midDown);
  264. String rightUp = encode(uplat, rightLng);
  265. list.add(rightUp);
  266. String rightMid = encode(lat, rightLng);
  267. list.add(rightMid);
  268. String rightDown = encode(downLat, rightLng);
  269. list.add(rightDown);
  270. log.info("getArroundGeoHash list = " + list.toString());
  271. return list;
  272. }
  273. public static void main(String[] args) throws Exception{
  274. //得到建筑物当前矩形点位
  275. String s = getGeoHashStr(40.222012, 116.248283);
  276. System.out.println("字符串:"+s);
  277. //得到范围比较大的点位
  278. // System.out.println(getGeoHashByLikeStr(3,40.222012, 116.248283));
  279. // //查询最近的点
  280. // getArroundGeoHashByLikeStr(SCOPE_MIX,40.222012, 116.248283);
  281. // getArroundGeoHashByLikeStr(5,40.222012, 116.248283);
  282. // //得到经纬度
  283. double[] geo = getDecodeByGeoHashStr(s);
  284. System.out.println(geo[0]+" "+geo[1]);
  285. }
  286. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BlcnNpc3RlbmNlZ29pbmc_size_16_color_FFFFFF_t_70

发表评论

表情:
评论列表 (有 0 条评论,123人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GeoHash

    1、geohash及其性质 一种空间索引技术。 (1)将二维的经纬度位置数据转换为一维的字符串(基本上hash族的算法都是这样); 其优点在于hash编码后的字符串,可以

    相关 geohash算法原理

    1.geohash算法实现的功能: 在很多应用中会用到查询附近的人的功能,具体实现就是根据自己所在地点的经纬度与别人的经纬度做计算。如果在某个范围内则取出显示,但是经纬度是