mobility-toolbox-js 1.6.0-beta.7 → 1.6.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,15 @@
3
3
  import { buffer, containsCoordinate } from 'ol/extent';
4
4
  import { unByKey } from 'ol/Observable';
5
5
  import Feature from 'ol/Feature';
6
+ import qs from 'query-string';
6
7
  import Tracker from '../Tracker';
7
8
  import { timeSteps } from '../trackerConfig';
9
+ import createFilters from '../utils/createTrackerFilters';
10
+
11
+ /* Permalink parameter used to filters vehicles */
12
+ const LINE_FILTER = 'publishedlinename';
13
+ const ROUTE_FILTER = 'tripnumber';
14
+ const OPERATOR_FILTER = 'operator';
8
15
 
9
16
  /**
10
17
  * TrackerLayerInterface.
@@ -129,6 +136,13 @@ const TrackerLayerMixin = (Base) =>
129
136
  live,
130
137
  } = options;
131
138
 
139
+ let {
140
+ regexPublishedLineName,
141
+ publishedLineName,
142
+ tripNumber,
143
+ operator,
144
+ } = options;
145
+
132
146
  const initTrackerOptions = {
133
147
  pixelRatio: pixelRatio || window.devicePixelRatio || 1,
134
148
  interpolate,
@@ -314,6 +328,74 @@ const TrackerLayerMixin = (Base) =>
314
328
  default: false,
315
329
  writable: true,
316
330
  },
331
+
332
+ /**
333
+ * Filter properties used in combination with permalink parameters.
334
+ */
335
+ publishedLineName: {
336
+ get: () => {
337
+ return publishedLineName;
338
+ },
339
+ set: (newPublishedLineName) => {
340
+ publishedLineName = newPublishedLineName;
341
+ this.updateFilters();
342
+ },
343
+ },
344
+ tripNumber: {
345
+ get: () => {
346
+ return tripNumber;
347
+ },
348
+ set: (newTripNumber) => {
349
+ tripNumber = newTripNumber;
350
+ this.updateFilters();
351
+ },
352
+ },
353
+ operator: {
354
+ get: () => {
355
+ return operator;
356
+ },
357
+ set: (newOperator) => {
358
+ operator = newOperator;
359
+ this.updateFilters();
360
+ },
361
+ },
362
+ regexPublishedLineName: {
363
+ get: () => {
364
+ return regexPublishedLineName;
365
+ },
366
+ set: (newRegex) => {
367
+ regexPublishedLineName = newRegex;
368
+ this.updateFilters();
369
+ },
370
+ },
371
+
372
+ /**
373
+ * Style properties.
374
+ */
375
+ delayDisplay: {
376
+ value: options.delayDisplay || 300000,
377
+ writable: true,
378
+ },
379
+ delayOutlineColor: {
380
+ value: options.delayOutlineColor || '#000000',
381
+ writable: true,
382
+ },
383
+ useDelayStyle: {
384
+ value: options.useDelayStyle || false,
385
+ writable: true,
386
+ },
387
+
388
+ /**
389
+ * Debug properties.
390
+ */
391
+ // Not used anymore, but could be useful for debugging.
392
+ // showVehicleTraj: {
393
+ // value:
394
+ // options.showVehicleTraj !== undefined
395
+ // ? options.showVehicleTraj
396
+ // : true,
397
+ // writable: true,
398
+ // },
317
399
  });
318
400
  }
319
401
 
@@ -375,6 +457,7 @@ const TrackerLayerMixin = (Base) =>
375
457
  */
376
458
  start() {
377
459
  this.stop();
460
+ this.updateFilters();
378
461
  this.tracker.setVisible(true);
379
462
  this.renderTrajectories();
380
463
  this.startUpdateTime();
@@ -587,10 +670,25 @@ const TrackerLayerMixin = (Base) =>
587
670
  const roundedZoom = Math.round(zoom);
588
671
  const timeStep = timeSteps[roundedZoom] || 25;
589
672
  const nextTick = Math.max(25, timeStep / this.speed);
590
- console.log(`Next render in ${nextTick} ms.`);
673
+ // console.log(`Next render in ${nextTick} ms.`);
591
674
  return nextTick;
592
675
  }
