最近在做一个业务功能,需求涉及到了通过终端的定位获取距离终端最近的实体店铺的需求。因为之前没有涉及到地理位置坐标计算距离的知识,只知道redis可以计算两个坐标点(经纬度)之间的距离。但是不满足目前的需求,因为除了计算距离外还要进行筛选、排序、分页。所以就不考虑在redis实现。更不可能全表查询内存排序。所以上网搜索资料,找到了pgsql的插件postgis,是数据库层面支持经纬度距离计算的插件。
postgis官网:https://postgis.net/
postgis的安装其实是有两种方法的。
第一种是在安装pgsql的时候通过stack builder进行安装。但是这种可能会出现现在不下来,活着很慢会卡顿的情况。
所以我是使用第二种方法安装,直接官网下载postgis插件安装包进行安装,整个过程也很简单。我使用的是pgsql 14(postgis需要和pgsql版本对应,具体可以看官网介绍)。安装过程大概就是下面的几步:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZcYgzWY-1667556907747)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bda1ec95cae48b19a3578480296e3af~tplv-k3u1fbpfcp-zoom-1.image)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k94b2IEO-1667556907748)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9edcd0ce5423488a8c45d7ec4d3d7d5d~tplv-k3u1fbpfcp-zoom-1.image)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9WZLrtOP-1667556907748)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e62be35163847c1b40e731a011bf1c4~tplv-k3u1fbpfcp-zoom-1.image)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jAn8sDSm-1667556907749)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf6a08a3d46b40328b30782641eb31c3~tplv-k3u1fbpfcp-zoom-1.image)]
然后选下面的步骤全部是即可
-- 先创建数据库,然后执行创建插件postgis
CREATE EXTENSION postgis;
create table t_store
(
id serial not null
constraint t_store_pk
primary key,
name varchar(64) not null,
longitude varchar,
latitude varchar not null,
geom geometry(Point, 4326) not null,
create_time timestamp default now() not null,
update_time timestamp default now() not null
);
comment on column t_store.name is '名称';
comment on column t_store.longitude is '经度';
comment on column t_store.latitude is '纬度';
comment on column t_store.geom is '地理位置信息';
alter table t_store
owner to postgres;
搭建代码主要有下面几个注意点。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.1'
// postgis 插件依赖包
compile group: 'net.postgis', name: 'postgis-jdbc', version: '2.5.1'
compile group: 'org.postgresql', name: 'postgresql'
}
spring.application.name=gradle_test
server.port=8080
# pgsql
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgis_test
spring.datasource.username=postgres
spring.datasource.password=123456
# mybatis config :自定义的类型转换类
mybatis-plus.type-handlers-package=com.unfbx.gradle_test.config.typehandler
package com.unfbx.gradle_test.config.typehandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgis.Geometry;
import org.postgis.PGgeometry;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 描述:
*
* @author
* @date 2022-04-03
*/
public class AbstractGeometryTypeHandler extends BaseTypeHandler {
/**
* 表示向PreparedStatement里面设置值
*
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
PGgeometry geometry = new PGgeometry();
geometry.setGeometry(parameter);
ps.setObject(i, geometry);
}
/**
* 根据列名获取值
*
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnName);
if (pGgeometry == null) {
return null;
}
return (T) pGgeometry.getGeometry();
}
/**
* 根据列索引位置获取值
*
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnIndex);
if (pGgeometry == null) {
return null;
}
return (T) pGgeometry.getGeometry();
}
/**
* 获取值 通过列索引
*
* @param cs
* @param columnIndex
* @return
* @throws SQLException
*/
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
PGgeometry pGgeometry = (PGgeometry) cs.getObject(columnIndex);
if (pGgeometry == null) {
return null;
}
return (T) pGgeometry.getGeometry();
}
}
自定义坐标点处理类型转换继承AbstractGeometryTypeHandler
@MappedTypes(Point.class)
public class PointTypeHandler extends AbstractGeometryTypeHandler {
}
public class CoordinatesUtil {
public static Point buildPoint(String longitude, String latitude) throws Exception {
if (StringUtils.isEmpty(longitude) || StringUtils.isEmpty(latitude)) {
throw new Exception("坐标数据异常");
}
try {
Point point = new Point(Double.parseDouble(longitude),Double.parseDouble(latitude));
return point;
} catch (Exception e) {
throw e;
}
}
}
这里只做了俩接口。坐标经纬度拾取直接使用百度地图或者高德地图即可。
百度地图坐标拾取系统
@PostMapping("/store")
public String add(@RequestBody Store store) throws Exception {
store.setGeom(CoordinatesUtil.buildPoint(store.getLongitude(), store.getLatitude()));
storeService.save(store);
return "添加成功,id:" + store.getId();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpfqVzYS-1667556907749)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50d03431382e408c98152d1282944098~tplv-k3u1fbpfcp-zoom-1.image)]
请求参数携带经纬度信息。下面查询的是距离南汇嘴10000m以内的地方。
@PostMapping("/stores")
public List queryList(@RequestBody StoreReq req){
return storeService.queryList(req);
}
{
"longitude": "121.979515",
"latitude": "30.888643",
"distance":10000
}
返回信息
[
{
"id": 1,
"name": "上海天文馆",
"distance": 5585.5020385
},
{
"id": 2,
"name": "南汇嘴",
"distance": 0.0
}
]
数据库查询的sql其实是:
select
id, name,geom,
ST_Distance(ST_GeographyFromText(ST_AsText('POINT(121.926753 30.912628)')),
ST_GeographyFromText(ST_AsText(geom))) as distance
from
t_store;
https://github.com/Grt1228/postgis
[1] 百度地图坐标拾取系统: https://api.map.baidu.com/lbsapi/getpoint/index.html
[2] 参考csdn文章: https://blog.csdn.net/zxt521yt/article/details/113402227