Article Directory
Preface
Recently, there is a demand on the project that needs to store some latitude and longitude information to achieve similar return to points of interest within 5 kilometers (for example: toilets within 5 kilometers around). Taking into account technical proficiency and operation and maintenance costs, the selection is as follows :
-
Persistence layer framework: mybatis-plus
-
Database: Mysql5.7.x
General realization
database
The database creates 2 fields, longitude:, lng
latitude:lat
-
lng: field type decimal(9, 6)
-
lat: field type decimal (9, 6)
Code
class toilet {
String name;
double lng;
double lat
}
I won’t go into details here, it’s a very common way to achieve
Since it is not a spatial data structure, a spatial index cannot be created, and query performance is likely to encounter bottlenecks.
Custom type + spatial data type
MySQL supports spatial data types.
Spatial data types and functions can be used MyISAM
, InnoDB
, NDB
, and ARCHIVE
tables. Used for indexing spatial columns, MyISAM
and InnoDB
supports both SPATIAL
and non- SPATIAL
index. Other storage engines support non- SPATIAL
index
MySQL underlying storage format
Query the mysql official website, the results are as follows
The actual storage format of Geometry is: 25 bytes in length
- 4 bytes for integer SRID (0)
- 1 byte (integer byte order) (1 = little endian)
- 4 bytes for the integer type information (MySQL using values from 1 to 7 to indicate
Point
,LineString
,Polygon
,MultiPoint
,MultiLineString
,MultiPolygon
, andGeometryCollection
.) - 8-byte double-precision X coordinate
- 8-byte double-precision Y coordinate
For example, it POINT(1 -1)
consists of the following 25-byte sequence, each sequence is represented by two hexadecimal digits:
mysql> SET @g = ST_GeomFromText('POINT(1 -1)');
mysql> SELECT LENGTH(@g);
+------------+
| LENGTH(@g) |
+------------+
| 25 |
+------------+
mysql> SELECT HEX(@g);
+----------------------------------------------------+
| HEX(@g) |
+----------------------------------------------------+
| 000000000101000000000000000000F03F000000000000F0BF |
+----------------------------------------------------+
composition | size | value |
---|---|---|
SRID | 4 bytes | 00000000 |
Byte order | 1 byte | 01 |
WKB type | 4 bytes | 01000000 |
X coordinate | 8 bytes | 000000000000F03F |
Y coordinate | 8 bytes | 000000000000F0BF |
database
Create a field in the databasecoordinate
- coordinate: field type point
Code
Entity class
class toilet {
String name;
geopoint location;
}
// 自定义数据类型
class geopoint {
double lng;
double lat
}
GeoPointTypeHandler
@Slf4j
@MappedTypes({
GeoPoint.class})
public class GeoPointTypeHandler extends BaseTypeHandler<GeoPoint> {
/**
* 空间参照标识系 MySQL数据库默认为0
*/
private static int SRID = 0;
/**
* 字节顺序指示符为1或0表示小端或大端存储。小字节序和大字节序分别也称为网络数据表示(NDR)和外部数据表示(XDR)
*/
private static byte ENDIAN = (byte) 1;
@Override
public void setNonNullParameter(PreparedStatement ps, int i, GeoPoint parameter, JdbcType jdbcType) throws SQLException {
ps.setBytes(i, to(parameter));
}
@Override
public GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parse(rs.getBytes(columnName));
}
@Override
public GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parse(rs.getBytes(columnIndex));
}
@Override
public GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parse(cs.getBytes(columnIndex));
}
/**
* bytes转GeoPoint对象
*
* @param bytes
*/
private GeoPoint parse(byte[] bytes) {
ByteBuffer wrap = ByteBuffer.wrap(bytes)
// 小端点排序(Java默认是大端点排序,这里要改下)
.order(ByteOrder.LITTLE_ENDIAN);
int SRID = wrap.getInt();
byte endian = wrap.get();
int wkbType = wrap.getInt();
double x = wrap.getDouble();
double y = wrap.getDouble();
GeoPoint geoPoint = new GeoPoint(x, y);
log.info("geo-point:{}", JSONUtil.toJsonStr(geoPoint));
return geoPoint;
}
/**
* GeoPoint转bytes对象
*
* @param geoPoint
*/
private byte[] to(GeoPoint geoPoint) {
ByteBuffer wrap = ByteBuffer.allocate(25)
// 小端点排序(Java默认是大端点排序,这里要改下)
.order(ByteOrder.LITTLE_ENDIAN);
// SRID: 0
wrap.putInt(SRID);
// 字节顺序指示符为1或0表示小端或大端存储。小字节序和大字节序分别也称为网络数据表示(NDR)和外部数据表示(XDR)
wrap.put(ENDIAN);
// WKB类型是指示几何类型的代码 wkbType: 1 MySQL使用从1个值至7,以表示 Point,LineString, Polygon,MultiPoint, MultiLineString, MultiPolygon,和 GeometryCollection。
wrap.putInt(1);
// X坐标
wrap.putDouble(geoPoint.getLon());
// Y坐标
wrap.putDouble(geoPoint.getLat());
return wrap.array();
}
}
Entity class plus related solution
@TableName(autoResultMap = true) // 注意!! 必须开启映射注解
class toilet {
String name;
@TableField(typeHandler = GeoPointTypeHandler.class)
geopoint location;
}
test
Toilet toilet = new Toilet();
toilet.setName("laker");
toilet.setLocation(new GeoPoint(123.23, 1.2)); // 直接塞实体
toiletService.save(toilet);
// 查询
List<Toilet> toilets = toiletService.list();
...
[{
"name":"laker",location:{
"lng":123.23,"lat":1.2}}]
Calculate the distance
The calculation results have been consistent with the calculation distance api provided by AutoNavi.
Gaode calculate distance webapi
SELECT ( st_distance_sphere ( point ( 116.481028,39.989643 ), point ( 114.465302,40.004717 ), 6378137.0 ) ) AS distance
reference:
- https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html