PostgreSQL+GeoHash地图点位聚合

打印 上一主题 下一主题

主题 845|帖子 845|积分 2535

PG数据库安装扩展

需要用到pg数据库的空间扩展postgis,在进行操作之前需要在数据库中安装扩展。
  1. CREATE EXTENSION postgis;
  2. CREATE EXTENSION postgis_topology;
  3. CREATE EXTENSION postgis_geohash;
复制代码
GeoHash

GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。具体原理这里不再详细说明,GeoHash算法大体上分为三步:

  • 将经纬度变成二进制
  • 将经纬度的二进制合并
  • 通过Base32对合并后的二进制进行编码
Geohash比直接用经纬度的高效很多,而且使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

  • GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引(某些情况下无法在两列上同时应用索引)
  • GeoHash表示的并不是一个点,而是一个矩形区域
  • GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索
  • 编码越长,表示的范围越小,位置也越精确。因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离
建表

在创建数据库表时,表中除了经纬度字段以外,再创建两个字段:
① 经纬度对应的Geometry字段(类型:geometry)
② 经纬度对应的geoHash值字段(类型:varchar)
如:alter table 表名 add 字段名 geometry(point, 4326); // 创建geometry字段
alter table 表名 add 字段名 varchar; // 创建geoHash字段
JPA中定义
  1. @Type(type="jts_geometry")
  2. @Column(name="geometry",columnDefinition = "geometry(Point,4326)")
  3. @JsonIgnore
  4. private Geometry geometry; // 实体类的Geometry字段
复制代码
根据经纬度计算 geometry 和 geoHash

Java生成geometry和geoHash

geometry字段 和 geoHash字段均可以在java代码中根据经纬度生成。
根据经纬度生成geometry
使用org.locationtech.jts.io包下的WKTReader类,可以根据经纬度生成Geometry对象。
  1. String wkt = "POINT("+longitude+" "+latitude+")"; // longitude 经度,latitude纬度
  2. WKTReader wktReader = new WKTReader();
  3. Geometry geometry = wktReader.read(wkt); // Geometry对象
  4. if(geometry!=null) {
  5.     geometry.setSRID(4326);
  6. }
