UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

149 lines (118 loc) 4.68 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type { Feature } from 'ol'; import type FeatureFormat from 'ol/format/Feature'; import type { Type } from 'ol/format/Feature'; import type { Geometry } from 'ol/geom'; import GeoJSON from 'ol/format/GeoJSON'; import CoordinateSystem from '../core/geographic/CoordinateSystem'; import Fetcher from '../utils/Fetcher'; import { nonNull } from '../utils/tsutils'; import { filterByExtent, processFeatures } from './features/processor'; import { FeatureSourceBase, type GetFeatureRequest, type GetFeatureResult } from './FeatureSource'; export type Getter = (url: string, type: Type) => Promise<unknown>; const defaultGetter: Getter = (url, type) => { switch (type) { case 'arraybuffer': return Fetcher.arrayBuffer(url); case 'json': return Fetcher.json(url); case 'text': return Fetcher.text(url); case 'xml': return Fetcher.xml(url); } }; export interface FileFeatureSourceOptions { /** * The URL to the remote file. */ url: string; /** * The format to parse the file. * @defaultValue {@link GeoJSON} */ format?: FeatureFormat; /** * A function to retrieve the file remotely. * If not specified, will use standard fetch functions to download the file. * Mostly useful for unit testing. */ getter?: Getter; /** * The coordinate system of the features in this source. * 1. If not provided, will attempt to read it from the file. * 2. If the file does not contain coordinate system information, will assume EPSG:4326. */ sourceCoordinateSystem?: CoordinateSystem; } /** * Loads features from a remote file (such as GeoJSON, GPX, etc.) */ export default class FileFeatureSource extends FeatureSourceBase { public readonly isFileFeatureSource = true as const; public readonly type = 'FileFeatureSource' as const; private readonly _format: FeatureFormat; private _features: Feature<Geometry>[] | null = null; private _loadFeaturePromise: Promise<Feature<Geometry>[]> | null = null; private _getter: Getter; private _url: string; private _sourceCoordinateSystem?: CoordinateSystem; public constructor(params: FileFeatureSourceOptions) { super(); this._format = params.format ?? new GeoJSON(); this._url = params.url; this._sourceCoordinateSystem = params.sourceCoordinateSystem; this._getter = params.getter ?? defaultGetter; } private loadFeatures(): Promise<Feature[]> { this.throwIfNotInitialized(); if (this._features != null) { return Promise.resolve(this._features); } if (this._loadFeaturePromise != null) { return this._loadFeaturePromise; } this._loadFeaturePromise = this.loadFeaturesOnce(); return this._loadFeaturePromise; } private async loadFeaturesOnce(): Promise<Feature<Geometry>[]> { const data = await this._getter(this._url, this._format.getType()); if (!this._sourceCoordinateSystem) { const dataProjection = this._format.readProjection(data); if (dataProjection) { this._sourceCoordinateSystem = CoordinateSystem.get(dataProjection?.getCode()); } else { this._sourceCoordinateSystem = CoordinateSystem.epsg4326; } } const features = this._format.readFeatures(data) as Feature[]; const targetProjection = nonNull( this._targetCoordinateSystem, 'this source is not initialized', ); const sourceProjection = nonNull(this._sourceCoordinateSystem); const actualFeatures = await processFeatures(features, sourceProjection, targetProjection); this._features = actualFeatures; return actualFeatures; } public async getFeatures(request: GetFeatureRequest): Promise<GetFeatureResult> { request.signal?.throwIfAborted(); const features = await this.loadFeatures(); request.signal?.throwIfAborted(); const filtered = await filterByExtent(features, request.extent, { signal: request.signal }); request.signal?.throwIfAborted(); return { features: filtered }; } /** * Deletes the already loaded features, and dispatch an event to reload the features. */ public reload(): void { this._features = null; this._loadFeaturePromise = null; this.dispatchEvent({ type: 'updated' }); } }