修复 SGS SDK 导览数据源与模型加载
升级 SDK API 数据契约,补齐 guideStops、业务 POI、诊断与路线数据接口。 修复 SDK 模式下 POI/模型/楼层混用静态数据的问题,并为 ThreeMap 模型加载增加短退避重试。
This commit is contained in:
@@ -350,6 +350,7 @@ const floorExitConfirmThresholdMultiplier = 1.34
|
||||
const floorExitZoomOutIntentMinDeltaRatio = 0.015
|
||||
const floorExitAccumulatedZoomOutRatio = 0.26
|
||||
const floorExitCandidateHoldMs = 900
|
||||
const modelLoadRetryDelaysMs = [300, 900]
|
||||
|
||||
const syncControlInteractionOptions = () => {
|
||||
if (!controls) return
|
||||
@@ -2165,7 +2166,11 @@ const clearSceneData = () => {
|
||||
clearPoiGroupChildren()
|
||||
}
|
||||
|
||||
const loadModel = (
|
||||
const wait = (delayMs: number) => new Promise<void>((resolve) => {
|
||||
window.setTimeout(resolve, delayMs)
|
||||
})
|
||||
|
||||
const loadModelOnce = (
|
||||
url: string,
|
||||
label: string,
|
||||
loadToken?: number,
|
||||
@@ -2193,6 +2198,41 @@ const loadModel = (
|
||||
)
|
||||
})
|
||||
|
||||
const loadModel = async (
|
||||
url: string,
|
||||
label: string,
|
||||
loadToken?: number,
|
||||
options: LoadModelOptions = {}
|
||||
) => {
|
||||
let lastError: unknown = null
|
||||
|
||||
for (let attempt = 0; attempt <= modelLoadRetryDelaysMs.length; attempt += 1) {
|
||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) {
|
||||
throw createStaleModelLoadError()
|
||||
}
|
||||
|
||||
try {
|
||||
return await loadModelOnce(url, label, loadToken, options)
|
||||
} catch (error) {
|
||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) {
|
||||
throw createStaleModelLoadError()
|
||||
}
|
||||
|
||||
lastError = error
|
||||
const retryDelay = modelLoadRetryDelaysMs[attempt]
|
||||
if (retryDelay === undefined) break
|
||||
|
||||
if (!options.suppressProgress) {
|
||||
setProgress(18, `${label} 加载失败,正在重试 ${attempt + 1}/${modelLoadRetryDelaysMs.length}`)
|
||||
}
|
||||
|
||||
await wait(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError instanceof Error ? lastError : new Error('模型资源加载失败')
|
||||
}
|
||||
|
||||
const prepareModel = (model: THREE.Object3D) => {
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
|
||||
@@ -6,10 +6,12 @@ import type {
|
||||
GuideLocationPreview,
|
||||
GuideMapDiagnostics,
|
||||
GuideRouteReadiness,
|
||||
MuseumCategory,
|
||||
MuseumFloor,
|
||||
MuseumPoi
|
||||
} from '@/domain/museum'
|
||||
MuseumCategory,
|
||||
MuseumFloor,
|
||||
MuseumPoi,
|
||||
ExplainGuideStop,
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
NAV_ROUTE_UNAVAILABLE_MESSAGE
|
||||
} from '@/domain/guideReadiness'
|
||||
@@ -17,23 +19,30 @@ import {
|
||||
isIndoorNavigableFloor
|
||||
} from '@/domain/guideFloor'
|
||||
import type {
|
||||
SgsFloorDiagnosticsPayload,
|
||||
SgsMapDiagnosticsPayload,
|
||||
SgsNavigablePlacePayload,
|
||||
SgsPoiPayload,
|
||||
SgsFloorDiagnosticsPayload,
|
||||
SgsGuideStopPayload,
|
||||
SgsMapDiagnosticsPayload,
|
||||
SgsNavigablePlacePayload,
|
||||
SgsPoiPayload,
|
||||
SgsPositionPayload,
|
||||
SgsSdkFloorSummaryPayload,
|
||||
SgsSdkManifestPayload,
|
||||
SgsSpacePayload
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
|
||||
const defaultCategory: MuseumCategory = {
|
||||
id: 'operation_experience',
|
||||
label: '导览点位',
|
||||
iconType: 'poi'
|
||||
}
|
||||
|
||||
const hallCategory: MuseumCategory = {
|
||||
const defaultCategory: MuseumCategory = {
|
||||
id: 'operation_experience',
|
||||
label: '导览点位',
|
||||
iconType: 'poi'
|
||||
}
|
||||
|
||||
const businessCategory: MuseumCategory = {
|
||||
id: 'business_poi',
|
||||
label: '运营服务',
|
||||
iconType: 'business'
|
||||
}
|
||||
|
||||
const hallCategory: MuseumCategory = {
|
||||
id: 'exhibition_hall',
|
||||
label: '展厅',
|
||||
iconType: 'exhibition_hall'
|
||||
@@ -45,7 +54,7 @@ const hallEntranceCategory: MuseumCategory = {
|
||||
iconType: 'hall_entrance'
|
||||
}
|
||||
|
||||
const categoryBySgsType: Record<string, MuseumCategory & { accessible?: boolean }> = {
|
||||
const categoryBySgsType: Record<string, MuseumCategory & { accessible?: boolean }> = {
|
||||
toilet: {
|
||||
id: 'basic_service_facility',
|
||||
label: '卫生间',
|
||||
@@ -237,6 +246,16 @@ export interface SgsHallPoiDiagnostics {
|
||||
skippedPlaceCount: number
|
||||
}
|
||||
|
||||
const businessTypeLabels: Record<string, string> = {
|
||||
shop: '商店',
|
||||
restaurant: '餐饮',
|
||||
cafe: '咖啡',
|
||||
vending: '售卖机',
|
||||
bookstore: '书店',
|
||||
cultural_shop: '文创商店',
|
||||
photo_spot: '打卡点'
|
||||
}
|
||||
|
||||
interface SgsHallPoiBuildOptions {
|
||||
fallbackY?: number
|
||||
}
|
||||
@@ -381,13 +400,23 @@ const normalizePlacePosition = (
|
||||
fallbackY
|
||||
)
|
||||
|
||||
const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolean } => {
|
||||
const normalizedType = poi.type?.trim()
|
||||
if (normalizedType && categoryBySgsType[normalizedType]) {
|
||||
return categoryBySgsType[normalizedType]
|
||||
}
|
||||
|
||||
if (poi.typeName) {
|
||||
const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolean } => {
|
||||
const normalizedType = poi.type?.trim().toLowerCase()
|
||||
const normalizedBusinessType = poi.businessType?.trim().toLowerCase()
|
||||
|
||||
if (String(poi.poiGroup || '').toUpperCase() === 'BUSINESS') {
|
||||
return {
|
||||
...businessCategory,
|
||||
label: normalizedBusinessType ? businessTypeLabels[normalizedBusinessType] || poi.typeName || businessCategory.label : poi.typeName || businessCategory.label,
|
||||
iconType: normalizedBusinessType || businessCategory.iconType
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedType && categoryBySgsType[normalizedType]) {
|
||||
return categoryBySgsType[normalizedType]
|
||||
}
|
||||
|
||||
if (poi.typeName) {
|
||||
return {
|
||||
...defaultCategory,
|
||||
label: poi.typeName,
|
||||
@@ -395,8 +424,15 @@ const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolea
|
||||
}
|
||||
}
|
||||
|
||||
return defaultCategory
|
||||
}
|
||||
return defaultCategory
|
||||
}
|
||||
|
||||
const kindForSgsPoi = (poi: SgsPoiPayload, category: MuseumCategory) => {
|
||||
if (category.id === 'business_poi') return 'facility'
|
||||
if (category.id === 'operation_experience') return 'guide'
|
||||
|
||||
return 'facility'
|
||||
}
|
||||
|
||||
export const toMuseumPoiFromSgs = (
|
||||
poi: SgsPoiPayload,
|
||||
@@ -425,12 +461,41 @@ export const toMuseumPoiFromSgs = (
|
||||
],
|
||||
positionGltf: normalizePosition(poi),
|
||||
sourceObjectName: poi.anchorNodeName || undefined,
|
||||
sourceConfidence: 'backend-sgs-sdk',
|
||||
navigationReadiness: '位置预览',
|
||||
accessible: category.accessible === true,
|
||||
kind: category.id === 'operation_experience' ? 'guide' : 'facility'
|
||||
}
|
||||
}
|
||||
sourceConfidence: 'backend-sgs-sdk',
|
||||
navigationReadiness: '位置预览',
|
||||
accessible: category.accessible === true,
|
||||
kind: kindForSgsPoi(poi, category)
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeAudioTargetType = (targetType?: string | null): AudioPlayTargetType | undefined => {
|
||||
const normalized = targetType?.toUpperCase()
|
||||
if (normalized === 'ITEM' || normalized === 'STOP') return normalized
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const toExplainGuideStopFromSgs = (stop: SgsGuideStopPayload): ExplainGuideStop | null => {
|
||||
const id = stringifyId(stop.id)
|
||||
if (!id) return null
|
||||
|
||||
return {
|
||||
id,
|
||||
name: normalizedText(stop.name) || `讲解点${id}`,
|
||||
hallId: stringifyId(stop.hallId) || undefined,
|
||||
hallName: normalizedText(stop.hallName) || undefined,
|
||||
floorId: stringifyId(stop.floorId) || undefined,
|
||||
targetType: normalizeAudioTargetType(stop.targetType) || 'STOP',
|
||||
targetId: stringifyId(stop.targetId) || id,
|
||||
coverImageUrl: normalizedText(stop.coverImageUrl) || undefined,
|
||||
description: normalizedText(stop.description) || undefined,
|
||||
hasAudio: stop.hasAudio === true || Boolean(normalizedText(stop.audioUrl)),
|
||||
poiId: stringifyId(stop.poiId) || undefined,
|
||||
outlineId: stringifyId(stop.outlineId || stop.routeId) || undefined,
|
||||
outlineName: normalizedText(stop.outlineName || stop.routeName) || undefined,
|
||||
sort: optionalNumber(stop.sort ?? stop.seqOrder)
|
||||
}
|
||||
}
|
||||
|
||||
export const toLocationPreviewFromPoi = (poi: MuseumPoi): GuideLocationPreview => ({
|
||||
poiId: poi.id,
|
||||
|
||||
@@ -10,17 +10,22 @@ import {
|
||||
import type {
|
||||
ExplainContentProvider
|
||||
} from '@/data/providers/staticMuseumContentProvider'
|
||||
import {
|
||||
defaultSgsSdkApiProvider,
|
||||
type SgsSdkApiProvider
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
import {
|
||||
toBackendExplainTrack,
|
||||
toBackendGuideStop,
|
||||
toBackendHallFromList,
|
||||
toBackendMediaAsset,
|
||||
toBackendMuseumExhibit,
|
||||
groupGuideStopsByOutline,
|
||||
type BackendExhibit,
|
||||
type BackendGuideStop,
|
||||
type BackendHall
|
||||
} from '@/data/adapters/backendExplainDataAdapter'
|
||||
import {
|
||||
toExplainGuideStopFromSgs
|
||||
} from '@/data/adapters/sgsSdkGuideAdapter'
|
||||
|
||||
interface CommonResult<T> {
|
||||
code: number
|
||||
@@ -28,7 +33,14 @@ interface CommonResult<T> {
|
||||
data?: T
|
||||
}
|
||||
|
||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
||||
const normalizeBaseUrl = (baseUrl: string) => {
|
||||
const trimmed = baseUrl.trim().replace(/\/+$/, '')
|
||||
if (!trimmed || /^https?:\/\//i.test(trimmed) || trimmed.startsWith('/')) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
return `/${trimmed}`
|
||||
}
|
||||
|
||||
const resolveAppApiBaseUrl = () => {
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.apiBaseUrl)
|
||||
@@ -77,7 +89,10 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
private readonly guideStopCache = new Map<string, ExplainGuideStop[]>()
|
||||
private readonly guideStopInflight = new Map<string, Promise<ExplainGuideStop[]>>()
|
||||
|
||||
constructor(private readonly fallbackProvider: ExplainContentProvider) {}
|
||||
constructor(
|
||||
private readonly fallbackProvider: ExplainContentProvider,
|
||||
private readonly sgsSdkApiProvider: SgsSdkApiProvider = defaultSgsSdkApiProvider
|
||||
) {}
|
||||
|
||||
private async requestStaticExplainExhibits() {
|
||||
const cached = this.searchCache.get('')
|
||||
@@ -223,14 +238,8 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
if (inflight) return inflight
|
||||
|
||||
const promise = (async () => {
|
||||
const url = `${resolveAppApiBaseUrl()}/gis/sdk/halls/${encodeURIComponent(normalizedHallId)}/guide-stops`
|
||||
const response = await requestJson<CommonResult<BackendGuideStop[]>>(url)
|
||||
if (response.code !== 0 || !Array.isArray(response.data)) {
|
||||
throw new Error(response.msg || '后端展厅讲解点加载失败')
|
||||
}
|
||||
|
||||
const stops = response.data
|
||||
.map(toBackendGuideStop)
|
||||
const stops = (await this.sgsSdkApiProvider.getGuideStopsByHall(normalizedHallId))
|
||||
.map(toExplainGuideStopFromSgs)
|
||||
.filter(Boolean) as ExplainGuideStop[]
|
||||
|
||||
this.guideStopCache.set(normalizedHallId, stops)
|
||||
|
||||
@@ -26,6 +26,27 @@ export interface SgsSdkManifestPayload {
|
||||
capabilities?: Record<string, boolean | undefined>
|
||||
}
|
||||
|
||||
export type SgsPoiGroupPayload = 'SERVICE' | 'BUSINESS' | 'OTHER'
|
||||
|
||||
export type SgsBusinessPoiTypePayload =
|
||||
| 'shop'
|
||||
| 'restaurant'
|
||||
| 'cafe'
|
||||
| 'vending'
|
||||
| 'bookstore'
|
||||
| 'cultural_shop'
|
||||
| 'photo_spot'
|
||||
| string
|
||||
|
||||
export interface SgsPoiQueryParamsPayload {
|
||||
mapId?: string | number
|
||||
floorId?: string | number
|
||||
group?: SgsPoiGroupPayload
|
||||
types?: string[]
|
||||
keyword?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface SgsPositionPayload {
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
@@ -47,6 +68,14 @@ export interface SgsPoiPayload {
|
||||
anchorNodeName?: string | null
|
||||
description?: string | null
|
||||
iconUrl?: string | null
|
||||
poiGroup?: SgsPoiGroupPayload | string | null
|
||||
businessType?: SgsBusinessPoiTypePayload | null
|
||||
coverImageUrl?: string | null
|
||||
phone?: string | null
|
||||
businessHours?: string | null
|
||||
sortOrder?: number | null
|
||||
spatialAreaId?: string | number | null
|
||||
spatialAreaName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsSpacePayload {
|
||||
@@ -79,6 +108,36 @@ export interface SgsNavigablePlacePayload {
|
||||
ownerName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsGuideStopPayload {
|
||||
id: string | number
|
||||
name?: string | null
|
||||
type?: string | null
|
||||
typeName?: string | null
|
||||
floorId?: string | number | null
|
||||
position?: SgsPositionPayload | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
targetType?: string | null
|
||||
targetId?: string | number | null
|
||||
audioUrl?: string | null
|
||||
coverImageUrl?: string | null
|
||||
description?: string | null
|
||||
status?: string | null
|
||||
located?: boolean | null
|
||||
hasAudio?: boolean | null
|
||||
poiId?: string | number | null
|
||||
outlineId?: string | number | null
|
||||
outlineName?: string | null
|
||||
hallId?: string | number | null
|
||||
hallName?: string | null
|
||||
sort?: number | null
|
||||
routeId?: string | number | null
|
||||
routeName?: string | null
|
||||
seqOrder?: number | null
|
||||
stayMinutes?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFloorDiagnosticsPayload {
|
||||
floorId: string | number
|
||||
floorCode?: string | null
|
||||
@@ -121,7 +180,7 @@ export interface SgsFloorBundlePayload {
|
||||
model?: SgsModelInfoPayload | null
|
||||
pois?: SgsPoiPayload[]
|
||||
spaces?: SgsSpacePayload[]
|
||||
guideStops?: unknown[]
|
||||
guideStops?: SgsGuideStopPayload[]
|
||||
routeSummary?: {
|
||||
hasRouteNetwork?: boolean
|
||||
nodeCount?: number
|
||||
@@ -196,6 +255,54 @@ export interface SgsRoutePlanResponsePayload {
|
||||
segments?: SgsRouteSegmentPayload[]
|
||||
}
|
||||
|
||||
export interface SgsRouteEndpointPayload {
|
||||
id?: string | number | null
|
||||
name?: string | null
|
||||
floorId?: string | number | null
|
||||
floorCode?: string | null
|
||||
floorName?: string | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteSummaryPayload {
|
||||
id: string | number
|
||||
mapId?: string | number | null
|
||||
routeName?: string | null
|
||||
routeNameEn?: string | null
|
||||
routeType?: string | null
|
||||
theme?: string | null
|
||||
description?: string | null
|
||||
coverImageUrl?: string | null
|
||||
estimatedDuration?: number | null
|
||||
distanceMeters?: number | null
|
||||
waypointCount?: number | null
|
||||
sortOrder?: number | null
|
||||
status?: string | number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteWaypointPayload {
|
||||
order?: number | null
|
||||
spaceId?: string | number | null
|
||||
spaceName?: string | null
|
||||
guideStopId?: string | number | null
|
||||
guideStopName?: string | null
|
||||
floorId?: string | number | null
|
||||
floorCode?: string | null
|
||||
floorName?: string | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
stayMinutes?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteDetailPayload extends SgsFeaturedRouteSummaryPayload {
|
||||
startEntrance?: SgsRouteEndpointPayload | null
|
||||
endEntrance?: SgsRouteEndpointPayload | null
|
||||
waypoints?: SgsFeaturedRouteWaypointPayload[]
|
||||
}
|
||||
|
||||
interface CommonResult<T> {
|
||||
code: number
|
||||
msg?: string
|
||||
@@ -209,7 +316,19 @@ export interface SgsSdkApiProvider {
|
||||
getFloorBundle(floorId: string): Promise<SgsFloorBundlePayload>
|
||||
getFloorPois(floorId: string): Promise<SgsPoiPayload[]>
|
||||
getFloorSpaces(floorId: string): Promise<SgsSpacePayload[]>
|
||||
getGuideStops(floorId: string): Promise<SgsGuideStopPayload[]>
|
||||
getGuideStopsByHall(hallId: string): Promise<SgsGuideStopPayload[]>
|
||||
getNavigablePlaces(floorId: string): Promise<SgsNavigablePlacePayload[]>
|
||||
getFloorBusinessPois(
|
||||
floorId: string,
|
||||
options?: { types?: string[]; keyword?: string; limit?: number }
|
||||
): Promise<SgsPoiPayload[]>
|
||||
queryPois(params: SgsPoiQueryParamsPayload): Promise<SgsPoiPayload[]>
|
||||
getFeaturedRoutes(
|
||||
mapId?: string,
|
||||
options?: { limit?: number }
|
||||
): Promise<SgsFeaturedRouteSummaryPayload[]>
|
||||
getFeaturedRoute(routeId: string): Promise<SgsFeaturedRouteDetailPayload>
|
||||
planRoute(payload: SgsRoutePlanRequestPayload): Promise<SgsRoutePlanResponsePayload>
|
||||
}
|
||||
|
||||
@@ -295,6 +414,21 @@ const requestJson = <T>(
|
||||
})
|
||||
})
|
||||
|
||||
const buildQueryString = (params: object) => {
|
||||
const query = new URLSearchParams()
|
||||
|
||||
Object.entries(params as Record<string, unknown>).forEach(([key, value]) => {
|
||||
if (value === null || typeof value === 'undefined' || value === '') return
|
||||
|
||||
query.set(key, Array.isArray(value) ? value.join(',') : String(value))
|
||||
})
|
||||
|
||||
const queryString = query.toString()
|
||||
return queryString ? `?${queryString}` : ''
|
||||
}
|
||||
|
||||
const stableCacheKey = (value: unknown) => JSON.stringify(value)
|
||||
|
||||
export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
const manifestCache = new Map<string, SgsSdkManifestPayload>()
|
||||
const mapDiagnosticsCache = new Map<string, SgsMapDiagnosticsPayload>()
|
||||
@@ -302,10 +436,17 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
const floorBundleCache = new Map<string, SgsFloorBundlePayload>()
|
||||
const floorPoiCache = new Map<string, SgsPoiPayload[]>()
|
||||
const floorSpaceCache = new Map<string, SgsSpacePayload[]>()
|
||||
const floorGuideStopCache = new Map<string, SgsGuideStopPayload[]>()
|
||||
const hallGuideStopCache = new Map<string, SgsGuideStopPayload[]>()
|
||||
const navigablePlaceCache = new Map<string, SgsNavigablePlacePayload[]>()
|
||||
const floorBusinessPoiCache = new Map<string, SgsPoiPayload[]>()
|
||||
const poiQueryCache = new Map<string, SgsPoiPayload[]>()
|
||||
const featuredRoutesCache = new Map<string, SgsFeaturedRouteSummaryPayload[]>()
|
||||
const featuredRouteDetailCache = new Map<string, SgsFeaturedRouteDetailPayload>()
|
||||
|
||||
const mapIdFor = (mapId?: string) => mapId || dataSourceConfig.sgsMapId
|
||||
const floorIdFor = (floorId: string) => encodeURIComponent(floorId)
|
||||
const hallIdFor = (hallId: string) => encodeURIComponent(hallId)
|
||||
|
||||
return {
|
||||
async getManifest(mapId) {
|
||||
@@ -370,6 +511,26 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
floorSpaceCache.set(floorId, spaces)
|
||||
return spaces
|
||||
},
|
||||
async getGuideStops(floorId) {
|
||||
const cached = floorGuideStopCache.get(floorId)
|
||||
if (cached) return cached
|
||||
|
||||
const guideStops = await requestJson<SgsGuideStopPayload[]>(
|
||||
`/gis/sdk/floors/${floorIdFor(floorId)}/guide-stops`
|
||||
)
|
||||
floorGuideStopCache.set(floorId, guideStops)
|
||||
return guideStops
|
||||
},
|
||||
async getGuideStopsByHall(hallId) {
|
||||
const cached = hallGuideStopCache.get(hallId)
|
||||
if (cached) return cached
|
||||
|
||||
const guideStops = await requestJson<SgsGuideStopPayload[]>(
|
||||
`/gis/sdk/halls/${hallIdFor(hallId)}/guide-stops`
|
||||
)
|
||||
hallGuideStopCache.set(hallId, guideStops)
|
||||
return guideStops
|
||||
},
|
||||
async getNavigablePlaces(floorId) {
|
||||
const cached = navigablePlaceCache.get(floorId)
|
||||
if (cached) return cached
|
||||
@@ -380,6 +541,50 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
navigablePlaceCache.set(floorId, places)
|
||||
return places
|
||||
},
|
||||
async getFloorBusinessPois(floorId, options = {}) {
|
||||
const cacheKey = `${floorId}:${stableCacheKey(options)}`
|
||||
const cached = floorBusinessPoiCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const pois = await requestJson<SgsPoiPayload[]>(
|
||||
`/gis/sdk/floors/${floorIdFor(floorId)}/business-pois${buildQueryString(options)}`
|
||||
)
|
||||
floorBusinessPoiCache.set(cacheKey, pois)
|
||||
return pois
|
||||
},
|
||||
async queryPois(params) {
|
||||
const cacheKey = stableCacheKey(params)
|
||||
const cached = poiQueryCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const pois = await requestJson<SgsPoiPayload[]>(
|
||||
`/gis/sdk/pois${buildQueryString(params)}`
|
||||
)
|
||||
poiQueryCache.set(cacheKey, pois)
|
||||
return pois
|
||||
},
|
||||
async getFeaturedRoutes(mapId, options = {}) {
|
||||
const normalizedMapId = mapIdFor(mapId)
|
||||
const cacheKey = `${normalizedMapId}:${stableCacheKey(options)}`
|
||||
const cached = featuredRoutesCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const routes = await requestJson<SgsFeaturedRouteSummaryPayload[]>(
|
||||
`/gis/sdk/maps/${encodeURIComponent(normalizedMapId)}/featured-routes${buildQueryString(options)}`
|
||||
)
|
||||
featuredRoutesCache.set(cacheKey, routes)
|
||||
return routes
|
||||
},
|
||||
async getFeaturedRoute(routeId) {
|
||||
const cached = featuredRouteDetailCache.get(routeId)
|
||||
if (cached) return cached
|
||||
|
||||
const route = await requestJson<SgsFeaturedRouteDetailPayload>(
|
||||
`/gis/sdk/featured-routes/${encodeURIComponent(routeId)}`
|
||||
)
|
||||
featuredRouteDetailCache.set(routeId, route)
|
||||
return route
|
||||
},
|
||||
async planRoute(payload) {
|
||||
return requestJson<SgsRoutePlanResponsePayload>(
|
||||
'/gis/sdk/routes/plan',
|
||||
|
||||
@@ -96,12 +96,8 @@ import type {
|
||||
GuideModelSource,
|
||||
GuideRenderPoi
|
||||
} from '@/domain/guideModel'
|
||||
import {
|
||||
StaticGuideModelRepository
|
||||
} from '@/repositories/GuideModelRepository'
|
||||
|
||||
const fallbackIndoorModelSource = new StaticGuideModelRepository()
|
||||
const indoorModelSource = ref<GuideModelSource>(fallbackIndoorModelSource)
|
||||
const indoorModelSource: GuideModelSource = guideUseCase.getModelSource()
|
||||
const guideFloors = ref<MuseumFloor[]>([])
|
||||
const normalizeGuideFloorId = (labelOrId: string) =>
|
||||
guideFloors.value.find((floor) => floor.id === labelOrId || floor.label === labelOrId)?.id
|
||||
@@ -123,19 +119,16 @@ const facility = ref<FacilityDetailViewModel>({
|
||||
|
||||
const loadGuideFloors = async () => {
|
||||
try {
|
||||
const modelPackage = await fallbackIndoorModelSource.loadPackage()
|
||||
guideFloors.value = modelPackage.floors.map((floor) => ({
|
||||
id: floor.floorId,
|
||||
label: floor.label,
|
||||
order: floor.order
|
||||
}))
|
||||
guideFloors.value = await guideUseCase.getFloors()
|
||||
const floors = guideFloors.value
|
||||
const defaultFloor = floors.find((floor) => floor.label === activeFloor.value)?.label
|
||||
|| floors.find((floor) => floor.id === 'L1')?.label
|
||||
|| floors[0]?.label
|
||||
const normalizedActiveFloorId = activeFloor.value
|
||||
? normalizeGuideFloorId(activeFloor.value)
|
||||
: ''
|
||||
const defaultFloor = floors.find((floor) => floor.id === normalizedActiveFloorId)?.id
|
||||
|| floors.find((floor) => floor.label === '1F')?.id
|
||||
|| floors[0]?.id
|
||||
|| activeFloor.value
|
||||
activeFloor.value = floors.find((floor) => floor.label === activeFloor.value)?.label
|
||||
|| defaultFloor
|
||||
activeFloor.value = defaultFloor
|
||||
} catch (error) {
|
||||
console.error('加载导览楼层失败:', error)
|
||||
guideFloors.value = []
|
||||
@@ -146,7 +139,7 @@ const focusFacilityPoi = (poi: MuseumPoi) => {
|
||||
if (!poi.floorId) return
|
||||
|
||||
activeMode.value = '3d'
|
||||
activeFloor.value = poi.floorLabel
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
@@ -164,7 +157,7 @@ const focusRenderPoi = (poi: GuideRenderPoi) => {
|
||||
|
||||
const floorLabel = guideFloors.value.find((floor) => floor.id === poi.floorId)?.label || poi.floorId
|
||||
activeMode.value = '3d'
|
||||
activeFloor.value = floorLabel
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
@@ -242,10 +235,10 @@ const resolveRenderPoiByName = async () => {
|
||||
const normalizedTarget = normalizePoiName(facility.value.name)
|
||||
if (!normalizedTarget) return null
|
||||
|
||||
const modelPackage = await indoorModelSource.value.loadPackage()
|
||||
const modelPackage = await indoorModelSource.loadPackage()
|
||||
for (const floor of modelPackage.floors) {
|
||||
const floorId = floor.floorId
|
||||
const pois = await indoorModelSource.value.loadFloorPois(floorId).catch(() => [])
|
||||
const pois = await indoorModelSource.loadFloorPois(floorId).catch(() => [])
|
||||
const matchedPoi = pois.find((poi) => {
|
||||
const poiName = normalizePoiName(poi.name)
|
||||
const hallName = normalizePoiName(poi.hallName || '')
|
||||
|
||||
@@ -255,7 +255,6 @@ export class SgsSdkGuideRepository implements GuideRepository {
|
||||
private floorsCache: MuseumFloor[] | null = null
|
||||
private poiCache: MuseumPoi[] | null = null
|
||||
private floorAliases: Map<string, string> | null = null
|
||||
private readonly staticFallback = new StaticGuideRepository()
|
||||
|
||||
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
|
||||
|
||||
@@ -331,7 +330,7 @@ export class SgsSdkGuideRepository implements GuideRepository {
|
||||
const pois = await this.listPois()
|
||||
return pois.find((poi) => poi.id === id)
|
||||
|| findPoiByNavIdNameFallback(pois, id)
|
||||
|| this.staticFallback.getPoiById(id)
|
||||
|| null
|
||||
}
|
||||
|
||||
async searchPois(keyword = '') {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@/repositories/GuideRouteRepository'
|
||||
import {
|
||||
defaultSgsSdkApiProvider,
|
||||
type SgsFloorDiagnosticsPayload,
|
||||
type SgsNavigablePlacePayload,
|
||||
type SgsSdkApiProvider
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
@@ -29,13 +30,40 @@ const createUnavailableReadiness = (message: string): GuideRouteReadiness => ({
|
||||
})
|
||||
|
||||
const logSdkRouteReadinessIssue = (message: string, payload: Record<string, unknown>) => {
|
||||
console.error(`[SDKRouteReadiness] ${message}`, payload)
|
||||
console.warn(`[SDKRouteReadiness] ${message}`, payload)
|
||||
}
|
||||
|
||||
const toNumber = (value: unknown) => (
|
||||
Number.isFinite(Number(value)) ? Number(value) : undefined
|
||||
)
|
||||
|
||||
const isPositiveNumber = (value: unknown) => {
|
||||
const numberValue = toNumber(value)
|
||||
return typeof numberValue === 'number' && numberValue > 0
|
||||
}
|
||||
|
||||
const hasRouteNodeId = (place: SgsNavigablePlacePayload) => (
|
||||
place.nodeId !== null && place.nodeId !== undefined && place.nodeId !== ''
|
||||
)
|
||||
|
||||
const hasRouteGraphEvidence = (floor: Pick<SgsFloorDiagnosticsPayload, 'routePlanningReady' | 'routeNodeCount' | 'routeEdgeCount'>) => (
|
||||
floor.routePlanningReady === true
|
||||
|| (isPositiveNumber(floor.routeNodeCount) && isPositiveNumber(floor.routeEdgeCount))
|
||||
)
|
||||
|
||||
const hasRouteTargetEvidence = (
|
||||
floor: Pick<SgsFloorDiagnosticsPayload, 'navigablePlaceCount'>,
|
||||
routeTargetCount?: number
|
||||
) => {
|
||||
if (typeof routeTargetCount === 'number') return routeTargetCount > 0
|
||||
return floor.navigablePlaceCount === undefined || isPositiveNumber(floor.navigablePlaceCount)
|
||||
}
|
||||
|
||||
const isRouteReadyFromDiagnostics = (
|
||||
floor: Pick<SgsFloorDiagnosticsPayload, 'routePlanningReady' | 'routeNodeCount' | 'routeEdgeCount' | 'navigablePlaceCount'>,
|
||||
routeTargetCount?: number
|
||||
) => hasRouteGraphEvidence(floor) && hasRouteTargetEvidence(floor, routeTargetCount)
|
||||
|
||||
const positionFromPlace = (place: SgsNavigablePlacePayload): [number, number, number] | undefined => {
|
||||
const source = place.position || {
|
||||
x: place.x,
|
||||
@@ -70,36 +98,42 @@ export class SdkGuideRouteRepository {
|
||||
return createUnavailableReadiness('SDK 地图楼层数据为空')
|
||||
}
|
||||
|
||||
// 收集有路线数据的楼层
|
||||
const readyFloors = floors.filter(
|
||||
(floor) =>
|
||||
floor.routePlanningReady &&
|
||||
floor.routeNodeCount &&
|
||||
floor.routeNodeCount > 0 &&
|
||||
floor.routeEdgeCount &&
|
||||
floor.routeEdgeCount > 0
|
||||
)
|
||||
// 地图级 diagnostics 的楼层摘要只保证 routePlanningReady/navigablePlaceCount;
|
||||
// routeNodeCount/routeEdgeCount 可能只出现在 floor diagnostics 或 summary 中。
|
||||
let readyFloors = floors.filter((floor) => isRouteReadyFromDiagnostics(floor))
|
||||
let notReadyFloors = floors.filter((floor) => !isRouteReadyFromDiagnostics(floor))
|
||||
|
||||
if (readyFloors.length === 0) {
|
||||
const floorEvidence = await Promise.all(
|
||||
floors.map((floor) => this.loadFloorRouteEvidence(floor))
|
||||
)
|
||||
readyFloors = floorEvidence
|
||||
.filter((item) => item.ready)
|
||||
.map((item) => item.floor)
|
||||
notReadyFloors = floorEvidence
|
||||
.filter((item) => !item.ready)
|
||||
.map((item) => item.floor)
|
||||
}
|
||||
|
||||
// 没有楼层有路线数据时不可用
|
||||
if (readyFloors.length === 0) {
|
||||
logSdkRouteReadinessIssue('SDK 路线数据不完整', {
|
||||
logSdkRouteReadinessIssue('SDK 路线数据未达到位置关系生成条件', {
|
||||
floorCount: floors.length,
|
||||
readyFloorCount: readyFloors.length,
|
||||
summary: diagnostics.summary,
|
||||
floors: floors.map((floor) => ({
|
||||
floorId: floor.floorId,
|
||||
floorName: floor.floorName,
|
||||
floorCode: floor.floorCode,
|
||||
routePlanningReady: floor.routePlanningReady,
|
||||
routeNodeCount: floor.routeNodeCount,
|
||||
routeEdgeCount: floor.routeEdgeCount
|
||||
routeEdgeCount: floor.routeEdgeCount,
|
||||
navigablePlaceCount: floor.navigablePlaceCount
|
||||
}))
|
||||
})
|
||||
return createUnavailableReadiness('无法定位,当前仅支持点位位置预览')
|
||||
}
|
||||
|
||||
// 收集未就绪的楼层
|
||||
const notReadyFloors = floors.filter((floor) => !floor.routePlanningReady)
|
||||
|
||||
// 如果有楼层未就绪,给出警告但仍然可用
|
||||
if (notReadyFloors.length > 0) {
|
||||
const floorLabels = notReadyFloors
|
||||
@@ -125,6 +159,25 @@ export class SdkGuideRouteRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadFloorRouteEvidence(floor: SgsFloorDiagnosticsPayload) {
|
||||
const rawFloorId = stringifyId(floor.floorId)
|
||||
const [floorDiagnostics, navigablePlaces] = await Promise.all([
|
||||
this.sdkApiProvider.getFloorDiagnostics(rawFloorId).catch(() => null),
|
||||
this.sdkApiProvider.getNavigablePlaces(rawFloorId).catch(() => [])
|
||||
])
|
||||
const routeTargetCount = navigablePlaces.filter(hasRouteNodeId).length
|
||||
const evidence = floorDiagnostics || floor
|
||||
|
||||
return {
|
||||
floor: {
|
||||
...floor,
|
||||
...floorDiagnostics
|
||||
},
|
||||
ready: isRouteReadyFromDiagnostics(evidence, routeTargetCount),
|
||||
routeTargetCount
|
||||
}
|
||||
}
|
||||
|
||||
async listRouteTargets(): Promise<GuideRouteTarget[]> {
|
||||
if (this.targetsCache) return this.targetsCache
|
||||
|
||||
@@ -141,7 +194,7 @@ export class SdkGuideRouteRepository {
|
||||
|
||||
for (const place of places) {
|
||||
// 过滤掉没有 nodeId 的点位,这些无法用于路线规划
|
||||
if (place.nodeId === null || place.nodeId === undefined || place.nodeId === '') {
|
||||
if (!hasRouteNodeId(place)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type SgsMapNode =
|
||||
| { type: 'coord'; floorId: string | number; x: number; z: number; y?: number }
|
||||
| { type: 'poiId'; value: number }
|
||||
| { type: 'nodeName'; value: string }
|
||||
| { type: 'poiId'; value: string | number; floorId?: string | number }
|
||||
| { type: 'nodeName'; value: string; floorId?: string | number }
|
||||
|
||||
export interface SgsSnapDetail {
|
||||
original: { x: number; z: number }
|
||||
@@ -14,13 +14,14 @@ export interface SgsRouteResult {
|
||||
distance: number
|
||||
duration: number
|
||||
pathPoints: Array<{ x: number; y: number; z: number }>
|
||||
startSnap: SgsSnapDetail
|
||||
endSnap: SgsSnapDetail
|
||||
computeTimeMs: number
|
||||
polyCount: number
|
||||
startSnap?: SgsSnapDetail
|
||||
endSnap?: SgsSnapDetail
|
||||
computeTimeMs?: number
|
||||
polyCount?: number
|
||||
warnings: string[]
|
||||
errorCode?: string
|
||||
message?: string
|
||||
segments?: SgsRouteSegment[]
|
||||
}
|
||||
|
||||
export interface SgsMapSDKOptions {
|
||||
@@ -28,17 +29,39 @@ export interface SgsMapSDKOptions {
|
||||
sdkUrl?: string
|
||||
targetOrigin?: string
|
||||
floorId?: string | number
|
||||
mapId?: string | number
|
||||
timeout?: number
|
||||
readyTimeout?: number
|
||||
}
|
||||
|
||||
export interface SgsMapEvents {
|
||||
ready: { engineVersion: string; protocolVersion: number }
|
||||
poiClick: { id?: number; poiId?: number; name: string; type: string; floorId?: string | number; description?: string }
|
||||
poiClick: { id: string | number; poiId?: string | number; name: string; type: string; floorId?: string | number; description?: string }
|
||||
floorChanged: { floorId: string | number }
|
||||
error: { code: string; message: string }
|
||||
loadError: { type: string; detail: string }
|
||||
routeTransfer: {
|
||||
currentFloorId: string | number
|
||||
targetFloorId: string | number
|
||||
fromFloorId: string | number
|
||||
fromNodeName: string
|
||||
toNodeName: string
|
||||
transferType: SgsRouteTransferType
|
||||
connectorName?: string
|
||||
segmentIndex: number
|
||||
totalSegments: number
|
||||
routeId?: string
|
||||
}
|
||||
modelLoading: { floorId: string | number; modelUrl: string }
|
||||
modelReady: { floorId: string | number; parseTimeMs: number }
|
||||
modelError: { floorId: string | number; error: string; fallbackUsed: boolean }
|
||||
floorBundleReady: { floorId: string | number; dataVersion: string }
|
||||
routeReady: { routeId: string; distance: number; duration: number; segmentCount?: number; hasTransfer?: boolean }
|
||||
}
|
||||
|
||||
export type SgsRouteTransferType = 'ELEVATOR' | 'STAIRS' | 'ESCALATOR'
|
||||
export type SgsRouteSegmentType = 'WALK' | 'TRANSFER'
|
||||
|
||||
export type SgsErrorCode =
|
||||
| 'ERR_TIMEOUT'
|
||||
| 'ERR_NO_PATH'
|
||||
@@ -50,6 +73,9 @@ export type SgsErrorCode =
|
||||
| 'ERR_UNAUTHORIZED'
|
||||
| 'ERR_UNSUPPORTED'
|
||||
| 'ERR_FAILED'
|
||||
| 'ERR_DESTROYED'
|
||||
| 'ERR_IFRAME_LOAD'
|
||||
| 'ERR_READY_TIMEOUT'
|
||||
|
||||
export interface SgsMarkerConfig {
|
||||
id: string
|
||||
@@ -64,21 +90,316 @@ export interface SgsMapSDKInstance {
|
||||
off<K extends keyof SgsMapEvents>(event: K, listener: (payload: SgsMapEvents[K]) => void): void
|
||||
getIsReady(): boolean
|
||||
getCurrentFloor(): string | number
|
||||
getMapId(): string | number
|
||||
getVersion(): string
|
||||
whenReady(): Promise<unknown>
|
||||
ready(): Promise<unknown>
|
||||
focusTo(node: SgsMapNode | string): void
|
||||
changeFloor(floorId: string | number, timeout?: number): Promise<{ success: boolean; floorId: string | number }>
|
||||
addMarker(config: SgsMarkerConfig): void
|
||||
removeMarker(markerId: string): void
|
||||
clearMarkers(): void
|
||||
highlightPolygon(nodeName: string): void
|
||||
highlightPolygon(payload: string | object): void
|
||||
planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: unknown, timeout?: number): Promise<SgsRouteResult>
|
||||
planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: unknown, timeout?: number): Promise<SgsRouteResult>
|
||||
clearRoute(): void
|
||||
getState(): Promise<unknown>
|
||||
resetView(): void
|
||||
setViewMode(mode: '2d' | '3d'): void
|
||||
resetView(timeout?: number): Promise<{ success: boolean }>
|
||||
setViewMode(mode: '2d' | '3d', timeout?: number): Promise<{ success: boolean; mode: string }>
|
||||
toggleLayer(types: string[], visible: boolean, timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }>
|
||||
setVisibleLayers(types: string[], timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }>
|
||||
setRouteViewActive(active: boolean, timeout?: number): Promise<{ success: boolean; active: boolean }>
|
||||
getManifest(timeout?: number): Promise<SgsSdkManifest>
|
||||
loadFloorBundle(floorId: string | number, timeout?: number): Promise<SgsFloorBundle>
|
||||
getFloorPois(floorId: string | number, timeout?: number): Promise<SgsPoi[]>
|
||||
getNavigablePlaces(floorId: string | number, timeout?: number): Promise<SgsNavigablePlace[]>
|
||||
getPois(floorCode: string, timeout?: number): Promise<SgsPoi[]>
|
||||
getSpaces(floorId: string | number, timeout?: number): Promise<SgsSpaceArea[]>
|
||||
getGuideStops(floorId: string | number, timeout?: number): Promise<SgsGuideStop[]>
|
||||
getGuideStopsByHall(hallId: string | number, timeout?: number): Promise<SgsGuideStop[]>
|
||||
preloadFloor(floorId: string | number, timeout?: number): Promise<{ cached: boolean }>
|
||||
getPerformanceStats(timeout?: number): Promise<SgsPerformanceStats>
|
||||
getDiagnostics(timeout?: number): Promise<SgsMapDiagnostics>
|
||||
getFloorDiagnostics(floorId: string | number, timeout?: number): Promise<SgsFloorDiagnostics>
|
||||
clearModelCache(): void
|
||||
getBusinessPois(floorId: string | number, options?: { types?: string[]; keyword?: string; limit?: number }, timeout?: number): Promise<SgsPoi[]>
|
||||
queryPois(params: SgsPoiQueryParams, timeout?: number): Promise<SgsPoi[]>
|
||||
getFeaturedRoutes(mapId?: string | number, options?: { limit?: number }, timeout?: number): Promise<SgsFeaturedRouteSummary[]>
|
||||
getFeaturedRoute(routeId: string | number, timeout?: number): Promise<SgsFeaturedRouteDetail>
|
||||
showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ success: boolean; routeId: string | number }>
|
||||
clearFlowline(): void
|
||||
destroy(): void
|
||||
}
|
||||
|
||||
export type SgsPoiGroup = 'SERVICE' | 'BUSINESS' | 'OTHER'
|
||||
export type SgsBusinessPoiType =
|
||||
| 'shop'
|
||||
| 'restaurant'
|
||||
| 'cafe'
|
||||
| 'vending'
|
||||
| 'bookstore'
|
||||
| 'cultural_shop'
|
||||
| 'photo_spot'
|
||||
| string
|
||||
|
||||
export interface SgsPoiQueryParams {
|
||||
mapId?: string | number
|
||||
floorId?: string | number
|
||||
group?: SgsPoiGroup
|
||||
types?: string[]
|
||||
keyword?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface SgsPoi {
|
||||
id: string | number
|
||||
name: string
|
||||
type: string
|
||||
typeName: string
|
||||
floorCode: string
|
||||
floorId?: string | number
|
||||
position: { x: number; y: number; z: number }
|
||||
status: 'ACTIVE' | 'INACTIVE'
|
||||
anchorNodeName?: string | null
|
||||
description?: string
|
||||
iconUrl?: string
|
||||
poiGroup?: SgsPoiGroup
|
||||
businessType?: SgsBusinessPoiType
|
||||
coverImageUrl?: string | null
|
||||
phone?: string | null
|
||||
businessHours?: string | null
|
||||
sortOrder?: number | null
|
||||
spatialAreaId?: string | number | null
|
||||
spatialAreaName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsSdkManifest {
|
||||
mapId: string | number
|
||||
mapName: string
|
||||
sdkVersion: string
|
||||
protocolVersion: number
|
||||
dataVersion: string
|
||||
updatedAt: string
|
||||
coordinateSystem: string
|
||||
floors: SgsSdkFloorSummary[]
|
||||
capabilities: Record<string, boolean | undefined>
|
||||
}
|
||||
|
||||
export interface SgsSdkFloorSummary {
|
||||
floorId: string | number
|
||||
floorCode: string
|
||||
floorName: string
|
||||
sortOrder: number
|
||||
modelSizeBytes: number
|
||||
poiCount: number
|
||||
spaceCount: number
|
||||
}
|
||||
|
||||
export interface SgsFloorBundle {
|
||||
floor: SgsSdkFloorSummary
|
||||
model: {
|
||||
modelUrl: string
|
||||
fallbackModelUrl: string
|
||||
compressionType: 'draco' | 'none'
|
||||
modelVersion: string
|
||||
nodeCount: number
|
||||
sizeBytes: number
|
||||
originSizeBytes: number
|
||||
}
|
||||
pois: SgsPoi[]
|
||||
spaces: SgsSpaceArea[]
|
||||
guideStops: SgsGuideStop[]
|
||||
routeSummary: {
|
||||
hasRouteNetwork: boolean
|
||||
nodeCount: number
|
||||
edgeCount: number
|
||||
}
|
||||
hiddenSceneNodeNames: string[]
|
||||
dataVersion: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface SgsSpaceArea {
|
||||
id: string | number
|
||||
name: string
|
||||
type: string
|
||||
floorId: string | number
|
||||
boundaryWkt: string
|
||||
center: { x: number; y: number; z: number }
|
||||
sourceNodeName?: string
|
||||
status: 'ACTIVE' | 'INACTIVE'
|
||||
colorHex?: string
|
||||
}
|
||||
|
||||
export interface SgsGuideStop {
|
||||
id: string | number
|
||||
name: string
|
||||
type: string
|
||||
typeName?: string
|
||||
floorId: string | number
|
||||
position: { x: number; y: number; z: number }
|
||||
targetType: string
|
||||
targetId?: string | number
|
||||
audioUrl?: string
|
||||
coverImageUrl?: string
|
||||
description?: string
|
||||
status: 'ACTIVE' | 'INACTIVE'
|
||||
located?: boolean
|
||||
hasAudio?: boolean
|
||||
poiId?: string | number
|
||||
outlineId?: string | number
|
||||
outlineName?: string
|
||||
hallId?: string | number
|
||||
hallName?: string
|
||||
sort?: number
|
||||
routeId?: string | number
|
||||
routeName?: string
|
||||
seqOrder?: number
|
||||
stayMinutes?: number
|
||||
}
|
||||
|
||||
export interface SgsNavigablePlace {
|
||||
id: string | number
|
||||
name: string
|
||||
category: 'door' | 'poi' | 'space' | 'guide_stop'
|
||||
floorId: string | number
|
||||
floorCode: string
|
||||
floorName?: string
|
||||
x: number
|
||||
z: number
|
||||
nodeId?: string | number
|
||||
ownerName?: string
|
||||
typeCode?: string
|
||||
typeName?: string
|
||||
}
|
||||
|
||||
export interface SgsRouteSegment {
|
||||
segmentIndex: number
|
||||
floorId?: string | number | null
|
||||
floorCode: string
|
||||
floorName: string
|
||||
segmentType?: SgsRouteSegmentType
|
||||
type: 'WALK' | SgsRouteTransferType
|
||||
transferType?: 'WALK' | SgsRouteTransferType
|
||||
distance: number
|
||||
duration: number
|
||||
pathPoints: Array<{ x: number; y: number; z: number }>
|
||||
points?: Array<{ x: number; y: number; z: number }>
|
||||
fromNodeName?: string | null
|
||||
toNodeName?: string | null
|
||||
startNodeName?: string | null
|
||||
endNodeName?: string | null
|
||||
connectorName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsPerformanceStats {
|
||||
sdkCreatedAt?: number
|
||||
iframeDomReadyMs?: number
|
||||
engineReadyMs?: number
|
||||
manifestLoadMs?: number
|
||||
bundleLoadMs?: number
|
||||
modelDownloadMs?: number
|
||||
modelDownloadAndParseMs?: number
|
||||
modelParseMs?: number
|
||||
firstRenderMs?: number
|
||||
routePlanMs?: number
|
||||
dracoUsed?: boolean
|
||||
fallbackUsed?: boolean
|
||||
currentFloorId?: string | number
|
||||
}
|
||||
|
||||
export interface SgsMapDiagnostics {
|
||||
mapId: string | number
|
||||
mapName: string
|
||||
sdkVersion: string
|
||||
status: 'OK' | 'WARN' | 'ERROR'
|
||||
floors: SgsFloorDiagnosticsSummary[]
|
||||
summary: {
|
||||
floorCount: number
|
||||
modelReadyFloorCount: number
|
||||
poiCount: number
|
||||
spaceCount: number
|
||||
guideStopCount: number
|
||||
navigablePlaceCount: number
|
||||
routeNodeCount: number
|
||||
routeEdgeCount: number
|
||||
}
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface SgsFloorDiagnosticsSummary {
|
||||
floorId: string | number
|
||||
floorCode: string
|
||||
floorName: string
|
||||
status: 'OK' | 'WARN' | 'ERROR'
|
||||
modelReady: boolean
|
||||
routePlanningReady: boolean
|
||||
navigablePlaceCount: number
|
||||
}
|
||||
|
||||
export interface SgsFloorDiagnostics extends SgsFloorDiagnosticsSummary {
|
||||
poiCount: number
|
||||
spaceCount: number
|
||||
guideStopCount: number
|
||||
routeNodeCount: number
|
||||
routeEdgeCount: number
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteSummary {
|
||||
id: string | number
|
||||
mapId: string | number
|
||||
routeName: string
|
||||
routeNameEn?: string | null
|
||||
description?: string | null
|
||||
descriptionEn?: string | null
|
||||
coverImageUrl?: string | null
|
||||
routeColor?: string | null
|
||||
estimatedMinutes?: number | null
|
||||
displayOrder?: number | null
|
||||
waypointCount: number
|
||||
startEntranceId?: string | number | null
|
||||
endEntranceId?: string | number | null
|
||||
updatedAt?: string | null
|
||||
}
|
||||
|
||||
export interface SgsRouteEndpoint {
|
||||
id: string | number
|
||||
name: string
|
||||
floorId: string | number
|
||||
floorCode?: string | null
|
||||
position?: { x: number; y: number; z: number } | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteWaypoint {
|
||||
order: number
|
||||
spaceId: string | number
|
||||
spaceName?: string | null
|
||||
floorId?: string | number | null
|
||||
floorCode?: string | null
|
||||
position: { x: number; y: number; z: number }
|
||||
title?: string | null
|
||||
subtitle?: string | null
|
||||
description?: string | null
|
||||
imageUrl?: string | null
|
||||
audioUrl?: string | null
|
||||
staySeconds?: number | null
|
||||
tags?: string[]
|
||||
highlight?: boolean
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteDetail extends SgsFeaturedRouteSummary {
|
||||
startEntrance?: SgsRouteEndpoint | null
|
||||
endEntrance?: SgsRouteEndpoint | null
|
||||
waypoints: SgsFeaturedRouteWaypoint[]
|
||||
}
|
||||
|
||||
export interface SgsFlowlineOptions {
|
||||
autoFit?: boolean
|
||||
showLabels?: boolean
|
||||
animate?: boolean
|
||||
}
|
||||
|
||||
export type SgsMapSDKConstructor = new (options: SgsMapSDKOptions) => SgsMapSDKInstance
|
||||
|
||||
declare global {
|
||||
|
||||
Reference in New Issue
Block a user