美文网首页Java开发那些事程序员技术干货
spring计算方圆300km内其它城市(附完整代码)

spring计算方圆300km内其它城市(附完整代码)

作者: lkee6760 | 来源:发表于2017-11-27 13:23 被阅读133次

    最近工作需要,页面展示某个城市附近300km范围内所有的其它城市。找了半天也没有合适的方法,这里给出一种解决方法,如果有更好的,请不吝赐教。

    一、准备工作

    问题抽象:求城市300km范围内所有的其它城市,可以抽象为球面上两个点的距离是否在一定的范围内

    地球两点距离

    球面距离:球面上两点之间的最短连线的长度,就是经过这两点的大圆在这两点间的一段劣弧的长度。(大圆就是经过球心的平面截球面所得的圆)
    球面距离计算公式:设两点A、B的经、纬度分别为(jA,wA)(jB,wB),则半径为R的球面上两点间的最短距离(大圆弧)为:弧AB=Rarccos[sin(wA)sin(wB)+cos(wA)cos(wB)cos(jA-jB)
    地球半径: 地球的平均半径6371.393千米

    表结构:


    二、sql计算

    SELECT province, city FROM city WHERE city != "苏州" and
    6371.393
     * ACOS
    (
        SIN(31.30703 * PI() / 180) * SIN(latitude * PI() / 180)
        +
        COS(31.30703 * PI() / 180) * COS(latitude * PI() / 180) * COS(120.591431 * PI() / 180
        -
        longitude * PI() / 180)
    ) <= 300
    

    使用sql计算直接给出结果,但是sql计算会占用较多cpu资源,如果并发量较高,不建议使用。

    三、使用spring缓存

    1. 在项目启动时,spring缓存所有的城市信息
    2. 遍历城市集合,判断每个城市是否在范围内
    3. 然后返回页面展示

    1. 代码展示

    • controller
      • url要符合restful风格,不建议使用中文,这里是为了演示方便
    @RequestMapping("/aroundCity/{cityName:[\u4E00-\u9FFF]+}") // 汉字正则表达式
    public String aroundCityMapping(Model model, @PathVariable String cityName) {
    
        List<City> listAroundcities = aroundCityService.listAroundcities(cityName, distance);
        if (listAroundcities != null && listAroundcities.size() > 0) {
            model.addAttribute("listAroundcities", listAroundcities);
            model.addAttribute("distance", distance);
            model.addAttribute("cityName", cityName);
        }
        return "aroundCity";
    }
    
    
    • service
      • dao层使用mybatis的逆向工程生成(逆向工程及mybatis语法以后单独总结一份)
    public List<City> listAroundcities(String cityName, int distance) {
        List<City> cityList = basicInfoService.getCityList();
        CityExample cityExample = new CityExample();
        Criteria criteria = cityExample.createCriteria();
        criteria.andCityEqualTo(cityName);
        List<City> cities = cityMapper.selectByExample(cityExample);
        City city = null;
        if (cities == null || cities.size() < 1) {
            return null;
        }
        city = cities.get(0);
        List<City> aroundCitiesByDistance = addAroundCitiesByDistance(city, cityList, distance);
        return aroundCitiesByDistance;
    }
    
    /**
     * 添加当前城市x公里内的所有城市
     */
    private List<City> addAroundCitiesByDistance(City localCity, List<City> cityList, int distance) {
        List<City> aroundCityList = new ArrayList<City>(); // 周围城市列表
        for (City city : cityList) {
            Float lat = city.getLatitude();
            Float lng = city.getLongitude();
            if (!Objects.equals(city.getCity(), localCity.getCity()) && withinRange(localCity.getLatitude(), localCity.getLongitude(), lat, lng, distance)) {
                aroundCityList.add(city);
            }
        }
        return aroundCityList;
    }
    
    /**
     * 判断两个经纬坐标之间的距离是否在distance km内
     */
    private boolean withinRange(float lat1, float lng1, 
                                float lat2, float lng2, 
                                int distance) {
        double exp1 = Math.sin(lat1 * Math.PI / 180) * Math.sin(lat2 * Math.PI / 180);
        double exp2 = Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180)
                * Math.cos(lng1 * Math.PI / 180 - lng2 * Math.PI / 180);
        return EARTH_RADIUS * Math.acos(exp1 + exp2) <= distance;
    }
    
    • BasicInfoService
      • 使用@PostConstruct注解,可以在项目启动时执行
      • 获取所有的城市信息,并交给spring管理
      • spring默认是单例模式,而且List集合不是线程安全的,所有不要提供setter方法
    private List<City> cityList;
    
    @PostConstruct
    public void init() {
        cityList = cityMapper.selectByExample(new CityExample());
    }
    
    public List<City> getCityList() {
        return cityList;
    }
    
    • view
      • freemarker作为视图
      • 多视图配置详见共享代码(下方有百度云地址)
      • freemarker的语法及使用技巧,以后总结
    <table>
        <tr>
            <td>ID</td>
            <td>省份</td>
            <td>城市</td>
            <td>经度</td>
            <td>纬度</td>
        </tr>
        <#list listAroundcities as city>
            <tr>
                <td>${city.id}</td>
                <td>${city.province}</td>
                <td>${city.city}</td>
                <td>${city.longitude}</td>
                <td>${city.latitude}</td>
            </tr>
        </#list>
    </table>
    

    2. 测试

    url:http://localhost:8090/aroundCity/aroundCity/苏州;

    3. 结果

    4. 验证

    使用百度地图的测距功能逐个验证


    百度测距工具
    验证结果

    总结

    • 想了解更详细的内容可以直接下载代码:链接:http://pan.baidu.com/s/1ge5fMxX 密码:j2qv
    • BasicInfoService缓存也可以换成redismemcache等主流的nosql数据库管理,这里主要是为了方便演示
    • 参考内容1:百度百科-球面距离
    • 参考内容2:百度百科-地球半径
    • 地球是一个两极稍扁、赤道略鼓的不规则球体,平均半径6371千米,这只是近似计算

    相关文章

      网友评论

        本文标题:spring计算方圆300km内其它城市(附完整代码)

        本文链接:https://www.haomeiwen.com/subject/verrbxtx.html