@juun-roh/cesium-utils
Version:
Utilities to handle Cesium classes easier.
473 lines (469 loc) • 16 kB
text/typescript
import { BillboardCollection, DataSourceCollection, EntityCollection, ImageryLayerCollection, LabelCollection, PointPrimitiveCollection, PolylineCollection, PrimitiveCollection, Billboard, DataSource, Entity, ImageryLayer, Label, PointPrimitive, Polyline, Primitive, Cesium3DTileset, GroundPrimitive } from 'cesium';
type CesiumCollection = BillboardCollection | DataSourceCollection | EntityCollection | ImageryLayerCollection | LabelCollection | PointPrimitiveCollection | PolylineCollection | PrimitiveCollection;
type Primitives = Primitive | Cesium3DTileset | GroundPrimitive;
type CesiumCollectionItem = Billboard | DataSource | Entity | ImageryLayer | Label | PointPrimitive | Polyline | Primitives;
type Tag = string | number;
interface WithTag {
[key: symbol]: Tag;
}
/**
* Collection event types
*/
type CollectionEventType = 'add' | 'remove' | 'update' | 'clear';
/**
* Event handler function type
*/
type EventHandler<I> = (event: {
type: CollectionEventType;
items?: I[];
tag?: Tag;
}) => void;
type NonFunction<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
/**
* @class
* A wrapper class that enhances Cesium collection objects with tagging functionality.
* This class provides a consistent API for working with different types of Cesium collections
* and allows grouping and manipulating collection items by custom tags.
*
* @template C - The type of Cesium collection (e.g., EntityCollection, PrimitiveCollection)
* @template I - The type of items in the collection (e.g., Entity, Primitive)
*
* @example
* // Example 1: Managing Complex Scene with Multiple Object Types
* class SceneOrganizer {
* private entities: Collection<EntityCollection, Entity>;
* private billboards: Collection<BillboardCollection, Billboard>;
* private primitives: Collection<PrimitiveCollection, Primitive>;
*
* constructor(viewer: Viewer) {
* this.entities = new Collection({ collection: viewer.entities });
* this.billboards = new Collection({
* collection: viewer.scene.primitives.add(new BillboardCollection())
* });
* this.primitives = new Collection({
* collection: viewer.scene.primitives
* });
* }
*
* // Unified API across different collection types!
* showLayer(layerName: string) {
* this.entities.show(layerName);
* this.billboards.show(layerName);
* this.primitives.show(layerName);
* }
*
* hideLayer(layerName: string) {
* this.entities.hide(layerName);
* this.billboards.hide(layerName);
* this.primitives.hide(layerName);
* }
*
* removeLayer(layerName: string) {
* this.entities.remove(layerName);
* this.billboards.remove(layerName);
* this.primitives.remove(layerName);
* }
* }
*
* // Example 2: Extend the class for Domain-Specific Needs
* class BuildingCollection extends Collection<EntityCollection, Entity> {
* constructor(viewer: Viewer) {
* super({ collection: viewer.entities, tag: 'buildings' });
* }
*
* addBuilding(options: {
* position: Cartesian3;
* height: number;
* floors: number;
* type: 'residential' | 'commercial' | 'industrial';
* }): Entity {
* const building = new Entity({
* position: options.position,
* box: {
* dimensions: new Cartesian3(50, 50, options.height),
* material: this.getMaterialForType(options.type)
* }
* });
*
* // Tag by type AND by floor count
* this.add(building, options.type);
* this.add(building, `floors-${options.floors}`);
*
* return building;
* }
*
* getByFloorRange(min: number, max: number): Entity[] {
* const results: Entity[] = [];
* for (let i = min; i <= max; i++) {
* results.push(...this.get(`floors-${i}`));
* }
* return results;
* }
*
* private getMaterialForType(type: string): Material {
* const colors = {
* residential: Color.GREEN,
* commercial: Color.BLUE,
* industrial: Color.YELLOW
* };
* return new ColorMaterialProperty(colors[type] || Color.WHITE);
* }
* }
*/
declare class Collection<C extends CesiumCollection, I extends CesiumCollectionItem> {
/**
* Symbol used as a property key to store tags on collection items.
* Using a Symbol ensures no property naming conflicts with the item's own properties.
* @readonly
*/
static readonly symbol: unique symbol;
/**
* Default tag used when adding items without specifying a tag.
* @protected
*/
protected tag: Tag;
/**
* The underlying Cesium collection being wrapped.
* @protected
*/
protected collection: C;
/**
* Cache for values array to improve performance
* @private
*/
private _valuesCache;
/**
* Tag to items map for faster lookups
* @private
*/
private _tagMap;
/**
* Event listeners
* @private
*/
private _eventListeners;
/**
* Creates a new Collection instance.
*
* @param options - Configuration options
* @param options.collection - The Cesium collection to wrap
* @param options.tag - The default tag to use for items (defaults to 'default')
*/
constructor({ collection, tag }: {
collection: C;
tag?: Tag;
});
/**
* Makes the collection directly iterable, allowing it to be used in `for...of` loops
* and with spread operators.
*
* @returns An iterator for the items in the collection
*
* @example
* // Iterate through all items in the collection
* for (const entity of collection) {
* console.log(entity.id);
* }
*
* // Convert collection to array using spread syntax
* const entitiesArray = [...collection];
*/
[Symbol.iterator](): Iterator<I>;
/**
* Emits an event to all registered listeners.
*
* @private
* @param type - The event type
* @param data - Additional event data
*/
private _emit;
/**
* Adds an item to the internal tag map for quick lookups.
*
* @private
* @param item - The item to add
* @param tag - The tag to associate with the item
*/
private _addToTagMap;
/**
* Removes an item from the internal tag map.
*
* @private
* @param item - The item to remove
*/
private _removeFromTagMap;
/**
* Invalidates the values cache when collection changes.
*
* @private
*/
private _invalidateCache;
/**
* Registers an event listener for collection events.
*
* @param type - The event type to listen for
* @param handler - The callback function
* @returns The collection instance for method chaining
*/
addEventListener(type: CollectionEventType, handler: EventHandler<I>): this;
/**
* Removes an event listener.
*
* @param type - The event type
* @param handler - The callback function to remove
* @returns The collection instance for method chaining
*/
removeEventListener(type: CollectionEventType, handler: EventHandler<I>): this;
/**
* Adds a single item with a tag to the collection.
*
* @param item - The item to add to the collection
* @param tag - Tag to associate with this item (defaults to the collection's default tag)
* @param index - The index to add the item at (if supported by the collection)
* @returns The added item for chaining
*
* @example
* const entity = collection.add(new Entity({ ... }), 'landmarks');
*/
add(item: I, tag?: Tag, index?: number): I;
/**
* Adds multiple items with the same tag to the collection.
*
* @param items - The array of items to add to the collection
* @param tag - Tag to associate with this item (defaults to the collection's default tag)
* @returns The array of added items
*
* @example
* // Add multiple entities with the same tag
* const entities = [new Entity({ ... }), new Entity({ ... })];
* const addedEntities = collection.add(entities, 'buildings');
*/
add(items: I[], tag?: Tag): I[];
/**
* Returns true if the provided item is in this collection, false otherwise.
*
* @param item - The item instance to check for
* @returns True if the item is in the collection, false otherwise
*/
contains(item: I): boolean;
/**
* Checks if the collection has any items with the specified tag.
*
* @param tag - The tag to check for
* @returns True if items with the tag exist, false otherwise
*
* @example
* if (collection.contains('temporary')) {
* console.log('Temporary items exist');
* }
*/
contains(tag: Tag): boolean;
/**
* Removes an item from the collection.
*
* @param item - The item to remove
* @returns True if the item was removed, false if it wasn't found
*/
remove(item: I): boolean;
/**
* Removes all items with the specified tag from the collection.
*
* @param by - The tag identifying which items to remove
* @returns True if the item was removed, false if it wasn't found
*/
remove(by: Tag): boolean;
/**
* Removes all items with the array of tags from the collection.
*
* @param by - The tags identifying which items to remove
* @returns True if the item was removed, false if it wasn't found
*/
remove(by: Tag[]): boolean;
/**
* Removes all items from the collection.
*/
removeAll(): void;
/**
* Gets all item instances in the collection.
* This array should not be modified directly.
*
* @returns An array of all items in the collection
*/
get values(): I[];
/**
* Gets the number of items in the collection.
*
* @returns The item count
*/
get length(): number;
/**
* Gets all items with the specified tag from the collection.
* Uses an optimized internal map for faster lookups.
*
* @param by - The tag to filter by
* @returns An array of items with the specified tag, or an empty array if none found
*
* @example
* // Get all buildings
* const buildings = collection.get('buildings');
*/
get(by: Tag): I[];
/**
* Gets the first item matching the tag. More efficient than `get` when
* you only need one item, especially for large collections.
*
* @param by - The tag to search for
* @returns The first matching item or undefined if none found
*
* @example
* // Get the first building
* const firstBuilding = collection.first('buildings');
*/
first(by: Tag): I | undefined;
/**
* Gets all unique tags currently in use in the collection.
*
* @returns An array of all unique tags
*
* @example
* // Get all tags
* const tags = collection.tags;
* console.log(`Collection has these tags: ${tags.join(', ')}`);
*/
get tags(): Tag[];
/**
* Updates the tag for all items with the specified tag.
*
* @param from - The tag to replace
* @param to - The new tag to assign
* @returns The number of items updated
*
* @example
* // Rename a tag
* const count = collection.update('temp', 'temporary');
* console.log(`Updated ${count} items`);
*/
update(from: Tag, to: Tag): number;
/**
* Makes all items with the specified tag visible.
* Only affects items that have a 'show' property.
*
* @param by - The tag identifying which items to show
* @returns The collection itself.
*
* @example
* // Show all buildings
* collection.show('buildings');
*/
show(by: Tag): this;
/**
* Hides all items with the specified tag.
* Only affects items that have a 'show' property.
*
* @param by - The tag identifying which items to hide
* @returns The collection itself.
*
* @example
* // Hide all buildings
* collection.hide('buildings');
*/
hide(by: Tag): this;
/**
* Toggles visibility of all items with the specified tag.
* Only affects items that have a 'show' property.
*
* @param by - The tag identifying which items to toggle
* @returns The collection itself.
*
* @example
* // Toggle visibility of all buildings
* collection.toggle('buildings');
*/
toggle(by: Tag): this;
/**
* Sets a property value on all items with the specified tag.
*
* @param by - The tag identifying which items to update
* @param property - The property name to set
* @param value - The value to set
* @returns The number of items updated
*
* @example
* // Change color of all buildings to red
* collection.setProperty('buildings', 'color', Color.RED);
*/
setProperty<K extends NonFunction<I>>(property: K, value: I[K], by?: Tag): this;
/**
* Filters items in the collection based on a predicate function.
* Optionally only filters items with a specific tag.
*
* @param predicate - Function that tests each item
* @param by - Optional tag to filter by before applying the predicate
* @returns Array of items that pass the test
*
* @example
* // Get all buildings taller than 100 meters
* const tallBuildings = collection.filter(
* entity => entity.properties?.height?.getValue() > 100,
* 'buildings'
* );
*/
filter(predicate: (item: I) => boolean, by?: Tag): I[];
/**
* Executes a callback function for each item in the collection.
* Optionally filters items by tag before execution.
*
* @param callback - Function to execute for each item
* @param by - Optional tag to filter items (if not provided, processes all items)
*
* @example
* // Highlight all buildings
* collection.forEach((entity) => {
* if (entity.polygon) {
* entity.polygon.material = new ColorMaterialProperty(Color.YELLOW);
* }
* }, 'buildings');
*/
forEach(callback: (value: I, index: number) => void, by?: Tag): void;
/**
* Creates a new array with the results of calling a provided function on every element
* in the collection. Optionally filters by tag before mapping.
*
* @param callbackfn - Function that produces an element of the new array
* @param by - Optional tag to filter items by before mapping
* @returns A new array with each element being the result of the callback function
*
* @example
* // Get all entity IDs
* const entityIds = collection.map(entity => entity.id);
*
* // Get positions of all buildings
* const buildingPositions = collection.map(
* entity => entity.position.getValue(Cesium.JulianDate.now()),
* 'buildings'
* );
*/
map<R>(callbackfn: (value: I, index: number) => R, by?: Tag): R[];
/**
* Returns the first element in the collection that satisfies the provided testing function.
* Optionally filters by tag before searching.
*
* @param predicate - Function to test each element
* @param by - Optional tag to filter items by before searching
* @returns The first element that passes the test, or undefined if no elements pass
*
* @example
* // Find the first entity with a specific name
* const namedEntity = collection.find(entity => entity.name === 'Target');
*
* // Find the first building taller than 100 meters
* const tallBuilding = collection.find(
* entity => entity.properties?.height?.getValue() > 100,
* 'buildings'
* );
*/
find(predicate: (value: I) => boolean, by?: Tag): I | undefined;
}
export { type CesiumCollection, type CesiumCollectionItem, Collection, type CollectionEventType, type EventHandler, type NonFunction, type Tag, type WithTag };