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,
|
PASSED_ROUTE_POLYLINE_ID,
|
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';
|
|
type UsePolylineRenderParams = {
|
deps: Deps;
|
};
|
|
const FIRST_ROUTE_ERASE_READY_DELAY_MS = 120;
|
|
// 统一获取路线降采样上限
|
const resolveRoutePointLimit = (isAndroid: boolean) => (
|
isAndroid ? ANDROID_ROUTE_POINT_LIMIT : DEFAULT_ROUTE_POINT_LIMIT
|
);
|
|
// 轻量签名,避免轮询同一路线时重复 setState
|
const buildPointsSignature = (points: any[]) => {
|
if (!Array.isArray(points) || points.length === 0) {
|
return 'empty';
|
}
|
const firstPoint = points[0];
|
const middlePoint = points[Math.floor(points.length / 2)];
|
const lastPoint = points[points.length - 1];
|
return [
|
points.length,
|
firstPoint?.latitude ?? '',
|
firstPoint?.longitude ?? '',
|
middlePoint?.latitude ?? '',
|
middlePoint?.longitude ?? '',
|
lastPoint?.latitude ?? '',
|
lastPoint?.longitude ?? ''
|
].join('|');
|
};
|
|
export default function usePolylineRender(params: UsePolylineRenderParams) {
|
const { deps } = 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 routePointsSignatureRef = useRef<string>('empty');
|
const lastRoutePointRef = useRef<{ latitude: number; longitude: number } | null>(null);
|
const lastRouteUpdateAtRef = useRef<number>(0);
|
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;
|
}
|
};
|
|
const applyRoutePoints = (points: any[]) => {
|
const nextPoints = Array.isArray(points) ? points : [];
|
const nextSignature = buildPointsSignature(nextPoints);
|
if (routePointsSignatureRef.current === nextSignature) {
|
return false;
|
}
|
routePointsSignatureRef.current = nextSignature;
|
if (nextPoints.length > 0) {
|
const firstPoint = nextPoints[0];
|
const nextLatitude = Number(firstPoint?.latitude);
|
const nextLongitude = Number(firstPoint?.longitude);
|
if (Number.isFinite(nextLatitude) && Number.isFinite(nextLongitude)) {
|
lastRoutePointRef.current = {
|
latitude: nextLatitude,
|
longitude: nextLongitude
|
};
|
lastRouteUpdateAtRef.current = Date.now();
|
} else {
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
}
|
} else {
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
}
|
setRoutePoints(nextPoints);
|
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([]);
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
setPolyline([]);
|
polylineKeyRef.current = '';
|
routePointsSignatureRef.current = 'empty';
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
lastPolylineUpdateAtRef.current = 0;
|
currentFullRouteRef.current = [];
|
lastErasedIndexRef.current = 0;
|
orderRouteInitializedRef.current = false;
|
routeEraseReadyRef.current = false;
|
pendingRouteEraseRef.current = null;
|
clearRouteEraseReadyTimer();
|
};
|
|
const applyErasedRoutePoints = (newPoints: any[]) => {
|
currentFullRouteRef.current = [...newPoints];
|
lastErasedIndexRef.current = 0;
|
applyRoutePoints([...newPoints]);
|
};
|
|
const erasePassedRouteSegments = (newPoints: any[], _servedPoints: any[] = []) => {
|
const oldPoints = currentFullRouteRef.current;
|
if (!oldPoints?.length || !newPoints?.length) {
|
currentFullRouteRef.current = [...newPoints];
|
applyRoutePoints([...newPoints]);
|
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 = () => {
|
// 对齐 begin-route 当前主链:分享页不单独绘制贴着车辆的浅色已行驶轨迹,
|
// 避免视觉上像车辆 marker 移动后残留一层浅蓝色影子。
|
const shouldShowPassedRoute = false
|
&& deps.tripStatus === TripStatus.InService
|
&& Array.isArray(deps.vehicleMoveAlongPoints)
|
&& deps.vehicleMoveAlongPoints.length > 1;
|
if (!shouldShowPassedRoute) {
|
return [];
|
}
|
return reducePoints(
|
deps.vehicleMoveAlongPoints || [],
|
resolveRoutePointLimit(deps.isAndroid)
|
);
|
};
|
|
const buildPolylineKey = (displayRoutePoints: any[]) => {
|
const passedRoutePoints = getPassedRoutePoints();
|
return [
|
deps.shouldShowRoute ? 1 : 0,
|
deps.statusRule.showWalking ? 1 : 0,
|
buildPointsSignature(displayRoutePoints),
|
buildPointsSignature(passedRoutePoints),
|
buildPointsSignature(walkingPoints)
|
].join('|');
|
};
|
|
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 && 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,
|
points: walkingPoints,
|
color: '#DFB369',
|
width: deps.isAndroid ? 4 : 6,
|
dottedLine: true,
|
arrowLine: false
|
});
|
}
|
return lines;
|
};
|
|
useEffect(() => {
|
const displayRoutePoints = getDisplayRoutePoints();
|
const nextPolylineKey = buildPolylineKey(displayRoutePoints);
|
if (nextPolylineKey === polylineKeyRef.current) {
|
return;
|
}
|
polylineKeyRef.current = nextPolylineKey;
|
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();
|
}
|
setPolyline(buildPolylineLines(displayRoutePoints));
|
lastPolylineUpdateAtRef.current = Date.now();
|
return;
|
}
|
if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
|
setPolyline([]);
|
}
|
lastPolylineUpdateAtRef.current = 0;
|
}, [
|
deps.isAndroid,
|
deps.shouldShowRoute,
|
deps.statusRule.showWalking,
|
deps.tripStatus,
|
deps.vehicleMoveAlongPoints,
|
routePoints,
|
walkingPoints
|
]);
|
|
useEffect(() => {
|
return () => {
|
clearRouteEraseReadyTimer();
|
};
|
}, []);
|
|
return {
|
polyline,
|
setPolyline,
|
routePoints,
|
setRoutePoints,
|
walkingPoints,
|
setWalkingPoints,
|
walkingTime,
|
setWalkingTime,
|
polylineKeyRef,
|
lastRoutePointRef,
|
lastRouteUpdateAtRef,
|
lastPolylineUpdateAtRef,
|
currentFullRouteRef,
|
lastErasedIndexRef,
|
orderRouteInitializedRef,
|
applyRoutePoints,
|
reducePoints,
|
resetRouteRuntimeState,
|
erasePassedRouteSegments,
|
buildPolylineKey,
|
buildPolylineLines
|
};
|
}
|