import * as Cesium from 'cesium' import { getPointPositionsHeight } from './mapUtil' const DEVICE_RANGE_MATERIAL_TYPE = 'DeviceRangeEllipsoid' let deviceRangeMaterialRegistered = false export const registerDeviceRangeMaterial = () => { if (deviceRangeMaterialRegistered || !Cesium?.Material) return deviceRangeMaterialRegistered = true Cesium.Material._materialCache.addMaterial(DEVICE_RANGE_MATERIAL_TYPE, { fabric: { type: DEVICE_RANGE_MATERIAL_TYPE, uniforms: { color: new Cesium.Color(1.0, 0.0, 0.0, 0.7), speed: 3.0, globalAlpha: 0.9 }, source: ` uniform vec4 color; uniform float speed; uniform float globalAlpha; czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); vec2 st = materialInput.st; float time = fract(czm_frameNumber * speed / 1000.0); if (st.t < 0.5) { discard; } float alphaMars3D = abs(smoothstep(0.5, 1.0, fract(-st.t - time))); alphaMars3D += 0.1; material.alpha = alphaMars3D * globalAlpha; material.diffuse = color.rgb; return material; } `, }, translucent: () => true, }) } export const createDeviceRangeMaterial = () => { registerDeviceRangeMaterial() return Cesium.Material.fromType(DEVICE_RANGE_MATERIAL_TYPE, { color: Cesium.Color.fromBytes(0, 255, 255, 155), }) } export const getDeviceLngLat = device => { const lngRaw = device?.longitude ?? device?.lng ?? device?.lon const latRaw = device?.latitude ?? device?.lat const lng = Number(lngRaw) const lat = Number(latRaw) if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null return { lng, lat } } export const normalizeDeviceRange = value => { const range = Number(value) if (!Number.isFinite(range) || range <= 0) return 2000 return range } export const resolveDeviceTerrainHeight = async (viewer, position) => { if (!viewer || !position) return 0 const lng = position.lng ?? position.longitude const lat = position.lat ?? position.latitude if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return 0 try { const results = await getPointPositionsHeight([{ lng: Number(lng), lat: Number(lat) }], viewer) const height = Number(results?.[0]?.ASL) return Number.isFinite(height) ? height : 0 } catch (error) { return 0 } } export const createDeviceRangePrimitiveWithHeight = (position, rangeMeters, height, material = null) => { if (!position || !Number.isFinite(rangeMeters)) return null const lng = position.lng ?? position.longitude const lat = position.lat ?? position.latitude if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null const center = Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height) || 0) const radii = new Cesium.Cartesian3(rangeMeters, rangeMeters, rangeMeters) const geometry = new Cesium.EllipsoidGeometry({ radii, vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_NORMAL, }) const instance = new Cesium.GeometryInstance({ geometry, modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(center), }) return new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.MaterialAppearance({ material: material || createDeviceRangeMaterial(), closed: true, translucent: true, flat: true, faceForward: true, }), asynchronous: false }) } export const createDeviceRangePrimitive = async (viewer, position, rangeMeters, material = null) => { if (!viewer) return null const height = await resolveDeviceTerrainHeight(viewer, position) return createDeviceRangePrimitiveWithHeight(position, rangeMeters, height, material) }