复制代码
根据经纬度生成geoHash
  1. import org.apache.commons.lang3.StringUtils;
  2. import org.springframework.stereotype.Component;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. @Component
  6. public class GeoHashUtil {
  7.     public final double Max_Lat = 90;
  8.     public final double Min_Lat = -90;
  9.     public final double Max_Lng = 180;
  10.     public final double Min_Lng = -180;
  11.     private final String[] base32Lookup = {
  12.             "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "b", "c", "d", "e", "f", "g", "h", "j", "k",
  13.             "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
  14.     };
  15.     /**
  16.      * 根据geoHash串获取中心点经纬度
  17.      * @param geoHashCode
  18.      * @return  lng->x  lat->y
  19.      */
  20.     public double[] getSpaceCoordinate(String geoHashCode) {
  21.         if(StringUtils.isBlank(geoHashCode)){
  22.             return new double[2];
  23.         }
  24.         List<Integer> list = base32Decode(geoHashCode);
  25.         String str = convertToIndex(list);
  26.         GeoHashPoint geoHashPoint = splitLatAndLng(str);
  27.         double y = revert(Min_Lat, Max_Lat, geoHashPoint.getLatList());
  28.         double x = revert(Min_Lng, Max_Lng, geoHashPoint.getLngList());
  29.         return new double[]{x, y};
  30.     }
  31.     /**
  32.      * 根据精度获取GeoHash串
  33.      * @param lng 经度 x
  34.      * @param lat 纬度 y
  35.      * @param precise 精度
  36.      * @return
  37.      */
  38.     public String getGeoHash( double lng, double lat, int precise) {
  39.         // 纬度二值串长度
  40.         int latLength;
  41.         //  经度二值串长度
  42.         int lngLength;
  43.         if (precise < 1 || precise > 12) {
  44.             precise = 12;
  45.         }
  46.          latLength = (precise * 5) / 2;
  47.         if (precise % 2 == 0) {
  48.             lngLength = latLength;
  49.         } else {
  50.             lngLength = latLength + 1;
  51.         }
  52.         return encode(lat, lng, latLength, lngLength);
  53.     }
  54.     /**
  55.      * 经纬度二值串合并:偶数位放经度,奇数位放纬度,把2串编码组合生成新串
  56.      *
  57.      */
  58.     public String encode(double lat, double lng, int latLength, int lngLength) {
  59.         if (latLength < 1 || lngLength < 1) {
  60.             return StringUtils.EMPTY;
  61.         }
  62.         List<Character> latList = new ArrayList<>(latLength);
  63.         List<Character> lngList = new ArrayList<>(lngLength);
  64.         // 获取维度二值串
  65.         convert(Min_Lat, Max_Lat, lat, latLength, latList);
  66.         // 获取经度二值串
  67.         convert(Min_Lng, Max_Lng, lng, lngLength, lngList);
  68.         StringBuilder sb = new StringBuilder();
  69.         for (int index = 0; index < latList.size(); index++) {
  70.             sb.append(lngList.get(index)).append(latList.get(index));
  71.         }
  72. //        如果二者长度不一样,说明要求的精度为奇数,经度长度比纬度长度大1
  73.         if (lngLength != latLength) {
  74.             sb.append(lngList.get(lngList.size() - 1));
  75.         }
  76.         return base32Encode(sb.toString());
  77.     }
  78.     /**
  79.      * 将合并的二值串转为base32串
  80.      *
  81.      * @param str 合并的二值串
  82.      * @return base32串
  83.      */
  84.     private String base32Encode(final String str) {
  85.         String unit = "";
  86.         StringBuilder sb = new StringBuilder();
  87.         for (int start = 0; start < str.length(); start = start + 5) {
  88.             unit = str.substring(start, start + 5);
  89.             sb.append(base32Lookup[convertToIndex(unit)]);
  90.         }
  91.         return sb.toString();
  92.     }
  93.     /**
  94.      * 每五个一组将二进制转为十进制
  95.      *
  96.      * @param str 五个为一个unit
  97.      * @return 十进制数
  98.      */
  99.     private int convertToIndex(String str) {
  100.         int length = str.length();
  101.         int result = 0;
  102.         for (int index = 0; index < length; index++) {
  103.             result += str.charAt(index) == '0' ? 0 : 1 << (length - 1 - index);
  104.         }
  105.         return result;
  106.     }
  107.     private void convert(double min, double max, double value, int count, List<Character> list) {
  108.         if (list.size() > (count - 1)) {
  109.             return;
  110.         }
  111.         double mid = (max + min) / 2;
  112.         if (value < mid) {
  113.             list.add('0');
  114.             convert(min, mid, value, count, list);
  115.         } else {
  116.             list.add('1');
  117.             convert(mid, max, value, count, list);
  118.         }
  119.     }
  120.     /**
  121.      * 将二值串转换为经纬度值
  122.      *
  123.      * @param min  区间最小值
  124.      * @param max  区间最大值
  125.      * @param list 二值串列表
  126.      */
  127.     private double revert(double min, double max, List<String> list) {
  128.         double value = 0;
  129.         double mid;
  130.         if (list.size() <= 0) {
  131.             return (max + min) / 2.0;
  132.         }
  133.         for (String flag : list) {
  134.             mid = (max + min) / 2;
  135.             if ("0".equals(flag)) {
  136.                 max = mid;
  137.             }
  138.             if ("1".equals(flag)) {
  139.                 min = mid;
  140.             }
  141.             value = (max + min) / 2;
  142.         }
  143.         return Double.parseDouble(String.format("%.6f", value));
  144.     }
  145.     /**
  146.      * 分离经度与纬度串
  147.      *
  148.      * @param latAndLngStr 经纬度二值串
  149.      */
  150.     private GeoHashPoint splitLatAndLng(String latAndLngStr) {
  151.         GeoHashPoint geoHashPoint = new GeoHashPoint();
  152.         // 纬度二值串
  153.         List<String> latList = new ArrayList<>();
  154.        // 经度二值串
  155.         List<String> lngList = new ArrayList<>();
  156.         for (int i = 0; i < latAndLngStr.length(); i++) {
  157. //            奇数位,纬度
  158.             if (i % 2 == 1) {
  159.                 latList.add(String.valueOf(latAndLngStr.charAt(i)));
  160.             } else {
  161. //                偶数位,经度
  162.                 lngList.add(String.valueOf(latAndLngStr.charAt(i)));
  163.             }
  164.         }
  165.         geoHashPoint.setLatList(latList);
  166.         geoHashPoint.setLngList(lngList);
  167.         return geoHashPoint;
  168.     }
  169.     /**
  170.      * 将十进制数转为五个二进制数
  171.      *
  172.      * @param nums 十进制数
  173.      * @return 五个二进制数
  174.      */
  175.     private String convertToIndex(List<Integer> nums) {
  176.         StringBuilder str = new StringBuilder();
  177.         for (Integer num : nums) {
  178.             StringBuilder sb = new StringBuilder(Integer.toBinaryString(num));
  179.             int length = sb.length();
  180.             if (length < 5) {
  181.                 for (int i = 0; i < 5 - length; i++) {
  182.                     sb.insert(0, "0");
  183.                 }
  184.             }
  185.             str.append(sb);
  186.         }
  187.         return str.toString();
  188.     }
  189.     /**
  190.      * 将base32串转为合并的二值串
  191.      *
  192.      * @param str base32串
  193.      * @return 合并的二值串
  194.      */
  195.     private List<Integer> base32Decode(String str) {
  196.         List<Integer> list = new ArrayList<>();
  197.         for (int i = 0; i < str.length(); i++) {
  198.             String ch = String.valueOf(str.charAt(i));
  199.             for (int j = 0; j < base32Lookup.length; j++) {
  200.                 if (base32Lookup[j].equals(ch)) {
  201.                     list.add(j);
  202.                 }
  203.             }
  204.         }
  205.         return list;
  206.     }
  207.     public static class GeoHashPoint{
  208.         /**
  209.          * 纬度二值串
  210.          */
  211.         private List<String> latList;
  212.         /**
  213.          * 经度二值串
  214.          */
  215.         private  List<String> lngList;
  216.         public List<String> getLatList() {
  217.             return latList;
  218.         }
  219.         public void setLatList(List<String> latList) {
  220.             this.latList = latList;
  221.         }
  222.         public List<String> getLngList() {
  223.             return lngList;
  224.         }
  225.         public void setLngList(List<String> lngList) {
  226.             this.lngList = lngList;
  227.         }
  228.     }
  229.     public static void main(String[] args) {
  230.         GeoHashUtil geoHashUtil = new GeoHashUtil();
  231.         // 根据精度获取GeoHash串
  232.         String geoHash = geoHashUtil.getGeoHash( 120.234133,30.402616, 12);
  233.         System.out.println(geoHash);
  234.         // 根据geoHash串获取中心点经纬度
  235.         double[] spaceCoordinate = geoHashUtil.getSpaceCoordinate(geoHash);
  236.         System.out.println(spaceCoordinate[0]+","+spaceCoordinate[1]);
  237.     }
  238. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表