vesium
Version:
Vue component and composition-api library for Cesium.
1,492 lines (1,452 loc) • 52.1 kB
JavaScript
import { computedAsync, promiseTimeout, refThrottled, tryOnScopeDispose, useElementBounding, useElementSize, useMutationObserver, watchImmediate, watchThrottled } from "@vueuse/core";
import { CallbackProperty, Cartesian2, Cartesian3, Cartographic, ConstantProperty, Ellipsoid, EllipsoidGeodesic, Material, Math as Math$1, ScreenSpaceEventHandler, ScreenSpaceEventType, Viewer, defined } from "cesium";
import { computed, getCurrentScope, inject, markRaw, nextTick, provide, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toValue, watch, watchEffect } from "vue";
//#region createViewer/index.ts
/**
* @internal
*/
const CREATE_VIEWER_INJECTION_KEY = Symbol("CREATE_VIEWER_INJECTION_KEY");
/**
* @internal
*/
const CREATE_VIEWER_COLLECTION = /* @__PURE__ */ new WeakMap();
function createViewer(...args) {
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);
useMutationObserver(document?.body, () => {
if (canvas.value && !document?.body.contains(canvas.value)) viewer.value = void 0;
}, {
childList: true,
subtree: true
});
watchEffect((onCleanup) => {
const [arg1, arg2] = args;
const value = toRaw(toValue(arg1));
if (value instanceof Viewer) viewer.value = markRaw(value);
else if (value) {
const element = value;
const options = arg2;
viewer.value = new Viewer(element, options);
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 utils/arrayDiff.ts
/**
* 计算两个数组的差异,返回新增和删除的元素
*/
function arrayDiff(list, oldList) {
const oldListSet = new Set(oldList);
const added = list.filter((obj) => !oldListSet.has(obj));
const newListSet = new Set(list);
const removed = oldList?.filter((obj) => !newListSet.has(obj)) ?? [];
return {
added,
removed
};
}
//#endregion
//#region utils/canvasCoordToCartesian.ts
/**
* Convert canvas coordinates to Cartesian coordinates
*
* @param canvasCoord Canvas coordinates
* @param scene Cesium.Scene instance
* @param mode optional values are 'pickPosition' | 'globePick' | 'auto' | 'noHeight' @default 'auto'
*
* `pickPosition`: Use scene.pickPosition for conversion, which can be used for picking models, oblique photography, etc.
* However, if depth detection is not enabled (globe.depthTestAgainstTerrain=false), picking terrain or inaccurate issues may occur
*
* `globePick`: Use camera.getPickRay for conversion, which cannot be used for picking models or oblique photography,
* but can be used for picking terrain. If terrain does not exist, the picked elevation is 0
*
* `auto`: Automatically determine which picking content to return
*
* Calculation speed comparison: globePick > auto >= pickPosition
*/
function canvasCoordToCartesian(canvasCoord, scene, mode = "auto") {
if (mode === "pickPosition") return scene.pickPosition(canvasCoord);
else if (mode === "globePick") {
const ray = scene.camera.getPickRay(canvasCoord);
return ray && scene.globe.pick(ray, scene);
} else {
if (scene.globe.depthTestAgainstTerrain) return scene.pickPosition(canvasCoord);
const position1 = scene.pickPosition(canvasCoord);
const ray = scene.camera.getPickRay(canvasCoord);
const position2 = ray && scene.globe.pick(ray, scene);
if (!position1) return position2;
const height1 = (position1 && Ellipsoid.WGS84.cartesianToCartographic(position1).height) ?? 0;
const height2 = (position2 && Ellipsoid.WGS84.cartesianToCartographic(position2).height) ?? 0;
return height1 < height2 ? position1 : position2;
}
}
//#endregion
//#region utils/cartesianToCanvasCoord.ts
/**
* Convert Cartesian coordinates to canvas coordinates
*
* @param position Cartesian coordinates
* @param scene Cesium.Scene instance
*/
function cartesianToCanvasCoord(position, scene) {
return scene.cartesianToCanvasCoordinates(position);
}
//#endregion
//#region utils/is.ts
const toString = Object.prototype.toString;
function isDef(val) {
return typeof val !== "undefined";
}
function isBoolean(val) {
return typeof val === "boolean";
}
function isFunction(val) {
return typeof val === "function";
}
function isNumber(val) {
return typeof val === "number";
}
function isString(val) {
return typeof val === "string";
}
function isObject(val) {
return toString.call(val) === "[object Object]";
}
function isWindow(val) {
return typeof window !== "undefined" && toString.call(val) === "[object Window]";
}
function isPromise(val) {
return !!val && (typeof val === "object" || typeof val === "function") && typeof val.then === "function";
}
function isElement(val) {
return !!(val && val.nodeName && val.nodeType === 1);
}
const isArray = Array.isArray;
function isBase64(val) {
const reg = /^\s*data:([a-z]+\/[\d+.a-z-]+(;[a-z-]+=[\da-z-]+)?)?(;base64)?,([\s\w!$%&'()*+,./:;=?@~-]*?)\s*$/i;
return reg.test(val);
}
function assertError(condition, error) {
if (condition) throw new Error(error);
}
//#endregion
//#region utils/cesiumEquals.ts
/**
* Determines if two Cesium objects are equal.
*
* This function not only judges whether the instances are equal,
* but also judges the equals method in the example.
*
* @param left The first Cesium object
* @param right The second Cesium object
* @returns Returns true if the two Cesium objects are equal, otherwise false
*/
function cesiumEquals(left, right) {
return left === right || isFunction(left?.equals) && left.equals(right) || isFunction(right?.equals) && right.equals(left);
}
//#endregion
//#region utils/toCoord.ts
/**
* Converts coordinates to an array or object in the specified format.
*
* @param position The coordinate to be converted, which can be a Cartesian3, Cartographic, array, or object.
* @param options Conversion options, including conversion type and whether to include altitude information.
* @returns The converted coordinate, which may be an array or object. If the input position is empty, undefined is returned.
*
* @template T Conversion type, optional values are 'Array' or 'Object', @default 'Array'.
* @template Alt Whether to include altitude information, default is false
*/
function toCoord(position, options = {}) {
if (!position) return void 0;
const { type = "Array", alt = false } = options;
let longitude, latitude, height;
if (position instanceof Cartesian3) {
const cartographic = Ellipsoid.WGS84.cartesianToCartographic(position);
longitude = Math$1.toDegrees(cartographic.longitude);
latitude = Math$1.toDegrees(cartographic.latitude);
height = cartographic.height;
} else if (position instanceof Cartographic) {
const cartographic = position;
longitude = Math$1.toDegrees(cartographic.longitude);
latitude = Math$1.toDegrees(cartographic.latitude);
height = cartographic.height;
} else if (Array.isArray(position)) {
longitude = Math$1.toDegrees(position[0]);
latitude = Math$1.toDegrees(position[1]);
height = position[2];
} else {
longitude = position.longitude;
latitude = position.latitude;
height = position.height;
}
if (type === "Array") return alt ? [
longitude,
latitude,
height
] : [longitude, latitude];
else return alt ? {
longitude,
latitude,
height
} : {
longitude,
latitude
};
}
//#endregion
//#region utils/convertDMS.ts
/**
* Convert degrees to DMS (Degrees Minutes Seconds) format string
*
* @param degrees The angle value
* @param precision The number of decimal places to retain for the seconds, defaults to 3
* @returns A DMS formatted string in the format: degrees° minutes′ seconds″
*/
function dmsEncode(degrees, precision = 3) {
const str = `${degrees}`;
let i = str.indexOf(".");
const d = i < 0 ? str : str.slice(0, Math.max(0, i));
let m = "0";
let s = "0";
if (i > 0) {
m = `0${str.slice(Math.max(0, i))}`;
m = `${+m * 60}`;
i = m.indexOf(".");
if (i > 0) {
s = `0${m.slice(Math.max(0, i))}`;
m = m.slice(0, Math.max(0, i));
s = `${+s * 60}`;
i = s.indexOf(".");
s = s.slice(0, Math.max(0, i + 4));
s = (+s).toFixed(precision);
}
}
return `${Math.abs(+d)}°${+m}′${+s}″`;
}
/**
* Decode a DMS (Degrees Minutes Seconds) formatted string to a decimal angle value
*
* @param dmsCode DMS formatted string, e.g. "120°30′45″N"
* @returns The decoded decimal angle value, or 0 if decoding fails
*/
function dmsDecode(dmsCode) {
const [dd, msStr] = dmsCode.split("°") ?? [];
const [mm, sStr] = msStr?.split("′") ?? [];
const ss = sStr?.split("″")[0];
const d = Number(dd) || 0;
const m = (Number(mm) || 0) / 60;
const s = (Number(ss) || 0) / 60 / 60;
const degrees = d + m + s;
if (degrees === 0) return 0;
else {
let res = degrees;
if ([
"W",
"w",
"S",
"s"
].includes(dmsCode[dmsCode.length - 1])) res = -res;
return res;
}
}
/**
* Convert latitude and longitude coordinates to degrees-minutes-seconds format
*
* @param position The latitude and longitude coordinates
* @param precision The number of decimal places to retain for 'seconds', default is 3
* @returns Returns the coordinates in degrees-minutes-seconds format, or undefined if the conversion fails
*/
function degreesToDms(position, precision = 3) {
const coord = toCoord(position, { alt: true });
if (!coord) return;
const [longitude, latitude, height] = coord;
const x = dmsEncode(longitude, precision);
const y = dmsEncode(latitude, precision);
return [
`${x}${longitude > 0 ? "E" : "W"}`,
`${y}${latitude > 0 ? "N" : "S"}`,
height
];
}
/**
* Convert DMS (Degrees Minutes Seconds) format to decimal degrees for latitude and longitude coordinates
*
* @param dms The latitude or longitude coordinate in DMS format
* @returns Returns the coordinate in decimal degrees format, or undefined if the conversion fails
*/
function dmsToDegrees(dms) {
const [x, y, height] = dms;
const longitude = dmsDecode(x);
const latitude = dmsDecode(y);
return [
longitude,
latitude,
Number(height) || 0
];
}
//#endregion
//#region utils/isCesiumConstant.ts
/**
* Determines if the Cesium property is a constant.
*
* @param value Cesium property
*/
function isCesiumConstant(value) {
return !defined(value) || !!value.isConstant;
}
//#endregion
//#region utils/material.ts
/**
* Only as a type fix for `Cesium.Material`
*/
var CesiumMaterial = class extends Material {
constructor(options) {
super(options);
}
};
/**
* Get material from cache, alias of `Material._materialCache.getMaterial`
*/
function getMaterialCache(type) {
return Material._materialCache.getMaterial(type);
}
/**
* Add material to Cesium's material cache, alias of `Material._materialCache.addMaterial`
*/
function addMaterialCache(type, material) {
return Material._materialCache.addMaterial(type, material);
}
//#endregion
//#region utils/pick.ts
/**
* Analyze the result of Cesium's `scene.pick` and convert it to an array format
*/
function resolvePick(pick = {}) {
const { primitive, id, primitiveCollection, collection } = pick;
const entityCollection = id && id.entityCollection || null;
const dataSource = entityCollection && entityCollection.owner || null;
const ids = Array.isArray(id) ? id : [id].filter(Boolean);
return [
...ids,
primitive,
primitiveCollection,
collection,
entityCollection,
dataSource
].filter((e) => !!e);
}
/**
* Determine if the given array of graphics is hit by Cesium's `scene.pick`
*
* @param pick The `scene.pick` object used for matching
* @param graphic An array of graphics to check for hits
*/
function pickHitGraphic(pick, graphic) {
if (!Array.isArray(graphic) || !graphic.length) return false;
const elements = resolvePick(pick);
if (!elements.length) return false;
return elements.some((element) => graphic.includes(element));
}
//#endregion
//#region utils/property.ts
/**
* Is Cesium.Property
* @param value - The target object
*/
function isProperty(value) {
return value && isFunction(value.getValue);
}
/**
* Converts a value that may be a Property into its target value, @see {toProperty} for the reverse operation
* ```typescript
* toPropertyValue('val') //=> 'val'
* toPropertyValue(new ConstantProperty('val')) //=> 'val'
* toPropertyValue(new CallbackProperty(()=>'val')) //=> 'val'
* ```
*
* @param value - The value to convert
*/
function toPropertyValue(value, time) {
return isProperty(value) ? value.getValue(time) : value;
}
/**
* Converts a value that may be a Property into a Property object, @see {toPropertyValue} for the reverse operation
*
* @param value - The property value or getter to convert, can be undefined or null
* @param isConstant - The second parameter for converting to CallbackProperty
* @returns Returns the converted Property object, if value is undefined or null, returns undefined
*/
function toProperty(value, isConstant = false) {
return isProperty(value) ? value : isFunction(value) ? new CallbackProperty(value, isConstant) : new ConstantProperty(value);
}
/**
* Create a Cesium property key
*
* @param scope The host object
* @param field The property name
* @param maybeProperty Optional property or getter
* @param readonly Whether the property is read-only
*/
function createPropertyField(scope, field, maybeProperty, readonly$1) {
let removeOwnerListener;
const ownerBinding = (value) => {
removeOwnerListener?.();
if (defined(value?.definitionChanged)) removeOwnerListener = value?.definitionChanged?.addEventListener(() => {
scope.definitionChanged.raiseEvent(scope, field, value, value);
});
};
const privateField = `_${field}`;
const property = toProperty(maybeProperty);
scope[privateField] = property;
ownerBinding(property);
if (readonly$1) Object.defineProperty(scope, field, { get() {
return scope[privateField];
} });
else Object.defineProperty(scope, field, {
get() {
return scope[privateField];
},
set(value) {
const previous = scope[privateField];
if (scope[privateField] !== value) {
scope[privateField] = value;
ownerBinding(value);
if (defined(scope.definitionChanged)) scope.definitionChanged.raiseEvent(scope, field, value, previous);
}
}
});
}
function createCesiumAttribute(scope, key, value, options = {}) {
const allowToProperty = !!options.toProperty;
const shallowClone = !!options.shallowClone;
const changedEventKey = options.changedEventKey || "definitionChanged";
const changedEvent = Reflect.get(scope, changedEventKey);
const privateKey = `_${String(key)}`;
const attribute = allowToProperty ? toProperty(value) : value;
Reflect.set(scope, privateKey, attribute);
const obj = { get() {
const value$1 = Reflect.get(scope, privateKey);
if (shallowClone) return Array.isArray(value$1) ? [...value$1] : { ...value$1 };
else return value$1;
} };
let previousListener;
const serial = (property, previous) => {
previousListener?.();
previousListener = property?.definitionChanged?.addEventListener(() => {
changedEvent?.raiseEvent.bind(changedEvent)(scope, key, property, previous);
});
};
if (!options.readonly) {
if (allowToProperty && isProperty(value)) serial(value);
obj.set = (value$1) => {
if (allowToProperty && !isProperty(value$1)) throw new Error(`The value of ${String(key)} must be a Cesium.Property object`);
const previous = Reflect.get(scope, privateKey);
if (previous !== value$1) {
Reflect.set(scope, privateKey, value$1);
changedEvent?.raiseEvent.bind(changedEvent)(scope, key, value$1, previous);
if (allowToProperty) serial(value$1);
}
};
}
Object.defineProperty(scope, key, obj);
}
function createCesiumProperty(scope, key, value, options = {}) {
return createCesiumAttribute(scope, key, value, {
...options,
toProperty: true
});
}
//#endregion
//#region utils/throttle.ts
/**
* Throttle function, which limits the frequency of execution of the function
*
* @param callback raw function
* @param delay Throttled delay duration (ms)
* @param trailing Trigger callback function after last call @default true
* @param leading Trigger the callback function immediately on the first call @default false
* @returns Throttle function
*/
function throttle(callback, delay = 100, trailing = true, leading = false) {
const restList = [];
let tracked = false;
const trigger = async () => {
await promiseTimeout(delay);
tracked = false;
if (leading) try {
callback(...restList[0]);
} catch (error) {
console.error(error);
}
if (trailing && (!leading || restList.length > 1)) try {
callback(...restList[restList.length - 1]);
} catch (error) {
console.error(error);
}
restList.length = 0;
};
return (...rest) => {
if (restList.length < 2) restList.push(rest);
else restList[1] = rest;
if (!tracked) {
tracked = true;
trigger();
}
};
}
//#endregion
//#region utils/toCartesian3.ts
/**
* Converts position to a coordinate point in the Cartesian coordinate system
*
* @param position Position information, which can be a Cartesian coordinate point (Cartesian3), a geographic coordinate point (Cartographic), an array, or an object containing WGS84 latitude, longitude, and height information
* @returns The converted Cartesian coordinate point. If the input parameter is invalid, undefined is returned
*/
function toCartesian3(position) {
if (!position) return void 0;
if (position instanceof Cartesian3) return position.clone();
else if (position instanceof Cartographic) return Ellipsoid.WGS84.cartographicToCartesian(position);
else if (Array.isArray(position)) return Cartesian3.fromDegrees(position[0], position[1], position[2]);
else return Cartesian3.fromDegrees(position.longitude, position.latitude, position.height);
}
//#endregion
//#region utils/toCartographic.ts
/**
* Converts a position to a Cartographic coordinate point
*
* @param position Position information, which can be a Cartesian3 coordinate point, a Cartographic coordinate point, an array, or an object containing WGS84 longitude, latitude, and height information
* @returns The converted Cartographic coordinate point, or undefined if the input parameter is invalid
*/
function toCartographic(position) {
if (!position) return void 0;
if (position instanceof Cartesian3) return Ellipsoid.WGS84.cartesianToCartographic(position);
else if (position instanceof Cartographic) return position.clone();
else if (Array.isArray(position)) return Cartographic.fromDegrees(position[0], position[1], position[2]);
else return Cartographic.fromDegrees(position.longitude, position.latitude, position.height);
}
//#endregion
//#region utils/tryRun.ts
/**
* Safely execute the provided function without throwing errors,
* essentially a simple wrapper around a `try...catch...` block
*/
function tryRun(fn) {
return (...args) => {
try {
return fn?.(...args);
} catch (error) {
console.error(error);
}
};
}
//#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
*
* @example
* ```ts
*
* const data = computedAsync(async ()=> {
* return await toPromiseValue(promiseRef)
* })
*
* ```
*/
async function toPromiseValue(source, options = {}) {
try {
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;
} catch (error) {
console.error(error);
throw error;
}
}
//#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.
*/
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$1) => {
const e = toValue(event$1);
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
*/
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?.directionWC?.clone() : void 0),
rightWC: computed(() => changedSymbol.value ? camera.value?.directionWC?.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 ? 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
*/
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
* @param addFn - add SideEffect function. eg.`entites.add`
* @param removeFn - Clean SideEffect function. eg.`entities.remove`
* @param removeScopeArgs - The parameters to pass for `removeScope` triggered when the component is unmounted
*/
function useCollectionScope(addFn, removeFn, removeScopeArgs) {
const scope = shallowReactive(/* @__PURE__ */ new Set());
const add = (instance, ...args) => {
const result = addFn(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 removeFn(instance, ...args);
};
const removeWhere = (predicate, ...args) => {
scope.forEach((instance) => {
if (predicate(instance)) remove(instance, ...args);
});
};
const removeScope = (...args) => {
scope.forEach((instance) => {
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) => {
const _isActive = toValue(isActive);
if (_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;
});
const addFn = (dataSource) => {
if (!collection.value) throw new Error("collection is not defined");
return collection.value.add(dataSource);
};
const removeFn = (dataSource, destroy) => {
return !!collection.value?.remove(dataSource, destroy);
};
const { scope, add, remove, removeWhere, removeScope } = useCollectionScope(addFn, removeFn, [destroyOnRemove]);
return {
scope,
add,
remove,
removeWhere,
removeScope
};
}
//#endregion
//#region useElementOverlay/index.ts
/**
* Cesium HtmlElement Overlay
*/
function useElementOverlay(target, position, options = {}) {
const { referenceWindow, horizontal = "center", vertical = "bottom", offset = {
x: 0,
y: 0
} } = options;
const cartesian3 = computed(() => toCartesian3(toValue(position)));
const viewer = useViewer();
const coord = shallowRef();
useCesiumEventListener(() => viewer.value?.scene.postRender, () => {
if (!viewer.value?.scene) return;
if (!cartesian3.value) coord.value = void 0;
else {
const reslut = cartesianToCanvasCoord(cartesian3.value, viewer.value.scene);
coord.value = !Cartesian2.equals(reslut, coord.value) ? reslut : coord.value;
}
});
const canvasBounding = useElementBounding(() => viewer.value?.canvas.parentElement);
const targetBounding = useElementBounding(target);
const finalOffset = computed(() => {
const _offset = toValue(offset);
let x$1 = _offset?.x ?? 0;
const _horizontal = toValue(horizontal);
if (_horizontal === "center") x$1 -= targetBounding.width.value / 2;
else if (_horizontal === "right") x$1 -= targetBounding.width.value;
let y$1 = _offset?.y ?? 0;
const _vertical = toValue(vertical);
if (_vertical === "center") y$1 -= targetBounding.height.value / 2;
else if (_vertical === "bottom") y$1 -= targetBounding.height.value;
return {
x: x$1,
y: y$1
};
});
const location = computed(() => {
const data = {
x: coord.value?.x ?? 0,
y: coord.value?.y ?? 0
};
if (toValue(referenceWindow)) {
data.x += canvasBounding.x.value;
data.y += canvasBounding.y.value;
}
return {
x: finalOffset.value.x + data.x,
y: finalOffset.value.y + data.y
};
});
const x = computed(() => location.value.x);
const y = computed(() => location.value.y);
const style = computed(() => ({
left: `${x.value?.toFixed(2)}px`,
top: `${y.value?.toFixed(2)}px`
}));
watchEffect(() => {
const element = toValue(target);
if (element && toValue(options.applyStyle ?? true)) {
element.style?.setProperty?.("left", style.value.left);
element.style?.setProperty?.("top", style.value.top);
}
});
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) => {
const _isActive = toValue(isActive);
if (_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;
});
const addFn = (entity) => {
if (!collection.value) throw new Error("collection is not defined");
if (!collection.value.contains(entity)) collection.value.add(entity);
return entity;
};
const removeFn = (entity) => {
return !!collection.value?.remove(entity);
};
const { scope, add, remove, removeWhere, removeScope } = useCollectionScope(addFn, removeFn, []);
return {
scope,
add,
remove,
removeWhere,
removeScope
};
}
//#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 cache = pickCache.get(viewer.value);
if (cache && cache[0].equals(position.value)) pick.value = cache[1];
else {
pickCache.set(viewer.value, [position.value.clone(), pick.value]);
pick.value = viewer.value?.scene.pick(position.value, toValue(width), toValue(height));
}
}
});
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);
const handler = computed(() => {
if (viewer.value?.cesiumWidget?.canvas) return new ScreenSpaceEventHandler(viewer.value.cesiumWidget.canvas);
});
const cleanup1 = watch(handler, (_value, previous) => {
viewer.value?.cesiumWidget && previous?.destroy();
});
const cleanup2 = watchEffect((onCleanup) => {
const typeValue = toValue(type);
const modifierValue = toValue(modifier);
const handlerValue = toValue(handler);
if (!handlerValue || !isActive.value || !inputAction) return;
if (isDef(typeValue)) {
handlerValue.setInputAction(inputAction, typeValue, modifierValue);
onCleanup(() => handlerValue.removeInputAction(typeValue, modifierValue));
}
});
const stop = () => {
cleanup1();
cleanup2();
};
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, (cameraLocked$1) => {
viewer.value && (viewer.value.scene.screenSpaceCameraController.enableRotate = !cameraLocked$1);
});
const lockCamera = () => {
cameraLocked.value = true;
};
const execute = (pick$1, startPosition, endPosition) => {
listener({
event: {
startPosition: startPosition.clone(),
endPosition: endPosition.clone()
},
pick: pick$1,
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], ([pick$1, motionEvent$1]) => {
if (pick$1 && motionEvent$1) {
const { startPosition, endPosition } = motionEvent$1;
dragging.value && execute(pick$1, 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 = (pick$1, startPosition, endPosition, hovering) => {
listener({
event: {
startPosition: startPosition.clone(),
endPosition: endPosition.clone()
},
pick: pick$1,
hovering
});
};
useScreenSpaceEventHandler(ScreenSpaceEventType.MOUSE_MOVE, ({ startPosition, endPosition }) => {
if (!startPosition.equals(motionEvent.value?.startPosition) || !endPosition.equals(motionEvent.value?.endPosition)) motionEvent.value = {
startPosition: startPosition.clone(),
endPosition: endPosition.clone()
};
});
watch([pick, motionEvent], ([pick$1, motionEvent$1]) => {
if (pick$1 && motionEvent$1) {
const { startPosition, endPosition } = motionEvent$1;
execute(pick$1, startPosition, endPosition, true);
}
});
watch(pick, (pick$1, 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 removeGraphicEvent = (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 addGraphicEvent = (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());
const listeners = eventTypeMap.get(type);
listeners.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 () => removeGraphicEvent(graphic, type, listener);
};
const clearGraphicEvent = (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) => {
const graphics = resolvePick(event.pick);
graphics.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) => {
const graphics = resolvePick(event.pick).concat(GLOBAL_GRAPHIC_SYMBOL);
graphics.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 {
addGraphicEvent,
removeGraphicEvent,
clearGraphicEvent
};
}
//#endregion
//#region useImageryLayer/index.ts
function useImageryLayer(data, options = {}) {
const { destroyOnRemove, collection, isActive = true, evaluating } = options;
const result = computedAsync(() => toPromiseValue(data), [], { evaluating });
const viewer = useViewer();
watchEffect((onCleanup) => {
const _isActive = toValue(isActive);
if (_isActive) {
const list = Array.isArray(result.value) ? [...result.value] : [result.value];
const _collection = collection ?? viewer.value?.imageryLayers;
if (collection?.isDestroyed()) return;
list.forEach((item) => {
if (!item) {
console.warn("ImageryLayer is undefined");
return;
}
if (item?.isDestroyed()) {
console.warn("ImageryLayer is destroyed");
return;
}
_collection?.add(item);
});
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;
});
const addFn = (imageryLayer, index) => {
if (!collection.value) throw new Error("collection is not defined");
collection.value.add(imageryLayer, index);
return imageryLayer;
};
const removeFn = (imageryLayer, destroy) => {
return !!collection.value?.remove(imageryLayer, destroy);
};
const { scope, add, remove, removeWhere, removeScope } = useCollectionScope(addFn, removeFn, [destroyOnRemove]);
return {
scope,
add,
remove,
removeWhere,
removeScope
};
}
//#endregion
//#region usePostProcessStage/index.ts
function usePostProcessStage(data, options = {}) {
const { collection, isActive = true, evaluating } = options;
const result = computedAsync(() => toPromiseValue(data), void 0, { evaluating });
const viewer = useViewer();
watchEffect((onCleanup) => {
if (!viewer.value) return;
const _isActive = toValue(isActive);
if (_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) => item && _collection.remove(item));
});
}
});
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 } = options;
const viewer = useViewer();
const collection = computed(() => {
return toValue(_collection) ?? viewer.value?.postProcessStages;
});
const addFn = (postProcessStage) => {
if (!collection.value) throw new Error("collection is not defined");
return collection.value.add(postProcessStage);
};
const removeFn = (postProcessStage) => {
return !!collection.value?.remove(postProcessStage);
};
const { scope, add, remove, removeWhere, removeScope } = useCollectionScope(addFn, removeFn, []);
return {
scope,
add,
remove,
removeWhere,
removeScope
};
}
//#endregion
//#region usePrimitive/index.ts
function usePrimitive(data, options = {}) {
const { collection, isActive = true, evaluating } = options;
const result = computedAsync(() => toPromiseValue(data), void 0, { evaluating });
const viewer = useViewer();
watchEffect((onCleanup) => {
const _isActive = toValue(isActive);
if (_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) => item && _collection?.remove(item));
});
}
});
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 } = options;
const viewer = useViewer();
const collection = computed(() => {
return toValue(_collection) ?? viewer.value?.scene.primitives;
});
const addFn = (primitive) => {
if (!collection.value) throw new Error("collection is not defined");
return collection.value.add(primitive);
};
const removeFn = (primitive) => {
return !!collection.value?.remove(primitive);
};
const { scope, add, remove, removeWhere, removeScope } = useCollectionScope(addFn, removeFn, []);
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;
const leftCartographic = scene.globe.ellipsoid.cartesianToCartographic(leftPosition);
const rightCartographic = scene.globe.ellipsoid.cartesianToCartographic(rightPosition);
const geodesic = new EllipsoidGeodesic(leftCartographic, rightCartographic);
pixelDistance.value = geodesic.surfaceDistance;
};
watchImmediate(viewer, () => setPixelDistance());
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) {
const value = distance.value / pixelDistance.value;
return value;
}
return 0;
});
const distanc