修复导览与讲解位置预览逻辑
This commit is contained in:
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user