修复导览与讲解位置预览逻辑

This commit is contained in:
lyf
2026-06-30 11:00:49 +08:00
parent f2a33888b1
commit 1c2cc788d1
29 changed files with 1111 additions and 756 deletions

View File

@@ -67,5 +67,7 @@ export const toMuseumPoi = (poi: StaticNavPoiPayload): MuseumPoi => ({
sourceObjectName: poi.sourceObjectName,
sourceConfidence: poi.sourceConfidence,
navigationReadiness: poi.navigationReadiness,
accessible: isPoiAccessible(poi)
accessible: isPoiAccessible(poi),
kind: poi.primaryCategory === 'touring_poi' ? 'hall' : 'facility',
hallName: poi.primaryCategory === 'touring_poi' ? poi.name : undefined
})

View File

@@ -7,6 +7,7 @@ import type {
GuideRouteTarget
} from '@/domain/museum'
import type {
SgsRoutePathPointPayload,
SgsRoutePlanResponsePayload
} from '@/data/providers/sgsSdkApiProvider'
import type {
@@ -31,10 +32,25 @@ type GeoJsonLineString = {
coordinates?: Array<[number, number] | [number, number, number]>
}
const finiteNumber = (value: unknown) => {
const numericValue = Number(value)
return Number.isFinite(numericValue) ? numericValue : undefined
}
const stringifyRouteId = (value: unknown, fallback = '') => {
if (value === null || typeof value === 'undefined' || value === '') return fallback
return String(value)
}
const samePosition = (
a: [number, number, number],
b: [number, number, number]
) => a.every((value, index) => Math.abs(value - b[index]) < 0.001)
const pointPosition = (point: SgsRoutePathNode): [number, number, number] => [
Number(point.x || 0),
Number(point.y || 0),
Number(point.z || 0)
finiteNumber(point.x) ?? 0,
finiteNumber(point.y) ?? 0,
finiteNumber(point.z) ?? 0
]
const targetEndpoint = (target: GuideRouteTarget): GuideRouteEndpoint => ({
@@ -165,8 +181,10 @@ export const toGuideRouteResultFromSgs = (
const floorLabelFor = (
floorId: string,
startTarget: GuideRouteTarget,
endTarget: GuideRouteTarget
endTarget: GuideRouteTarget,
fallbackLabel?: string | null
) => {
if (fallbackLabel) return fallbackLabel
if (floorId === startTarget.floorId) return startTarget.floorLabel
if (floorId === endTarget.floorId) return endTarget.floorLabel
return floorId
@@ -176,19 +194,54 @@ const pointFromBackendNode = (
node: NonNullable<SgsRoutePlanResponsePayload['nodePaths']>[number],
index: number,
fallbackFloorId: string
): GuideRoutePoint => ({
nodeId: String(node.id || `sgs-route-node-${index}`),
floorId: toAppFloorId(node.floorId ?? fallbackFloorId),
position: [
Number(node.x || 0),
0,
Number(node.y || 0)
]
})
): GuideRoutePoint | null => {
const x = finiteNumber(node.x)
const z = finiteNumber(node.y)
if (typeof x !== 'number' || typeof z !== 'number') return null
return {
nodeId: stringifyRouteId(node.id, `sgs-route-node-${index}`),
floorId: stringifyRouteId(node.floorId, fallbackFloorId),
position: [
x,
finiteNumber(node.z) ?? 0,
z
]
}
}
const pointFromPathPoint = (
point: SgsRoutePathPointPayload,
floorId: string,
nodeId: string
): GuideRoutePoint | null => {
const x = finiteNumber(point.x)
const z = finiteNumber(point.z)
if (typeof x !== 'number' || typeof z !== 'number') return null
return {
nodeId,
floorId,
position: [
x,
finiteNumber(point.y) ?? 0,
z
]
}
}
const pointsFromPathPoints = (
pathPoints: SgsRoutePathPointPayload[] | null | undefined,
floorId: string,
nodeIdPrefix: string
) => (pathPoints || [])
.map((point, index) => pointFromPathPoint(point, floorId, `${nodeIdPrefix}-pp-${index}`))
.filter((point): point is GuideRoutePoint => Boolean(point))
const pointsFromGeoJson = (
geoJson: string | null | undefined,
floorId: string
floorId: string,
nodeIdPrefix = floorId
): GuideRoutePoint[] => {
if (!geoJson) return []
@@ -198,20 +251,147 @@ const pointsFromGeoJson = (
? parsed.coordinates
: []
return coordinates.map((coordinate, index) => ({
nodeId: `${floorId}-geo-${index}`,
floorId,
position: [
Number(coordinate[0] || 0),
0,
Number(coordinate[1] || 0)
]
}))
return coordinates
.map((coordinate, index) => {
const x = finiteNumber(coordinate[0])
const z = finiteNumber(coordinate[1])
if (typeof x !== 'number' || typeof z !== 'number') return null
return {
nodeId: `${nodeIdPrefix}-geo-${index}`,
floorId,
position: [
x,
finiteNumber(coordinate[2]) ?? 0,
z
]
}
})
.filter((point): point is GuideRoutePoint => Boolean(point))
} catch {
return []
}
}
const routePointFromEndpoint = (
endpoint: GuideRouteEndpoint,
suffix: string
): GuideRoutePoint => ({
nodeId: endpoint.routeNodeId || `${endpoint.poiId}-${suffix}`,
floorId: endpoint.floorId,
position: endpoint.position
})
const prependIfDifferent = (
points: GuideRoutePoint[],
point: GuideRoutePoint
) => {
const firstPoint = points[0]
return firstPoint && samePosition(firstPoint.position, point.position)
? points
: [point, ...points]
}
const appendIfDifferent = (
points: GuideRoutePoint[],
point: GuideRoutePoint
) => {
const lastPoint = points[points.length - 1]
return lastPoint && samePosition(lastPoint.position, point.position)
? points
: [...points, point]
}
const createPlanFloorSegments = (
points: GuideRoutePoint[],
startTarget: GuideRouteTarget,
endTarget: GuideRouteTarget
): GuideRouteFloorSegment[] => points.reduce<GuideRouteFloorSegment[]>((segments, point) => {
const current = segments[segments.length - 1]
if (current && current.floorId === point.floorId) {
current.points.push(point)
return segments
}
segments.push({
floorId: point.floorId,
floorLabel: floorLabelFor(point.floorId, startTarget, endTarget),
points: [point]
})
return segments
}, [])
const isWalkSegment = (
segment: NonNullable<SgsRoutePlanResponsePayload['segments']>[number]
) => {
const type = stringifyRouteId(segment.transferType || segment.segmentType || segment.type).toUpperCase()
return !type || type === 'WALK'
}
const createSegmentFloorSegments = (
route: SgsRoutePlanResponsePayload,
start: GuideRouteEndpoint,
end: GuideRouteEndpoint,
startTarget: GuideRouteTarget,
endTarget: GuideRouteTarget,
nodePoints: GuideRoutePoint[]
): GuideRouteFloorSegment[] => {
const segments = (route.segments || [])
.filter(isWalkSegment)
.map<GuideRouteFloorSegment | null>((segment, segmentIndex) => {
const floorId = stringifyRouteId(segment.floorId, start.floorId)
const nodeIdPrefix = `${floorId}-segment-${segmentIndex}`
const nodePathIds = new Set(
(segment.nodePathIds || [])
.map((nodeId) => stringifyRouteId(nodeId))
.filter(Boolean)
)
const points = pointsFromPathPoints(segment.pathPoints, floorId, nodeIdPrefix)
const geoPoints = points.length
? points
: pointsFromGeoJson(segment.pathGeoJson, floorId, nodeIdPrefix)
const fallbackPoints = geoPoints.length
? geoPoints
: nodePoints.filter((point) => (
point.floorId === floorId
&& (!nodePathIds.size || nodePathIds.has(point.nodeId))
))
if (!fallbackPoints.length) return null
return {
floorId,
floorLabel: floorLabelFor(floorId, startTarget, endTarget, segment.floorName),
points: fallbackPoints.map((point, index) => ({
...point,
nodeId: point.nodeId || `${nodeIdPrefix}-${index}`
}))
}
})
.filter((segment): segment is GuideRouteFloorSegment => Boolean(segment))
if (!segments.length) return []
const startPoint = routePointFromEndpoint(start, 'start')
const endPoint = routePointFromEndpoint(end, 'end')
const anchoredSegments = segments.map((segment) => ({
...segment,
points: [...segment.points]
}))
const firstSegment = anchoredSegments[0]
const lastSegment = anchoredSegments[anchoredSegments.length - 1]
if (firstSegment?.floorId === start.floorId) {
firstSegment.points = prependIfDifferent(firstSegment.points, startPoint)
}
if (lastSegment?.floorId === end.floorId) {
lastSegment.points = appendIfDifferent(lastSegment.points, endPoint)
}
return anchoredSegments
}
export const toGuideRouteResultFromSgsPlan = (
route: SgsRoutePlanResponsePayload,
startTarget: GuideRouteTarget,
@@ -219,67 +399,39 @@ export const toGuideRouteResultFromSgsPlan = (
): GuideRouteResult => {
const start = targetEndpoint(startTarget)
const end = targetEndpoint(endTarget)
const nodePoints = (route.nodePaths || []).map((node, index) => (
pointFromBackendNode(node, index, start.floorId)
))
const routePoints = nodePoints.length
? [
{
nodeId: start.routeNodeId,
floorId: start.floorId,
position: start.position
},
...nodePoints,
{
nodeId: end.routeNodeId,
floorId: end.floorId,
position: end.position
}
]
: pointsFromGeoJson(route.pathGeoJson, start.floorId)
const fallbackPoints = routePoints.length
? routePoints
: [
{
nodeId: start.routeNodeId,
floorId: start.floorId,
position: start.position
},
{
nodeId: end.routeNodeId,
floorId: end.floorId,
position: end.position
}
]
const floorSegments = route.segments?.length
? route.segments.map((segment, segmentIndex) => {
const floorId = String(segment.floorId || start.floorId)
const points = pointsFromGeoJson(segment.pathGeoJson, floorId)
return {
floorId,
floorLabel: segment.floorName || floorLabelFor(floorId, startTarget, endTarget),
points: points.length
? points
: fallbackPoints.filter((point) => point.floorId === floorId).map((point, index) => ({
...point,
nodeId: point.nodeId || `${floorId}-segment-${segmentIndex}-${index}`
}))
}
}).filter((segment) => segment.points.length)
: createFloorSegments(fallbackPoints, startTarget, endTarget)
const startPoint = routePointFromEndpoint(start, 'start')
const endPoint = routePointFromEndpoint(end, 'end')
const nodePoints = (route.nodePaths || [])
.map((node, index) => pointFromBackendNode(node, index, start.floorId))
.filter((point): point is GuideRoutePoint => Boolean(point))
const segmentFloorSegments = createSegmentFloorSegments(
route,
start,
end,
startTarget,
endTarget,
nodePoints
)
const routeGeoPoints = pointsFromGeoJson(route.pathGeoJson, start.floorId, 'sgs-route')
const fallbackPoints = nodePoints.length
? [startPoint, ...nodePoints, endPoint]
: routeGeoPoints.length
? [startPoint, ...routeGeoPoints, endPoint]
: [startPoint, endPoint]
const floorSegments = segmentFloorSegments.length
? segmentFloorSegments
: createPlanFloorSegments(fallbackPoints, startTarget, endTarget)
const routePoints = floorSegments.flatMap((segment) => segment.points)
return {
id: `sgs-api-route-${start.poiId}-${end.poiId}`,
start,
end,
distanceMeters: Number(route.distance || 0),
nodeIds: fallbackPoints.map((point) => point.nodeId),
points: fallbackPoints,
nodeIds: routePoints.map((point) => point.nodeId),
points: routePoints,
floorSegments,
connectorPoints: extractFloorConnectorPoints(fallbackPoints, startTarget, endTarget)
connectorPoints: extractFloorConnectorPoints(routePoints, startTarget, endTarget)
}
}
@@ -297,8 +449,8 @@ const extractFloorConnectorPoints = (
])
for (let i = 0; i < points.length - 1; i++) {
const currentFloorId = toAppFloorId(points[i].floorId)
const nextFloorId = toAppFloorId(points[i + 1].floorId)
const currentFloorId = points[i].floorId
const nextFloorId = points[i + 1].floorId
if (currentFloorId !== nextFloorId) {
const fromLabel = labels.get(currentFloorId) || currentFloorId

View File

@@ -161,19 +161,30 @@ export interface SgsRouteStepNodePayload {
nodeType?: string | null
x?: number | null
y?: number | null
z?: number | null
}
export interface SgsRoutePathPointPayload {
x?: number | null
y?: number | null
z?: number | null
}
export interface SgsRouteSegmentPayload {
floorId?: string | number | null
floorCode?: string | null
floorName?: string | null
startNodeId?: string | number | null
endNodeId?: string | number | null
startNodeName?: string | null
endNodeName?: string | null
segmentType?: string | null
type?: string | null
transferType?: string | null
distance?: number | null
duration?: number | null
pathGeoJson?: string | null
pathPoints?: SgsRoutePathPointPayload[]
nodePathIds?: Array<string | number | null>
}

View File

@@ -230,7 +230,7 @@ export const createStaticNavAssetsProvider = (
manifestRequest = requestJson<StaticNavManifestPayload>(provider.assetUrl('app_nav_manifest.json'))
.then((data) => {
if (data.status !== 'pass') {
throw new Error('内导览资源包状态不是 pass')
throw new Error('内导览资源包状态不是 pass')
}
manifestCache = data