593
676
 
677
+ /**
678
+ * Update filter provided by properties or permalink.
679
+ */
680
+ updateFilters() {
681
+ // Setting filters from the permalink if no values defined by the layer.
682
+ const parameters = qs.parse(window.location.search.toLowerCase());
683
+ // filter is the property in TrackerLayerMixin.
684
+ this.filter = createFilters(
685
+ this.publishedLineName || parameters[LINE_FILTER],
686
+ this.tripNumber || parameters[ROUTE_FILTER],
687
+ this.operator || parameters[OPERATOR_FILTER],
688
+ this.regexPublishedLineName,
689
+ );
690
+ }
691
+
594
692
  /**
595
693
  * @private
596
694
  */
@@ -101,74 +101,6 @@ export class TrajservLayerInterface {
101
101
  defaultStyle(trajectory, viewState) {}
102
102
  }
103
103
 
104
- const LINE_FILTER = 'publishedlinename';
105
- const ROUTE_FILTER = 'tripnumber';
106
- const OPERATOR_FILTER = 'operator';
107
-
108
- /**
109
- * Create a array of filter functions based on some parameters.
110
- * @param {string} line
111
- * @param {string} route
112
- * @param {string} operator
113
- * @param {string} regexLine
114
- * @private
115
- */
116
- const createFilters = (line, route, operator, regexLine) => {
117
- const filterList = [];
118
-
119
- if (!line && !route && !operator && !regexLine) {
120
- return null;
121
- }
122
-
123
- if (regexLine) {
124
- const regexLineList =
125
- typeof regexLine === 'string' ? [regexLine] : regexLine;
126
- const lineFilter = (t) =>
127
- regexLineList.some((tr) => new RegExp(tr, 'i').test(t.name));
128
- filterList.push(lineFilter);
129
- }
130
-
131
- if (line) {
132
- const lineFiltersList = typeof line === 'string' ? line.split(',') : line;
133
- const lineList = lineFiltersList.map((l) =>
134
- l.replace(/\s+/g, '').toUpperCase(),
135
- );
136
- const lineFilter = (l) =>
137
- lineList.some((filter) => filter === l.name.toUpperCase());
138
- filterList.push(lineFilter);
139
- }
140
-
141
- if (route) {
142
- const routes = typeof route === 'string' ? route.split(',') : route;
143
- const routeList = routes.map((item) => parseInt(item, 10));
144
- const routeFilter = (item) => {
145
- const routeId = parseInt(item.routeIdentifier.split('.')[0], 10);
146
- return routeList.some((id) => id === routeId);
147
- };
148
- filterList.push(routeFilter);
149
- }
150
-
151
- if (operator) {
152
- const operatorList = typeof operator === 'string' ? [operator] : operator;
153
- const operatorFilter = (t) =>
154
- operatorList.some((op) => new RegExp(op, 'i').test(t.operator));
155
- filterList.push(operatorFilter);
156
- }
157
-
158
- if (!filterList.length) {
159
- return null;
160
- }
161
-
162
- return (t) => {
163
- for (let i = 0; i < filterList.length; i += 1) {
164
- if (!filterList[i](t)) {
165
- return false;
166
- }
167
- }
168
- return true;
169
- };
170
- };
171
-
172
104
  /**
173
105
  * Mixin for TrajservLayerInterface.
174
106
  *
@@ -185,12 +117,6 @@ const TrajservLayerMixin = (TrackerLayer) =>
185
117
  */
186
118
  defineProperties(options) {
187
119
  super.defineProperties(options);
188
- let {
189
- regexPublishedLineName,
190
- publishedLineName,
191
- tripNumber,
192
- operator,
193
- } = options;
194
120
 
195
121
  let requestIntervalSeconds = 3;
196
122
  let defaultApi;
@@ -205,25 +131,6 @@ const TrajservLayerMixin = (TrackerLayer) =>
205
131
  defaultApi = new TrajservAPI(apiOptions);
206
132
  }
