0
点赞
收藏
分享

微信扫一扫

依据 坐标(经度-longitude、纬度-latitude)和范围(rangeRadius) 获取 符合条件的 坐标

滚过红尘说红尘 2023-10-19 阅读 19

使用 Java + MySQL 实现 依据 坐标(经度-longitude、纬度-latitude)和范围(rangeRadius) 获取 符合条件的 坐标。

1、纯MySQL 实现 (存在效率瓶颈)

	SELECT * FROM 'location' WHERE ( 
	 ACOS( 
		SIN((23.146436 * 3.1415) / 180)
		* SIN((latitude * 3.1415) / 180) -- 纬度字段
		+ COS((23.146436 * 3.1415) / 180)
		* COS((latitude * 3.1415) / 180)
		* COS((113.323568 * 3.1415) / 180 - (longitude * 3.1415) / 180) -- 经度字段
	 ) * 6371.393 -- 地球平均半径
	) <= 5 -- 范围:5千米

2、Java + MySQL (极力推荐)

package com.aienuo.utils;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * Coordinate<br>
 * 依据 坐标(经度-longitude、纬度-latitude)和范围(rangeRadius) 获取 符合条件的 坐标
 * <br>
 * 设计思路:
 * 泛型,任何业务实体类即可继承本类,方便实现功能
 * <br>
 * 使用说明:
 * 1、先输入 当前坐标(经度-longitude、纬度-latitude)和范围(rangeRadius)推算出  坐标(经度、纬度)的极值(经度最大-getMaxLongitude,经度最小-getMinLongitude,纬度最大-getMinLatitude,纬度最小-getMaxLatitude)
 * 2、根据 坐标(经度、纬度)极值 作为查询条件去数据库内检索 符合条件的 坐标对象(伪集合,此为正方形范围,实际应该为 圆形范围)
 * 3、调用 setMemberList 方法 将 符合条件的坐标对象 放入, 再调用 getMemberList 方法 即可获取 最终符合条件的 坐标对象,同时也计算出距离(距离源点直线距离-distance)
 * </p>
 * @author SanJin
 * @version 1.0
 * @since 2023年10月19日 16:47
 */
@Data
@Accessors(chain = true)
public class Coordinate<T> {

    /**
     * 弧度计算公式: 弧度 = 弧长 / 半径
     * 角度转弧度计算公式: 弧度 = 角度数 * (π / 180)
     * 弧度转角度计算公式: 角度数 = 弧度 * (180 / π)
     */

    /**
     * 地球平局半径(千米)
     */
    private static final double RADIUS = 6371.393;

    /**
     * 经度
     */
    private Double longitude;

    /**
     * 纬度
     */
    private Double latitude;

    /**
     * 距离(千米)
     */
    private Double distance;

    /**
     * 范围(半径:千米)
     */
    private Double rangeRadius;

    /**
     * 范围内的数据(正方形内的)
     */
    private List<Coordinate<T>> memberList;

    /**
     * 获取 指定距离之后的 经度偏差值
     *
     * @return Double - 指定距离之后的 经度偏差值
     */
    public Double getLongitudeDeviation() {
        // 指定距离之后的 经度偏差值
        double longitudeDeviation = 2 * Math.asin(Math.sin(this.rangeRadius / (2 * RADIUS)) / Math.cos(this.rad(this.latitude)));
        // 弧度转角度(角度数 = 弧度 * (180 / π))
        return longitudeDeviation * (180 / Math.PI);
    }

    /**
     * 获取 指定距离之后的 纬度偏差值
     *
     * @return Double - 指定距离之后的 纬度偏差值
     */
    public Double getLatitudeDeviation() {
        // 指定距离之后的 纬度偏差值(弧度 = 弧长 / 半径)
        double latitudeDeviation = this.rangeRadius / RADIUS;
        // 弧度转角度(角度数 = 弧度 * (180 / π))
        return latitudeDeviation * (180 / Math.PI);
    }

    public Double getMinLongitude() {
        return this.longitude - this.getLongitudeDeviation();
    }

    public Double getMaxLongitude() {
        return this.longitude + this.getLongitudeDeviation();
    }

    public Double getMinLatitude() {
        return this.latitude - this.getLatitudeDeviation();
    }

    public Double getMaxLatitude() {
        return this.latitude + this.getLatitudeDeviation();
    }

    /**
     * 获取 过滤 后的数据
     *
     * @return List<Coordinate<T>> - 过滤后的
     */
    public List<Coordinate<T>> getMemberList() {
        if (this.memberList != null && !this.memberList.isEmpty()) {
            this.memberList = memberList.stream().filter(member -> this.computeDistance(member) <= this.rangeRadius).sorted(Comparator.comparing(this::computeDistance)).collect(Collectors.toList());
        }
        return this.memberList;
    }

    /**
     * 计算 基准点 与 目标点 之间的距离(千米)
     *
     * @param coordinate - 目标点
     * @return Double - 距离:单位 千米
     */
    public Double computeDistance(final Coordinate<T> coordinate) {
        // 基准点 纬度坐标 的 弧度
        double datumLatitudeRad = this.rad(this.latitude);
        // 目标点 纬度坐标 的 弧度
        double targetLatitudeRad = this.rad(coordinate.latitude);
        // 基准点 纬度坐标 与 目标点 纬度坐标 的 弧度差
        double latitudeRadDifference = datumLatitudeRad - targetLatitudeRad;
        // 基准点 经度坐标 与 目标点 经度坐标 的 弧度差
        double longitudeRadDifference = this.rad(this.longitude) - this.rad(coordinate.longitude);
        // 基准点 与 目标点 之间的 弧度差
        double radDifference = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(latitudeRadDifference / 2), 2) + Math.cos(datumLatitudeRad) * Math.cos(targetLatitudeRad) * Math.pow(Math.sin(longitudeRadDifference / 2), 2)));
        // 基准点 与 目标点 之间的 直线距离(千米)  弧长计算公式: 弧长 = 弧度 * 半径
        double distance = radDifference * RADIUS;
        coordinate.setDistance(distance);
        return distance;
    }

    /**
     * 计算弧度
     *
     * @param angle - 坐标值(角度)
     * @return - 弧度
     */
    private Double rad(final Double angle) {
        // 角度转弧度计算公式: 弧度 = 角度数 * (π / 180)
        return angle * (Math.PI / 180);
    }

}

举报

相关推荐

0 条评论