vesium
Version:
Core Vue composition hooks for Cesium
1,027 lines (1,026 loc) • 36.5 kB
JavaScript
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