207
133
  Object.defineProperties(this, {
208
- showVehicleTraj: {
209
- value:
210
- options.showVehicleTraj !== undefined
211
- ? options.showVehicleTraj
212
- : true,
213
- writable: true,
214
- },
215
- delayDisplay: {
216
- value: options.delayDisplay || 300000,
217
- writable: true,
218
- },
219
- delayOutlineColor: {
220
- value: options.delayOutlineColor || '#000000',
221
- writable: true,
222
- },
223
- useDelayStyle: {
224
- value: options.useDelayStyle || false,
225
- writable: true,
226
- },
227
134
  requestIntervalSeconds: {
228
135
  get: () => {
229
136
  return requestIntervalSeconds;
@@ -238,42 +145,7 @@ const TrajservLayerMixin = (TrackerLayer) =>
238
145
  }
239
146
  },
240
147
  },
241
- publishedLineName: {
242
- get: () => {
243
- return publishedLineName;
244
- },
245
- set: (newPublishedLineName) => {
246
- publishedLineName = newPublishedLineName;
247
- this.updateFilters();
248
- },
249
- },
250
- tripNumber: {
251
- get: () => {
252
- return tripNumber;
253
- },
254
- set: (newTripNumber) => {
255
- tripNumber = newTripNumber;
256
- this.updateFilters();
257
- },
258
- },
259
- operator: {
260
- get: () => {
261
- return operator;
262
- },
263
- set: (newOperator) => {
264
- operator = newOperator;
265
- this.updateFilters();
266
- },
267
- },
268
- regexPublishedLineName: {
269
- get: () => {
270
- return regexPublishedLineName;
271
- },
272
- set: (newRegex) => {
273
- regexPublishedLineName = newRegex;
274
- this.updateFilters();
275
- },
276
- },
148
+
277
149
  api: {
278
150
  value: options.api || defaultApi,
279
151
  },
@@ -407,18 +279,6 @@ const TrajservLayerMixin = (TrackerLayer) =>
407
279
  });
408
280
  }
409
281
 
