目录
- 1. MySQL空间数据类型的基本介绍
- 1.1 什么是MySQL空间数据类型
- 1.2 有哪些空间数据类型
- 1.3 支持空间数据类型的引擎
- 1.4 坐标系类型
- 2. 存储坐标系的示例代码
- 2.1 `geomtry`和`point`都可以存储坐标系,有什么区别呢?
- 2.2 创建测试表
- 2.3 新增坐标
- 2.3 计算两地之间的距离
- 2.5 列举距离我5km内的地址
1. MySQL空间数据类型的基本介绍
MySQL空间数据类型官方文档
1.1 什么是MySQL空间数据类型
MySQL空间数据类型是指MySQL数据库中支持的存储地理位置信息的数据类型,包括点、线、多边形等。这些数据类型可以用来存储地图坐标、地址信息等。
MySQL空间数据类型基于OpenGIS空间数据类型标准,提供了对空间数据进行索引和查询的功能,可以方便地实现地图应用、位置搜索等功能。
1.2 有哪些空间数据类型
- GEOMETRY:表示任意类型的几何对象,可以是点、线、面等,不受任何限制。
- POINT:表示一个点。
- LINESTRING:表示一条线。
- POLYGON:表示一个多边形。
- MULTIPOINT:表示多个点的集合。
- MULTILINESTRING:表示多条线的集合。
- MULTIPOLYGON:表示多个多边形的集合。
- GEOMETRYCOLLECTION:表示多个几何对象的集合。
空间数据类型之间存在继承关系、实例化和不可实例化,这些含义在后续的使用中体会下有什么区别。
1.3 支持空间数据类型的引擎
存储引擎 | 空间数据类型及其对应函数 | 空间索引 | 非空间索引 |
---|---|---|---|
InnoDB | Y | Y | Y |
MyISAM | Y | Y | Y |
NDB | Y | N | Y |
ARCHIVE | Y | N | Y |
总而言之,若无其它特殊需求,用InnoDB就好。
1.4 坐标系类型
既然是存储空间数据,那么就有不同的规范和标准。比如说gps、gcj02等,同一个位置,它的经纬度坐标是不一样的。在创建空间索引列的时候,可以通过SRID显示制定空间索引的类型。
SRID是空间参考系统标识符(Spatial Reference System Identifier)的缩写,用于标识空间数据的坐标系。在不同的坐标系中,相同的坐标点可能有不同的坐标值。SRID的存在可以帮助我们正确地对空间数据进行分析和处理。常见的SRID标识符有EPSG、ESRI和SR-ORG等。
中国的坐标绘制是不同于GPS的,也可以理解成中国地图反馈给我们的坐标是gps坐标偏移之后的。由于存储系统是国外的MySQL,自身不支持国内的坐标系,除非寻找相关的插件。比如说微信小程序获取到的坐标是gcj02标准下、百度地图的坐标是bd09,我们可以通过hutool
提供的坐标转换工具类进行转换,将不同标准下坐标统一转换成SRID为4326(GPS坐标
)的坐标存储并计算。
hutool
版本 5.8.10+ cn.hutool.core.util.CoordinateUtil
2. 存储坐标系的示例代码
2.1 geomtry
和point
都可以存储坐标系,有什么区别呢?
GEOMETRY 是通用的几何类型,它可以存储点、线、面等多种类型的空间数据,且可以在不同的坐标系中表示空间数据。在声明空间索引列时,可以使用 GEOMETRY 类型,MySQL 会根据数据的实际类型自动选择对应的索引类型。
POINT 是 GEOMETRY 类型的一种特殊情况,它专门用于存储点类型的空间数据。在声明空间索引列时,如果确定该列只存储点类型的数据,可以使用 POINT 类型,这样可以提高索引的查询效率。
因此,使用 GEOMETRY 类型声明空间索引列可以存储多种类型的空间数据,但可能会降低索引的查询效率;使用 POINT 类型声明空间索引列只能存储点类型的数据,但可以提高索引的查询效率。
2.2 创建测试表
检查数据库是否支持SRID为4326的坐标系系统
SELECT *
FROM INFORMATION_SCHEMA.ST_SPATIAL_REFERENCE_SYSTEMS
WHERE SRS_ID = 4326;
DDL
CREATE TABLE point_test (
ID INT(11) unsigned not null auto_increment comment '主键',
address VARCHAR ( 64 ) NOT NULL comment '名称',
coordinate geometry NOT NULL SRID 4326 comment '位置坐标',
primary key (ID),
spatial index `spi_coordinate` (`coordinate`)
) ENGINE = INNODB charset = utf8;
2.3 新增坐标
工具一:gcj02
坐标系在线拾取
工具二:gcj02
与wgs84
坐标系在线转换
通过工具一获取到三个位置的gcj02
坐标,然后通过工具二转换成wgs84
(SRID=4326)坐标。分别是:
位置名称 | gcj02 | wgs84 |
---|---|---|
上海虹桥机场 | 121.34,31.20 | 121.33543277248731,31.201919080690047 |
上海虹桥火车站 | 121.32,31.19 | 121.31547880904075,31.191962005342543 |
上海野生动物园 | 121.72,31.06 | 121.71564763519164,31.062121772948835 |
insert into point_test(
address, coordinate
) values
('上海虹桥机场', st_geomFromText('point(31.201919080690047 121.33543277248731)', 4326));
查询
新增时point()函数里的坐标是没有用逗号分隔的。
输入时的st_geomFromText(‘point({纬度} {经度})’, 4326),point()里的经纬度顺序变成了纬经度,查询时coordinate的POINT()确实经纬度,这是由于4326下的坐标系在新增时point的顺序是纬经度。可以通过下面的方式更换新增时的顺序。
insert into point_test(
address, coordinate
) values
('上海虹桥火车站', st_geomFromText('point(121.31547880904075 31.191962005342543)', 4326, 'axis-order=long-lat'));
在
st_geomFromText(...)
可通过'axis-order=long-lat'
来调整经纬度顺序。
把剩下一条也新增进来
insert into point_test(
address, coordinate
) values
('上海野生动物园', st_geomFromText('point(121.71564763519164 31.062121772948835)', 4326, 'axis-order=long-lat'));
2.3 计算两地之间的距离
官方函数列表:https://dev.mysql.com/doc/refman/8.0/en/built-in-function-reference.html
st_distance_sphere
返回值单位:米
select st_distance_sphere((select coordinate from point_test where id = 1), (select coordinate from point_test where id = 2))
as distance;
查询结果
2197.2655430832847
意思是虹桥机场
距离虹桥火车站
的球面距离约为2197米,即2.1公里。
这里的球面距离不代表行使总路程,毕竟甚少有路是点对点的,多少要走几个弯路。
执行计划显示查询性能较好
2.5 列举距离我5km内的地址
有两种方式,一是通过st_distance_sphere
计算距离,然后在where条件里面筛选,这样做是不走空间索引的,由于你给空间索引coordinate用了函数。
另一种方式是通过MBRContains
函数,接下来重点介绍该函数。
假设我目前处于虹桥火车站
,即我的坐标是121.31547880904075,31.191962005342543
。
步骤一:利用st_buffer(...)
生成我的坐标为圆心,5000米为半径的圆形区域。
st_buffer
https://dev.mysql.com/doc/refman/8.0/en/spatial-operator-functions.html#function_st-buffer
官方的介绍有点复杂,尤其是第三个参数。
st_buffer
第一个参数是geometry
。当你的geometry的SRID为4326的时候,st_buffer的第二个参数的含义是度
(🤮)。在你以点为圆心,5km为半径生成区域的时候,需要将5m转换为度。计算公式如:距离(米)= 度数 × 111.32千米,那5km对应的度数=5000/11132。(不算了,具体让SQL自己去算,省的有放大误差)。
SELECT ST_Buffer(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)'), 5000 / 111320);
MBContaine和St_containe的区别?
MBRContains和ST_Contains是MySQL中用于空间查询的两个函数,它们的功能类似,但存在一些区别。
MBRContains函数是MySQL提供的较早的空间查询函数,它接受一个矩形框和一个几何对象作为输入,并返回一个布尔值来指示矩形框是否包含该几何对象。MBRContains函数的计算基于几何对象的最小外接矩形(MBR),而不考虑几何对象的实际形状。
ST_Contains函数是MySQL提供的较新的空间查询函数,它接受两个几何对象作为输入,并返回一个布尔值来指示第一个几何对象是否包含第二个几何对象。与MBRContains函数不同,ST_Contains函数计算基于几何对象的实际形状,而不是最小外接矩形。
因此,ST_Contains函数通常比MBRContains函数更准确和更精细。如果您需要更精确的空间查询结果,则应该使用ST_Contains函数。但是,由于ST_Contains函数需要比MBRContains函数更多的计算和处理,因此在某些情况下可能会更慢。
select * from point_test
where ST_Contains(ST_Buffer(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)'), 5000 / 111320), coordinate)
执行报错
3643 - The SRID of the geometry does not match the SRID of the column ‘coordinate’. The SRID of the geometry is 0, but the SRID of the column is 4326. Consider changing the SRID of the geometry or the SRID property of the column
意思就是st_buffer生成的geometry的SRID为0,但point_test的coordinate的SRID为4326,两个标准不一样。那怎么半呢?
st_buffer函数有这样的说明:
The result is in the same SRS as the geometry argument.
即st_buffer函数返回的SRID和入参中的一致。
上述的版本基于MySQL 8.0.23。
事实证明,MySQL 8.0.23的st_buffer函数有bug,我换了最新的MySQL版本8.0.32,可以参考我的其它文章(docker安装mysql),就可以正常使用了。
下述的版本基于MySQL 8.0.32。
而且它返回的srid和目标srid是一致的。
select st_srid(st_buffer((select coordinate from point_test where id = 1), 5000));
这个查询也可以正常用了
SELECT ST_Buffer(st_geomFromText('point(121.71564763519164 31.062121772948835)', 4326, 'axis-order=long-lat'), 5000);
SELECT ST_Buffer(st_geomFromText('point(121.71564763519164 31.062121772948835)', 4326, 'axis-order=long-lat'), 5000);
注意:在MySQL 8.0.32中,st_buffer第二个参数的含义是
米
。
通过st_distance_sphere校验一下
select st_distance_sphere(st_geometryFromText('point(121.71564763519164 31.062121772948835)', 4326, 'axis-order=long-lat'),
st_geometryFromText('point(121.715700047596 31.1072192878852)', 4326, 'axis-order=long-lat'))
as distance;
发现结果是5014.624253057652,可以看到存于米级别的误差。
查询5000米之内的地址
select * from point_test
where MBRContains(ST_Buffer(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)', 4326,'axis-order=long-lat'), 5000), coordinate)
查看查询计划,发现也是走索引的
explain select * from point_test
where MBRContains(ST_Buffer(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)', 4326,'axis-order=long-lat'), 5000), coordinate)
加一个按照距离生序
select *, st_distance_sphere(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)', 4326,'axis-order=long-lat'), coordinate) as distance
from point_test
where MBRContains(ST_Buffer(ST_GeomFromText('POINT(121.31547880904075 31.191962005342543)', 4326,'axis-order=long-lat'), 5000), coordinate)
order by distance