Java根据经纬度计算两个坐标之间的距离(含SQL计算)

最近接到两个需求,一个是通过小程序扫码开门的,我这边主要就是根据用户定位判断用户离扫码店铺距离小于多少米的时候才可以调远程调开门接口,另外一个就是获取用户周围有哪些店铺。

需求很简单,就是根据定位获取的经度维度计算两个点之间的球面距离,这里我们主要采用Haversine公式来计算,据说这是目前比较精确用来计算地球上两个点之间距离的算法

直接上代码

/**
 * @author Sakura
 * @date 2024/8/30 10:28
 */
public class HaversineCalculator {

    private static final double EARTH_RADIUS = 6371000; // 地球半径,单位:米

    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        // 将纬度和经度从度转换为弧度
        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));

        // 计算并返回距离
        return EARTH_RADIUS * c;
    }

    public static void main(String[] args) {
        // 示例:计算北京(纬度:39.9042,经度:116.4074)和上海(纬度:31.2304,经度:121.4737)之间的距离
        double distance = calculateDistance(39.9042, 116.4074, 31.2304, 121.4737);
        System.out.println("北京到上海的距离: " + distance + " 米");
    }
}

根据这个我们只需要在获取到用户位置坐标后就可以计算出用户和店铺之间的距离,scanShopCodeParam 里面就是前端传过来的用户经纬度坐标

        // 扫码的时候顺便开门,目前无开门程序入口,待完善
        // 根据经度维度判断距离,距离小于500m才开门(注意这个距离可以根据定位工具的精确度进行调整,目前不知道定位精确度)
        if (shop.getLongitude() != null && shop.getLatitude() != null && scanShopCodeParam.getLongitude() != null
                && scanShopCodeParam.getLatitude() != null) {
            // 使用Haversine公式计算两个坐标的球面距离
            double distance = HaversineCalculator.calculateDistance(shop.getLatitude().doubleValue(),
                    shop.getLongitude().doubleValue(), scanShopCodeParam.getLatitude().doubleValue(),
                    scanShopCodeParam.getLongitude().doubleValue());
            log.info("当前开门距离为:" + distance);
            // 在这里面调用开门程序即可
            if (distance < 500) {
                log.info("执行开门程序+++++++++++++++++++++");
            }
        }

接下来我们看一下怎么获取用户周围的店铺,首先店铺信息肯定已经在数据库了,并且已经有了定位的经纬度数据,我们要做的就是获取当前用户的定位信息然后拿去和数据库里店铺坐标比较,获取周围多少米之内的店铺信息返回即可

我们的店铺信息里面有经度和维度两个信息

在这里插入图片描述

然后直接看 SQL

SELECT shop_no,
    TRUNCATE(
        6371000 * ACOS(
            LEAST(1, GREATEST(-1,
                COS(RADIANS(22.145874)) * COS(RADIANS(latitude)) *
                COS(RADIANS(longitude) - RADIANS(113.876414)) +
                SIN(RADIANS(22.145874)) * SIN(RADIANS(latitude))
            ))
        ), 0) AS distance
FROM
    qy_shop;

看下执行结果,可以看到应该大差不差,毕竟我也没有实际量过,数据也是随手写的,准不准等后面上线了有真实数据我再更新一下

在这里插入图片描述

接着我们就给它加上条件,获取10公里内的店铺

SELECT shop_no, 
    TRUNCATE(
        6371000 * ACOS(
            LEAST(1, GREATEST(-1,
                COS(RADIANS(22.145874)) * COS(RADIANS(latitude)) *
                COS(RADIANS(longitude) - RADIANS(113.876414)) +
                SIN(RADIANS(22.145874)) * SIN(RADIANS(latitude))
            ))
        ), 0) AS distance
FROM
    qy_shop
WHERE 
6371000 * ACOS(
        LEAST(1, GREATEST(-1,
            COS(RADIANS(22.145874)) * COS(RADIANS(latitude)) *
            COS(RADIANS(longitude) - RADIANS(113.876414)) +
            SIN(RADIANS(22.145874)) * SIN(RADIANS(latitude))
        ))
    ) <= 10000
ORDER BY distance;

看下查询结果,有两个店铺,应该差不多

在这里插入图片描述
把这个封装到代码里面,这里只写 mapper.xml,其它的都是业务逻辑

	<select id="getAroundShopList" resultType="com.yike.user.vo.ShopVo">
        SELECT shop_no, shop_name, logo_url, contact, mobile, prov_code,
               city_code, dist_code, address, longitude, latitude, create_time,
               TRUNCATE(6371000 * ACOS(
                               LEAST(1, GREATEST(-1,
                                    COS(RADIANS(#{param.latitude,jdbcType=DECIMAL})) * COS(RADIANS(latitude)) *
                                    COS(RADIANS(longitude) - RADIANS(#{param.longitude,jdbcType=DECIMAL})) +
                                    SIN(RADIANS(#{param.latitude,jdbcType=DECIMAL})) * SIN(RADIANS(latitude))))
            ), 0) AS distance
        FROM
        qy_shop
        <where>
            6371000 * ACOS(
            LEAST(1, GREATEST(-1,
                COS(RADIANS(#{param.latitude,jdbcType=DECIMAL})) * COS(RADIANS(latitude)) *
                COS(RADIANS(longitude) - RADIANS(#{param.longitude,jdbcType=DECIMAL})) +
                SIN(RADIANS(#{param.latitude,jdbcType=DECIMAL})) * SIN(RADIANS(latitude))
            ))) &lt;= #{param.distance,jdbcType=INTEGER}
            <if test="param.keyword != null and param.keyword != ''">
                AND shop_name LIKE CONCAT('%',#{param.keyword,jdbcType=VARCHAR},'%')
            </if>
        </where>
        ORDER BY distance;
    </select>

当然为了证明数据库和代码里面计算结果是否一致我还是测试了一下的

在这里插入图片描述

从下面结果可以看出代码计算的和SQL计算的结果是一致的 (小数点后面的忽略啊,我数据库里面计算是去掉了小数点后面的值的)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值