410
- updateFilters() {
411
- // Setting filters from the permalink if no values defined by the layer.
412
- const parameters = qs.parse(window.location.search.toLowerCase());
413
- // filter is the property in TrackerLayerMixin.
414
- this.filter = createFilters(
415
- this.publishedLineName || parameters[LINE_FILTER],
416
- this.tripNumber || parameters[ROUTE_FILTER],
417
- this.operator || parameters[OPERATOR_FILTER],
418
- this.regexPublishedLineName,
419
- );
420
- }
421
-
422
282
  abortFetchTrajectories() {
423
283
  if (this.abortController) {
424
284
  this.abortController.abort();
@@ -164,6 +164,12 @@ const TralisLayerMixin = (TrackerLayer) =>
164
164
  if (!data.content) {
165
165
  return;
166
166
  }
167
+
168
+ // Temporary fix to avoid displaying old trains cached .
169
+ if (data.content.properties.mot) {
170
+ return;
171
+ }
172
+
167
173
  const feat = this.format.readFeature(data.content);
168
174
 
169
175
  feat.set('timeOffset', Date.now() - data.timestamp);
@@ -12,19 +12,23 @@ const trackerRadiusMapping = {
12
12
  };
13
13
 
14
14
  /**
15
+ * Trajserv value: 'Tram', 'Subway / Metro / S-Bahn', 'Train', 'Bus', 'Ferry', 'Cable Car', 'Gondola', 'Funicular', 'Long distance bus', 'Rail',
16
+ * New endpoint use Rail instead of Train.
17
+ * New tracker values: null, "tram", "subway", "rail", "bus", "ferry", "cablecar", "gondola", "funicular", "coach".
18
+ *
15
19
  * @ignore
16
20
  */
17
21
  export const types = [
18
- 'Tram',
19
- 'Subway / Metro / S-Bahn',
20
- 'Train',
21
- 'Bus',
22
- 'Ferry',
23
- 'Cable Car',
24
- 'Gondola',
25
- 'Funicular',
26
- 'Long distance bus',
27
- 'Rail', // New endpoint use Rail instead of Train.
22
+ /^Tram/i,
23
+ /^Subway( \/ Metro \/ S-Bahn)?/i,
24
+ /^Train/i,
25
+ /^Bus/i,
26
+ /^Ferry/i,
27
+ /^Cable ?Car/i,
28
+ /^Gondola/i,
29
+ /^Funicular/i,
30
+ /^(Long distance bus|coach)/i,
31
+ /^Rail/i, // New endpoint use Rail instead of Train.
28
32
  ];
29
33
 
30
34
  /**
@@ -89,10 +93,9 @@ export const timeSteps = [
89
93
  /**
90
94
  * @ignore
91
95
  */
92
- const getTypeIndex = (type) => {
96
+ export const getTypeIndex = (type) => {
93
97
  if (typeof type === 'string') {
94
- const matched = types.find((t) => new RegExp(type).test(t));
95
- return types.indexOf(matched);
98
+ return types.findIndex((t) => t.test(type));
96
99
  }
97
100
  return type;
98
101
  };
@@ -0,0 +1,38 @@
1
+ import { getTypeIndex } from './trackerConfig';
2
+
3
+ describe('trackerConfig', () => {
4
+ describe('#getTypeIndex()', () => {
5
+ test("retrurn the type is it's not a string", () => {
6
+ const obj = { foo: 'foo' };
7
+ expect(getTypeIndex(obj)).toBe(obj);
8
+ expect(getTypeIndex(0)).toBe(0);
9
+ expect(getTypeIndex(null)).toBe(null);
10
+ expect(getTypeIndex(undefined)).toBe(undefined);
11
+ });
12
+
13
+ test('find good index for old trajserv values', () => {
14
+ expect(getTypeIndex('Tram')).toBe(0);
15
+ expect(getTypeIndex('Subway / Metro / S-Bahn')).toBe(1);
16
+ expect(getTypeIndex('Train')).toBe(2);
17
+ expect(getTypeIndex('Bus')).toBe(3);
18
+ expect(getTypeIndex('Ferry')).toBe(4);
19
+ expect(getTypeIndex('Cable Car')).toBe(5);
20
+ expect(getTypeIndex('Gondola')).toBe(6);
21
+ expect(getTypeIndex('Funicular')).toBe(7);
22
+ expect(getTypeIndex('Long distance bus')).toBe(8);
23
+ expect(getTypeIndex('Rail')).toBe(9);
24
+ });
25
+
26
+ test('find good index for new tracker values', () => {
27
+ expect(getTypeIndex('tram')).toBe(0);
28
+ expect(getTypeIndex('subway')).toBe(1);
29
+ expect(getTypeIndex('bus')).toBe(3);
30
+ expect(getTypeIndex('ferry')).toBe(4);
31
+ expect(getTypeIndex('cablecar')).toBe(5);
32
+ expect(getTypeIndex('gondola')).toBe(6);
33
+ expect(getTypeIndex('funicular')).toBe(7);
34
+ expect(getTypeIndex('coach')).toBe(8);
35
+ expect(getTypeIndex('rail')).toBe(9);
36
+ });
37
+ });
38
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Return a filter functions based on some parameters of a vehicle.
3
+ *
4
+ * @param {string|Array<string>} line - A list of vehicle's name to filter. Names can be separated by a comma. Ex: 'S1,S2,S3'
5
+ * @param {string|Array<string} route - A list of vehicle's route (contained in routeIdentifier property) to filter. Indentifiers can be separated by a comma. Ex: 'id1,id2,id3'
6
+ * @param {string|Array<string} operator A list of vehicle's operator to filter. Operators can be separated by a comma. Ex: 'SBB,DB'
7
+ * @param {Regexp} regexLine - A regex aplly of vehcile's name.
8
+ * @private
9
+ */
10
+ const createFilters = (line, route, operator, regexLine) => {
11
+ const filterList = [];
12
+
13
+ if (!line && !route && !operator && !regexLine) {
14
+ return null;
15
+ }
16
+
17
+ if (regexLine) {
18
+ const regexLineList =
19
+ typeof regexLine === 'string' ? [regexLine] : regexLine;
20
+ const lineFilter = (item) => {
21
+ const name = item.name || (item.line && item.line.name) || '';
22
+ if (!name) {
23
+ return false;
24
+ }
25
+ return regexLineList.some((regexStr) =>
26
+ new RegExp(regexStr, 'i').test(name),
27
+ );
28
+ };
29
+ filterList.push(lineFilter);
30
+ }
31
+
32
+ if (line) {
33
+ const lineFiltersList = typeof line === 'string' ? line.split(',') : line;
34
+ const lineList = lineFiltersList.map((l) =>
35
+ l.replace(/\s+/g, '').toUpperCase(),
36
+ );
37
+ const lineFilter = (item) => {
38
+ const name = (
39
+ item.name ||
40
+ (item.line && item.line.name) ||
41
+ ''
42
+ ).toUpperCase();
43
+ if (!name) {
44
+ return false;
45
+ }
46
+ return lineList.includes(name);
47
+ };
48
+ filterList.push(lineFilter);
49
+ }
50
+
51
+ if (route) {
52
+ const routes = typeof route === 'string' ? route.split(',') : route;
53
+ const routeList = routes.map((item) => parseInt(item, 10));
54
+ const routeFilter = (item) => {
55
+ const routeId = parseInt(item.routeIdentifier.split('.')[0], 10);
56
+ return routeList.includes(routeId);
57
+ };
58
+ filterList.push(routeFilter);
59
+ }
60
+
61
+ if (operator) {
62
+ const operatorList = typeof operator === 'string' ? [operator] : operator;
63
+ const operatorFilter = (item) =>
64
+ operatorList.some((op) => new RegExp(op, 'i').test(item.operator));
65
+ filterList.push(operatorFilter);
66
+ }
67
+
68
+ if (!filterList.length) {
69
+ return null;
70
+ }
71
+
72
+ return (t) => {
73
+ for (let i = 0; i < filterList.length; i += 1) {
74
+ if (!filterList[i](t)) {
75
+ return false;
76
+ }
77
+ }
78
+ return true;
79
+ };
80
+ };
81
+
82
+ export default createFilters;
@@ -0,0 +1,89 @@
1
+ import createTrackerFilters from './createTrackerFilters';
2
+
3
+ const u1 = {
4
+ routeIdentifier: '001.000827.004:7',
5
+ operator: 'FoO',
6
+ line: {
7
+ name: 'U1',
8
+ },
9
+ };
10
+ const ireta = {
11
+ routeIdentifier: '0022.000827.004:7',
12
+ operator: 'BAR',
13
+ line: {
14
+ name: 'IRETA',
15
+ },
16
+ };
17
+ const arb = {
18
+ routeIdentifier: '00333.000827.004:7',
19
+ operator: 'qux',
20
+ line: {
21
+ name: 'ARB',
22
+ },
23
+ };
24
+
25
+ const trajectories = [u1, ireta, arb];
26
+
27
+ describe('#createTrackerFilter()', () => {
28
+ test('returns null', () => {
29
+ const filterFunc = createTrackerFilters();
30
+ expect(filterFunc).toBe(null);
31
+ });
32
+
33
+ describe('using line', () => {
34
+ test('as string', () => {
35
+ const filterFunc = createTrackerFilters('u1,foo');
36
+ expect(trajectories.filter(filterFunc)).toEqual([u1]);
37
+ });
38
+
39
+ test('as array of string', () => {
40
+ const filterFunc = createTrackerFilters(['u1', 'foo', 'IRETA']);
41
+ expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]);
42
+ });
43
+ });
44
+
45
+ describe('using route identifier', () => {
46
+ test('as string', () => {
47
+ const filterFunc = createTrackerFilters(null, '1,foo');
48
+ expect(trajectories.filter(filterFunc)).toEqual([u1]);
49
+ });
50
+
51
+ test('as array of string', () => {
52
+ const filterFunc = createTrackerFilters(null, ['22', 'foo', '1']);
53
+ expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]);
54
+ });
55
+ });
56
+
57
+ describe('using operator', () => {
58
+ test('as string', () => {
59
+ const filterFunc = createTrackerFilters(null, null, 'foo');
60
+ expect(trajectories.filter(filterFunc)).toEqual([u1]);
61
+ });
62
+
63
+ test('as array of string', () => {
64
+ const filterFunc = createTrackerFilters(null, null, ['bar', 'foo', '1']);
65
+ expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]);
66
+ });
67
+ });
68
+
69
+ describe('using regexLine', () => {
70
+ test('as string', () => {
71
+ const filterFunc = createTrackerFilters(
72
+ null,
73
+ null,
74
+ null,
75
+ '^(S|R$|RE|PE|D|IRE|RB|TER)',
76
+ );
77
+ expect(trajectories.filter(filterFunc)).toEqual([ireta]);
78
+ });
79
+
80
+ test('as array of string', () => {
81
+ const filterFunc = createTrackerFilters(null, null, null, [
82
+ '^IR',
83
+ '^ARB$',
84
+ 'foo',
85
+ ]);
86
+ expect(trajectories.filter(filterFunc)).toEqual([ireta, arb]);
87
+ });
88
+ });
89
+ });
@@ -18,47 +18,43 @@ const style = (trajectory, viewState, trackerLayer) => {
18
18
  delayDisplay,
19
19
  } = trackerLayer;
