订单轨迹回放

This commit is contained in:
19173159168
2025-10-18 22:01:02 +08:00
parent fa80c90edd
commit b022cbf8f9
8 changed files with 534 additions and 0 deletions

View File

@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.orders.domain.ZcOrderSub;
import com.ruoyi.orders.dto.PushItemRequest;
import com.ruoyi.orders.dto.RefundRequest;
import com.ruoyi.orders.dto.TrajectoryPoint;
import com.ruoyi.orders.util.OrderTrajectoryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -13,6 +15,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@ -36,6 +40,10 @@ public class DataPushApi {
@Value(value = "${dataPush.sendAddUserUrl}")
private String sendAddUserUrl;
@Value(value = "${dataPush.orderTrajectoryUrl}")
private String orderTrajectoryUrl;
/**
* 发送添加运营商信息
* @param companyId
@ -219,4 +227,66 @@ public class DataPushApi {
}
}
/**
* 获取订单轨迹
* @param clientId
* @param startTm
* @param endTm
* @return
*/
public List<TrajectoryPoint> fetchOrderTrajectory(String clientId, String startTm, String endTm) {
try {
// 准备请求参数
Map<String, String> requestBody = new HashMap<>();
requestBody.put("clientId", clientId);
requestBody.put("startTm", startTm);
requestBody.put("endTm", endTm);
// 打印请求参数便于调试
String jsonParams = objectMapper.writeValueAsString(requestBody);
logger.info("订单轨迹请求参数: {}", jsonParams);
// 发送HTTP POST请求调用订单轨迹接口
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(orderTrajectoryUrl, requestEntity, String.class);
// 处理订单轨迹接口响应
if (response.getStatusCode() == HttpStatus.OK) {
String responseBody = response.getBody();
if (responseBody != null) {
try {
// 使用ObjectMapper解析JSON响应
OrderTrajectoryResponse result = objectMapper.readValue(responseBody, OrderTrajectoryResponse.class);
if (result.getCode() != null && result.getCode() == 200) {
logger.info("获取订单轨迹成功客户端ID: {},轨迹点数量: {}", clientId,
result.getData() != null ? result.getData().size() : 0);
return result.getData();
} else {
logger.error("获取订单轨迹失败客户端ID: {},响应: {}", clientId, responseBody);
return null;
}
} catch (Exception jsonEx) {
logger.error("解析订单轨迹接口响应异常客户端ID: {},响应: {}", clientId, responseBody, jsonEx);
return null;
}
} else {
logger.error("订单轨迹接口返回空响应客户端ID: {}", clientId);
return null;
}
} else {
logger.error("获取订单轨迹失败客户端ID: {},响应: {}", clientId, response.getBody());
return null;
}
} catch (Exception e) {
logger.error("调用订单轨迹接口异常客户端ID: " + clientId, e);
return null;
}
}
}

View File

