feat(platform): add amap reverse geocode for trajectory

This commit is contained in:
lingniu
2026-07-04 21:25:32 +08:00
parent cd932dc097
commit da3a639dd8
8 changed files with 405 additions and 1 deletions

View File

@@ -2,7 +2,7 @@ import { Button, Card, Form, Select, SideSheet, Space, Table, Tabs, Tag, Toast,
import { IconCopy, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useState } from 'react';
import { api, type RawFrameQuery } from '../api/client';
import type { HistoryLocationRow, Page, RawFrameRow } from '../api/types';
import type { HistoryLocationRow, MapReverseGeocode, Page, RawFrameRow } from '../api/types';
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
import { PageHeader } from '../components/PageHeader';
import { isAMapConfigured } from '../config/appConfig';
@@ -351,6 +351,7 @@ function trajectoryReviewPackageText({
currentPlayback,
currentPlaybackIndex,
playbackCount,
currentAddress,
anomalySummary
}: {
filters: HistoryFilters;
@@ -368,6 +369,7 @@ function trajectoryReviewPackageText({
currentPlayback?: HistoryLocationRow;
currentPlaybackIndex: number;
playbackCount: number;
currentAddress?: MapReverseGeocode;
anomalySummary: TrajectoryAnomalySummary;
}) {
const vehicle = filters.keyword?.trim() || '';
@@ -390,6 +392,7 @@ function trajectoryReviewPackageText({
`起点:${firstLocation?.deviceTime || firstLocation?.serverTime || '-'}`,
`终点:${lastLocation?.deviceTime || lastLocation?.serverTime || '-'}`,
`当前回放点:${currentPlayback ? `${currentPlaybackIndex + 1}/${playbackCount}${currentPlayback.deviceTime || currentPlayback.serverTime || '-'}${formatNumber(currentPlayback.speedKmh, ' km/h')}${formatNumber(currentPlayback.totalMileageKm, ' km')}` : '-'}`,
`当前点地址:${currentAddress?.formattedAddress || '-'}`,
`异常判读:断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
`最大断点:${formatDurationMinutes(anomalySummary.maxGapMinutes)}`,
`最大里程回退:${formatNumber(anomalySummary.maxMileageRollbackKm, ' km')}`,
@@ -475,6 +478,8 @@ export function History({
const [playbackIndex, setPlaybackIndex] = useState(0);
const [playbackPlaying, setPlaybackPlaying] = useState(false);
const [playbackSpeedMs, setPlaybackSpeedMs] = useState(1200);
const [reverseGeocodeByPoint, setReverseGeocodeByPoint] = useState<Record<string, MapReverseGeocode>>({});
const [reverseGeocoding, setReverseGeocoding] = useState(false);
const buildParams = (nextFilters: HistoryFilters, limit: number, offset: number, raw: boolean) => {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
@@ -507,6 +512,7 @@ export function History({
setLocationPagination({ currentPage: page, pageSize });
setPlaybackIndex(0);
setPlaybackPlaying(false);
setReverseGeocodeByPoint({});
})
.catch((error: Error) => Toast.error(error.message))
.finally(() => setLoadingLocations(false));
@@ -622,6 +628,7 @@ export function History({
const currentPlaybackIndex = playbackRows.length === 0 ? -1 : Math.min(playbackIndex, playbackRows.length - 1);
const currentPlayback = currentPlaybackIndex >= 0 ? playbackRows[currentPlaybackIndex] : undefined;
const currentPlaybackPointId = currentPlayback ? playbackPointId(currentPlayback, currentPlaybackIndex) : undefined;
const currentPlaybackAddress = currentPlaybackPointId ? reverseGeocodeByPoint[currentPlaybackPointId] : undefined;
const playbackAtEnd = playbackRows.length > 0 && currentPlaybackIndex >= playbackRows.length - 1;
const playbackPoints: VehicleMapPoint[] = validLocations.map((row, index) => ({
id: playbackPointId(row, index),
@@ -715,6 +722,7 @@ export function History({
currentPlayback,
currentPlaybackIndex,
playbackCount: playbackRows.length,
currentAddress: currentPlaybackAddress,
anomalySummary: trajectoryAnomalies
}),
'轨迹复盘交接包'
@@ -728,6 +736,28 @@ export function History({
}
window.open(url, '_blank', 'noopener,noreferrer');
};
const resolveCurrentPlaybackAddress = () => {
if (!currentPlayback || !currentPlaybackPointId || !hasValidCoordinate(currentPlayback)) {
Toast.warning('当前回放点没有有效坐标');
return;
}
if (currentPlaybackAddress) {
Toast.info('当前回放点地址已解析');
return;
}
const params = new URLSearchParams({
longitude: String(currentPlayback.longitude),
latitude: String(currentPlayback.latitude)
});
setReverseGeocoding(true);
api.reverseGeocode(params)
.then((address) => {
setReverseGeocodeByPoint((current) => ({ ...current, [currentPlaybackPointId]: address }));
Toast.success('已解析当前点地址');
})
.catch((error: Error) => Toast.error(error.message))
.finally(() => setReverseGeocoding(false));
};
useEffect(() => {
if (!playbackPlaying || playbackRows.length <= 1) return undefined;
const timer = window.setInterval(() => {
@@ -913,6 +943,12 @@ export function History({
<Tag color="green">{formatNumber(currentPlayback.speedKmh, ' km/h')}</Tag>
<Tag color="blue">{formatNumber(currentPlayback.totalMileageKm, ' km')}</Tag>
</Space>
<div className="vp-playback-address">
<Tag color={currentPlaybackAddress ? 'green' : 'grey'}>{currentPlaybackAddress?.provider || '地址未解析'}</Tag>
<Typography.Text type={currentPlaybackAddress ? 'secondary' : 'tertiary'} ellipsis={{ showTooltip: true }}>
{currentPlaybackAddress?.formattedAddress || `${currentPlayback.longitude}, ${currentPlayback.latitude}`}
</Typography.Text>
</div>
<Space wrap>
<Button
size="small"
@@ -937,6 +973,7 @@ export function History({
<Space wrap>
<Button size="small" disabled={currentPlaybackIndex <= 0} onClick={() => movePlayback(-1)}></Button>
<Button size="small" disabled={currentPlaybackIndex >= playbackRows.length - 1} onClick={() => movePlayback(1)}></Button>
<Button size="small" loading={reverseGeocoding} disabled={!hasValidCoordinate(currentPlayback)} onClick={resolveCurrentPlaybackAddress}></Button>
<Button size="small" disabled={!canOpenVehicle(currentPlayback.vin)} onClick={openPlaybackVehicle}></Button>
</Space>
<input