xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
New file @@ -0,0 +1,42 @@ package com.xiaopeng.api.dto.pushMessage; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotNull; import java.io.Serializable; @Data @ApiModel(value = "推送订单轨迹信息") public class PushOrderRouteDTO implements Serializable { @NotNull(message = "订单id不能为空") @ApiModelProperty(value = "订单id") private Long orderId; @NotNull(message = "经度不能为空") @ApiModelProperty(value = "经度") private Double longitude; @NotNull(message = "纬度不能为空") @ApiModelProperty(value = "纬度") private Double latitude; @NotNull(message = "剩余距离不能为空") @ApiModelProperty(value = "剩余距离") private Integer distance; @NotNull(message = "剩余时间不能为空") @ApiModelProperty(value = "剩余时间") private Integer duration; @NotNull(message = "用户id不能为空") @ApiModelProperty(value = "用户id") private Long appUserId; @NotNull(message = "轨迹类型不能为空") @ApiModelProperty(value = "轨迹类型 1=接单轨迹 2=服务轨迹 3=巡游轨迹") private Integer routeType; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java
@@ -1,5 +1,6 @@ package com.xiaopeng.api.factory.pushMessage; import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO; import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO; @@ -41,6 +42,11 @@ public R<String> sendVehicleLocation(SendMessageDTO dto) { return R.fail("推送车辆实时定位失败,"+cause.getMessage()); } @Override public R<String> pushOrderRoute(PushOrderRouteDTO dto) { return R.fail("通过车辆实时定位推送订单轨迹失败,"+cause.getMessage()); } }; } } xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java
@@ -1,10 +1,12 @@ package com.xiaopeng.api.feignClient.pushMessage; import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.factory.pushMessage.XpPushMessageClientFallbackFactory; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -45,5 +47,13 @@ */ @PostMapping("/inner/websocket/order/sendVehicleLocation") R<String> sendVehicleLocation(@RequestBody SendMessageDTO dto); /** * 通过车辆实时定位推送订单轨迹 * * @param dto 消息实体 * @return 通过车辆实时定位推送订单轨迹 */ @PostMapping("/inner/v1/order/pushOrderRoute") R<String> pushOrderRoute(@RequestBody PushOrderRouteDTO dto); } xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java
@@ -120,7 +120,11 @@ */ public static final String ORDER_OLD_LOCATION = "order_old_location:"; /** * 订单车辆位置 * 订单车辆轨迹 */ public static final String ORDER_ROUTE = "order_route:"; /** * 订单车辆轨迹,禁止用于其他用处 */ public static final String ORDER_ROUTE_CAPTURE = "order_route_capture:"; } xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java
@@ -11,6 +11,7 @@ ORDER_STATUS("ORDER_STATUS"), VEHICLE_LOCATION("VEHICLE_LOCATION"), ORDER_ROUTE("ORDER_ROUTE"); @Getter xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -111,6 +111,9 @@ // 存储redis redisService.setCacheObject(CacheConstants.ORDER_ROUTE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo); // 存储路线偏离缓存,后续进行轨迹截取 redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo); return R.ok(routeVo); } xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
New file @@ -0,0 +1,90 @@ package com.xiaopeng.pushMessage.controller; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.pushMessage.utils.MapMatchingUtil; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.*; @Api(tags = "订单推送轨迹接口") @RestController @RequestMapping("") public class PushOrderRouteController { @Autowired private RedisService redisService; @Autowired private XpPushMessageClient xpPushMessageClient; /** * 通过定位推送订单轨迹 * @return */ @PostMapping("/inner/v1/order/pushOrderRoute") public R<String> pushOrderRoute(@Validated @RequestBody PushOrderRouteDTO dto) { RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType()); if(Objects.nonNull(routeVo)){ // 计算实时点位到线段的最优截取 List<LonLatDto> lonLatDtos = MapMatchingUtil.clipBySegmentDistance(routeVo.getPolyline(), new LonLatDto(dto.getLongitude(), dto.getLatitude())); routeVo.setDistance(dto.getDistance()); routeVo.setDuration(dto.getDuration()); routeVo.setPolyline(lonLatDtos); // 存储最新路线偏离缓存,后续进行轨迹截取 redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo); // 封装轨迹点位集合 Map<String, Object> polyline = new HashMap<>(); for (LonLatDto lonLatDto : lonLatDtos) { polyline.put("longitude",lonLatDto.getLongitude()); polyline.put("latitude",lonLatDto.getLatitude()); } // 封装路由详情 Map<String, Object> route = new HashMap<>(); route.put("distance",dto.getDistance()); route.put("duration",dto.getDuration()); route.put("polyline",JSONObject.toJSONString(polyline)); // 封装消息内容 Map<String, Object> content = new HashMap<>(); content.put("orderId",dto.getOrderId()); content.put("routeType",dto.getRouteType()); content.put("route",JSONObject.toJSONString(route)); // 封装轨迹信息 Map<String, Object> params = new HashMap<>(); params.put("type", PushMessageTypeEnum.ORDER_ROUTE.getDesc()); params.put("content", JSONObject.toJSONString(content)); params.put("timestamp", new Date().getTime()+""); // 进行消息推送 SendMessageDTO sendMessageDTO = new SendMessageDTO(); sendMessageDTO.setAppUserId(String.valueOf(dto.getAppUserId())); sendMessageDTO.setMessage(JSONObject.toJSONString(params)); xpPushMessageClient.sendRoute(sendMessageDTO); } return R.ok(); } } xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java
New file @@ -0,0 +1,123 @@ package com.xiaopeng.pushMessage.utils; import com.xiaopeng.api.dto.operation.LonLatDto; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.Collections; import java.util.List; @Slf4j public class MapMatchingUtil { /** * 轨迹裁剪(线段版本) * @param polyline * @param currentPoint * @return */ public static List<LonLatDto> clipBySegmentDistance(List<LonLatDto> polyline,LonLatDto currentPoint) { if (polyline == null || polyline.size() < 2) { return Collections.emptyList(); } double minDistance = Double.MAX_VALUE; int minSegmentIndex = 0; int increaseCount = 0; final int threshold = 3; final double tolerance = 1.0; for (int i = 0; i < polyline.size() - 1; i++) { double d = pointToSegmentDistance( currentPoint, polyline.get(i), polyline.get(i + 1) ); if (d < minDistance) { minDistance = d; minSegmentIndex = i; increaseCount = 0; } else if (d > minDistance + tolerance) { increaseCount++; } if (increaseCount >= threshold) { break; } } // 从最优线段的后一个点开始截取 return new ArrayList<>( polyline.subList(minSegmentIndex + 1, polyline.size()) ); } /** * 点到线段距离 * @param p * @param a * @param b * @return */ public static double pointToSegmentDistance( LonLatDto p, LonLatDto a, LonLatDto b) { double refLat = p.getLatitude(); double[] pXY = toXY(p.getLatitude(), p.getLongitude(), refLat); double[] aXY = toXY(a.getLatitude(), a.getLongitude(), refLat); double[] bXY = toXY(b.getLatitude(), b.getLongitude(), refLat); double x = pXY[0]; double y = pXY[1]; double x1 = aXY[0]; double y1 = aXY[1]; double x2 = bXY[0]; double y2 = bXY[1]; double dx = x2 - x1; double dy = y2 - y1; if (dx == 0 && dy == 0) { return Math.hypot(x - x1, y - y1); } double t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); if (t < 0) { return Math.hypot(x - x1, y - y1); } else if (t > 1) { return Math.hypot(x - x2, y - y2); } double projX = x1 + t * dx; double projY = y1 + t * dy; return Math.hypot(x - projX, y - projY); } /** * 经纬度转平面坐标(局部近似) * @param lat * @param lon * @param refLat * @return */ private static double[] toXY(double lat, double lon, double refLat) { double R = 6378137; // 地球半径 double x = Math.toRadians(lon) * R * Math.cos(Math.toRadians(refLat)); double y = Math.toRadians(lat) * R; return new double[]{x, y}; } }