|
|
@@ -1,5 +1,8 @@
|
|
|
package com.kym.service.impl;
|
|
|
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.github.pagehelper.Page;
|
|
|
import com.github.pagehelper.PageHelper;
|
|
|
import com.kym.common.utils.CommUtil;
|
|
|
import com.kym.entity.common.PageBean;
|
|
|
@@ -18,7 +21,10 @@ import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -95,8 +101,18 @@ public class WashStationServiceImpl extends MyBaseServiceImpl<WashStationMapper,
|
|
|
|
|
|
|
|
|
// ====================================================== 小程序接口 Start ===========================================================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 默认最大距离(公里)
|
|
|
+ */
|
|
|
+ private static final double DEFAULT_MAX_DISTANCE_KM = 50d;
|
|
|
+
|
|
|
@Override
|
|
|
public PageBean<WashStationVo> listStationForApp(StationQueryParams params) {
|
|
|
+ // 传入有效经纬度时启用附近站点模式:内存计算距离、过滤、排序后手动分页
|
|
|
+ if (hasUserLocation(params)) {
|
|
|
+ return listStationByDistance(params);
|
|
|
+ }
|
|
|
PageHelper.startPage(params.getPageNum(), params.getPageSize());
|
|
|
var stationList = lambdaQuery()
|
|
|
.eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId())
|
|
|
@@ -105,18 +121,105 @@ public class WashStationServiceImpl extends MyBaseServiceImpl<WashStationMapper,
|
|
|
if (stationList.isEmpty()) {
|
|
|
return new PageBean<>(Collections.emptyList());
|
|
|
}
|
|
|
+ var voList = buildStationVoList(stationList, Collections.emptyMap());
|
|
|
+ return new PageBean<>(voList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 附近站点:站点表数据量小且坐标存于 JSON 字符串无法走索引,
|
|
|
+ * 故全量查出后在内存中按 Haversine 公式计算距离,过滤超远站点并按距离升序,再分页。
|
|
|
+ */
|
|
|
+ private PageBean<WashStationVo> listStationByDistance(StationQueryParams params) {
|
|
|
+ double userLat = params.getLatitude();
|
|
|
+ double userLng = params.getLongitude();
|
|
|
+ double maxDistance = params.getMaxDistance() != null && params.getMaxDistance() > 0
|
|
|
+ ? params.getMaxDistance() : DEFAULT_MAX_DISTANCE_KM;
|
|
|
+
|
|
|
+ // 距离模式下需要先过滤排序再分页,因此不能用 PageHelper 分页查询
|
|
|
+ Map<String, Double> distanceMap = new HashMap<>();
|
|
|
+ var matchedList = lambdaQuery()
|
|
|
+ .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId())
|
|
|
+ .like(CommUtil.isNotEmptyAndNull(params.getStationName()), WashStation::getStationName, params.getStationName())
|
|
|
+ .list().stream()
|
|
|
+ .filter(station -> {
|
|
|
+ Double distance = calcDistanceKm(userLat, userLng, station.getLocation());
|
|
|
+ if (distance == null || distance > maxDistance) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ distanceMap.put(station.getStationId(), distance);
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ .sorted(Comparator.comparing(distanceMap::get))
|
|
|
+ .toList();
|
|
|
+
|
|
|
+ int total = matchedList.size();
|
|
|
+ int pageNum = Math.max(params.getPageNum(), 1);
|
|
|
+ int pageSize = Math.max(params.getPageSize(), 1);
|
|
|
+ int fromIndex = (pageNum - 1) * pageSize;
|
|
|
+ List<WashStation> pageList = fromIndex >= total
|
|
|
+ ? Collections.emptyList()
|
|
|
+ : matchedList.subList(fromIndex, Math.min(fromIndex + pageSize, total));
|
|
|
+ if (pageList.isEmpty()) {
|
|
|
+ Page<WashStationVo> emptyPage = new Page<>(pageNum, pageSize);
|
|
|
+ emptyPage.setTotal(total);
|
|
|
+ return new PageBean<>(emptyPage);
|
|
|
+ }
|
|
|
+ var voList = buildStationVoList(pageList, distanceMap);
|
|
|
+ Page<WashStationVo> page = new Page<>(pageNum, pageSize);
|
|
|
+ page.setTotal(total);
|
|
|
+ page.addAll(voList);
|
|
|
+ return new PageBean<>(page);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean hasUserLocation(StationQueryParams params) {
|
|
|
+ return params.getLatitude() != null && params.getLongitude() != null
|
|
|
+ && (params.getLatitude() != 0 || params.getLongitude() != 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析站点 location JSON 并计算与用户的球面距离(公里),坐标缺失或格式异常返回 null
|
|
|
+ */
|
|
|
+ private static Double calcDistanceKm(double userLat, double userLng, String location) {
|
|
|
+ if (CommUtil.isEmptyOrNull(location)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ double stationLat;
|
|
|
+ double stationLng;
|
|
|
+ try {
|
|
|
+ JSONObject json = JSON.parseObject(location);
|
|
|
+ stationLat = json.getDoubleValue("stationLat");
|
|
|
+ stationLng = json.getDoubleValue("stationLng");
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (stationLat == 0 && stationLng == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ double radLat1 = Math.toRadians(userLat);
|
|
|
+ double radLat2 = Math.toRadians(stationLat);
|
|
|
+ double a = radLat1 - radLat2;
|
|
|
+ double b = Math.toRadians(userLng) - Math.toRadians(stationLng);
|
|
|
+ double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
|
|
|
+ + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
|
|
|
+ s = s * 6378.137;
|
|
|
+ return Math.round(s * 100) / 100.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装站点 VO(含空闲设备数),distanceMap 非空时回填距离
|
|
|
+ */
|
|
|
+ private List<WashStationVo> buildStationVoList(List<WashStation> stationList, Map<String, Double> distanceMap) {
|
|
|
// 查询站点下的所有设备,按照站点统计可用设备数量
|
|
|
var res = washDeviceService.lambdaQuery().in(WashDevice::getStationId, stationList.stream().map(WashStation::getStationId).toList())
|
|
|
.eq(WashDevice::getState, WashDevice.STATE_空闲)
|
|
|
.list().stream().collect(Collectors.groupingBy(WashDevice::getStationId, Collectors.counting()));
|
|
|
- var voList = stationList.stream().map(station -> {
|
|
|
+ return stationList.stream().map(station -> {
|
|
|
var vo = new WashStationVo();
|
|
|
BeanUtils.copyProperties(station, vo);
|
|
|
vo.setIdleParkingNum(Math.toIntExact(res.getOrDefault(station.getStationId(), 0L)));
|
|
|
+ vo.setDistance(distanceMap.get(station.getStationId()));
|
|
|
return vo;
|
|
|
}).toList();
|
|
|
- return new PageBean<>(voList);
|
|
|
-
|
|
|
}
|
|
|
|
|
|
// ====================================================== 小程序接口 End ===========================================================
|