@ -4,16 +4,19 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.ruoyi.api.DataPushApi;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.operation.domain.Company;
import com.ruoyi.operation.service.ICompanyService;
import com.ruoyi.orders.domain.ZcOrderCarChange;
import com.ruoyi.orders.dto.StatisticsHomeOrder;
import com.ruoyi.orders.dto.TrajectoryPoint;
import com.ruoyi.orders.service.IZcOrderCarChangeService;
import com.ruoyi.orders.util.OrderStatusEnum;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
@ -48,6 +51,9 @@ public class ZcOrderMainController extends BaseController
private IZcOrderCarChangeService zcOrderCarChangeService;
@Autowired
private ICompanyService companyService;
@Autowired
private DataPushApi dataPushApi;
@RequiresPermissions("orders:order:view")
@GetMapping()
@ -166,6 +172,57 @@ public class ZcOrderMainController extends BaseController
return toAjax(flag);
}
/**
* 租车订单轨迹
*/
@GetMapping("/orderTrajectory/{id}")
public String orderTrajectory(@PathVariable("id") Long id, ModelMap mmap)
{
// 调用
List<TrajectoryPoint> trajectoryPoints = dataPushApi.fetchOrderTrajectory("15135683068", "2025-10-10 08:08:80", "2025-10-10 16:80:00");
// List<TrajectoryPoint> trajectoryPoints = new ArrayList<>();
// double[][] testData = {
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925285, 27.900163},
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925285, 27.900163},
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925285, 27.900163},
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925285, 27.900163},
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925285, 27.900163},
// {112.925285, 27.900163}, {112.925285, 27.900163}, {112.925855, 27.898977},
// {112.925745, 27.898878}, {112.9261, 27.898802}, {112.926718, 27.898633},
// {112.927295, 27.898673}, {112.92779, 27.898652}, {112.928342, 27.898685},
// {112.928483, 27.89868}, {112.929135, 27.898667}, {112.9299, 27.898445},
// {112.930225, 27.898263}, {112.93024, 27.898233}, {112.930213, 27.898052},
// {112.929905, 27.897193}, {112.929437, 27.895972}, {112.929417, 27.895775},
// {112.929483, 27.895702}, {112.929832, 27.895443}, {112.929875, 27.89528},
// {112.930153, 27.894773}, {112.930123, 27.894712}, {112.929758, 27.894537},
// {112.929558, 27.894395}, {112.929668, 27.894208}, {112.929712, 27.89416},
// {112.930053, 27.893827}, {112.930018, 27.89383}, {112.930027, 27.893798},
// {112.930027, 27.893798}, {112.930027, 27.893798}, {112.93002, 27.893797},
// {112.93002, 27.893797}, {112.93002, 27.893797}, {112.929775, 27.89305},
// {112.929775, 27.89305}, {112.92978, 27.893495}, {112.92978, 27.893495}
// };
// // 创建轨迹点列表
// for (int i = 0; i < testData.length; i++) {
// TrajectoryPoint point = new TrajectoryPoint();
// point.setLng(testData[i][0]); // 经度
// point.setLat(testData[i][1]); // 纬度
// // 时间戳可以按需添加
// // point.setTs("2025-10-17 14:" + String.format("%02d", (39 + i)) + ":36.0");
// trajectoryPoints.add(point);
// }
// 设置中心点和轨迹点列表
if (!trajectoryPoints.isEmpty()) {
mmap.put("centerPoint", trajectoryPoints.get(0));
mmap.put("orderTrajectoryPointList", trajectoryPoints);
mmap.put("orderTrajectoryPointLen", trajectoryPoints.size()-1);
}
return prefix + "/orderTrajectory";
}
@PostMapping("/statisticsHomeOrder")
@ResponseBody
public AjaxResult statisticsHomeOrder(String type) {

View File

@ -0,0 +1,18 @@
package com.ruoyi.orders.dto;
public class TrajectoryPoint {
private Double lng; // 经度
private Double lat; // 纬度
private String ts; // 时间戳
// getter和setter方法
public Double getLng() { return lng; }
public void setLng(Double lng) { this.lng = lng; }
public Double getLat() { return lat; }
public void setLat(Double lat) { this.lat = lat; }
public String getTs() { return ts; }
public void setTs(String ts) { this.ts = ts; }
}

View File

@ -0,0 +1,22 @@
package com.ruoyi.orders.util;
import com.ruoyi.orders.dto.TrajectoryPoint;
import java.util.List;
public class OrderTrajectoryResponse {
private Integer code;
private List<TrajectoryPoint> data;
// getter和setter方法
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public List<TrajectoryPoint> getData() {
return data;
}
public void setData(List<TrajectoryPoint> data) {
this.data = data;
}
}

View File

@ -87,7 +87,10 @@ dataPush:
refundUrl: http://115.190.8.52:8019/api/payment/refund
alirefundUrl: http://115.190.8.52:8019/api/alipay/refund
pushItemUrl: http://115.190.8.52:8019/item/sync/item
# 轨迹接口地址
orderTrajectoryUrl: http://106.14.7.172:8100/device/getHistoryDeviceInfo
sendAddCompanyUrl: http://sczx-sync-container:8016/send/companyinfo/
sendAddStoreUrl: http://sczx-sync-container:8016/send/storeinfo/
sendAddUserUrl: http://sczx-sync-container:8016/send/userinfo/

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -263,11 +263,41 @@
field: 'startRentTime',
title: '开始计费时间',
width: '160px'
},
{
title: '操作',
align: 'center',
width: '100px',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs btnOption" href="javascript:void(0)" onclick="orderTrajectory(\'' + row.orderId + '\')"><i class="fa fa-edit"></i>轨迹</a> ');
return actions.join('');
}
}]
};
$.table.init(options);
});
function orderTrajectory(id){
var url = prefix + "/orderTrajectory/" + id;
top.layer.open({
type: 2,
area: ['1400px', '800px'],
fix: false,
//不固定
maxmin: true,
shade: 0.3,
title: "订单轨迹回放",
content: url,
btn: ['关闭'],
// 弹层外区域关闭
shadeClose: true,
cancel: function(index) {
return true;
}
});
//$.modal.open("订单轨迹", prefix + "/orderTrajectory/" + id, '1400', '800', false);
}
function manualClose(id){
var index = layer.open({
type: 2, // iframe模式

View File

@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>marker轨迹回放-跟随模式</title>
</head>
<script src="https://map.qq.com/api/gljs?v=1.exp&key=GPYBZ-Q4TY3-MWZ3S-OO6TP-5GA43-I4BRH&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #919aac;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
}
.btnContainer button:focus {
outline: none;
}
.btnContainer .btn1 {
padding: 10px 14px;
background: #3876ff;
border-radius: 2px;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #fff;
line-height: 14px;
font-family: PingFangSC-Regular;
}
input {
height: 25px;
}
.info {
background-color: white;
padding: 10px;
font-size: 14px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">点击小车开始移动</button>
<button class="btn2">重置</button>
<button class="btn3">暂停</button>
<button class="btn4">恢复</button>
<div class="info">
<p></p>
<p></p>
<p>当前小车行驶距离0米</p>
<p></p>
</div>
</div>
<div id="container"></div>
<script type="text/javascript" th:inline="javascript">
var centerPoint = /*[[${centerPoint}]]*/ null;
var len = /*[[${orderTrajectoryPointLen}]]*/ 0;
var orderTrajectoryPointList = /*[[${orderTrajectoryPointList}]]*/ [];
var center = new TMap.LatLng(28.398338642835437,112.84714169618475);
if(centerPoint != null){
var lng = centerPoint.lng;
var lat = centerPoint.lat;
// 设置地图中心点(注意腾讯地图 LatLng 构造函数参数顺序是纬度,经度)
center = new TMap.LatLng(lat, lng);
}
// 初始化地图
var map = new TMap.Map('container', {
zoom: 16,
center
});
var isMoving = false;
var roation;
var position;
var path = [];
if (orderTrajectoryPointList && orderTrajectoryPointList.length > 0) {
path = orderTrajectoryPointList.map(function(point) {
// 注意腾讯地图 LatLng 参数顺序是 (纬度, 经度)
return new TMap.LatLng(point.lat, point.lng);
});
}
var limitDistance = 0; // 限制距离
// MultiPolyline文档https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector#1
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
// 折线样式定义
styles: {
style_blue: new TMap.PolylineStyle({
color: '#2A88FF', // 线填充色
width: 8, // 折线宽度
borderWidth: 3, // 边线宽度
borderColor: '#0569FF', // 边线颜色
lineCap: 'round', // 线端头方式
showArrow: true
}),
style_gray: new TMap.PolylineStyle({
color: '#ccc', // 线填充色
width: 8, // 折线宽度
borderWidth: 3, // 边线宽度
borderColor: '#FFF', // 边线颜色
lineCap: 'round' // 线端头方式
})
},
geometries: [
{
id: 'path1',
styleId: 'style_blue',
paths: path
},
{
id: 'path2',
styleId: 'style_blue',
paths: path
}
]
});
// marker文档https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
var marker = new TMap.MultiMarker({
id: 'car',
map,
styles: {
'car-down': new TMap.MarkerStyle({
width: 48,
height: 72,
anchor: {
x: 24,
y: 36
},
faceTo: 'map',
rotate: 180,
src: '../../../img/model_taxi.png'
}),
start: new TMap.MarkerStyle({
anchor: {
x: 16,
y: 32
},
src: 'https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-start.png'
}),
end: new TMap.MarkerStyle({
src: 'https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-end.png'
})
},
geometries: [
{
id: 'car',
styleId: 'car-down',
position: path[0]
},
{
id: 'start',
styleId: 'start',
position: path[0]
},
{
id: 'end',
styleId: 'end',
position: path[len]
}
]
});
function initInfo() {
// 初始化全程长度及小车起始坐标
var distance = TMap.geometry.computeDistance(path);
var fullLength = document.querySelectorAll('.info p')[0];
fullLength.innerHTML = '路线全程长度:' + parseNumber(distance, 2) + '米';
var currentLatLng = document.querySelectorAll('.info p')[3];
currentLatLng.innerHTML =
'当前小车坐标:' +
parseNumber(path[0].lat, 6) +
',' +
parseNumber(path[0].lng, 6);
}
initInfo();
function parseNumber(value, num) {
// 解析数字
return parseFloat(value).toFixed(num);
}
function carMove() {
map.off('idle', carMove);
marker.moveAlong(
{
car: {
path,
speed: 200
}
},
{
autoRotation: true
}
);
isMoving = true;
}
marker.on('move_ended', function () {
isMoving = false;
});
marker.on('move_stopped', function (e) {
isMoving = false;
});
marker.on('moving', function (e) {
if (!e.car) return;
roation = TMap.geometry.computeHeading(
e.car.passedLatLngs[e.car.passedLatLngs.length - 2],
e.car.passedLatLngs[e.car.passedLatLngs.length - 1]
);
position = TMap.geometry.computeDestination(
marker.getGeometryById('car').position,
roation,
60
);
map.easeTo(
{
center: position,
rotation: e.car.angle && e.car.angle,
zoom: 20,
pitch: 70
},
{
duration: 300
}
);
// 移动路线置灰
polylineLayer.updateGeometries([
{
id: 'path2',
styleId: 'style_gray',
paths: e.car.passedLatLngs
}
]);
// 计算当前移动距离
var currentDistance = TMap.geometry.computeDistance(e.car.passedLatLngs);
if (limitDistance && currentDistance > limitDistance) {
// 大于限制距离 停止移动
marker.stopMove();
}
// 移动过程中更新小车已行驶距离
document.querySelectorAll('.info p')[2].innerHTML =
'当前小车行驶距离:' + parseNumber(currentDistance, 2) + '米';
// 移动过程中更新小车当前坐标
var movingLatLng = e.car.passedLatLngs[e.car.passedLatLngs.length - 1];
document.querySelectorAll('.info p')[3].innerHTML =
'当前小车坐标:' +
parseNumber(movingLatLng.lat, 6) +
',' +
parseNumber(movingLatLng.lng, 6);
});
document.querySelector('.btn1').onclick = function () {
if (isMoving) return;
map.easeTo(
{
zoom: 20,
rotation: 180,
pitch: 80
},
{
duration: 1000
}
);
map.on('idle', carMove);
};
document.querySelector('.btn2').onclick = function () {
marker.stopMove();
isMoving = false;
polylineLayer.setGeometries([
{
id: 'path1',
styleId: 'style_blue',
paths: path
},
{
id: 'path2',
styleId: 'style_blue',
paths: path
}
]);
map.easeTo({
center,
zoom: 16,
rotation: 0,
pitch: 0
});
};
document.querySelector('.btn3').onclick = function () {
marker.pauseMove();
};
document.querySelector('.btn4').onclick = function () {
marker.resumeMove();
};
document.querySelector('.btn5').onclick = function () {
// 限制小车移动距离0||NAN 不限制
if (isMoving) return;
var value = parseFloat(document.querySelector('input').value);
value ? (limitDistance = value) : (limitDistance = 0);
document.querySelectorAll('.info p')[1].innerHTML =
'当前限制小车最大移动距离:' + limitDistance + '米';
};
</script>
</body>
</html>