当前位置:网站首页>"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
边栏推荐
猜你喜欢
2022年5月20日最全摸鱼游戏导航
未来小间距竞争的着力点在哪里
The soul asks: How does MySQL solve phantom reads?
LeetCode50天刷题计划(Day 8—— 盛最多水的容器(23.00-1.20)
HTB-Mirai
JSON数据转换总结(VIP典藏版)
Inflation continues, Kenya's food security a concern
有限合伙人与普通合伙人的区别
The role of the final keyword final and basic types, reference types
hzero-resource秒退
随机推荐
HDU 2602: Bone Collector ← 0-1背包问题
docker部署mysql并修改其占用内存大小
表白代码vbs不同意无法关闭(vbs表白代码不同意按键会跑)
Wovent Bio IPO: Annual revenue of 480 million pension fund is a shareholder
LeetCode50天刷题计划(Day 6—— 整数反转 14.20-15.20)
leetcode:33. 搜索旋转排序数组
信息录入率百分百上海强化施工现场建筑工人实名制管理
pytorch中tensor转成图片保存
xmind2testcase:高效的测试用例导出工具
五分钟带你上手ShardingJDBC实现MySQL分库分表
Stored procedures in MySQL (detailed)
SQL查询数据以及排序
游戏元宇宙发展趋势展望分析
Performance Optimization - Animation Optimization Notes
MySQL:索引
Grid布局 容器属性(一) `grid-template`系列属性
给网站增加离开页面改变网站标题效果
DHCP配置命令(DHCP配置命令)
wordpress模板函数说明备注整理收藏
选择合适的 DevOps 工具,从理解 DevOps 开始