最近工作需要,页面展示某个城市附近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缓存
- 在项目启动时,spring缓存所有的城市信息
- 遍历城市集合,判断每个城市是否在范围内
- 然后返回页面展示
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. 验证
使用百度地图的测距功能逐个验证
百度测距工具
验证结果
网友评论