美文网首页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