import Taro from '@tarojs/taro';
|
import { useEffect, useRef, useState } from 'react';
|
import { getDistance } from '../../../../utils/Robotaxi-public-method';
|
import { TripStatus } from '../../../constants/trip-status';
|
import {
|
ANDROID_ROUTE_POINT_LIMIT,
|
DEFAULT_ROUTE_POINT_LIMIT,
|
Deps,
|
ROUTE_ERASE_BREAK_DISTANCE_FACTOR,
|
ROUTE_ERASE_BREAK_DISTANCE_METERS,
|
ROUTE_ERASE_MAX_DISTANCE_METERS,
|
ROUTE_POLYLINE_BASE_ID,
|
WALKING_POLYLINE_ID_OFFSET,
|
} from './use-route-manager.shared';
|
import { emitDisplayRoutePoints } from './route-display-channel';
|
|
/** Polyline 渲染 Hook 入参。 */
|
type UsePolylineRenderParams = {
|
/** 路线管理依赖。 */
|
deps: Deps;
|
/** 当前选中的路线索引。 */
|
selectedRouteIndex: number;
|
/** 全量可选路线点集合。 */
|
allRoutePoints: any[][];
|
};
|
|
/** Polyline 渲染 Hook 返回值。 */
|
type UsePolylineRenderResult = {
|
/** 地图折线集合。 */
|
polyline: any[];
|
/** 设置地图折线集合。 */
|
setPolyline: React.Dispatch<React.SetStateAction<any[]>>;
|
/** 主路线点集合。 */
|
routePoints: any[];
|
/** 设置主路线点集合。 */
|
setRoutePoints: React.Dispatch<React.SetStateAction<any[]>>;
|
/** 步行路线点集合。 */
|
walkingPoints: any[];
|
/** 设置步行路线点集合。 */
|
setWalkingPoints: React.Dispatch<React.SetStateAction<any[]>>;
|
/** 步行时长。 */
|
walkingTime: number;
|
/** 设置步行时长。 */
|
setWalkingTime: React.Dispatch<React.SetStateAction<number>>;
|
/** 路线请求签名引用。 */
|
polylineKeyRef: React.MutableRefObject<string>;
|
/** 最近一次路线起点引用。 */
|
lastRoutePointRef: React.MutableRefObject<{ latitude: number; longitude: number } | null>;
|
/** 最近一次路线更新时间引用。 */
|
lastRouteUpdateAtRef: React.MutableRefObject<number>;
|
/** 最近一次 polyline 更新时间引用。 */
|
lastPolylineUpdateAtRef: React.MutableRefObject<number>;
|
/** 当前完整订单路线引用。 */
|
currentFullRouteRef: React.MutableRefObject<any[]>;
|
/** 最近一次擦除索引引用。 */
|
lastErasedIndexRef: React.MutableRefObject<number>;
|
/** 订单路线初始化引用。 */
|
orderRouteInitializedRef: React.MutableRefObject<boolean>;
|
/** 应用路线点。 */
|
applyRoutePoints: (points: any[]) => boolean;
|
/** 路线点降采样。 */
|
reducePoints: (points: any[], maxCount: number) => any[];
|
/** 重置路线运行时状态。 */
|
resetRouteRuntimeState: () => void;
|
/** 擦除已行驶路线。 */
|
erasePassedRouteSegments: (newPoints: any[], servedPoints?: any[]) => void;
|
/** 构建 polyline 签名。 */
|
buildPolylineKey: (displayRoutePoints: any[]) => string;
|
/** 构建 polyline 数据。 */
|
buildPolylineLines: (displayRoutePoints: any[]) => any[];
|
};
|
|
const PASSED_ROUTE_POLYLINE_ID = 1999; // 已走过路线浅色折线 ID
|
const FIRST_ROUTE_ERASE_READY_DELAY_MS = 120;
|
|
/** 统一获取路线降采样上限。 */
|
const resolveRoutePointLimit = (isAndroid: boolean) => (
|
isAndroid ? ANDROID_ROUTE_POINT_LIMIT : DEFAULT_ROUTE_POINT_LIMIT
|
);
|
|
/**
|
* 管理路线点、擦线与 polyline 渲染。
|
*/
|
export default function usePolylineRender(params: UsePolylineRenderParams): UsePolylineRenderResult {
|
const { deps, selectedRouteIndex, allRoutePoints } = params;
|
|
/** 地图折线集合。 */
|
const [polyline, setPolyline] = useState<any[]>([]);
|
/** 主路线点集合。 */
|
const [routePoints, setRoutePoints] = useState<any[]>([]);
|
/** 步行路线点集合。 */
|
const [walkingPoints, setWalkingPoints] = useState<any[]>([]);
|
/** 步行时长。 */
|
const [walkingTime, setWalkingTime] = useState<number>(0);
|
|
/** 路线渲染签名引用。 */
|
const polylineKeyRef = useRef<string>('');
|
/** 最近一次路线起点引用。 */
|
const lastRoutePointRef = useRef<{ latitude: number; longitude: number } | null>(null);
|
/** 最近一次路线更新时间引用。 */
|
const lastRouteUpdateAtRef = useRef<number>(0);
|
/** 最近一次 polyline 更新时间引用。 */
|
const lastPolylineUpdateAtRef = useRef<number>(0);
|
/** 当前完整订单路线引用。 */
|
const currentFullRouteRef = useRef<any[]>([]);
|
/** 最近一次擦除索引引用。 */
|
const lastErasedIndexRef = useRef<number>(0);
|
/** 订单路线初始化引用。 */
|
const orderRouteInitializedRef = useRef<boolean>(false);
|
/** 主路线首次进入原生地图后的擦线就绪标记。 */
|
const routeEraseReadyRef = useRef<boolean>(false);
|
/** 待执行的最新擦线路径。 */
|
const pendingRouteEraseRef = useRef<any[] | null>(null);
|
/** 首次擦线延时器。 */
|
const routeEraseReadyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
/**
|
* 清理首次擦线延时器,避免旧任务串到下一条路线。
|
*/
|
const clearRouteEraseReadyTimer = () => {
|
if (routeEraseReadyTimerRef.current) {
|
clearTimeout(routeEraseReadyTimerRef.current);
|
routeEraseReadyTimerRef.current = null;
|
}
|
};
|
|
/**
|
* 同步擦线后的剩余路线点,保持 React 与原生图层一致。
|
*/
|
const applyErasedRoutePoints = (newPoints: any[]) => {
|
currentFullRouteRef.current = [...newPoints];
|
lastErasedIndexRef.current = 0;
|
applyRoutePoints([...newPoints]);
|
};
|
|
/**
|
* 应用路线点并同步运行时引用。
|
*/
|
const applyRoutePoints = (points: any[]) => {
|
if (Array.isArray(points) && points.length > 0) {
|
const firstPoint = points[0];
|
const nextLatitude = Number(firstPoint?.latitude);
|
const nextLongitude = Number(firstPoint?.longitude);
|
const now = Date.now();
|
if (Number.isFinite(nextLatitude) && Number.isFinite(nextLongitude)) {
|
lastRoutePointRef.current = { latitude: nextLatitude, longitude: nextLongitude };
|
lastRouteUpdateAtRef.current = now;
|
} else {
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
}
|
setRoutePoints(points);
|
return true;
|
}
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
setRoutePoints([]);
|
return true;
|
};
|
|
/**
|
* 对路线点做降采样,减少渲染压力。
|
*/
|
const reducePoints = (points: any[], maxCount: number) => {
|
if (!Array.isArray(points) || points.length <= maxCount) {
|
return points;
|
}
|
const step = Math.ceil(points.length / maxCount);
|
const reducedPoints: any[] = [];
|
for (let index = 0; index < points.length; index += step) {
|
reducedPoints.push(points[index]);
|
}
|
const lastPoint = points[points.length - 1];
|
if (reducedPoints[reducedPoints.length - 1] !== lastPoint) {
|
reducedPoints.push(lastPoint);
|
}
|
return reducedPoints;
|
};
|
|
/**
|
* 重置路线运行时状态。
|
*/
|
const resetRouteRuntimeState = () => {
|
setRoutePoints([]);
|
setPolyline([]);
|
polylineKeyRef.current = '';
|
lastPolylineUpdateAtRef.current = 0;
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
currentFullRouteRef.current = [];
|
lastErasedIndexRef.current = 0;
|
orderRouteInitializedRef.current = false;
|
routeEraseReadyRef.current = false;
|
pendingRouteEraseRef.current = null;
|
clearRouteEraseReadyTimer();
|
};
|
|
/**
|
* 擦除已行驶路段,并同步 React 中的路线点。
|
*/
|
const erasePassedRouteSegments = (newPoints: any[], _servedPoints: any[] = []) => {
|
const oldPoints = currentFullRouteRef.current;
|
if (!oldPoints?.length || !newPoints?.length) {
|
return;
|
}
|
const boundaryPoint = newPoints[0];
|
if (
|
!boundaryPoint
|
|| typeof boundaryPoint.latitude !== 'number'
|
|| typeof boundaryPoint.longitude !== 'number'
|
) {
|
return;
|
}
|
|
let bestMatchIndex = -1;
|
let bestDistance = Infinity;
|
const startIndex = lastErasedIndexRef.current;
|
for (let index = startIndex; index < oldPoints.length; index += 1) {
|
const distance = getDistance(
|
oldPoints[index].latitude,
|
oldPoints[index].longitude,
|
boundaryPoint.latitude,
|
boundaryPoint.longitude
|
);
|
if (distance < bestDistance) {
|
bestDistance = distance;
|
bestMatchIndex = index;
|
}
|
if (
|
distance > bestDistance * ROUTE_ERASE_BREAK_DISTANCE_FACTOR
|
&& bestDistance < ROUTE_ERASE_BREAK_DISTANCE_METERS
|
) {
|
break;
|
}
|
}
|
|
const executeRouteErase = () => {
|
if (bestMatchIndex >= startIndex && bestDistance < ROUTE_ERASE_MAX_DISTANCE_METERS) {
|
Taro.createMapContext('map').eraseLines({
|
lines: [{
|
id: ROUTE_POLYLINE_BASE_ID,
|
index: Math.max(0, bestMatchIndex - 1),
|
point: {
|
latitude: boundaryPoint.latitude,
|
longitude: boundaryPoint.longitude,
|
},
|
clear: true
|
}]
|
});
|
}
|
applyErasedRoutePoints(newPoints);
|
};
|
|
if (process.env.TARO_ENV !== 'weapp' || routeEraseReadyRef.current) {
|
executeRouteErase();
|
return;
|
}
|
pendingRouteEraseRef.current = [...newPoints];
|
clearRouteEraseReadyTimer();
|
const elapsedSincePolylineUpdate = lastPolylineUpdateAtRef.current > 0
|
? Math.max(0, Date.now() - lastPolylineUpdateAtRef.current)
|
: 0;
|
const waitMs = lastPolylineUpdateAtRef.current > 0
|
? Math.max(0, FIRST_ROUTE_ERASE_READY_DELAY_MS - elapsedSincePolylineUpdate)
|
: FIRST_ROUTE_ERASE_READY_DELAY_MS;
|
routeEraseReadyTimerRef.current = setTimeout(() => {
|
routeEraseReadyTimerRef.current = null;
|
routeEraseReadyRef.current = true;
|
if (!pendingRouteEraseRef.current) {
|
return;
|
}
|
const latestPoints = pendingRouteEraseRef.current;
|
pendingRouteEraseRef.current = null;
|
erasePassedRouteSegments(latestPoints);
|
}, waitMs);
|
};
|
|
/**
|
* 获取当前用于渲染的主路线点。
|
*/
|
const getDisplayRoutePoints = () => {
|
const isEraseMode = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus);
|
return isEraseMode
|
? routePoints
|
: reducePoints(routePoints, resolveRoutePointLimit(deps.isAndroid));
|
};
|
|
/**
|
* 获取修改目的地预览态下的已走过路线点。
|
*/
|
const getPassedRoutePoints = () => {
|
const shouldShowPassedRoute = deps.tripStatus === TripStatus.InService
|
&& deps.allowSelectableRoutes
|
&& Array.isArray(deps.vehicleMoveAlongPoints)
|
&& deps.vehicleMoveAlongPoints.length > 1;
|
if (!shouldShowPassedRoute) {
|
return [];
|
}
|
return reducePoints(deps.vehicleMoveAlongPoints || [], resolveRoutePointLimit(deps.isAndroid));
|
};
|
|
/**
|
* 构建 polyline 渲染签名,避免重复 setState。
|
*/
|
const buildPolylineKey = (displayRoutePoints: any[]) => {
|
const passedRoutePoints = getPassedRoutePoints();
|
const lastRoutePoint = displayRoutePoints[displayRoutePoints.length - 1];
|
const lastPassedRoutePoint = passedRoutePoints[passedRoutePoints.length - 1];
|
const lastWalkingPoint = walkingPoints[walkingPoints.length - 1];
|
return [
|
deps.shouldShowRoute ? 1 : 0,
|
deps.statusRule.showWalking ? 1 : 0,
|
displayRoutePoints.length,
|
passedRoutePoints.length,
|
walkingPoints.length,
|
lastRoutePoint?.latitude ?? '',
|
lastRoutePoint?.longitude ?? '',
|
lastPassedRoutePoint?.latitude ?? '',
|
lastPassedRoutePoint?.longitude ?? '',
|
lastWalkingPoint?.latitude ?? '',
|
lastWalkingPoint?.longitude ?? '',
|
allRoutePoints.length,
|
selectedRouteIndex
|
].join('|');
|
};
|
|
/**
|
* 构建地图 polyline 数据。
|
*/
|
const buildPolylineLines = (displayRoutePoints: any[]) => {
|
const lines: any[] = [];
|
const passedRoutePoints = getPassedRoutePoints();
|
if (passedRoutePoints.length > 1) {
|
lines.push({
|
id: PASSED_ROUTE_POLYLINE_ID,
|
points: passedRoutePoints,
|
color: '#D6D9DC',
|
width: 10,
|
clickable: false,
|
dottedLine: false,
|
arrowLine: false,
|
});
|
}
|
if (deps.shouldShowRoute && deps.allowSelectableRoutes && allRoutePoints.length > 1) {
|
allRoutePoints.forEach((points, index) => {
|
if (!points?.length) {
|
return;
|
}
|
if (index !== selectedRouteIndex) {
|
lines.push({
|
id: ROUTE_POLYLINE_BASE_ID + index,
|
points: reducePoints(points, resolveRoutePointLimit(deps.isAndroid)),
|
color: '#BED6CA',
|
width: 10,
|
clickable: true,
|
dottedLine: false,
|
arrowLine: true,
|
});
|
}
|
});
|
|
const selectedPoints = allRoutePoints[selectedRouteIndex];
|
if (Array.isArray(selectedPoints) && selectedPoints.length > 0) {
|
lines.push({
|
id: ROUTE_POLYLINE_BASE_ID + selectedRouteIndex,
|
points: displayRoutePoints,
|
color: '#59B688',
|
width: 8,
|
borderColor: '#3D8F66',
|
borderWidth: 1,
|
clickable: true,
|
dottedLine: false,
|
arrowLine: true,
|
});
|
}
|
} else if (deps.shouldShowRoute && displayRoutePoints.length > 0) {
|
lines.push({
|
id: ROUTE_POLYLINE_BASE_ID,
|
points: displayRoutePoints,
|
color: '#59B688',
|
width: 8,
|
borderColor: '#3D8F66',
|
borderWidth: 1,
|
clickable: true,
|
dottedLine: false,
|
arrowLine: true,
|
});
|
}
|
|
if (deps.statusRule.showWalking && walkingPoints.length > 0) {
|
lines.push({
|
id: WALKING_POLYLINE_ID_OFFSET + allRoutePoints.length,
|
points: walkingPoints,
|
color: '#DFB369',
|
width: deps.isAndroid ? 5 : 8,
|
dottedLine: true,
|
arrowLine: false,
|
});
|
}
|
return lines;
|
};
|
|
useEffect(() => {
|
const displayRoutePoints = getDisplayRoutePoints();
|
const nextPolylineKey = buildPolylineKey(displayRoutePoints);
|
if (nextPolylineKey === polylineKeyRef.current) {
|
return;
|
}
|
polylineKeyRef.current = nextPolylineKey;
|
|
// 将剩余路线点推送给订阅方(如自动缩放),无论是否需要绘制路线都推送
|
emitDisplayRoutePoints(displayRoutePoints);
|
const shouldDrawRoute = deps.shouldShowRoute && displayRoutePoints.length > 0;
|
const shouldDrawWalking = deps.statusRule.showWalking && walkingPoints.length > 0;
|
if (shouldDrawRoute || shouldDrawWalking) {
|
if (lastPolylineUpdateAtRef.current === 0) {
|
routeEraseReadyRef.current = false;
|
pendingRouteEraseRef.current = null;
|
clearRouteEraseReadyTimer();
|
}
|
const polylineLines = buildPolylineLines(displayRoutePoints);
|
setPolyline(polylineLines);
|
lastPolylineUpdateAtRef.current = Date.now();
|
return;
|
}
|
if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
|
setPolyline([]);
|
}
|
lastPolylineUpdateAtRef.current = 0;
|
routeEraseReadyRef.current = false;
|
pendingRouteEraseRef.current = null;
|
clearRouteEraseReadyTimer();
|
}, [
|
allRoutePoints,
|
deps.allowSelectableRoutes,
|
deps.isAndroid,
|
deps.shouldShowRoute,
|
deps.statusRule.showWalking,
|
deps.tripStatus,
|
deps.vehicleMoveAlongPoints,
|
routePoints,
|
selectedRouteIndex,
|
walkingPoints
|
]);
|
|
useEffect(() => () => {
|
clearRouteEraseReadyTimer();
|
}, []);
|
|
return {
|
polyline,
|
setPolyline,
|
routePoints,
|
setRoutePoints,
|
walkingPoints,
|
setWalkingPoints,
|
walkingTime,
|
setWalkingTime,
|
polylineKeyRef,
|
lastRoutePointRef,
|
lastRouteUpdateAtRef,
|
lastPolylineUpdateAtRef,
|
currentFullRouteRef,
|
lastErasedIndexRef,
|
orderRouteInitializedRef,
|
applyRoutePoints,
|
reducePoints,
|
resetRouteRuntimeState,
|
erasePassedRouteSegments,
|
buildPolylineKey,
|
buildPolylineLines
|
};
|
}
|