当前位置:网站首页>"Find nearby shops" | Geohash+MySQL realizes geographic location filtering
"Find nearby shops" | Geohash+MySQL realizes geographic location filtering
2022-08-01 15:01:00 【InfoQ】
一、背景

二、方案一:MySQL使⽤GEOMETRY/POINTA field of type stores the geographic location




INSERT INTO t1 (pt_col) VALUES(Point(1,2));
/*应用中使用的SQL为 */
update `t_table` set `geom`=?
/* 在MyBatis-Plus Interceptor 把上面的SQL改写成 */
update `t_table` set `geom`=geomfromtext(?)
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author shishi on 21/3/22
*/
public class GeoPointHandler extends BaseTypeHandler<GeoPoint> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, GeoPoint geoPoint, JdbcType jdbcType) throws SQLException {
ps.setBytes(i, Converter.geoPointToBytes(geoPoint));
}
/**
* Gets the nullable result.
*
* @param rs the rs
* @param columnName Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the nullable result
* @throws SQLException the SQL exception
*/
@Override
public GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {
return Converter.geoPointFromBytes(rs.getBytes(columnName));
}
@Override
public GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return Converter.geoPointFromBytes(rs.getBytes(columnIndex));
}
@Override
public GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return Converter.geoPointFromBytes(cs.getBytes(columnIndex));
}
}
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import org.locationtech.jts.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
public class Converter {
private static final GeometryFactory GEOMETRY_FACTORY =
new GeometryFactory(new PrecisionModel(), 0, CoordinateArraySequenceFactory.instance());
private Converter() {
}
/**
* 通过jts,读取数据库中的geometryobject and converted toGeoPoint
*
* @param bytes Raw stream in database
* @return GeoPoint对象
*/
public static GeoPoint geoPointFromBytes(byte[] bytes) {
if (bytes == null) {
return null;
}
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
byte[] sridBytes = new byte[4];
inputStream.read(sridBytes);
int srid = ByteOrderValues.getInt(sridBytes, ByteOrderValues.LITTLE_ENDIAN);
GeometryFactory geometryFactory = GEOMETRY_FACTORY;
if (srid != Constants.SPATIAL_REFERENCE_ID) {
log.error("SRID different between database and application, db:{}, app:{}", srid, Constants.SPATIAL_REFERENCE_ID);
geometryFactory = new GeometryFactory(new PrecisionModel(), srid, CoordinateArraySequenceFactory.instance());
}
WKBReader wkbReader = new WKBReader(geometryFactory);
Geometry geometry = wkbReader.read(new InputStreamInStream(inputStream));
Point point = (Point) geometry;
return new GeoPoint(point.getX(), point.getY());
} catch (Exception e) {
log.error("failed when reading points from database.", e);
return null;
}
}
/**
* 通过jts,将GeoPointObjects are converted into data streams that the database can recognize
*
* @param geoPoint 原始GeoPoint对象
* @return mybatis可识别的byte[]
*/
public static byte[] geoPointToBytes(GeoPoint geoPoint) {
if (geoPoint == null) {
return new byte[0];
}
CoordinateArraySequence arraySequence = new CoordinateArraySequence(
new Coordinate[]{new Coordinate(geoPoint.getX(), geoPoint.getY())}, 2);
Point point = new Point(arraySequence, GEOMETRY_FACTORY);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] sridBytes = new byte[4];
ByteOrderValues.putInt(point.getSRID(), sridBytes, ByteOrderValues.LITTLE_ENDIAN);
outputStream.write(sridBytes);
WKBWriter wkbWriter = new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN);
wkbWriter.write(point, new OutputStreamOutStream(outputStream));
return outputStream.toByteArray();
} catch (Exception e) {
log.error("failed when writing points to database.", e);
return new byte[0];
}
}
}


三、方案二:⾃⼰实现Geohash
st_Geohash(`geom`, i)


