UNPKG

mobility-toolbox-js

Version:

Toolbox for JavaScript applications in the domains of mobility and logistics.

372 lines (371 loc) 17.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _MocoLayer_abortController, _MocoLayer_dataInternal, _MocoLayer_fetchCache; import { getFeatureCollectionToRenderFromSituation, getGraphByZoom, MocoAPI, } from '..'; import getGraphByZoomFromStyleMetadata, { DEFAULT_GRAPH, DEFAULT_GRAPH_MAPPING, } from '../utils/getGraphByZoomFromStyleMetadata'; import MaplibreStyleLayer from './MaplibreStyleLayer'; export const MOCO_SOURCE_ID = 'moco'; export const MAPS_MD_GENERAL_FILTER = 'general.filter'; export const MOCO_MD_LAYER_FILTER = 'moco'; export const MOCO_MD_LAYER_FILTER_LNP = `${MOCO_MD_LAYER_FILTER}.lnp`; const getMocoLayersFilter = (lnpLayer) => { var _a; const visible = (_a = lnpLayer === null || lnpLayer === void 0 ? void 0 : lnpLayer.getVisible()) !== null && _a !== void 0 ? _a : false; let mdValue = MOCO_MD_LAYER_FILTER; if (visible) { mdValue += MOCO_MD_LAYER_FILTER_LNP; } return (layerSpec) => { const metadata = layerSpec.metadata; const source = layerSpec.source; return ((metadata === null || metadata === void 0 ? void 0 : metadata[MAPS_MD_GENERAL_FILTER]) === mdValue || source === MOCO_SOURCE_ID); }; }; /** * An OpenLayers layer able to display data from the [geOps MOCO API](https://geops.com/de/solution/disruption-information). * * @example * import { MaplibreLayer, MaplibreStyleLayer } from 'mobility-toolbox-js/ol'; * * const maplibreLayer = new MaplibreLayer({ * apiKey: 'yourApiKey', * }); * * const layer = new MocoLayer({ * apiKey: 'yourApiKey', * maplibreLayer: maplibreLayer, * // publicAt: new Date(), * // loadAll: true, * // notifications: undefined, * // tenant: "geopstest", * // url: 'https://moco.geops.io' * }); * * @see <a href="/example/ol-maplibre-style-layer">OpenLayers MaplibreStyle layer example</a> * @extends {MaplibreStyleLayer} * @private */ class MocoLayer extends MaplibreStyleLayer { get api() { return this.get('api'); } set api(value) { this.set('api', value); void this.updateData(); } get apiKey() { return this.api.apiKey; } set apiKey(value) { this.api.apiKey = value; void this.updateData(); } get apiParameters() { return this.get('apiParameters'); } set apiParameters(value) { this.set('apiParameters', value); void this.updateData(); } get generalizationLevelByZoom() { var _a; return (_a = this.get('generalizationLevelByZoom')) !== null && _a !== void 0 ? _a : []; } set generalizationLevelByZoom(generalizationLevelByZoom) { this.set('generalizationLevelByZoom', generalizationLevelByZoom); void this.updateData(); } get graphByZoom() { return this.get('graphByZoom'); } set graphByZoom(graphByZoom) { this.set('graphByZoom', graphByZoom); void this.updateData(); } get lnpLayer() { return this.get('lnpLayer'); } get loadAll() { var _a; return (_a = this.get('loadAll')) !== null && _a !== void 0 ? _a : true; } set loadAll(value) { this.set('loadAll', value); void this.updateData(); } get loadByZoom() { var _a; return (_a = this.get('loadByZoom')) !== null && _a !== void 0 ? _a : false; } set loadByZoom(value) { this.set('loadByZoom', value); void this.updateData(); } set publicAt(value) { this.set('publicAt', value); void this.updateData(); } get publicAt() { return this.get('publicAt'); } set situations(value) { // If we set situations we do not want to load data from backend this.loadAll = false; this.set('situations', value); void this.updateData(); } get situations() { return this.get('situations'); } get tenant() { return this.get('tenant'); } set tenant(value) { this.set('tenant', value); void this.updateData(); } get url() { return this.api.url; } set url(value) { this.api.url = value; void this.updateData(); } /** * Constructor. * * @param {Object} options * @param {string} options.apiKey Access key for [geOps APIs](https://developer.geops.io/). * @param {string} [options.apiParameters] The url parameters to be included in the MOCO API request. * @param {boolean} [options.loadAll=true] If true, all active and published notifications will be loaded at once, otherwise only the notifications set in 'notifications' will be displayed. * @param {boolean} [options.loadByZoom=false] If true, notifications will be loaded based on the current zoom level. Use this option only if you see performance issues with loadAll. * @param {boolean} [options.useGraphs=false] If true, only the notifications using the current graphs for the current zoom level will be passed to the maplibre source. * @param {MocoNotification[]} [options.notifications] The notifications to display. If not set and loadAll is true, all active and published notifications will be loaded. * @param {string} [options.publicAt] The date to filter notifications. If not set, the current date is used. * @param {string} [options.tenant] The SSO config to use to get notifications from. * @param {string} [options.url] The URL of the [geOps MOCO API](https://geops.com/de/solution/disruption-information). * @public */ constructor(options) { super(Object.assign({ api: new MocoAPI({ apiKey: options.apiKey, tenant: options.tenant, url: options.url, }), layersFilter: getMocoLayersFilter(options.lnpLayer) }, options)); _MocoLayer_abortController.set(this, null); /** * This is used to store the notifications data that are rendered on the map and to filter them depending on the graph. */ _MocoLayer_dataInternal.set(this, { features: [], type: 'FeatureCollection', }); /** * Cache the request responses when loadByZoom is true */ _MocoLayer_fetchCache.set(this, {}); } attachToMap(map) { var _a, _b; super.attachToMap(map); // If the source is already there (no load event triggered), we update data const source = (_b = (_a = this.maplibreLayer) === null || _a === void 0 ? void 0 : _a.mapLibreMap) === null || _b === void 0 ? void 0 : _b.getSource(MOCO_SOURCE_ID); if (source) { void this.updateData(); } const mapInternal = this.getMapInternal(); if (mapInternal && this.loadByZoom) { this.olEventsKeys.push(mapInternal.on('moveend', () => { if (this.loadByZoom) { void this.updateData(); } })); } if (this.lnpLayer) { if (!this.layersFilter) { this.layersFilter = getMocoLayersFilter(this.lnpLayer); } this.olEventsKeys.push(this.lnpLayer.on('change:visible', () => { this.layersFilter = getMocoLayersFilter(this.lnpLayer); if (this.loadByZoom) { void this.updateData(); } })); } } detachFromMap() { var _a, _b; super.detachFromMap(); const source = (_b = (_a = this.maplibreLayer) === null || _a === void 0 ? void 0 : _a.mapLibreMap) === null || _b === void 0 ? void 0 : _b.getSource(MOCO_SOURCE_ID); if (source) { // Remove the data from the map source.setData({ features: [], type: 'FeatureCollection', }); } if (__classPrivateFieldGet(this, _MocoLayer_abortController, "f")) { __classPrivateFieldGet(this, _MocoLayer_abortController, "f").abort(); __classPrivateFieldSet(this, _MocoLayer_abortController, null, "f"); } } fetchData(params_1) { return __awaiter(this, arguments, void 0, function* (params, config = {}) { var _a, _b, _c; const requestParameters = Object.assign(Object.assign({ hasGeoms: true, publicAt: (_a = this.publicAt) === null || _a === void 0 ? void 0 : _a.toISOString(), publicNow: !this.publicAt }, ((_b = this.apiParameters) !== null && _b !== void 0 ? _b : {})), params); // Cache the request response for one minute to avoid too much requests on the backend let cacheKey; try { cacheKey = JSON.stringify({ config, requestParameters, time: Math.floor(Date.now() / 60000), }); if (__classPrivateFieldGet(this, _MocoLayer_fetchCache, "f")[cacheKey]) { return __classPrivateFieldGet(this, _MocoLayer_fetchCache, "f")[cacheKey]; } } catch (error) { // eslint-disable-next-line no-console console.warn('Error stringifying fetch parameters for caching', error); } try { const response = yield this.api.export(requestParameters, config); if (((_c = response === null || response === void 0 ? void 0 : response.paginatedSituations) === null || _c === void 0 ? void 0 : _c.results) && cacheKey) { __classPrivateFieldGet(this, _MocoLayer_fetchCache, "f")[cacheKey] = response.paginatedSituations.results; } return response.paginatedSituations.results; } catch (error) { if (error && error.name.includes('AbortError')) { // Ignore abort error return []; } throw error; } }); } getDataByGraph(data) { var _a, _b, _c, _d, _e; const zoom = (_b = (_a = this.getMapInternal()) === null || _a === void 0 ? void 0 : _a.getView()) === null || _b === void 0 ? void 0 : _b.getZoom(); const graphs = (_e = ((_d = (_c = this.maplibreLayer) === null || _c === void 0 ? void 0 : _c.mapLibreMap) === null || _d === void 0 ? void 0 : _d.getStyle()).metadata) === null || _e === void 0 ? void 0 : _e.graphs; const graph = getGraphByZoom(zoom, graphs); const newData = { features: ((data === null || data === void 0 ? void 0 : data.features) || []).filter((feature) => { var _a; return ((_a = feature.properties) === null || _a === void 0 ? void 0 : _a.graph) === graph; }), type: 'FeatureCollection', }; return newData; } /** * This functions load situations from backend depending on the current graph mapping in the style metadata. * @returns */ loadData() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (__classPrivateFieldGet(this, _MocoLayer_abortController, "f")) { __classPrivateFieldGet(this, _MocoLayer_abortController, "f").abort(); } __classPrivateFieldSet(this, _MocoLayer_abortController, new AbortController(), "f"); let graphs = [DEFAULT_GRAPH]; if (this.graphByZoom) { graphs = this.graphByZoom; } else if (!this.lnpLayer || this.lnpLayer.getVisible()) { // if no graphByzoom defined we use the ones from the style metadata // Get graphs mapping const mdGraphs = (_c = ((_b = (_a = this.maplibreLayer) === null || _a === void 0 ? void 0 : _a.mapLibreMap) === null || _b === void 0 ? void 0 : _b.getStyle()).metadata) === null || _c === void 0 ? void 0 : _c.graphs; const graphMapping = mdGraphs !== null && mdGraphs !== void 0 ? mdGraphs : DEFAULT_GRAPH_MAPPING; graphs = Object.values(graphMapping); } const graphsString = [...new Set(graphs)].join(','); return this.fetchData({ graph: graphsString }, { signal: __classPrivateFieldGet(this, _MocoLayer_abortController, "f").signal }); }); } /** * This functions load situations from backend depending on the current graph mapping in the style metadata. * @returns */ loadDataByZoom() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; if (__classPrivateFieldGet(this, _MocoLayer_abortController, "f")) { __classPrivateFieldGet(this, _MocoLayer_abortController, "f").abort(); } __classPrivateFieldSet(this, _MocoLayer_abortController, new AbortController(), "f"); const zoom = (_b = (_a = this.getMapInternal()) === null || _a === void 0 ? void 0 : _a.getView()) === null || _b === void 0 ? void 0 : _b.getZoom(); let graphsString = DEFAULT_GRAPH; if (this.graphByZoom) { graphsString = getGraphByZoom(zoom, this.graphByZoom); } else if (!this.lnpLayer || this.lnpLayer.getVisible()) { // if no graphByzoom defined we use the ones from the style metadata const mdGraphs = (_e = ((_d = (_c = this.maplibreLayer) === null || _c === void 0 ? void 0 : _c.mapLibreMap) === null || _d === void 0 ? void 0 : _d.getStyle()).metadata) === null || _e === void 0 ? void 0 : _e.graphs; graphsString = getGraphByZoomFromStyleMetadata(zoom, mdGraphs); } return this.fetchData({ graph: graphsString }, { signal: __classPrivateFieldGet(this, _MocoLayer_abortController, "f").signal }); }); } onLoad() { super.onLoad(); void this.updateData(); } /** * This function updates the GeoJSON source data, with the current situations available in this.situations. * @returns */ updateData() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (this.loadByZoom) { const situations = yield this.loadDataByZoom(); // We don't use the setter here to avoid infinite loop this.set('situations', situations !== null && situations !== void 0 ? situations : []); } else if (this.loadAll) { const situations = yield this.loadData(); // We don't use the setter here to avoid infinite loop this.set('situations', situations !== null && situations !== void 0 ? situations : []); } const source = (_b = (_a = this.maplibreLayer) === null || _a === void 0 ? void 0 : _a.mapLibreMap) === null || _b === void 0 ? void 0 : _b.getSource(MOCO_SOURCE_ID); if (!source) { // eslint-disable-next-line no-console console.warn('MocoLayer: No source found for id : ', MOCO_SOURCE_ID); return Promise.reject(new Error('No source found')); } const data = { features: ((_c = this.situations) !== null && _c !== void 0 ? _c : []).flatMap((situation) => { return getFeatureCollectionToRenderFromSituation(situation, this.publicAt).features; }), type: 'FeatureCollection', }; __classPrivateFieldSet(this, _MocoLayer_dataInternal, data, "f"); source.setData(__classPrivateFieldGet(this, _MocoLayer_dataInternal, "f")); return Promise.resolve(true); }); } } _MocoLayer_abortController = new WeakMap(), _MocoLayer_dataInternal = new WeakMap(), _MocoLayer_fetchCache = new WeakMap(); export default MocoLayer;