PG数据库安装扩展
需要用到pg数据库的空间扩展postgis,在进行操作之前需要在数据库中安装扩展。- CREATE EXTENSION postgis;
- CREATE EXTENSION postgis_topology;
- 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中定义
- @Type(type="jts_geometry")
- @Column(name="geometry",columnDefinition = "geometry(Point,4326)")
- @JsonIgnore
- private Geometry geometry; // 实体类的Geometry字段
复制代码 根据经纬度计算 geometry 和 geoHash
Java生成geometry和geoHash
geometry字段 和 geoHash字段均可以在java代码中根据经纬度生成。
根据经纬度生成geometry
使用org.locationtech.jts.io包下的WKTReader类,可以根据经纬度生成Geometry对象。- String wkt = "POINT("+longitude+" "+latitude+")"; // longitude 经度,latitude纬度
- WKTReader wktReader = new WKTReader();
- Geometry geometry = wktReader.read(wkt); // Geometry对象
- if(geometry!=null) {
- geometry.setSRID(4326);
- }
复制代码 根据经纬度生成geoHash- import org.apache.commons.lang3.StringUtils;
- import org.springframework.stereotype.Component;
- import java.util.ArrayList;
- import java.util.List;
- @Component
- public class GeoHashUtil {
- public final double Max_Lat = 90;
- public final double Min_Lat = -90;
- public final double Max_Lng = 180;
- public final double Min_Lng = -180;
- private final String[] base32Lookup = {
- "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"
- };
- /**
- * 根据geoHash串获取中心点经纬度
- * @param geoHashCode
- * @return lng->x lat->y
- */
- public double[] getSpaceCoordinate(String geoHashCode) {
- if(StringUtils.isBlank(geoHashCode)){
- return new double[2];
- }
- List<Integer> list = base32Decode(geoHashCode);
- String str = convertToIndex(list);
- GeoHashPoint geoHashPoint = splitLatAndLng(str);
- double y = revert(Min_Lat, Max_Lat, geoHashPoint.getLatList());
- double x = revert(Min_Lng, Max_Lng, geoHashPoint.getLngList());
- return new double[]{x, y};
- }
- /**
- * 根据精度获取GeoHash串
- * @param lng 经度 x
- * @param lat 纬度 y
- * @param precise 精度
- * @return
- */
- public String getGeoHash( double lng, double lat, int precise) {
- // 纬度二值串长度
- int latLength;
- // 经度二值串长度
- int lngLength;
- if (precise < 1 || precise > 12) {
- precise = 12;
- }
- latLength = (precise * 5) / 2;
- if (precise % 2 == 0) {
- lngLength = latLength;
- } else {
- lngLength = latLength + 1;
- }
- return encode(lat, lng, latLength, lngLength);
- }
- /**
- * 经纬度二值串合并:偶数位放经度,奇数位放纬度,把2串编码组合生成新串
- *
- */
- public String encode(double lat, double lng, int latLength, int lngLength) {
- if (latLength < 1 || lngLength < 1) {
- return StringUtils.EMPTY;
- }
- List<Character> latList = new ArrayList<>(latLength);
- List<Character> lngList = new ArrayList<>(lngLength);
- // 获取维度二值串
- convert(Min_Lat, Max_Lat, lat, latLength, latList);
- // 获取经度二值串
- convert(Min_Lng, Max_Lng, lng, lngLength, lngList);
- StringBuilder sb = new StringBuilder();
- for (int index = 0; index < latList.size(); index++) {
- sb.append(lngList.get(index)).append(latList.get(index));
- }
- // 如果二者长度不一样,说明要求的精度为奇数,经度长度比纬度长度大1
- if (lngLength != latLength) {
- sb.append(lngList.get(lngList.size() - 1));
- }
- return base32Encode(sb.toString());
- }
- /**
- * 将合并的二值串转为base32串
- *
- * @param str 合并的二值串
- * @return base32串
- */
- private String base32Encode(final String str) {
- String unit = "";
- StringBuilder sb = new StringBuilder();
- for (int start = 0; start < str.length(); start = start + 5) {
- unit = str.substring(start, start + 5);
- sb.append(base32Lookup[convertToIndex(unit)]);
- }
- return sb.toString();
- }
- /**
- * 每五个一组将二进制转为十进制
- *
- * @param str 五个为一个unit
- * @return 十进制数
- */
- private int convertToIndex(String str) {
- int length = str.length();
- int result = 0;
- for (int index = 0; index < length; index++) {
- result += str.charAt(index) == '0' ? 0 : 1 << (length - 1 - index);
- }
- return result;
- }
- private void convert(double min, double max, double value, int count, List<Character> list) {
- if (list.size() > (count - 1)) {
- return;
- }
- double mid = (max + min) / 2;
- if (value < mid) {
- list.add('0');
- convert(min, mid, value, count, list);
- } else {
- list.add('1');
- convert(mid, max, value, count, list);
- }
- }
- /**
- * 将二值串转换为经纬度值
- *
- * @param min 区间最小值
- * @param max 区间最大值
- * @param list 二值串列表
- */
- private double revert(double min, double max, List<String> list) {
- double value = 0;
- double mid;
- if (list.size() <= 0) {
- return (max + min) / 2.0;
- }
- for (String flag : list) {
- mid = (max + min) / 2;
- if ("0".equals(flag)) {
- max = mid;
- }
- if ("1".equals(flag)) {
- min = mid;
- }
- value = (max + min) / 2;
- }
- return Double.parseDouble(String.format("%.6f", value));
- }
- /**
- * 分离经度与纬度串
- *
- * @param latAndLngStr 经纬度二值串
- */
- private GeoHashPoint splitLatAndLng(String latAndLngStr) {
- GeoHashPoint geoHashPoint = new GeoHashPoint();
- // 纬度二值串
- List<String> latList = new ArrayList<>();
- // 经度二值串
- List<String> lngList = new ArrayList<>();
- for (int i = 0; i < latAndLngStr.length(); i++) {
- // 奇数位,纬度
- if (i % 2 == 1) {
- latList.add(String.valueOf(latAndLngStr.charAt(i)));
- } else {
- // 偶数位,经度
- lngList.add(String.valueOf(latAndLngStr.charAt(i)));
- }
- }
- geoHashPoint.setLatList(latList);
- geoHashPoint.setLngList(lngList);
- return geoHashPoint;
- }
- /**
- * 将十进制数转为五个二进制数
- *
- * @param nums 十进制数
- * @return 五个二进制数
- */
- private String convertToIndex(List<Integer> nums) {
- StringBuilder str = new StringBuilder();
- for (Integer num : nums) {
- StringBuilder sb = new StringBuilder(Integer.toBinaryString(num));
- int length = sb.length();
- if (length < 5) {
- for (int i = 0; i < 5 - length; i++) {
- sb.insert(0, "0");
- }
- }
- str.append(sb);
- }
- return str.toString();
- }
- /**
- * 将base32串转为合并的二值串
- *
- * @param str base32串
- * @return 合并的二值串
- */
- private List<Integer> base32Decode(String str) {
- List<Integer> list = new ArrayList<>();
- for (int i = 0; i < str.length(); i++) {
- String ch = String.valueOf(str.charAt(i));
- for (int j = 0; j < base32Lookup.length; j++) {
- if (base32Lookup[j].equals(ch)) {
- list.add(j);
- }
- }
- }
- return list;
- }
- public static class GeoHashPoint{
- /**
- * 纬度二值串
- */
- private List<String> latList;
- /**
- * 经度二值串
- */
- private List<String> lngList;
- public List<String> getLatList() {
- return latList;
- }
- public void setLatList(List<String> latList) {
- this.latList = latList;
- }
- public List<String> getLngList() {
- return lngList;
- }
- public void setLngList(List<String> lngList) {
- this.lngList = lngList;
- }
- }
- public static void main(String[] args) {
- GeoHashUtil geoHashUtil = new GeoHashUtil();
- // 根据精度获取GeoHash串
- String geoHash = geoHashUtil.getGeoHash( 120.234133,30.402616, 12);
- System.out.println(geoHash);
- // 根据geoHash串获取中心点经纬度
- double[] spaceCoordinate = geoHashUtil.getSpaceCoordinate(geoHash);
- System.out.println(spaceCoordinate[0]+","+spaceCoordinate[1]);
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |