@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
186 lines (152 loc) • 5.35 kB
text/typescript
/*
* 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;
}
}