MeteorCat / 空间坐标测距检索

Created Wed, 17 Apr 2024 19:55:51 +0800 Modified Wed, 29 Oct 2025 23:24:53 +0800
1707 Words

空间坐标测距检索

当时项目提出的具体项目需求, 后续实现之后感觉挺有趣的就把其单独剔除出来总结起来; 具体需求就是商家在我们自己商户平台 注册并绑定当前地址, 然后用户需要在平台检索最近的注册商家.

这里实现方式其实也十分简单, 只需要在注册的时候使用 MySQL|MariaDBGeometry 类型保存位置经纬度.

注意: MySQL版本必须在 5.7 之后才能支持该类型.

Geometry 类型是专门用于空间存储的类型, 具体支持以下细分类型:

  • Point(110.3 44.0): 点, 保存坐标点位置, 也就是位置标识经纬度
  • LineString(80.07 23.45, 99.23 34.56): 线, 两个点联系的直线, 用于标识两点直线
  • Polygon((93.30 35.301 90.332 30.341)): 面, 多点连接采用的多边形面
  • MultiPoint: 多点, 用于多个点记录, 一般是复杂的多点位置记录
  • MultiLineString: 多线, 用于多条路线记录, 一般是复杂的多路线记录
  • MultiPolygon: 多面, 用于多个面对象, 一般是复杂的多面记录
  • GeometryCollection: 集合, 这是很高级的经纬度混合集合, 实际上我个人很少去用到

回归需求上面, 实际上要求客户端|移动端在商户平台需要对接第三方SDK平台来提交坐标系, 这里以 百度地图开放平台SDK 做样例; 假设安卓客户端已经接入 百度安卓定位SDK, 如下官方文档说明:

/**
 * 实现定位回调
 */
public class MyLocationListener extends BDAbstractLocationListener {
    @Override
    public void onReceiveLocation(BDLocation location) {
        //此处的BDLocation为定位结果信息类,通过它的各种get方法可获取定位相关的全部结果
        //以下只列举部分获取经纬度相关(常用)的结果信息
        //更多结果信息获取说明,请参照类参考中BDLocation类中的说明

        //获取纬度信息 - 关键参数
        double latitude = location.getLatitude();
        //获取经度信息 - 关键参数
        double longitude = location.getLongitude();
        //获取定位精度,默认值为0.0f
        float radius = location.getRadius();
        //获取经纬度坐标类型,以LocationClientOption中设置过的坐标类型为准
        String coorType = location.getCoorType();
        //获取定位类型、定位错误返回码,具体信息可参照类参考中BDLocation类中的说明
        int errorCode = location.getLocType();
    }
}

上面最关键的参数 latitude(纬度)longitude(精度) 且坐标定位也能支持海外地址, 我们需要做的是将移动端商户经纬度提交到服务端.

数据库构建

这里构建个初始化的数据表来构建测试处理:

CREATE TABLE `bussiess_info`
(
    `id`       BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键标识',
    `name`     VARCHAR(255)        NOT NULL DEFAULT '' COMMENT '商户名称' COLLATE 'utf8mb4_unicode_ci',
    `position` POINT               NULL     DEFAULT NULL COMMENT '位置经纬, 允许NULL用于商户可能暂时不绑定地址',
    PRIMARY KEY (`id`) USING BTREE
)
    COMMENT ='商户位置绑定'
    COLLATE = 'utf8mb4_unicode_ci'
    ENGINE = InnoDB
;

这里推荐 百度官方坐标拾取器 用来测试定位

这里假设目前处于广州特色景点 广州塔(经度: 113.318977N, 纬度: 23.114155E), 目前假定有些商户平台在附近绑定:

  • XXX酒家: (23.113781E,113.316372N)
  • YYY庄园: (23.114113E,113.314297N)
  • ZZZ店: (23.113698E,113.311741N)
  • 远的店: (23.120557E,113.318151N)
  • 很远的旅馆: (23.126079E,113.324079N)
  • 超远的广场: (23.209921E,113.2792N)

假设添加数据库追加上这几个商户对象:

# 插入对应的数据, geomfromtext 是转换经纬度函数, 首位代表经度,次位为纬度

# XXX酒家
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, 'XXX酒家', geomfromtext('point(113.316372 23.113781)'));

# YYY庄园
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, 'YYY庄园', geomfromtext('point(113.314297 23.114113)'));

# ZZZ店
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, 'ZZZ店', geomfromtext('point(113.311741 23.113698)'));

# 远的店
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, '远的店', geomfromtext('point(113.318151 23.120557)'));

# 很远的旅馆
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, '很远的旅馆', geomfromtext('point(113.324079 23.126079)'));

# 超远的广场
INSERT INTO `bussiess_info`(`id`, `name`, `position`)
VALUES (default, '超远的广场', geomfromtext('point(113.2792 23.209921)'));

如果你用GUI会看到新增的数据在GUI内部都是乱码没办法看到坐标信息, 这里可以通过 astext 转化为文本信息:

# 查看坐标详情
SELECT `id`, `name`, astext(`position`) as `gis`
FROM `bussiess_info`;

这里假设目前你处于 广州塔(经度: 113.318977N, 纬度: 23.114155E) 需要测量出和这些绑定商户具体的两点距离:

# 查看所有相差的的距离位置
# 当前广州塔的位置: geomfromtext('point(113.318977 23.114155)')
SELECT `id`,
       `name`,
       floor(st_distance_sphere(`position`, geomfromtext('point(113.318977 23.114155)'))) as `distance`
FROM `bussiess_info`;

这里查询出来的信息如下, 这里 distance 单位为 :

id name distance
1 XXX酒家 269
2 YYY庄园 478
3 ZZZ店 741
4 远的店 716
5 很远的旅馆 1424
6 超远的广场 11398

这时候就能直接做匹配附近的商家从而推送给客户端处理:

# 附近商户检索, 只需要 500 米之内的商家
# 当前广州塔的位置: geomfromtext('point(113.318977 23.114155)')
# 注意这里采用函数处理, 所以不能使用 `WHERE` 查询而是要采用 `HAVING` 后查询
SELECT `id`,
       `name`,
       floor(st_distance_sphere(`position`, geomfromtext('point(113.318977 23.114155)'))) as `distance`
FROM `bussiess_info`
HAVING `distance` <= 500;

这里只作为简单思路开发, 因为这种直接查询性能上面可能有所损失, 后续可以采用 GeoHash 算法做模糊匹配, 将通过经纬度检索分号模糊匹配保存到 Redis 查询, 其实就是将 (113.31897, 23.11415) 这种高分结果以丢失精度方式 (113.32, 23.11) 归并结果集之后缓存 Redis 提供给用户结果; 好处就是性能效率上面能够提升很大效率, 但是坏处就是出现定位精度丢失问题.