import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
public class GeohashUtils {
/**
* 一共八位,每位通过base32编码,实际相当于40位,Latitude and longitude20位
*/
private static final int GEO_HASH_MAX_LENGTH = 8 * 5;
private static final char[] BASE32_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'};
private static final HashMap<Character, Integer> BASE32_DIGIT_MAP = new HashMap<>();
static {
int i = 0;
for (char c : BASE32_DIGITS) {
BASE32_DIGIT_MAP.put(c, i++);
}
}
private GeohashUtils() {}
/**
*
* @param GeohashPartial Target matching segment.That is the grid in the middle
* @return 供sqlThe match segment to use.i.e. all nine squares
*/
public static List<String> findNeighborGeohash(String GeohashPartial) {
int accuracyLength = GeohashPartial.length();
// Precision must be even,Otherwise, one more bit is ignored
String middle = ((accuracyLength & 1) == 0)? GeohashPartial: GeohashPartial.substring(0, accuracyLength - 1);
long middleDecode = decodeBase32(middle);
long[] middleCoordinates = ascensionUsingPeano(middleDecode);
List<long[]> allCoordinates = findNeighborCoordinates(middleCoordinates);
return allCoordinates.stream().map(it -> encodeBase32(dimensionUsingPeano(it)) + '%')
.collect(Collectors.toList());
}
/**
* 解码Base32
*/
private static long decodeBase32(String str) {
StringBuilder buffer = new StringBuilder();
for (char c : str.toCharArray()) {
int j = BASE32_DIGIT_MAP.get(c) + 32;
buffer.append(Integer.toString(j, 2).substring(1) );
}
return Long.parseLong(buffer.toString(), 2);
}
/**
* Use the Peano curve to increase the dimension
*/
private static long[] ascensionUsingPeano(long number) {
int i = 0;
long x = 0;
long y = 0;
while (number > 0) {
y += (number & 1) << i;
number >>>= 1;
x += (number & 1) << i;
number >>>= 1;
i++;
}
return new long[]{x, y};
}
/**
* Find eight adjacent squares
*/
private static List<long[]> findNeighborCoordinates(long[] middle) {
List<long[]> result = new ArrayList<>(9);
result.add(new long[]{middle[0] - 1, middle[1] - 1});
result.add(new long[]{middle[0] - 1, middle[1]});
result.add(new long[]{middle[0] - 1, middle[1] + 1});
result.add(new long[]{middle[0], middle[1] - 1});
result.add(middle);
result.add(new long[]{middle[0], middle[1] + 1});
result.add(new long[]{middle[0] + 1, middle[1] - 1});
result.add(new long[]{middle[0] + 1, middle[1]});
result.add(new long[]{middle[0] + 1, middle[1] + 1});
return result;
}
/**
* Dimensionality reduction using Peano curve
*/
private static long dimensionUsingPeano(long[] coordinates) {
int i = 0;
long x = coordinates[0];
long y = coordinates[1];
long result = 0;
while (i < GEO_HASH_MAX_LENGTH) {
result += (y & 1) << (i++);
result += (x & 1) << (i++);
y >>>= 1;
x >>>= 1;
}
return result;
}
/**
* 编码Base32
*/
private static String encodeBase32(long number) {
char[] buf = new char[65];
int charPos = 64;
boolean negative = (number < 0);
if (!negative){
number = -number;
}
while (number <= -32) {
buf[charPos--] = BASE32_DIGITS[(int) (-(number % 32))];
number /= 32;
}
buf[charPos] = BASE32_DIGITS[(int) (-number)];
if (negative){
buf[--charPos] = '-';
}
return new String(buf, charPos, (65 - charPos));
}
/**
* Calculate a coordinateGeohash
*/
public static String getGeohash(double longitude, double latitude) {
BitSet longitudeBits = encodeBits(longitude, -180, 180);
BitSet latitudeBits = encodeBits(latitude, -90, 90);
StringBuilder sb = new StringBuilder();
int singleBits = GEO_HASH_MAX_LENGTH / 2;
for (int i = 0; i < singleBits; i++) {
sb.append((longitudeBits.get(i))? '1': '0');
sb.append((latitudeBits.get(i))? '1': '0');
}
return encodeBase32(Long.parseLong(sb.toString(), 2));
}
/**
* through the given upper and lower bounds,Computes the binary string using the dichotomy method
*/
private static BitSet encodeBits(double number, double floor, double ceiling) {
int singleBits = GEO_HASH_MAX_LENGTH / 2;
BitSet buffer = new BitSet(singleBits);
for (int i = 0; i < singleBits; i++) {
double mid = (floor + ceiling) / 2;
if (number >= mid) {
buffer.set(i);
floor = mid;
} else {
ceiling = mid;
}
}
return buffer;
}
}
四、总结
关于领创集团(Advance Intelligence Group)
往期回顾 BREAK AWAY
边栏推荐
- datetime64[ns] converted to datetime
- 安培龙IPO过会:年营收5亿 同创伟业与中移创新是股东
- ffmpeg视频剪辑中报错Could not write header for output file #0 (incorrect codec parameters ?): ……
- Typora报错:This beta version of Typora is expired
- The problem that the column becomes indexed after pd groupby and the aggregation column has no column name
- 给网站增加离开页面改变网站标题效果
- VIM实用指南(0)基本概念与初次体验
- leetcode:33. 搜索旋转排序数组
- 如何使用 Mashup 技术在 SAP Cloud for Customer 页面嵌入自定义 UI
- Grid布局 容器属性(一) `grid-template`系列属性
猜你喜欢
gconf/dconf实战编程(2)利用gconf库读写配置实战以及诸多配套工具演示
有限合伙人与普通合伙人的区别
开放原子全球开源峰会原圆满结束,openEuler模式得到参会者高度认可
Inflation continues, Kenya's food security a concern
LeetCode50天刷题计划(Day 9—— 整数转罗马数字(20.40-22.10)
设计专业第一台笔记本 华硕灵耀Pro16 2022 新品首发超值入手
[Binary Tree] Path Sum II
会议OA项目(六)--- (待开会议、历史会议、所有会议)
2.8K 120Hz触控双屏加持 灵耀X 双屏Pro 2022让办公无惧想象
MySQL中根据日期进行范围查询
随机推荐
LeetCode50天刷题计划(Day 11—— 最接近的三数之和(8.40-10.00)
2022-08-01 Daily: 18 graphs to intuitively understand neural networks, manifolds and topology
flink -redis sink 可以sink 到集群吗?
兆骑科创科创赛事平台,创业赛事活动路演,线上直播路演
打破文件锁限制,以存储力量助力企业增长新动力
qt 通用ui
【二叉树】路径总和II
VIM实用指南(3)复制,粘贴 ,删除,撤销,重做指令速记
大佬们,datax同步数据,同步过程中要新增一个uuid,请问column 怎么写pgsql,uu
股票预测 lstm(时间序列的预测步骤)
沃文特生物IPO过会:年营收4.8亿 养老基金是股东
Performance Optimization - Animation Optimization Notes
Could not write header for output file #0 (incorrect codec parameters ?): ……
如何使用 Mashup 技术在 SAP Cloud for Customer 页面嵌入自定义 UI
DHCP配置命令(DHCP配置命令)
gconf/dconf编程实战(1)gconf和dconf介绍
输出0-1背包问题的具体方案 ← 利用二维数组
Kernel pwn 入门 (6)
docker部署mysql并修改其占用内存大小
荣信文化通过注册:年营收3.8亿 王艺桦夫妇为实控人