UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

202 lines (193 loc) 7.63 kB
import { Extent, CRS } from '@itowns/geographic'; import GeoJsonParser from "../Parser/GeoJsonParser.js"; import KMLParser from "../Parser/KMLParser.js"; import GDFParser from "../Parser/GDFParser.js"; import GpxParser from "../Parser/GpxParser.js"; import GTXParser from "../Parser/GTXParser.js"; import ISGParser from "../Parser/ISGParser.js"; import VectorTileParser from "../Parser/VectorTileParser.js"; import Fetcher from "../Provider/Fetcher.js"; // import Cache from 'Core/Scheduler/Cache'; import { LRUCache } from 'lru-cache'; /** @private */ export const supportedParsers = new Map([['application/geo+json', GeoJsonParser.parse], ['application/json', GeoJsonParser.parse], ['application/kml', KMLParser.parse], ['application/gpx', GpxParser.parse], ['application/x-protobuf;type=mapbox-vector', VectorTileParser.parse], ['application/gtx', GTXParser.parse], ['application/isg', ISGParser.parse], ['application/gdf', GDFParser.parse]]); const noCache = { get: () => {}, set: a => a, clear: () => {} }; /** * This interface describes parsing options. * @typedef {Object} ParsingOptions * @property {Source} in - data informations contained in the file. * @property {FeatureBuildingOptions|Layer} out - options indicates how the features should be built. */ let uid = 0; /** * Sources are object containing informations on how to fetch resources, from a * set source. * * To extend a Source, it is necessary to implement two functions: * `urlFromExtent` and `extentInsideLimit`. * * @extends InformationsData * * @property {boolean} isSource - Used to checkout whether this source is a * Source. Default is true. You should not change this, as it is used internally * for optimisation. * @property {number} uid - Unique uid mainly used to store data linked to this * source into Cache. * @property {string} url - The url of the resources that are fetched. * @property {string} format - The format of the resources that are fetched. * @property {function} fetcher - The method used to fetch the resources from * the source. iTowns provides some methods in {@link Fetcher}, but it can be * specified a custom one. This method should return a `Promise` containing the * fetched resource. If this property is set, it overrides the chosen fetcher * method with `format`. * @property {Object} networkOptions - Fetch options (passed directly to * `fetch()`), see [the syntax for more information]( * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax). * By default, set to `{ crossOrigin: 'anonymous' }`. * @property {string} crs - The crs projection of the resources. * @property {string} attribution - The intellectual property rights for the * resources. * @property {Extent} extent - The extent of the resources. * @property {function} parser - The method used to parse the resources attached * to the layer. iTowns provides some parsers, visible in the `Parser/` folder. * If the method is custom, it should return a `Promise` containing the parsed * resource. If this property is set, it overrides the default selected parser * method with `source.format`. If `source.format` is also empty, no parsing * action is done. * <br><br> * When calling this method, two parameters are passed: * <ul> * <li>the fetched data, i.e. the data to parse</li> * <li>an {@link ParsingOptions} containing severals properties, set when this method is * called: it is specific to each call, so the value of each property can vary * depending on the current fetched tile for example</li> * </ul> */ class Source { /** * @param {Object} source - An object that can contain all properties of a * Source. Only the `url` property is mandatory. */ constructor(source) { if (source.projection) { console.warn('Source projection parameter is deprecated, use crs instead.'); source.crs = source.crs || source.projection; } if (source.crs) { CRS.isValid(source.crs); } this.crs = source.crs; this.isSource = true; if (!source.url) { throw new Error('New Source: url is required'); } this.uid = uid++; this.url = source.url; this.format = source.format; this.fetcher = source.fetcher || Fetcher.get(source.format); this.parser = source.parser || supportedParsers.get(source.format) || ((d, opt) => { d.extent = opt.extent; return d; }); this.isVectorSource = (source.parser || supportedParsers.get(source.format)) != undefined; this.networkOptions = source.networkOptions || { crossOrigin: 'anonymous' }; this.attribution = source.attribution; /** @type {Promise<any>} */ this.whenReady = Promise.resolve(); this._featuresCaches = {}; if (source.extent && !source.extent.isExtent) { this.extent = new Extent(this.crs).setFromExtent(source.extent); } else { this.extent = source.extent; } } handlingError(err) { throw new Error(err); } /** * Generates an url from an extent. This url is a link to fetch the * resources inside the extent. * * @param {Extent} extent - Extent to convert in url. * @return {string} The URL constructed from the extent. */ // eslint-disable-next-line urlFromExtent() { throw new Error('In extended Source, you have to implement the method urlFromExtent!'); } getDataKey(extent) { return `z${extent.zoom}r${extent.row}c${extent.col}`; } /** * Load data from cache or Fetch/Parse data. * The loaded data is a Feature or Texture. * * @param {Extent} extent extent requested parsed data. * @param {FeatureBuildingOptions|Layer} out The feature returned options * @return {FeatureCollection|Texture} The parsed data. */ loadData(extent, out) { const cache = this._featuresCaches[out.crs]; const key = this.getDataKey(extent); // console.log('Source.loadData', key); // try to get parsed data from cache let features = cache.get(key); if (!features) { // otherwise fetch/parse the data features = this.fetcher(this.urlFromExtent(extent), this.networkOptions).then(file => this.parser(file, { out, in: this, extent })).catch(err => this.handlingError(err)); cache.set(key, features); } return features; } /** * Called when layer added. * * @param {object} options */ onLayerAdded(options) { // Added new cache by crs if (!this._featuresCaches[options.out.crs]) { // Cache feature only if it's vector data, the feature are cached in source. // It's not necessary to cache raster in Source, // because it's already cached on layer. this._featuresCaches[options.out.crs] = this.isVectorSource ? new LRUCache({ max: 500 }) : noCache; } } /** * Called when layer removed. * * @param {options} [options={}] options */ onLayerRemoved() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; // delete unused cache const unusedCache = this._featuresCaches[options.unusedCrs]; if (unusedCache) { unusedCache.clear(); delete this._featuresCaches[options.unusedCrs]; } } /** * Tests if an extent is inside the source limits. * * @param {Extent} extent - Extent to test. * @return {boolean} True if the extent is inside the limit, false otherwise. */ // eslint-disable-next-line extentInsideLimit() { throw new Error('In extented Source, you have to implement the method extentInsideLimit!'); } } export default Source;