根据当前门店经纬度,求出1km内的门店
文章目录
- 前言
- mysql计算经纬度之间的距离
- 准备工作
- 计算距离
- 正确性检验
- 其它方式实现
- 总结
前言
需求:
-
公司有一些门店的注册信息,注册信息中有门店详细地址,我们需要通过门店的地址信息获取到地图上的经纬度。
经纬度可以通过高德或者百度的接口来
付费
*获取,下面是高德通过地址获取经纬度的文档。
https://lbs.amap.com/api/webservice/guide/api/georegeo -
使用不同门店的经纬度计算出门店之间的距离。
其实高德或百度地图本身也有这种api,但是收费比较贵,所以我们采用其它方法来做,我们可以用
java的方式
来计算出,也可以通过mysq空间函数
来计算出,当然这种方式的精确度会有所出入但是不会相差太大,这里我们主要使用mysql来实现。
mysql计算经纬度之间的距离
准备工作
这里我们省略了从地图api获取门店经纬度这一步骤,下面有一些我从高德获取到的经纬度。
测试数据如下:
生成数据sql:
注意:location 是空间类型字段,并且添加了空间索引
CREATE TABLE `ums_member_company_coordinate` (`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键',`company_id` BIGINT ( 20 ) DEFAULT NULL COMMENT '门店ID',`company_name` VARCHAR ( 255 ) DEFAULT NULL,`address_detail` VARCHAR ( 255 ) DEFAULT NULL COMMENT '详细地址\n',`longitude` VARCHAR ( 64 ) DEFAULT NULL COMMENT '经度',`latitude` VARCHAR ( 64 ) DEFAULT NULL COMMENT '维度',`location` point NOT NULL COMMENT '经纬度',PRIMARY KEY ( `id` ) USING BTREE,UNIQUE KEY `index_company_id` ( `company_id` ),
SPATIAL KEY `spatial_index_location` ( `location` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '客户门店高德坐标';
location的类型是空间类型所以需要插入的时候使用POINT 函数
INSERT INTO `ums_member_company_coordinate` ( `company_id`, `company_name`, `address_detail`, `longitude`, `latitude`, `location` )
VALUES(17787,'宁波杭州湾新区春草堂大药房有限公司','宁波市慈溪市浙江省宁波杭州湾新区南洋商城4幢','121.294532','30.290669',POINT (CAST('121.294532' AS DECIMAL ( 12, 6 )),CAST('30.290669' AS DECIMAL ( 12, 6 ))) );
INSERT INTO `ums_member_company_coordinate` ( `company_id`, `company_name`, `address_detail`, `longitude`, `latitude`, `location` )
VALUES(10132,'宁波杭州湾热心大药房有限公司','宁波市慈溪市浙江省宁波杭州湾新区庵东镇海南村中心东路173号','121.298002','30.288234',POINT (CAST('121.298002' AS DECIMAL ( 12, 6 )),CAST('30.288234' AS DECIMAL ( 12, 6 ))) );
INSERT INTO `ums_member_company_coordinate` (`company_id`,`company_name`,`address_detail`,`longitude`,`latitude`,`location`
)
VALUES(1540,'七台河市恒健大药房','黑龙江省七台河市桃山区桃北街125号楼10号商服','131.017754','45.773341',POINT (CAST('131.017754' AS DECIMAL ( 12, 6 )),CAST('45.773341' AS DECIMAL ( 12, 6 ))) );
计算距离
计算距离sql:
SELECTt1.companyId,t1.companyAId,t1.companyBId,t1.companyAName,t1.companyBName,t1.companyALongitude,t1.companyALatitude,t1.companyBLongitude,t1.companyBLatitude,t1.distance -- 门店和门店之间的距离FROM(SELECTIF(a.company_id > b.company_id,CONCAT( a.company_id, "-", b.company_id ),CONCAT( b.company_id, "-", a.company_id )) AS companyId,ROW_NUMBER() OVER (PARTITION BY companyId ORDER BY companyId) AS row_num, -- 用来去重的字段a.company_id AS companyAId,b.company_id AS companyBId,a.company_name AS companyAName,b.company_name AS companyBName,a.longitude AS companyALongitude,a.latitude AS companyALatitude,b.longitude AS companyBLongitude,b.latitude AS companyBLatitude,ROUND( ST_DISTANCE_SPHERE ( a.location, b.location ), 2 ) AS distanceFROMums_member_company_coordinate aLEFT JOIN ums_member_company_coordinate b ON ST_INTERSECTS ( a.location, BUFFER ( b.location, 1 ) )WHEREROUND( ST_DISTANCE_SPHERE ( a.location, b.location ), 6 ) < 1000 -- mysql的空间函数,1000是指1000米的距离) t1WHERE 1=1AND t1.row_num = 1 -- 去重and t1.companyAId != t1.companyBId -- 去掉门店和门店自身比较的数据
用的是mysql的
空间函数
,ROUND( ST_DISTANCE_SPHERE ( a.location, b.location ), 6 ) <1000
这里的1000是指1000米
正确性检验
通过上面的sql最终计算的结果如下:
我们可以通过使用高德的工具检验我们结果数据的准确性。
https://lbs.amap.com/demo/jsapi-v2/example/calcutation/calculate-distance-between-two-markers
其它方式实现
- 使用Haversine公式计算:
public static void main(String[] args) {// 传入两个门店的经纬度对比距离double distance = calculateDistance(121.298002, 30.288234, 121.294532, 30.290669);System.out.println("distance = " + distance);}/*** 计算距离* @param lat1 a点纬度* @param lon1 a点经度* @param lat2 b点纬度* @param lon2 b点经度* @return*/public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {// 地球半径,单位:米final double R = 6371000;// 将经纬度转换为弧度double lat1Rad = Math.toRadians(lat1);double lon1Rad = Math.toRadians(lon1);double lat2Rad = Math.toRadians(lat2);double lon2Rad = Math.toRadians(lon2);// 计算经纬度的差值double deltaLat = lat2Rad - lat1Rad;double deltaLon = lon2Rad - lon1Rad;// 使用Haversine公式计算两点间的距离double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)+ Math.cos(lat1Rad) * Math.cos(lat2Rad)* Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));// 计算距离double distance = R * c;return distance;}
2.使用geo来计算距离
geo计算需要导入对应的依赖,我这里使用的gradle作为依赖包的管理我的,下面是我的build.gradle文件
plugins {id("java")
}group = "org.example"
version = "1.0-SNAPSHOT"repositories {mavenCentral().content {excludeModule("javax.media", "jai_core")}maven {url "https://repo.osgeo.org/repository/release/" // GeoTools 仓库}
}dependencies {implementation 'org.geotools:gt-main:25.0' // 使用 GeoTools 25.0 版本作为示例
}tasks.test {useJUnitPlatform()
}
public static void main(String[] args) {// 创建 GeometryFactory 实例GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();// 创建第一个点Coordinate point1Coordinate = new Coordinate(121.298002, 30.288234); // 经度, 纬度Point point1 = geometryFactory.createPoint(point1Coordinate);// 创建第二个点Coordinate point2Coordinate = new Coordinate(121.294532, 30.290669);Point point2 = geometryFactory.createPoint(point2Coordinate);// 计算两个点之间的直线距离(大圆距离)double distanceInMeters = calculateDistance(point1, point2);System.out.println("两个点之间的直线距离:" + distanceInMeters + " 米");}// 计算两个点之间的直线距离(大圆距离)private static double calculateDistance(Point point1, Point point2) {return point1.distance(point2) * 111000.0; // 将距离从度转换为米,约111千米/度}
总结
不管是java公式Haversine、geo、mysql的实现方式都会与地图api计算出的距离有一些差异的。