UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

186 lines (152 loc) 5.35 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type { Feature } from 'ol'; import { MathUtils } from 'three'; import type { GetFeatureRequest, GetFeatureResult } from './FeatureSource'; import CoordinateSystem from '../core/geographic/CoordinateSystem'; import { nonNull } from '../utils/tsutils'; import { filterByExtent } from './features/processor'; import { FeatureSourceBase } from './FeatureSource'; function preprocess(feature: Feature, src: CoordinateSystem, dst: CoordinateSystem): Feature { if (feature.getId() == null) { feature.setId(MathUtils.generateUUID()); } if (src.id !== dst.id) { feature.getGeometry()?.transform(src.id, dst.id); } return feature; } export interface StaticFeaturesSourceOptions { /** * The initial features in this source. */ features?: Feature[]; /** * The coordinate system of features contained in this source. * @defaultValue {@link CoordinateSystem.epsg4326} */ coordinateSystem?: CoordinateSystem; } /** * A feature source that does not read from any remote source, but * instead acts as a container for features added by the user. * * > [!note] * > When features are added to this source, they might be transformed to * > match the target coordinate system, as well as assigning them unique IDs. */ export default class StaticFeatureSource extends FeatureSourceBase { public readonly isStaticFeatureSource = true as const; public override readonly type = 'StaticFeatureSource' as const; private readonly _initialFeatures: Feature[] | undefined = undefined; private readonly _features: Set<Feature> = new Set(); private readonly _coordinateSystem: CoordinateSystem; /** * Returns a copy of the features contained in this source. * * Note: this property returns an empty array if the source is not yet initialized. */ public get features(): Readonly<Feature[]> { return [...this._features]; } public constructor(options?: StaticFeaturesSourceOptions) { super(); this._coordinateSystem = options?.coordinateSystem ?? CoordinateSystem.epsg4326; if (options?.features) { this._initialFeatures = [...options.features]; } } /** * Adds a single feature. * * Note: if you want to add multiple features at once, use {@link addFeatures} for better performance. */ public addFeature(feature: Feature): void { this.throwIfNotInitialized(); this.doAddFeatures(feature); this.update(); } /** * Removes a single feature. * * Note: if you want to remove multiple features at once, use {@link removeFeatures} for better performance. * * @returns `true` if the feature feature was actually removed, `false` otherwise. */ public removeFeature(feature: Feature): boolean { if (this._features.delete(feature)) { this.update(); return true; } return false; } /** * Adds multiple features. */ public addFeatures(features: Iterable<Feature>): void { this.throwIfNotInitialized(); this.doAddFeatures([...features]); this.update(); } /** * Removes multiple features. * * @returns `true` if at least one feature was actually removed, `false` otherwise. */ public removeFeatures(features: Iterable<Feature>): boolean { let actuallyRemoved = false; for (const feature of features) { if (this._features.delete(feature)) { actuallyRemoved = true; } } if (actuallyRemoved) { this.update(); return true; } return false; } /** * Removes all features. */ public clear(): void { if (this._features.size > 0) { this._features.clear(); this.update(); } } private doAddFeatures(features: Feature | Feature[]): void { if (Array.isArray(features)) { features.forEach(f => { preprocess(f, this._coordinateSystem, nonNull(this._targetCoordinateSystem)); this._features.add(f); }); } else { preprocess(features, this._coordinateSystem, nonNull(this._targetCoordinateSystem)); this._features.add(features); } } public override async initialize(options: { targetCoordinateSystem: CoordinateSystem; }): Promise<void> { await super.initialize(options); // Let's prepare the features that were added during construction. // We couldn't do that before since the target coordinate system was not known. if (this._initialFeatures) { this.doAddFeatures(this._initialFeatures); this._initialFeatures.length = 0; } } public override async getFeatures(request: GetFeatureRequest): Promise<GetFeatureResult> { const filtered = await filterByExtent([...this._features], request.extent, { signal: request.signal, }); const result: GetFeatureResult = { features: filtered, }; return result; } }