☰
计算以点为圆心半径R的站点信息列表
2026/10/11 7:10:30 网站建设 项目流程

下面给出一个完整的 Java 实现:根据当前点的经纬度,对 API 返回的所有站点计算距离,筛选出 10 公里内的站点,并按距离由近到远排序。

一、定义站点模型

java

@Data public class Station { private Long id; private String name; private Double longitude; // 经度 private Double latitude; // 纬度 }

返回结果可以复用站点模型,额外加一个距离字段:

java

@Data public class StationDistanceVO { private Long id; private String name; private Double longitude; private Double latitude; private Double distanceKm; // 距离,公里 }

二、Haversine 距离工具类

java

public class GeoUtils { private static final double EARTH_RADIUS_KM = 6371.0; /** * 计算两个经纬度之间的距离,单位:公里 */ public static double distanceKm(double lat1, double lng1, double lat2, double lng2) { double dLat = Math.toRadians(lat2 - lat1); double dLng = Math.toRadians(lng2 - lng1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_KM * c; } }

三、核心计算方法

java

public List<StationDistanceVO> findNearbyStations(double currentLat, double currentLng, List<Station> allStations, double radiusKm) { if (allStations == null || allStations.isEmpty()) { return Collections.emptyList(); } return allStations.stream() // 过滤掉经纬度为空的脏数据 .filter(s -> s.getLatitude() != null && s.getLongitude() != null) // 计算距离 .map(s -> { double distance = GeoUtils.distanceKm( currentLat, currentLng, s.getLatitude(), s.getLongitude() ); StationDistanceVO vo = new StationDistanceVO(); vo.setId(s.getId()); vo.setName(s.getName()); vo.setLongitude(s.getLongitude()); vo.setLatitude(s.getLatitude()); vo.setDistanceKm(distance); return vo; }) // 过滤出半径内的站点 .filter(vo -> vo.getDistanceKm() <= radiusKm) // 按距离升序排序 .sorted(Comparator.comparingDouble(StationDistanceVO::getDistanceKm)) .collect(Collectors.toList()); }

四、调用示例

假设你从 API 拿到了站点列表:

java

// 当前点经纬度 double currentLat = 39.909; double currentLng = 116.397; // API 返回的所有站点 List<Station> allStations = stationApi.getAllStations(); // 计算 10 公里内的站点,按距离排序 List<StationDistanceVO> nearbyList = findNearbyStations( currentLat, currentLng, allStations, 10.0 ); // 输出 nearbyList.forEach(vo -> System.out.println(vo.getName() + " -> " + vo.getDistanceKm() + " km") );

五、如果需要分页

分页只需在排序后截取子列表:

java

int page = 1; int size = 20; int from = (page - 1) * size; int to = Math.min(from + size, nearbyList.size()); List<StationDistanceVO> pageList = nearbyList.subList(from, to); int total = nearbyList.size();

或者直接返回全部,由前端分页。数据量不大时推荐前端分页,避免多次计算。

六、性能优化建议

  1. 边界框预过滤:如果站点数量很大(上万),先用矩形范围缩小候选集,再精确计算 Haversine。

    java

    double latDelta = radiusKm / 111.0; double lngDelta = radiusKm / (111.0 * Math.cos(Math.toRadians(currentLat))); List<Station> candidates = allStations.stream() .filter(s -> Math.abs(s.getLatitude() - currentLat) <= latDelta && Math.abs(s.getLongitude() - currentLng) <= lngDelta) .collect(Collectors.toList());

    然后再对 candidates 计算精确距离。

  2. 并行流:站点特别多时,可以用parallelStream()加速距离计算,但要注意线程安全和结果顺序。排序后再collect,顺序仍然可控。

  3. 缓存:如果当前点经常重复(比如热门商圈),可以把结果缓存到 Redis,设置短 TTL。

  4. 数据库层计算:如果站点在 MySQL 里,直接用 SQL 的 Haversine 公式 + 边界框 + 空间索引,减少数据传输量。

七、注意事项

  • 经纬度顺序:Haversine 公式参数是(lat1, lng1, lat2, lng2),别传反。

  • 空值处理:站点经纬度可能为 null,必须过滤,否则计算会 NPE。

  • 距离单位:上面返回的是公里,如需米,乘以 1000。

  • 浮点误差:Math.sqrt(a)中 a 可能因浮点误差略大于 1,导致sqrt为 NaN。可以用Math.min(1, a)保护:

    java

    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - Math.min(1, a)));
  • 排序稳定性:距离相同的站点,可以再按 id 排序,保证结果可复现。

八、完整工具类

java

public class NearbyStationUtils { private static final double EARTH_RADIUS_KM = 6371.0; public static List<StationDistanceVO> findNearby(double lat, double lng, List<Station> stations, double radiusKm) { if (stations == null || stations.isEmpty()) { return Collections.emptyList(); } return stations.stream() .filter(s -> s.getLatitude() != null && s.getLongitude() != null) .map(s -> { double d = distanceKm(lat, lng, s.getLatitude(), s.getLongitude()); StationDistanceVO vo = new StationDistanceVO(); vo.setId(s.getId()); vo.setName(s.getName()); vo.setLongitude(s.getLongitude()); vo.setLatitude(s.getLatitude()); vo.setDistanceKm(d); return vo; }) .filter(vo -> vo.getDistanceKm() <= radiusKm) .sorted(Comparator.comparingDouble(StationDistanceVO::getDistanceKm)) .collect(Collectors.toList()); } public static double distanceKm(double lat1, double lng1, double lat2, double lng2) { double dLat = Math.toRadians(lat2 - lat1); double dLng = Math.toRadians(lng2 - lng1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); a = Math.min(1, Math.max(-1, a)); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_KM * c; } }

九、总结

核心步骤:

  1. 拿到当前点经纬度和站点列表;

  2. 遍历站点,用 Haversine 公式计算距离;

  3. 过滤掉超过 10 公里的;

  4. 按距离升序排序;

  5. 返回结果,可分页。

如果站点很多,先用边界框缩小范围,再精确计算。注意空值、经纬度顺序和浮点误差保护。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询