20
20
 
21
- const { zoom, pixelRatio } = viewState;
22
-
23
- let { line = {} } = trajectory;
24
-
25
21
  const {
26
- id,
27
- delay,
28
- type = 'Rail',
29
- cancelled = false,
30
- operatorProvidesRealtime = 'no',
31
- } = trajectory;
22
+ zoom,
23
+ pixelRatio,
24
+ operator_provides_realtime_journey: operatorProvidesRealtime,
25
+ } = viewState;
26
+ let { line, type } = trajectory;
27
+ const { id, delay, cancelled = false } = trajectory;
28
+
29
+ if (!type) {
30
+ type = 'Rail';
31
+ }
32
32
 
33
33
  if (!line) {
34
34
  line = {};
35
35
  }
36
36
 
37
- const { name = 'I' } = line;
38
- let { text_color: textColor, color } = line;
37
+ let { name, text_color: textColor, color } = line;
39
38
 
40
- if (!color) {
41
- color = '#000000';
39
+ if (!name) {
40
+ name = 'I';
42
41
  }
43
42
 
44
43
  if (!textColor) {
45
- textColor = '#ffffff';
44
+ textColor = '#000000';
46
45
  }
47
46
 
48
- // Make sure color used have a # at the start
49
- if (color[0] !== '#') {
47
+ if (color && color[0] !== '#') {
50
48
  color = `#${color}`;
51
49
  }
52
50
 
53
51
  if (textColor[0] !== '#') {
54
52
  textColor = `#${textColor}`;
55
53
  }
56
- console.log(color, textColor);
57
54
 
58
55
  const z = Math.min(Math.floor(zoom || 1), 16);
59
56
  const hover = hoverVehicleId === id;
60
57
  const selected = selectedVehicleId === id;
61
- let key = `${z}${type}${name}${color}${textColor}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`;
62
58
 
63
59
  // Calcul the radius of the circle
64
60
  let radius = getRadius(type, z) * pixelRatio;
@@ -71,8 +67,14 @@ const style = (trajectory, viewState, trackerLayer) => {
71
67
  const mustDrawText = radius > 10 * pixelRatio;
72
68
 
73
69
  // Optimize the cache key, very important in high zoom level
74
- if (!mustDrawText) {
75
- key = `${z}${type}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`;
70
+ let key = `${z}${type}${color}${hover}${selected}${cancelled}${delay}`;
71
+
72
+ if (useDelayStyle) {
73
+ key += `${operatorProvidesRealtime}`;
74
+ }
75
+
76
+ if (mustDrawText) {
77
+ key += `${name}${textColor}`;
76
78
  }
77
79
 
78
80
  if (!styleCache[key]) {