test: cover SGS hall POI display rules
This commit is contained in:
179
scripts/check-sgs-hall-poi-adapter.mjs
Normal file
179
scripts/check-sgs-hall-poi-adapter.mjs
Normal file
@@ -0,0 +1,179 @@
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
const exhibitionSpaceTypeWhitelist = new Set([
|
||||
'exhibition_hall',
|
||||
'theater',
|
||||
'education_activity',
|
||||
'ramp'
|
||||
])
|
||||
|
||||
const exhibitionSpaceKeywords = [
|
||||
'展厅',
|
||||
'临展',
|
||||
'展览',
|
||||
'影院',
|
||||
'球幕',
|
||||
'巨幕',
|
||||
'报告厅',
|
||||
'活动室',
|
||||
'科普',
|
||||
'实验室',
|
||||
'展览坡道',
|
||||
'宇宙',
|
||||
'地球',
|
||||
'演化',
|
||||
'恐龙',
|
||||
'人类',
|
||||
'生物',
|
||||
'生态',
|
||||
'家园'
|
||||
]
|
||||
|
||||
const normalizedText = (value) => (value || '').trim()
|
||||
const normalizedHallName = (value) => normalizedText(value).replace(/[\s()()【】\[\]_-]/g, '')
|
||||
const hasExhibitionKeyword = (value) => exhibitionSpaceKeywords.some((keyword) => normalizedText(value).includes(keyword))
|
||||
|
||||
const isEligiblePublicExhibitionSpace = (space) => {
|
||||
const type = normalizedText(space.type).toLowerCase()
|
||||
const name = normalizedText(space.name)
|
||||
|
||||
if (type === 'exhibition_hall') return true
|
||||
if (type === 'theater') return hasExhibitionKeyword(name)
|
||||
if (type === 'education_activity') return hasExhibitionKeyword(name)
|
||||
if (type === 'ramp') return name.includes('展览坡道')
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const placeHallName = (place) => (
|
||||
normalizedText(place.ownerName)
|
||||
|| normalizedText(place.name)
|
||||
.replace(/(?:主)?(?:出入口|入口|出口|门)\s*[A-Za-z0-9一二三四五六七八九十号-]*$/u, '')
|
||||
.trim()
|
||||
|| normalizedText(place.name)
|
||||
)
|
||||
|
||||
const isExhibitionHallNavigablePlace = (place) => {
|
||||
const searchableText = [
|
||||
place.name,
|
||||
place.ownerName,
|
||||
place.category,
|
||||
place.type,
|
||||
place.typeCode,
|
||||
place.typeName
|
||||
]
|
||||
.map(normalizedText)
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
return hasExhibitionKeyword(searchableText)
|
||||
}
|
||||
|
||||
const findMatchedHallSpace = (place, spaces) => {
|
||||
const placeName = normalizedHallName(placeHallName(place))
|
||||
if (!placeName) return undefined
|
||||
|
||||
return spaces.find((space) => {
|
||||
const spaceName = normalizedHallName(space.name)
|
||||
return spaceName && (placeName.includes(spaceName) || spaceName.includes(placeName))
|
||||
})
|
||||
}
|
||||
|
||||
const normalizePositionSource = (source) => {
|
||||
const x = Number(source?.x)
|
||||
const y = Number(source?.y)
|
||||
const z = Number(source?.z)
|
||||
if (![x, y, z].every(Number.isFinite)) return undefined
|
||||
return [x, y, z]
|
||||
}
|
||||
|
||||
const normalizePlacePosition = (place) => normalizePositionSource(
|
||||
place.position
|
||||
? { x: place.position.x, y: place.position.y ?? 0, z: place.position.z }
|
||||
: { x: place.x, y: place.y ?? 0, z: place.z }
|
||||
)
|
||||
|
||||
const normalizeSpaceCenter = (space) => normalizePositionSource(space.center || {})
|
||||
|
||||
const createHallPoiId = (space, place, fallbackIndex) => {
|
||||
if (space?.id) return `hall-${space.id}`
|
||||
const ownerName = normalizedHallName(placeHallName(place))
|
||||
if (ownerName) return `hall-place-${ownerName}`
|
||||
return `hall-place-${place.id || fallbackIndex + 1}`
|
||||
}
|
||||
|
||||
const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
|
||||
const hallSpaces = spaces.filter(isEligiblePublicExhibitionSpace)
|
||||
const hallPlaces = navigablePlaces.filter(isExhibitionHallNavigablePlace)
|
||||
const halls = new Map()
|
||||
|
||||
hallPlaces.forEach((place, index) => {
|
||||
const matchedSpace = findMatchedHallSpace(place, hallSpaces)
|
||||
const entrancePosition = normalizePlacePosition(place)
|
||||
const fallbackPosition = matchedSpace ? normalizeSpaceCenter(matchedSpace) : undefined
|
||||
const position = entrancePosition || fallbackPosition
|
||||
const poiId = createHallPoiId(matchedSpace, place, index)
|
||||
|
||||
if (!position) return
|
||||
|
||||
const existing = halls.get(poiId)
|
||||
if (existing) {
|
||||
existing.entrances.push({ name: place.name, positionGltf: entrancePosition })
|
||||
return
|
||||
}
|
||||
|
||||
halls.set(poiId, {
|
||||
id: poiId,
|
||||
name: matchedSpace?.name || placeHallName(place),
|
||||
floorId: String(place.floorId || matchedSpace?.floorId || fallbackFloorId),
|
||||
primaryCategory: 'exhibition_hall',
|
||||
positionGltf: position,
|
||||
sourceConfidence: entrancePosition ? 'backend-sgs-sdk-hall-entrance' : 'backend-sgs-sdk-space-center',
|
||||
entrances: [{ name: place.name, positionGltf: entrancePosition }]
|
||||
})
|
||||
})
|
||||
|
||||
hallSpaces.forEach((space, index) => {
|
||||
const poiId = `hall-${space.id}`
|
||||
if (halls.has(poiId)) return
|
||||
const position = normalizeSpaceCenter(space)
|
||||
if (!position) return
|
||||
halls.set(poiId, {
|
||||
id: poiId,
|
||||
name: space.name,
|
||||
floorId: String(space.floorId || fallbackFloorId),
|
||||
primaryCategory: 'exhibition_hall',
|
||||
positionGltf: position,
|
||||
sourceConfidence: 'backend-sgs-sdk-space-center',
|
||||
entrances: []
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from(halls.values())
|
||||
}
|
||||
|
||||
const spaces = [
|
||||
{ id: 1, name: '展厅1宇宙厅', type: 'exhibition_hall', floorId: 'B2', center: { x: 10, y: 0, z: 20 } },
|
||||
{ id: 2, name: '巨幕影院', type: 'theater', floorId: 'L1', center: { x: 30, y: 0, z: 40 } },
|
||||
{ id: 3, name: '博物馆之友活动室', type: 'education_activity', floorId: 'L4', center: { x: 50, y: 0, z: 60 } },
|
||||
{ id: 4, name: '展览坡道', type: 'ramp', floorId: 'L1.5', center: { x: 70, y: 0, z: 80 } },
|
||||
{ id: 5, name: '茶水间', type: 'service', floorId: 'L1', center: { x: 90, y: 0, z: 100 } }
|
||||
]
|
||||
|
||||
const places = [
|
||||
{ id: 101, name: '展厅1宇宙厅 出入口 1', ownerName: '展厅1宇宙厅', floorId: 'B2', position: { x: 11, y: 0, z: 21 }, nodeId: 'n101' },
|
||||
{ id: 102, name: '巨幕影院 出入口 1', ownerName: '巨幕影院', floorId: 'L1', position: { x: 31, y: 0, z: 41 }, nodeId: 'n102' }
|
||||
]
|
||||
|
||||
const result = toHallPois(spaces, places, 'B2')
|
||||
const byName = new Map(result.map((poi) => [poi.name, poi]))
|
||||
|
||||
assert.equal(result.length, 4)
|
||||
assert.deepEqual(byName.get('展厅1宇宙厅').positionGltf, [11, 0, 21])
|
||||
assert.deepEqual(byName.get('巨幕影院').positionGltf, [31, 0, 41])
|
||||
assert.deepEqual(byName.get('博物馆之友活动室').positionGltf, [50, 0, 60])
|
||||
assert.deepEqual(byName.get('展览坡道').positionGltf, [70, 0, 80])
|
||||
assert.equal(byName.has('茶水间'), false)
|
||||
assert.equal(byName.get('博物馆之友活动室').sourceConfidence, 'backend-sgs-sdk-space-center')
|
||||
|
||||
console.log('SGS hall POI adapter smoke check passed')
|
||||
Reference in New Issue
Block a user