|
|
|
|
@@ -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
|
|
|
|
|
|