From c64c8dba6ea27f1d2222a743927d2cd3606b1ef3 Mon Sep 17 00:00:00 2001
From: xuhy <3313886187@qq.com>
Date: 星期四, 12 二月 2026 20:38:05 +0800
Subject: [PATCH] 通过车辆实时定位推送订单轨迹

---
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java              |   10 ++
 xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java               |  123 ++++++++++++++++++++++++
 xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java        |    6 +
 xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java |   90 ++++++++++++++++++
 xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java                          |    3 
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java                        |   42 ++++++++
 xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java      |    1 
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java   |    6 +
 8 files changed, 280 insertions(+), 1 deletions(-)

diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
new file mode 100644
index 0000000..dfeb801
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
@@ -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;
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java
index 832fa98..9f48a14 100644
--- a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java
+++ b/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());
+            }
         };
     }
 }
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java
index a0ac7d1..1ff9e08 100644
--- a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java
+++ b/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);
 
 }
diff --git a/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java b/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java
index 71b0b9b..ceeb89e 100644
--- a/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java
+++ b/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:";
 }
diff --git a/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java b/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java
index 6f81301..0182b16 100644
--- a/xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java
+++ b/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
diff --git a/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java b/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
index ceae241..34eec00 100644
--- a/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
+++ b/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);
     }
 
diff --git a/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java b/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
new file mode 100644
index 0000000..588b865
--- /dev/null
+++ b/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
@@ -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();
+    }
+
+}
diff --git a/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java b/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java
new file mode 100644
index 0000000..5784d42
--- /dev/null
+++ b/xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java
@@ -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};
+    }
+
+
+}

--
Gitblit v1.7.1