UNPKG

vesium

Version:

Core Vue composition hooks for Cesium

1,027 lines (1,026 loc) 36.5 kB
import { computedAsync, refThrottled, tryOnScopeDispose, unrefElement, useElementBounding, useElementSize, useMutationObserver, watchThrottled } from "@vueuse/core"; import { Cartesian2, EllipsoidGeodesic, ScreenSpaceEventHandler, ScreenSpaceEventType, Viewer } from "cesium"; import { computed, getCurrentScope, inject, markRaw, nextTick, provide, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toValue, watch, watchEffect } from "vue"; import { cartesianToCanvasCoord, isDef, isFunction, isPromise, resolvePick, throttle, toCartesian3, tryRun } from "@vesium/shared"; export * from "@vesium/shared"; //#region createViewer/index.ts /** * @internal */ const CREATE_VIEWER_INJECTION_KEY = Symbol("CREATE_VIEWER_INJECTION_KEY"); /** * @internal */ const CREATE_VIEWER_COLLECTION = /* @__PURE__ */ new WeakMap(); /** * @internal */ function createViewer(arg1, arg2) { const viewer = shallowRef(); const readonlyViewer = shallowReadonly(viewer); provide(CREATE_VIEWER_INJECTION_KEY, readonlyViewer); const scope = getCurrentScope(); if (scope) CREATE_VIEWER_COLLECTION.set(scope, readonlyViewer); const canvas = computed(() => viewer.value?.canvas); const body = typeof document !== "undefined" ? document.body : void 0; if (body) useMutationObserver(body, () => { if (canvas.value && !body.contains(canvas.value)) viewer.value = void 0; }, { childList: true, subtree: true }); watchEffect((onCleanup) => { const value = toRaw(toValue(arg1)); if (value instanceof Viewer) viewer.value = markRaw(value); else if (value) { viewer.value = new Viewer(unrefElement(value), arg2); onCleanup(() => !viewer.value?.isDestroyed() && viewer.value?.destroy()); } else viewer.value = void 0; }); tryOnScopeDispose(() => { viewer.value = void 0; }); return computed(() => { return viewer.value?.isDestroyed() ? void 0 : viewer.value; }); } //#endregion //#region toPromiseValue/index.ts /** * Similar to Vue's built-in `toValue`, but capable of handling asynchronous functions, thus returning a `await value`. * * Used in conjunction with VueUse's `computedAsync`. * * @param source The source value, which can be a reactive reference or an asynchronous getter. * @param options Conversion options * @returns The converted value. * * @example * ```ts * * const data = computedAsync(async ()=> { * return await toPromiseValue(promiseRef) * }) * * ``` */ async function toPromiseValue(source, options = {}) { const { raw = true } = options; let value; if (isFunction(source)) value = await source(); else { const result = toValue(source); value = isPromise(result) ? await result : result; } return raw ? toRaw(value) : value; } //#endregion //#region useCesiumEventListener/index.ts /** * Easily use the `addEventListener` in `Cesium.Event` instances, * when the dependent data changes or the component is unmounted, * the listener function will automatically reload or destroy. * * @param event The Cesium.Event instance * @param listener The listener function * @param options additional options * @returns A function that can be called to remove the event listener */ function useCesiumEventListener(event, listener, options = {}) { const isActive = toRef(options.isActive ?? true); const cleanup = watchEffect((onCleanup) => { const _event = toValue(event); const events = Array.isArray(_event) ? _event : [_event]; if (events) { if (events.length && isActive.value) { const stopFns = events.map((event) => { const e = toValue(event); return e?.addEventListener(listener, e); }); onCleanup(() => stopFns.forEach((stop) => stop?.())); } } }); tryOnScopeDispose(cleanup.stop); return cleanup.stop; } //#endregion //#region useViewer/index.ts /** * Obtain the `Viewer` instance injected through `createViewer` in the current component or its ancestor components. * * note: * - If `createViewer` and `useViewer` are called in the same component, the `Viewer` instance injected by `createViewer` will be used preferentially. * - When calling `createViewer` and `useViewer` in the same component, `createViewer` should be called before `useViewer`. */ function useViewer() { const scope = getCurrentScope(); const instanceViewer = scope ? CREATE_VIEWER_COLLECTION.get(scope) : void 0; if (instanceViewer) return instanceViewer; else { const injectViewer = inject(CREATE_VIEWER_INJECTION_KEY); if (!injectViewer) throw new Error("The `Viewer` instance injected by `createViewer` was not found in the current component or its ancestor components. Have you called `createViewer`?"); return injectViewer; } } //#endregion //#region useCameraState/index.ts /** * Reactive Cesium Camera state * @param options options * @returns Reactive camera states */ function useCameraState(options = {}) { let getCamera = options.camera; if (!getCamera) { const viewer = useViewer(); getCamera = () => viewer.value?.scene.camera; } const camera = computed(() => toValue(getCamera)); const event = computed(() => { const eventField = toValue(options.event) || "changed"; return camera.value?.[eventField]; }); const changedSymbol = refThrottled(shallowRef(Symbol("camera change")), options.delay ?? 8, true, false); const setChangedSymbol = () => { changedSymbol.value = Symbol("camera change"); }; watch(camera, () => setChangedSymbol()); useCesiumEventListener(event, () => setChangedSymbol()); return { camera, position: computed(() => changedSymbol.value ? camera.value?.position?.clone() : void 0), direction: computed(() => changedSymbol.value ? camera.value?.direction?.clone() : void 0), up: computed(() => changedSymbol.value ? camera.value?.up?.clone() : void 0), right: computed(() => changedSymbol.value ? camera.value?.right?.clone() : void 0), positionCartographic: computed(() => changedSymbol.value ? camera.value?.positionCartographic?.clone() : void 0), positionWC: computed(() => changedSymbol.value ? camera.value?.positionWC?.clone() : void 0), directionWC: computed(() => changedSymbol.value ? camera.value?.directionWC?.clone() : void 0), upWC: computed(() => changedSymbol.value ? camera.value?.upWC?.clone() : void 0), rightWC: computed(() => changedSymbol.value ? camera.value?.rightWC?.clone() : void 0), viewRectangle: computed(() => changedSymbol.value ? camera.value?.computeViewRectangle() : void 0), heading: computed(() => changedSymbol.value ? camera.value?.heading : void 0), pitch: computed(() => changedSymbol.value ? camera.value?.pitch : void 0), roll: computed(() => changedSymbol.value ? camera.value?.roll : void 0), level: computed(() => changedSymbol.value && camera.value?.positionCartographic?.height !== void 0 ? computeLevel(camera.value.positionCartographic.height) : void 0) }; } const A = 40487.57; const B = 7096758e-11; const C = 91610.74; const D = -40467.74; /** * Compute the camera level at a given height. */ function computeLevel(height) { return D + (A - D) / (1 + (height / C) ** B); } //#endregion //#region useCesiumFps/index.ts /** * Reactive get the frame rate of Cesium * @param options options * @returns Reactive fps states */ function useCesiumFps(options = {}) { const { delay = 100 } = options; const viewer = useViewer(); const p = shallowRef(performance.now()); useCesiumEventListener(() => viewer.value?.scene.postRender, () => p.value = performance.now()); const interval = ref(0); watchThrottled(p, (value, oldValue) => { interval.value = value - oldValue; }, { throttle: delay }); const fps = computed(() => { return 1e3 / interval.value; }); return { interval: readonly(interval), fps }; } //#endregion //#region useCollectionScope/index.ts /** * Scope the SideEffects of Cesium-related `Collection` and automatically remove them when unmounted. * - note: This is a basic function that is intended to be called by other lower-level function * @returns Contains side effect addition and removal functions */ function useCollectionScope(options) { const { addEffect, removeEffect, removeScopeArgs } = options; const scope = shallowReactive(/* @__PURE__ */ new Set()); const add = (instance, ...args) => { const result = addEffect(instance, ...args); if (isPromise(result)) return new Promise((resolve, reject) => { result.then((i) => { scope.add(i); resolve(i); }).catch((error) => reject(error)); }); else { scope.add(result); return result; } }; const remove = (instance, ...args) => { scope.delete(instance); return removeEffect(instance, ...args); }; const removeWhere = (predicate, ...args) => { for (const instance of Array.from(scope)) if (predicate(instance)) remove(instance, ...args); }; const removeScope = (...args) => { for (const instance of Array.from(scope)) remove(instance, ...args); }; tryOnScopeDispose(() => removeScope(...removeScopeArgs ?? [])); return { scope: shallowReadonly(scope), add, remove, removeWhere, removeScope }; } //#endregion //#region useDataSource/index.ts function useDataSource(dataSources, options = {}) { const { destroyOnRemove, collection, isActive = true, evaluating } = options; const result = computedAsync(() => toPromiseValue(dataSources), void 0, { evaluating }); const viewer = useViewer(); watchEffect((onCleanup) => { if (toValue(isActive)) { const list = Array.isArray(result.value) ? [...result.value] : [result.value]; const _collection = collection ?? viewer.value?.dataSources; list.forEach((item) => item && _collection?.add(item)); onCleanup(() => { const destroy = toValue(destroyOnRemove); !_collection?.isDestroyed() && list.forEach((dataSource) => dataSource && _collection?.remove(dataSource, destroy)); }); } }); return result; } //#endregion //#region useDataSourceScope/index.ts /** * Scope the SideEffects of `DataSourceCollection` operations and automatically remove them when unmounted */ function useDataSourceScope(options = {}) { const { collection: _collection, destroyOnRemove } = options; const viewer = useViewer(); const collection = computed(() => { return toValue(_collection) ?? viewer.value?.dataSources; }); return useCollectionScope({ addEffect(instance) { if (!collection.value) throw new Error("collection is not defined"); if (isPromise(instance)) return new Promise((resolve, reject) => { instance.then((i) => { try { collection.value.add(i); resolve(i); } catch (error) { reject(error); } }).catch((error) => reject(error)); }); else { collection.value.add(instance); return instance; } }, removeEffect(instance, destroy) { return !!collection.value?.remove(instance, destroy); }, removeScopeArgs: [destroyOnRemove] }); } //#endregion //#region useElementOverlay/index.ts /** * Cesium HTMLElement Overlay */ function useElementOverlay(target, position, options = {}) { const { referenceWindow, horizontal = "center", vertical = "bottom", clampToGround, offset = { x: 0, y: 0 } } = options; const viewer = useViewer(); const cartesian3 = computed(() => { const positionValue = toValue(position); if (!positionValue) return; return toCartesian3(positionValue); }); const coord = shallowRef(); useCesiumEventListener(() => viewer.value?.scene.postUpdate, () => { const scene = viewer.value?.scene; if (!scene || !cartesian3.value) { coord.value = void 0; return; } let finalPosition = toValue(clampToGround) ? scene.clampToHeight(cartesian3.value) : cartesian3.value; finalPosition ??= cartesian3.value; const result = cartesianToCanvasCoord(finalPosition, scene); if (result) { result.x = +result.x.toFixed(1); result.y = +result.y.toFixed(1); coord.value = !Cartesian2.equals(result, coord.value) ? result : coord.value; } }); const canvasBounding = useElementBounding(() => viewer.value?.canvas.parentElement); const targetSize = useElementSize(target, void 0, { box: "border-box" }); const finalOffset = computed(() => { const _offset = toValue(offset); let x = _offset?.x ?? 0; const _horizontal = toValue(horizontal); if (_horizontal === "center") x -= targetSize.width.value / 2; else if (_horizontal === "right") x -= targetSize.width.value; let y = _offset?.y ?? 0; const _vertical = toValue(vertical); if (_vertical === "center") y -= targetSize.height.value / 2; else if (_vertical === "bottom") y -= targetSize.height.value; return { x, y }; }); const x = computed(() => { let v = coord.value?.x ?? 0; if (toValue(referenceWindow)) v += canvasBounding.x.value; return +(v + finalOffset.value.x).toFixed(1); }); const y = computed(() => { let v = coord.value?.y ?? 0; if (toValue(referenceWindow)) v += canvasBounding.y.value; return +(v + finalOffset.value.y).toFixed(1); }); const style = computed(() => `left:${x.value}px;top:${y.value}px;`); watchEffect(() => { const applyStyle = toValue(options.applyStyle ?? true); if (!applyStyle) return; const element = unrefElement(target); if (element && applyStyle) { element.style?.setProperty?.("left", `${x.value}px`); element.style?.setProperty?.("top", `${y.value}px`); } }, { flush: "post" }); return { x, y, style }; } //#endregion //#region useEntity/index.ts function useEntity(data, options = {}) { const { collection, isActive = true, evaluating } = options; const result = computedAsync(() => toPromiseValue(data), [], { evaluating }); const viewer = useViewer(); watchEffect((onCleanup) => { if (toValue(isActive)) { const list = Array.isArray(result.value) ? [...result.value] : [result.value]; const _collection = collection ?? viewer.value?.entities; list.forEach((item) => item && _collection?.add(item)); onCleanup(() => { list.forEach((item) => item && _collection?.remove(item)); }); } }); return result; } //#endregion //#region useEntityScope/index.ts /** * Make `add` and `remove` operations of `EntityCollection` scoped, * automatically remove `Entity` instance when component is unmounted. */ function useEntityScope(options = {}) { const { collection: _collection } = options; const viewer = useViewer(); const collection = computed(() => { return toValue(_collection) ?? viewer.value?.entities; }); return useCollectionScope({ addEffect(instance) { if (!collection.value) throw new Error("collection is not defined"); if (isPromise(instance)) return new Promise((resolve, reject) => { instance.then((i) => { collection.value.add(i); resolve(i); }).catch((error) => reject(error)); }); else { collection.value.add(instance); return instance; } }, removeEffect(instance) { return !!collection.value?.remove(instance); }, removeScopeArgs: [] }); } //#endregion //#region useScenePick/index.ts const pickCache = /* @__PURE__ */ new WeakMap(); /** * Uses the `scene.pick` function in Cesium's Scene object to perform screen point picking, * return a computed property containing the pick result, or undefined if no object is picked. * * @param windowPosition The screen coordinates of the pick point. */ function useScenePick(windowPosition, options = {}) { const { width = 3, height = 3, throttled = 8 } = options; const isActive = toRef(options.isActive ?? true); const viewer = useViewer(); const position = refThrottled(computed(() => toValue(windowPosition)?.clone()), throttled, false, true); const pick = shallowRef(); watchEffect(() => { if (viewer.value && position.value && isActive.value) { const widthValue = toValue(width); const heightValue = toValue(height); const cache = pickCache.get(viewer.value); if (cache && cache.position.equals(position.value) && cache.width === widthValue && cache.height === heightValue) pick.value = cache.pick; else try { const nextPick = viewer.value?.scene.pick(position.value, widthValue, heightValue); pick.value = nextPick; pickCache.set(viewer.value, { position: position.value.clone(), width: widthValue, height: heightValue, pick: nextPick }); } catch (error) { console.error("[useScenePick] Error during pick:", error); pick.value = void 0; } } else pick.value = void 0; }); return pick; } //#endregion //#region useScreenSpaceEventHandler/index.ts /** * Easily use the `ScreenSpaceEventHandler`, * when the dependent data changes or the component is unmounted, * the listener function will automatically reload or destroy. * * @param type Types of mouse event * @param inputAction Callback function for listening */ function useScreenSpaceEventHandler(type, inputAction, options = {}) { const { modifier } = options; const viewer = useViewer(); const isActive = toRef(options.isActive ?? true); let currentHandler; const handler = computed(() => { if (viewer.value?.cesiumWidget?.canvas) return new ScreenSpaceEventHandler(viewer.value.cesiumWidget.canvas); }); const cleanup1 = watch(handler, (_value, previous) => { previous?.destroy(); currentHandler = _value; }); const cleanup2 = watchEffect((onCleanup) => { const typeValue = toValue(type); const modifierValue = toValue(modifier); const handlerValue = toValue(handler); currentHandler = handlerValue; if (!handlerValue || !isActive.value || !inputAction) return; if (isDef(typeValue)) { handlerValue.setInputAction(inputAction, typeValue, modifierValue); onCleanup(() => handlerValue.removeInputAction(typeValue, modifierValue)); } }); const stop = () => { cleanup1(); cleanup2(); currentHandler?.destroy(); currentHandler = void 0; }; tryOnScopeDispose(stop); return stop; } //#endregion //#region useGraphicEvent/useDrag.ts /** * Use graphic drag events with ease, and remove listener automatically on unmounted. */ function useDrag(listener) { const position = shallowRef(); const pick = useScenePick(position); const motionEvent = shallowRef(); const dragging = ref(false); const viewer = useViewer(); const cameraLocked = ref(false); watch(cameraLocked, (locked) => { viewer.value && (viewer.value.scene.screenSpaceCameraController.enableRotate = !locked); }); const lockCamera = () => { cameraLocked.value = true; }; const execute = (pickedValue, startPosition, endPosition) => { listener({ event: { startPosition: startPosition.clone(), endPosition: endPosition.clone() }, pick: pickedValue, dragging: dragging.value, lockCamera }); nextTick(() => { if (!dragging.value && cameraLocked.value) cameraLocked.value = false; }); }; const stopLeftDownWatch = useScreenSpaceEventHandler(ScreenSpaceEventType.LEFT_DOWN, (event) => { dragging.value = true; position.value = event.position.clone(); }); const stopMouseMoveWatch = useScreenSpaceEventHandler(ScreenSpaceEventType.MOUSE_MOVE, throttle(({ startPosition, endPosition }) => { motionEvent.value = { startPosition: motionEvent.value?.endPosition.clone() || startPosition.clone(), endPosition: endPosition.clone() }; }, 8, false, true)); watch([pick, motionEvent], ([pickValue, motionValue]) => { if (pickValue && motionValue) { const { startPosition, endPosition } = motionValue; dragging.value && execute(pickValue, startPosition, endPosition); } }); const stopLeftUpWatch = useScreenSpaceEventHandler(ScreenSpaceEventType.LEFT_UP, (event) => { dragging.value = false; if (pick.value && motionEvent.value) execute(pick.value, motionEvent.value.endPosition, event.position); position.value = void 0; motionEvent.value = void 0; }); const stop = () => { stopLeftDownWatch(); stopMouseMoveWatch(); stopLeftUpWatch(); }; tryOnScopeDispose(stop); return stop; } //#endregion //#region useGraphicEvent/useHover.ts /** * Use graphic hover events with ease, and remove listener automatically on unmounted. */ function useHover(listener) { const motionEvent = shallowRef(); const pick = useScenePick(() => motionEvent.value?.endPosition); const execute = (pickedValue, startPosition, endPosition, hovering) => { listener({ event: { startPosition: startPosition.clone(), endPosition: endPosition.clone() }, pick: pickedValue, hovering }); }; useScreenSpaceEventHandler(ScreenSpaceEventType.MOUSE_MOVE, ({ startPosition, endPosition }) => { const prevStart = motionEvent.value?.startPosition; const prevEnd = motionEvent.value?.endPosition; if (!prevStart || !prevEnd || !startPosition.equals(prevStart) || !endPosition.equals(prevEnd)) motionEvent.value = { startPosition: startPosition.clone(), endPosition: endPosition.clone() }; }); watch([pick, motionEvent], ([pickValue, motionValue]) => { if (pickValue && motionValue) { const { startPosition, endPosition } = motionValue; execute(pickValue, startPosition, endPosition, true); } }); watch(pick, (pickValue, prevPick) => { if (prevPick && motionEvent.value) { const { startPosition, endPosition } = motionEvent.value; execute(prevPick, startPosition, endPosition, false); } }); } //#endregion //#region useGraphicEvent/usePositioned.ts /** * @internal */ const EVENT_TYPE_RECORD = { LEFT_DOWN: ScreenSpaceEventType.LEFT_DOWN, LEFT_UP: ScreenSpaceEventType.LEFT_UP, LEFT_CLICK: ScreenSpaceEventType.LEFT_CLICK, LEFT_DOUBLE_CLICK: ScreenSpaceEventType.LEFT_DOUBLE_CLICK, RIGHT_DOWN: ScreenSpaceEventType.RIGHT_DOWN, RIGHT_UP: ScreenSpaceEventType.RIGHT_UP, RIGHT_CLICK: ScreenSpaceEventType.RIGHT_CLICK, MIDDLE_DOWN: ScreenSpaceEventType.MIDDLE_DOWN, MIDDLE_UP: ScreenSpaceEventType.MIDDLE_UP, MIDDLE_CLICK: ScreenSpaceEventType.MIDDLE_CLICK }; function usePositioned(type, listener) { const screenEvent = EVENT_TYPE_RECORD[type]; const viewer = useViewer(); useScreenSpaceEventHandler(screenEvent, (event) => { const position = event.position; const pick = viewer.value?.scene.pick(position); pick && position && listener({ event: { position }, pick }); }); } //#endregion //#region useGraphicEvent/index.ts const GLOBAL_GRAPHIC_SYMBOL = Symbol("GLOBAL_GRAPHIC_SYMBOL"); const POSITIONED_EVENT_TYPES = [ "LEFT_DOWN", "LEFT_UP", "LEFT_CLICK", "LEFT_DOUBLE_CLICK", "RIGHT_DOWN", "RIGHT_UP", "RIGHT_CLICK", "MIDDLE_DOWN", "MIDDLE_UP", "MIDDLE_CLICK" ]; /** * Handle graphic event listeners and cursor styles for Cesium graphics. * You don't need to overly worry about memory leaks from the function, as it automatically cleans up internally. */ function useGraphicEvent() { const collection = /* @__PURE__ */ new WeakMap(); const cursorCollection = /* @__PURE__ */ new WeakMap(); const dragCursorCollection = /* @__PURE__ */ new WeakMap(); const remove = (graphic, type, listener) => { const _graphic = graphic === "global" ? GLOBAL_GRAPHIC_SYMBOL : graphic; collection?.get(_graphic)?.get(type)?.delete(listener); cursorCollection?.get(_graphic)?.get(type)?.delete(listener); if (collection?.get(_graphic)?.get(type)?.size === 0) collection.get(_graphic).delete(type); if (collection.get(_graphic)?.size === 0) collection.delete(_graphic); cursorCollection?.get(_graphic)?.get(type)?.delete(listener); if (cursorCollection?.get(_graphic)?.get(type)?.size === 0) cursorCollection?.get(_graphic)?.delete(type); if (cursorCollection?.get(_graphic)?.size === 0) cursorCollection?.delete(_graphic); dragCursorCollection?.get(_graphic)?.get(type)?.delete(listener); if (dragCursorCollection?.get(_graphic)?.get(type)?.size === 0) dragCursorCollection?.get(_graphic)?.delete(type); if (dragCursorCollection?.get(_graphic)?.size === 0) dragCursorCollection?.delete(_graphic); }; const add = (graphic, type, listener, options = {}) => { const _graphic = graphic === "global" ? GLOBAL_GRAPHIC_SYMBOL : graphic; collection.get(_graphic) ?? collection.set(_graphic, /* @__PURE__ */ new Map()); const eventTypeMap = collection.get(_graphic); eventTypeMap.get(type) ?? eventTypeMap.set(type, /* @__PURE__ */ new Set()); eventTypeMap.get(type).add(listener); let { cursor = "pointer", dragCursor } = options; if (isDef(cursor)) { const _cursor = isFunction(cursor) ? cursor : () => cursor; cursorCollection.get(_graphic) ?? cursorCollection.set(_graphic, /* @__PURE__ */ new Map()); cursorCollection.get(_graphic).get(type) ?? cursorCollection.get(_graphic).set(type, /* @__PURE__ */ new Map()); cursorCollection.get(_graphic).get(type).set(listener, _cursor); } if (type === "DRAG") dragCursor ??= ((event) => event?.dragging ? "crosshair" : void 0); if (isDef(dragCursor)) { const _dragCursor = isFunction(dragCursor) ? dragCursor : () => dragCursor; dragCursorCollection.get(_graphic) ?? dragCursorCollection.set(_graphic, /* @__PURE__ */ new Map()); dragCursorCollection.get(_graphic).get(type) ?? dragCursorCollection.get(_graphic).set(type, /* @__PURE__ */ new Map()); dragCursorCollection.get(_graphic).get(type).set(listener, _dragCursor); } return () => remove(graphic, type, listener); }; const clear = (graphic, type) => { const _graphic = graphic === "global" ? GLOBAL_GRAPHIC_SYMBOL : graphic; if (type === "all") { collection.delete(_graphic); cursorCollection.delete(_graphic); dragCursorCollection.delete(_graphic); return; } collection.get(_graphic)?.delete(type); if (collection.get(_graphic)?.size === 0) collection.delete(_graphic); cursorCollection?.get(_graphic)?.delete(type); dragCursorCollection?.get(_graphic)?.delete(type); if (cursorCollection?.get(_graphic)?.size === 0) cursorCollection?.delete(_graphic); if (dragCursorCollection?.get(_graphic)?.size === 0) dragCursorCollection?.delete(_graphic); }; for (const type of POSITIONED_EVENT_TYPES) usePositioned(type, (event) => { resolvePick(event.pick).concat(GLOBAL_GRAPHIC_SYMBOL).forEach((graphic) => { collection.get(graphic)?.get(type)?.forEach((fn) => tryRun(fn)?.(event)); }); }); const dragging = ref(false); const viewer = useViewer(); useHover((event) => { resolvePick(event.pick).concat(GLOBAL_GRAPHIC_SYMBOL).forEach((graphic) => { collection.get(graphic)?.get("HOVER")?.forEach((fn) => tryRun(fn)?.(event)); if (!dragging.value) cursorCollection.get(graphic)?.forEach((map) => { map.forEach((fn) => { const cursor = event.hovering ? tryRun(fn)(event) : ""; viewer.value?.canvas.style?.setProperty("cursor", cursor); }); }); }); }); useDrag((event) => { const graphics = resolvePick(event.pick).concat(GLOBAL_GRAPHIC_SYMBOL); dragging.value = event.dragging; graphics.forEach((graphic) => { collection.get(graphic)?.get("DRAG")?.forEach((fn) => tryRun(fn)(event)); dragCursorCollection.get(graphic)?.forEach((map) => { map.forEach((fn) => { const cursor = event.dragging ? tryRun(fn)(event) : ""; viewer.value?.canvas.style?.setProperty("cursor", cursor); }); }); }); }); return { add, remove, clear }; } //#endregion //#region useImageryLayer/index.ts function useImageryLayer(data, options = {}) { const { destroyOnRemove, collection, isActive = true, evaluating, index } = options; const result = computedAsync(() => toPromiseValue(data), [], { evaluating }); const viewer = useViewer(); watchEffect((onCleanup) => { if (toValue(isActive)) { const list = Array.isArray(result.value) ? [...result.value] : [result.value]; const _collection = collection ?? viewer.value?.imageryLayers; const _index = toValue(index); if (collection?.isDestroyed()) return; list.forEach((item) => { if (!item) return; if (item?.isDestroyed()) { console.warn("ImageryLayer is destroyed"); return; } _collection?.add(item, _index); }); onCleanup(() => { const destroy = toValue(destroyOnRemove); list.forEach((item) => item && _collection?.remove(item, destroy)); }); } }); return result; } //#endregion //#region useImageryLayerScope/index.ts /** * Make `add` and `remove` operations of `ImageryLayerCollection` scoped, * automatically remove `ImageryLayer` instance when component is unmounted. */ function useImageryLayerScope(options = {}) { const { collection: _collection, destroyOnRemove } = options; const viewer = useViewer(); const collection = computed(() => { return toValue(_collection) ?? viewer.value?.imageryLayers; }); return useCollectionScope({ addEffect(instance, index) { if (!collection.value) throw new Error("collection is not defined"); if (isPromise(instance)) return new Promise((resolve, reject) => { instance.then((i) => { try { collection.value.add(i, index); resolve(i); } catch (error) { reject(error); } }).catch((error) => reject(error)); }); else { collection.value.add(instance, index); return instance; } }, removeEffect(instance, destroy) { return !!collection.value?.remove(instance, destroy); }, removeScopeArgs: [destroyOnRemove] }); } //#endregion //#region usePostProcessStage/index.ts function usePostProcessStage(data, options = {}) { const { collection, destroyOnRemove = true, isActive = true, evaluating } = options; const result = computedAsync(() => toPromiseValue(data), void 0, { evaluating }); const viewer = useViewer(); watchEffect((onCleanup) => { if (!viewer.value) return; if (toValue(isActive)) { const list = Array.isArray(result.value) ? [...result.value] : [result.value]; const _collection = collection ?? viewer.value.scene.postProcessStages; list.forEach((item) => item && _collection.add(item)); onCleanup(() => { list.forEach((item) => { if (item) { _collection.remove(item); if (destroyOnRemove && typeof item.destroy === "function" && !item.isDestroyed()) item.destroy(); } }); }); } }); return result; } //#endregion //#region usePostProcessStageScope/index.ts /** * Make `add` and `remove` operations of `PostProcessStageCollection` scoped, * automatically remove `PostProcessStage` instance when component is unmounted. */ function usePostProcessStageScope(options = {}) { const { collection: _collection, destroyOnRemove = true } = options; const viewer = useViewer(); const collection = computed(() => { return toValue(_collection) ?? viewer.value?.postProcessStages; }); return useCollectionScope({ addEffect(instance) { if (!collection.value) throw new Error("collection is not defined"); if (isPromise(instance)) return new Promise((resolve, reject) => { instance.then((resolvedInstance) => { collection.value.add(resolvedInstance); resolve(resolvedInstance); }).catch((error) => reject(error)); }); else return collection.value.add(instance); }, removeEffect(instance) { if (!collection.value) return false; const removed = collection.value.remove(instance); if (removed && destroyOnRemove && instance && typeof instance.destroy === "function" && !instance.isDestroyed()) instance.destroy(); return !!removed; }, removeScopeArgs: [] }); } //#endregion //#region usePrimitive/index.ts function usePrimitive(data, options = {}) { const { collection, destroyOnRemove = true, isActive = true, evaluating } = options; const result = computedAsync(() => toPromiseValue(data), void 0, { evaluating }); const viewer = useViewer(); watchEffect((onCleanup) => { if (!viewer.value) return; if (toValue(isActive)) { const list = Array.isArray(result.value) ? [...result.value] : [result.value]; const _collection = collection === "ground" ? viewer.value?.scene.groundPrimitives : collection ?? viewer.value?.scene.primitives; list.forEach((item) => item && _collection?.add(item)); onCleanup(() => { !_collection?.isDestroyed() && list.forEach((item) => { if (item) { _collection?.remove(item); if (destroyOnRemove && typeof item.destroy === "function" && !item.isDestroyed()) item.destroy(); } }); }); } }); return result; } //#endregion //#region usePrimitiveScope/index.ts /** * Make `add` and `remove` operations of `PrimitiveCollection` scoped, * automatically remove `Primitive` instance when component is unmounted. */ function usePrimitiveScope(options = {}) { const { collection: _collection, destroyOnRemove = true } = options; const viewer = useViewer(); const collection = computed(() => { const value = toValue(_collection); return value === "ground" ? viewer.value?.scene?.groundPrimitives : value || viewer.value?.scene.primitives; }); const { scope, add, remove, removeWhere, removeScope } = useCollectionScope({ addEffect(instance, ...args) { if (!collection.value) throw new Error("collection is not defined"); if (isPromise(instance)) return new Promise((resolve, reject) => { instance.then((instance) => resolve(collection.value.add(instance, ...args))).catch((error) => reject(error)); }); else return collection.value.add(instance, ...args); }, removeEffect(instance) { if (!collection.value) return false; const removed = collection.value.remove(instance); if (removed && destroyOnRemove && instance && typeof instance.destroy === "function" && !instance.isDestroyed()) instance.destroy(); return !!removed; }, removeScopeArgs: [] }); return { scope, add, remove, removeWhere, removeScope }; } //#endregion //#region useScaleBar/index.ts const distances = [ .01, .05, .1, .5, 1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500, 1e3, 2e3, 3e3, 5e3, 1e4, 2e4, 3e4, 5e4, 1e5, 2e5, 3e5, 5e5, 1e6, 2e6, 3e6, 5e6, 1e7, 2e7, 3e7, 5e7 ].reverse(); /** * Reactive generation of scale bars */ function useScaleBar(options = {}) { const { maxPixel = 80, delay = 8 } = options; const maxPixelRef = computed(() => toValue(maxPixel)); const viewer = useViewer(); const canvasSize = useElementSize(() => viewer.value?.canvas); const pixelDistance = ref(); const setPixelDistance = async () => { await nextTick(); const scene = viewer.value?.scene; if (!scene) return; const left = scene.camera.getPickRay(new Cartesian2(Math.floor(canvasSize.width.value / 2), canvasSize.height.value - 1)); const right = scene.camera.getPickRay(new Cartesian2(Math.floor(1 + canvasSize.width.value / 2), canvasSize.height.value - 1)); if (!left || !right) return; const leftPosition = scene.globe.pick(left, scene); const rightPosition = scene.globe.pick(right, scene); if (!leftPosition || !rightPosition) return; pixelDistance.value = new EllipsoidGeodesic(scene.globe.ellipsoid.cartesianToCartographic(leftPosition), scene.globe.ellipsoid.cartesianToCartographic(rightPosition)).surfaceDistance; }; watch([ viewer, () => canvasSize.width.value, () => canvasSize.height.value ], () => setPixelDistance(), { immediate: true }); useCesiumEventListener(() => viewer.value?.camera.changed, throttle(setPixelDistance, delay)); const distance = computed(() => { if (pixelDistance.value) return distances.find((item) => pixelDistance.value * maxPixelRef.value > item); }); const width = computed(() => { if (distance.value && pixelDistance.value) return distance.value / pixelDistance.value; return 0; }); const distanceText = computed(() => { if (distance.value) return distance.value > 1e3 ? `${distance.value / 1e3 || 0}km` : `${distance.value || 0}m`; }); return { pixelDistance: readonly(pixelDistance), width, distance, distanceText }; } //#endregion //#region useSceneDrillPick/index.ts /** * Uses the `scene.drillPick` function to perform screen point picking, * return a computed property containing the pick result, or undefined if no object is picked. * * @param windowPosition The screen coordinates of the pick point. */ function useSceneDrillPick(windowPosition, options = {}) { const { width = 3, height = 3, limit, throttled = 8, isActive = true } = options; const viewer = useViewer(); const position = refThrottled(computed(() => toValue(windowPosition)), throttled, false, true); return computed(() => { if (position.value && toValue(isActive)) return viewer.value?.scene.drillPick(position.value, toValue(limit), toValue(width), toValue(height)); }); } //#endregion export { CREATE_VIEWER_COLLECTION, CREATE_VIEWER_INJECTION_KEY, createViewer, toPromiseValue, useCameraState, useCesiumEventListener, useCesiumFps, useCollectionScope, useDataSource, useDataSourceScope, useElementOverlay, useEntity, useEntityScope, useGraphicEvent, useImageryLayer, useImageryLayerScope, usePostProcessStage, usePostProcessStageScope, usePrimitive, usePrimitiveScope, useScaleBar, useSceneDrillPick, useScenePick, useScreenSpaceEventHandler, useViewer }; //# sourceMappingURL=index.mjs.map