mobility-toolbox-js
Version:
Toolbox for JavaScript applications in the domains of mobility and logistics.
372 lines (371 loc) • 17.6 kB
JavaScript
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;