mobility-toolbox-js 1.5.0 → 1.6.0-beta.10
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.
- package/api/trajserv/TrajservAPIUtils.test.js +1 -1
- package/api/tralis/TralisAPI.js +51 -6
- package/api/tralis/WebSocketConnector.js +62 -75
- package/api/tralis/WebSocketConnector.test.js +98 -38
- package/common/layers/Layer.js +10 -7
- package/common/layers/Layer.test.js +27 -0
- package/common/mixins/TrackerLayerMixin.js +120 -26
- package/common/mixins/TrajservLayerMixin.js +76 -159
- package/common/mixins/TralisLayerMixin.js +64 -27
- package/common/trackerConfig.js +16 -13
- package/common/trackerConfig.test.js +38 -0
- package/common/utils/createTrackerFilters.js +82 -0
- package/common/utils/createTrackerFilters.test.js +89 -0
- package/common/utils/delayTrackerStyle.js +330 -0
- package/common/utils/index.js +2 -0
- package/common/utils/simpleTrackerStyle.js +20 -0
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/mapbox/layers/Layer.js +32 -6
- package/mapbox/layers/Layer.test.js +122 -0
- package/mapbox/layers/TrackerLayer.js +11 -7
- package/mapbox/layers/TrajservLayer.js +4 -88
- package/mapbox/layers/TrajservLayer.test.js +1 -1
- package/mapbox/layers/TralisLayer.js +56 -25
- package/ol/layers/Layer.js +39 -8
- package/ol/layers/Layer.test.js +81 -0
- package/ol/layers/TrackerLayer.js +78 -8
- package/ol/layers/TrackerLayer.test.js +8 -0
- package/ol/layers/TrajservLayer.js +34 -126
- package/ol/layers/TrajservLayer.test.js +2 -3
- package/ol/layers/TralisLayer.js +151 -0
- package/package.json +3 -2
|
@@ -2,7 +2,7 @@ import { translateTrajStationsResp } from './TrajservAPIUtils';
|
|
|
2
2
|
|
|
3
3
|
describe('TrajservAPIUtils', () => {
|
|
4
4
|
describe('#translateTrajStationsResp', () => {
|
|
5
|
-
test
|
|
5
|
+
test("should success even if data doesn't have all properties set", () => {
|
|
6
6
|
const empty = {
|
|
7
7
|
backgroundColor: undefined,
|
|
8
8
|
bicyclesAllowed: false,
|
package/api/tralis/TralisAPI.js
CHANGED
|
@@ -44,7 +44,7 @@ class TralisAPI {
|
|
|
44
44
|
* @param {string} options.url Service url.
|
|
45
45
|
* @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).
|
|
46
46
|
* @param {string} [options.prefix=''] Service prefix to specify tenant.
|
|
47
|
-
* @param {string} [options.projection
|
|
47
|
+
* @param {string} [options.projection] The epsg code of the projection for features.
|
|
48
48
|
* @param {number[4]} [options.bbox=[minX, minY, maxX, maxY] The bounding box to receive data from.
|
|
49
49
|
*/
|
|
50
50
|
constructor(options = {}) {
|
|
@@ -60,6 +60,12 @@ class TralisAPI {
|
|
|
60
60
|
wsUrl = `${wsUrl}?key=${options.apiKey}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** @ignore */
|
|
64
|
+
this.bbox = options.bbox;
|
|
65
|
+
|
|
66
|
+
/** @ignore */
|
|
67
|
+
this.projection = options.projection || 'EPSG:3857';
|
|
68
|
+
|
|
63
69
|
/** @ignore */
|
|
64
70
|
this.subscribedStationUic = null;
|
|
65
71
|
|
|
@@ -75,15 +81,47 @@ class TralisAPI {
|
|
|
75
81
|
/** @ignore */
|
|
76
82
|
this.prefix = options.prefix || '';
|
|
77
83
|
|
|
84
|
+
this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false;
|
|
85
|
+
|
|
78
86
|
/** @ignore */
|
|
79
87
|
this.conn = new WebSocketConnector(wsUrl);
|
|
80
|
-
this.conn.setProjection(options.projection || 'epsg:3857');
|
|
81
88
|
|
|
82
|
-
if (options.
|
|
83
|
-
this.
|
|
89
|
+
if (options.projection) {
|
|
90
|
+
this.setProjection(options.projection);
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Send the projection to use. Default to EPSG:3857.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} epsgCode The EPSG code of the projection.
|
|
98
|
+
*/
|
|
99
|
+
setProjection(epsgCode) {
|
|
100
|
+
this.conn.send(`PROJECTION ${epsgCode}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Send the bbox to the service, if the first time it will also subscribe to trajectory and deleted_vehicles channels.
|
|
105
|
+
*
|
|
106
|
+
* @param {number[5]} bbox The bounding box and the zoom level to receive data from.
|
|
107
|
+
*/
|
|
108
|
+
setBbox(bbox) {
|
|
109
|
+
/**
|
|
110
|
+
* The BBOX for websocket responses
|
|
111
|
+
* @type {Array<number>}
|
|
112
|
+
*/
|
|
113
|
+
this.bbox = bbox;
|
|
114
|
+
this.conn.send(`BBOX ${bbox.join(' ')}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Unsubscribe trajectory and deleted_vehicles channels. To resubscribe you have to set a new BBOX.
|
|
119
|
+
*/
|
|
120
|
+
// eslint-disable-next-line class-methods-use-this
|
|
121
|
+
reset() {
|
|
122
|
+
this.conn.send('RESET');
|
|
123
|
+
}
|
|
124
|
+
|
|
87
125
|
/**
|
|
88
126
|
* Subscribe to a channel.
|
|
89
127
|
*
|
|
@@ -98,7 +136,7 @@ class TralisAPI {
|
|
|
98
136
|
}
|
|
99
137
|
|
|
100
138
|
/**
|
|
101
|
-
* Unsubscribe
|
|
139
|
+
* Unsubscribe both modes of a channel.
|
|
102
140
|
*
|
|
103
141
|
* @param {string} channel Name of the websocket channel to unsubscribe.
|
|
104
142
|
* @param {string} [suffix=''] Suffix to add to the channel name.
|
|
@@ -377,7 +415,12 @@ class TralisAPI {
|
|
|
377
415
|
*/
|
|
378
416
|
subscribeTrajectory(mode, onMessage) {
|
|
379
417
|
this.unsubscribeTrajectory(onMessage);
|
|
380
|
-
this.subscribe(
|
|
418
|
+
this.subscribe(
|
|
419
|
+
`trajectory${getModeSuffix(mode, TralisModes)}`,
|
|
420
|
+
onMessage,
|
|
421
|
+
null,
|
|
422
|
+
this.isUpdateBboxOnMoveEnd,
|
|
423
|
+
);
|
|
381
424
|
}
|
|
382
425
|
|
|
383
426
|
/**
|
|
@@ -399,6 +442,8 @@ class TralisAPI {
|
|
|
399
442
|
this.subscribe(
|
|
400
443
|
`deleted_vehicles${getModeSuffix(mode, TralisModes)}`,
|
|
401
444
|
onMessage,
|
|
445
|
+
null,
|
|
446
|
+
this.isUpdateBboxOnMoveEnd,
|
|
402
447
|
);
|
|
403
448
|
}
|
|
404
449
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Class use to facilitate connection to a WebSocket
|
|
2
|
+
* Class use to facilitate connection to a WebSocket.
|
|
3
|
+
* This class must not conatain any specific WebSocket implementation.
|
|
4
|
+
*
|
|
3
5
|
* @private
|
|
4
6
|
*/
|
|
5
7
|
class WebSocketConnector {
|
|
@@ -7,19 +9,29 @@ class WebSocketConnector {
|
|
|
7
9
|
/**
|
|
8
10
|
* Array of subscriptions.
|
|
9
11
|
* @type {Array<subscription>}
|
|
12
|
+
* @private
|
|
10
13
|
*/
|
|
11
14
|
this.subscriptions = [];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List of channels subscribed.
|
|
18
|
+
* @type {Array<subscription>}
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
this.subscribed = {};
|
|
22
|
+
|
|
23
|
+
// Connect the websocket
|
|
12
24
|
this.connect(url);
|
|
13
25
|
|
|
14
26
|
// keep websocket alive
|
|
15
27
|
setInterval(() => {
|
|
16
28
|
this.send('PING');
|
|
17
29
|
}, 10000);
|
|
18
|
-
this.subscribed = {};
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
22
33
|
* Get the websocket request string.
|
|
34
|
+
*
|
|
23
35
|
* @param {string} method Request mehtod {GET, SUB}.
|
|
24
36
|
* @param {Object} params Request parameters.
|
|
25
37
|
* @param {string} params.channel Channel name
|
|
@@ -37,6 +49,7 @@ class WebSocketConnector {
|
|
|
37
49
|
|
|
38
50
|
/**
|
|
39
51
|
* (Re)connect the websocket.
|
|
52
|
+
*
|
|
40
53
|
* @param {string} url url to connect to
|
|
41
54
|
* @private
|
|
42
55
|
*/
|
|
@@ -48,16 +61,8 @@ class WebSocketConnector {
|
|
|
48
61
|
/** @ignore */
|
|
49
62
|
this.websocket = new WebSocket(url);
|
|
50
63
|
|
|
51
|
-
if (this.currentProj) {
|
|
52
|
-
this.setProjection(this.currentProj);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (this.currentBbox) {
|
|
56
|
-
this.setBbox(this.currentBbox);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
64
|
[...this.subscriptions].forEach((s) => {
|
|
60
|
-
this.subscribe(s.params, s.cb, s.errorCb,
|
|
65
|
+
this.subscribe(s.params, s.cb, s.errorCb, s.quiet);
|
|
61
66
|
});
|
|
62
67
|
|
|
63
68
|
// reconnect on close
|
|
@@ -70,6 +75,7 @@ class WebSocketConnector {
|
|
|
70
75
|
|
|
71
76
|
/**
|
|
72
77
|
* Sends a get request to the websocket.
|
|
78
|
+
*
|
|
73
79
|
* @param {Object} params Parameters for the websocket get request
|
|
74
80
|
* @param {function} cb callback on listen
|
|
75
81
|
* @param {function} errorCb Callback on error
|
|
@@ -83,6 +89,7 @@ class WebSocketConnector {
|
|
|
83
89
|
|
|
84
90
|
/**
|
|
85
91
|
* Sends a message to the websocket.
|
|
92
|
+
*
|
|
86
93
|
* @param {message} message Message to send.
|
|
87
94
|
* @private
|
|
88
95
|
*/
|
|
@@ -90,7 +97,6 @@ class WebSocketConnector {
|
|
|
90
97
|
const send = () => {
|
|
91
98
|
this.websocket.send(message);
|
|
92
99
|
};
|
|
93
|
-
|
|
94
100
|
if (this.websocket.readyState === WebSocket.CONNECTING) {
|
|
95
101
|
this.websocket.addEventListener('open', send);
|
|
96
102
|
} else {
|
|
@@ -99,50 +105,20 @@ class WebSocketConnector {
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @private
|
|
105
|
-
*/
|
|
106
|
-
setProjection(value) {
|
|
107
|
-
/**
|
|
108
|
-
* The projection for websocket responses
|
|
109
|
-
* @type {string}
|
|
110
|
-
*/
|
|
111
|
-
this.currentProj = value;
|
|
112
|
-
this.send(`PROJECTION ${value}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Set the BBOX for websocket responses.
|
|
117
|
-
* @param {Array<Array<number>>} coordinates array of coordinates
|
|
118
|
-
* @private
|
|
119
|
-
*/
|
|
120
|
-
setBbox(coordinates) {
|
|
121
|
-
/**
|
|
122
|
-
* The BBOX for websocket responses
|
|
123
|
-
* @type {Array<Array<number>>}
|
|
124
|
-
*/
|
|
125
|
-
this.currentBbox = coordinates;
|
|
126
|
-
this.send(`BBOX ${coordinates.join(' ')}`);
|
|
127
|
-
this.subscriptions.forEach((s) => {
|
|
128
|
-
this.get(s.params, s.cb, s.errorCb);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Listen to websocket responses.
|
|
134
|
-
* @private
|
|
108
|
+
* Listen to websocket messages.
|
|
109
|
+
*
|
|
135
110
|
* @param {Object} params Parameters for the websocket get request
|
|
136
111
|
* @param {function} cb callback on listen
|
|
137
112
|
* @param {function} errorCb Callback on error
|
|
138
113
|
* @returns {{onMessage: function, errorCb: function}} Object with onMessage and error callbacks
|
|
114
|
+
* @private
|
|
139
115
|
*/
|
|
140
116
|
listen(params, cb, errorCb) {
|
|
141
117
|
// Remove the previous identical callback
|
|
142
118
|
this.unlisten(params, cb);
|
|
143
119
|
|
|
144
|
-
const onMessage = (
|
|
145
|
-
const data = JSON.parse(
|
|
120
|
+
const onMessage = (evt) => {
|
|
121
|
+
const data = JSON.parse(evt.data);
|
|
146
122
|
let source = params.channel;
|
|
147
123
|
source += params.args ? ` ${params.args}` : '';
|
|
148
124
|
|
|
@@ -164,6 +140,13 @@ class WebSocketConnector {
|
|
|
164
140
|
return { onMessageCb: onMessage, onErrorCb: errorCb };
|
|
165
141
|
}
|
|
166
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Unlisten websocket messages.
|
|
145
|
+
*
|
|
146
|
+
* @param {Object} params Parameters for the websocket get request.
|
|
147
|
+
* @param {function} cb Callback used when listen.
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
167
150
|
unlisten(params, cb) {
|
|
168
151
|
this.subscriptions
|
|
169
152
|
.filter((s) => {
|
|
@@ -180,31 +163,33 @@ class WebSocketConnector {
|
|
|
180
163
|
|
|
181
164
|
/**
|
|
182
165
|
* Subscribe to a given channel.
|
|
183
|
-
*
|
|
166
|
+
*
|
|
184
167
|
* @param {Object} params Parameters for the websocket get request
|
|
185
168
|
* @param {function} cb callback on listen
|
|
186
169
|
* @param {function} errorCb Callback on error
|
|
187
|
-
* @param {boolean} quiet if
|
|
170
|
+
* @param {boolean} quiet if false, no GET or SUB requests are send, only the callback is registered.
|
|
171
|
+
* @private
|
|
188
172
|
*/
|
|
189
|
-
subscribe(params, cb, errorCb, quiet) {
|
|
173
|
+
subscribe(params, cb, errorCb, quiet = false) {
|
|
190
174
|
const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb);
|
|
191
175
|
const reqStr = WebSocketConnector.getRequestString('', params);
|
|
192
176
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.subscriptions.push(newSubscr);
|
|
202
|
-
}
|
|
177
|
+
const index = this.subscriptions.findIndex((subcr) => {
|
|
178
|
+
return params.channel === subcr.params.channel && cb === subcr.cb;
|
|
179
|
+
});
|
|
180
|
+
const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet };
|
|
181
|
+
if (index > -1) {
|
|
182
|
+
this.subscriptions[index] = newSubscr;
|
|
183
|
+
} else {
|
|
184
|
+
this.subscriptions.push(newSubscr);
|
|
203
185
|
}
|
|
204
186
|
|
|
205
187
|
if (!this.subscribed[reqStr]) {
|
|
206
|
-
|
|
207
|
-
|
|
188
|
+
if (!newSubscr.quiet) {
|
|
189
|
+
this.send(`GET ${reqStr}`);
|
|
190
|
+
this.send(`SUB ${reqStr}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
208
193
|
this.subscribed[reqStr] = true;
|
|
209
194
|
}
|
|
210
195
|
}
|
|
@@ -216,27 +201,29 @@ class WebSocketConnector {
|
|
|
216
201
|
* @private
|
|
217
202
|
*/
|
|
218
203
|
unsubscribe(source, cb) {
|
|
219
|
-
this.subscriptions
|
|
220
|
-
.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
204
|
+
const toRemove = this.subscriptions.filter((s) => {
|
|
205
|
+
return s.params.channel === source && (!cb || s.cb === cb);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
toRemove.forEach(({ onMessageCb, onErrorCb }) => {
|
|
209
|
+
this.websocket.removeEventListener('message', onMessageCb);
|
|
210
|
+
if (onErrorCb) {
|
|
211
|
+
this.websocket.removeEventListener('error', onErrorCb);
|
|
212
|
+
this.websocket.removeEventListener('close', onErrorCb);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
230
215
|
|
|
231
216
|
this.subscriptions = this.subscriptions.filter(
|
|
232
217
|
(s) => s.params.channel !== source || (cb && s.cb !== cb),
|
|
233
218
|
);
|
|
234
219
|
|
|
235
|
-
// If there is no more subscriptions to this channel
|
|
220
|
+
// If there is no more subscriptions to this channel, and the removed subscriptions didn't register quietly,
|
|
221
|
+
// we DEL it.
|
|
236
222
|
if (
|
|
237
223
|
source &&
|
|
238
224
|
this.subscribed[source] &&
|
|
239
|
-
!this.subscriptions.find((s) => s.params.channel === source)
|
|
225
|
+
!this.subscriptions.find((s) => s.params.channel === source) &&
|
|
226
|
+
toRemove.find((subscr) => !subscr.quiet)
|
|
240
227
|
) {
|
|
241
228
|
this.send(`DEL ${source}`);
|
|
242
229
|
this.subscribed[source] = false;
|
|
@@ -22,22 +22,85 @@ describe('WebSocketConnector', () => {
|
|
|
22
22
|
expect(server).toHaveReceivedMessages(['hello']);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
describe('#subscribe', () => {
|
|
26
|
+
test('adds subscrption to subscriptions array', async () => {
|
|
27
|
+
// eslint-disable-next-line no-unused-vars
|
|
28
|
+
const client = new Connector(`ws://foo:1234`);
|
|
29
|
+
await server.connected;
|
|
30
|
+
const params = { channel: 'bar', args: ['baz'], id: 'id' };
|
|
31
|
+
const cb = jest.fn();
|
|
32
|
+
const errorCb = jest.fn();
|
|
33
|
+
client.subscribe(params, cb, errorCb);
|
|
34
|
+
expect(client.subscriptions.length).toBe(1);
|
|
35
|
+
expect(client.subscriptions[0].params).toBe(params);
|
|
36
|
+
expect(client.subscriptions[0].cb).toBe(cb);
|
|
37
|
+
expect(client.subscriptions[0].errorCb).toBe(errorCb);
|
|
38
|
+
expect(client.subscriptions[0].quiet).toBe(false);
|
|
39
|
+
|
|
40
|
+
const obj = { source: 'bar baz', client_reference: 'id' };
|
|
41
|
+
server.send(JSON.stringify(obj));
|
|
42
|
+
|
|
43
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
44
|
+
expect(cb).toHaveBeenCalledWith(obj);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("doesn't duplicate subscriptions", async () => {
|
|
48
|
+
// eslint-disable-next-line no-unused-vars
|
|
49
|
+
const client = new Connector(`ws://foo:1234`);
|
|
50
|
+
await server.connected;
|
|
51
|
+
const params = { channel: 'bar', args: ['baz'], id: 'id' };
|
|
52
|
+
const cb = jest.fn();
|
|
53
|
+
const errorCb = jest.fn();
|
|
54
|
+
client.subscribe(params, cb, errorCb, true);
|
|
55
|
+
client.subscribe(params, cb, errorCb, true);
|
|
56
|
+
expect(client.subscriptions.length).toBe(1);
|
|
57
|
+
|
|
58
|
+
const obj = { source: 'bar baz', client_reference: 'id' };
|
|
59
|
+
server.send(JSON.stringify(obj));
|
|
60
|
+
|
|
61
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(cb).toHaveBeenCalledWith(obj);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('send GET and SUB requests.', async () => {
|
|
66
|
+
// eslint-disable-next-line no-unused-vars
|
|
67
|
+
const client = new Connector(`ws://foo:1234`);
|
|
68
|
+
client.send = jest.fn();
|
|
69
|
+
const params = { channel: 'bar', args: ['baz'], id: 'id' };
|
|
70
|
+
const cb = jest.fn();
|
|
71
|
+
const errorCb = jest.fn();
|
|
72
|
+
client.subscribe(params, cb, errorCb);
|
|
73
|
+
expect(client.send).toHaveBeenCalledTimes(2);
|
|
74
|
+
expect(client.send).toHaveBeenCalledWith('GET bar baz id');
|
|
75
|
+
expect(client.send).toHaveBeenCalledWith('SUB bar baz id');
|
|
76
|
+
client.send.mockRestore();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should register callback without sending GET and SUB requests (quiet=true).', async () => {
|
|
80
|
+
// eslint-disable-next-line no-unused-vars
|
|
81
|
+
const client = new Connector(`ws://foo:1234`);
|
|
82
|
+
await server.connected;
|
|
83
|
+
const params = { channel: 'bar', args: ['baz'], id: 'id' };
|
|
84
|
+
const cb = jest.fn();
|
|
85
|
+
const errorCb = jest.fn();
|
|
86
|
+
client.send = jest.fn();
|
|
87
|
+
client.subscribe(params, cb, errorCb, true);
|
|
88
|
+
expect(client.subscriptions.length).toBe(1);
|
|
89
|
+
expect(client.subscriptions[0].params).toBe(params);
|
|
90
|
+
expect(client.subscriptions[0].cb).toBe(cb);
|
|
91
|
+
expect(client.subscriptions[0].errorCb).toBe(errorCb);
|
|
92
|
+
expect(client.subscriptions[0].quiet).toBe(true);
|
|
93
|
+
expect(client.send).toBeCalledTimes(0);
|
|
94
|
+
client.send.mockRestore();
|
|
95
|
+
});
|
|
34
96
|
});
|
|
35
97
|
|
|
36
98
|
describe('#unsubscribe', () => {
|
|
37
|
-
test('should only unsubscribe the subscription using the good cb', () => {
|
|
99
|
+
test('should only unsubscribe the subscription using the good cb', async () => {
|
|
38
100
|
// eslint-disable-next-line no-unused-vars
|
|
39
101
|
const client = new Connector(`ws://foo:1234`);
|
|
40
|
-
|
|
102
|
+
await server.connected;
|
|
103
|
+
const params = { channel: 'foo', id: 'id' };
|
|
41
104
|
const cb = jest.fn();
|
|
42
105
|
const cb2 = jest.fn();
|
|
43
106
|
client.subscribe(params, cb);
|
|
@@ -50,6 +113,12 @@ describe('WebSocketConnector', () => {
|
|
|
50
113
|
|
|
51
114
|
client.unsubscribe('foo', cb);
|
|
52
115
|
expect(client.subscriptions.length).toBe(1);
|
|
116
|
+
|
|
117
|
+
const obj = { source: 'foo', client_reference: 'id' };
|
|
118
|
+
server.send(JSON.stringify(obj));
|
|
119
|
+
|
|
120
|
+
expect(cb).toHaveBeenCalledTimes(0);
|
|
121
|
+
expect(cb2).toHaveBeenCalledTimes(1);
|
|
53
122
|
});
|
|
54
123
|
|
|
55
124
|
test('should unsubscribe all subscriptions related to a channel', () => {
|
|
@@ -75,46 +144,37 @@ describe('WebSocketConnector', () => {
|
|
|
75
144
|
expect(client.subscriptions[0].params).toBe(params2);
|
|
76
145
|
expect(client.subscriptions[0].cb).toBe(cb2);
|
|
77
146
|
});
|
|
78
|
-
});
|
|
79
147
|
|
|
80
|
-
|
|
81
|
-
test.only('should remove subscriptions before re adding it ', () => {
|
|
148
|
+
test('send DEL when there is no more unquiet subscriptions on the channel', () => {
|
|
82
149
|
// eslint-disable-next-line no-unused-vars
|
|
83
150
|
const client = new Connector(`ws://foo:1234`);
|
|
151
|
+
client.send = jest.fn();
|
|
84
152
|
client.websocket.removeEventListener = jest.fn();
|
|
85
153
|
client.websocket.addEventListener = jest.fn();
|
|
86
|
-
client.send = jest.fn();
|
|
87
154
|
const params = { channel: 'foo' };
|
|
88
|
-
const params2 = { channel: 'bar' };
|
|
89
155
|
const cb = jest.fn();
|
|
90
|
-
const cb2 = jest.fn();
|
|
91
|
-
client.subscribe(params, cb);
|
|
92
156
|
client.subscribe(params, cb);
|
|
93
|
-
client.
|
|
94
|
-
client.
|
|
95
|
-
client.subscribe(params2, cb2);
|
|
96
|
-
expect(client.subscriptions.length).toBe(3);
|
|
97
|
-
expect(client.websocket.removeEventListener).toBeCalledTimes(2);
|
|
98
|
-
expect(client.websocket.addEventListener).toBeCalledTimes(5);
|
|
99
|
-
|
|
100
|
-
client.websocket.removeEventListener.mockReset();
|
|
101
|
-
client.websocket.addEventListener.mockReset();
|
|
157
|
+
expect(client.send).toHaveBeenCalledWith('GET foo');
|
|
158
|
+
expect(client.send).toHaveBeenCalledWith('SUB foo');
|
|
102
159
|
|
|
103
|
-
client.
|
|
160
|
+
client.unsubscribe('foo');
|
|
161
|
+
expect(client.send).toHaveBeenCalledWith('DEL foo');
|
|
162
|
+
});
|
|
104
163
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
164
|
+
test("doesn't send DEL when we unsubscribe a quiet channel", () => {
|
|
165
|
+
// eslint-disable-next-line no-unused-vars
|
|
166
|
+
const client = new Connector(`ws://foo:1234`);
|
|
167
|
+
client.send = jest.fn();
|
|
168
|
+
client.websocket.removeEventListener = jest.fn();
|
|
169
|
+
client.websocket.addEventListener = jest.fn();
|
|
170
|
+
const params = { channel: 'foo' };
|
|
171
|
+
const cb = jest.fn();
|
|
172
|
+
client.subscribe(params, cb, null, true);
|
|
173
|
+
expect(cb).toHaveBeenCalledTimes(0);
|
|
108
174
|
|
|
109
175
|
client.unsubscribe('foo');
|
|
110
|
-
expect(
|
|
111
|
-
expect(client.subscriptions[0].params).toBe(params2);
|
|
112
|
-
expect(client.subscriptions[0].cb).toBe(cb2);
|
|
113
|
-
client.unsubscribe('bar');
|
|
114
|
-
expect(client.subscriptions.length).toBe(0);
|
|
176
|
+
expect(cb).toHaveBeenCalledTimes(0);
|
|
115
177
|
client.send.mockRestore();
|
|
116
|
-
client.websocket.removeEventListener.mockRestore();
|
|
117
|
-
client.websocket.addEventListener.mockRestore();
|
|
118
178
|
});
|
|
119
179
|
});
|
|
120
180
|
});
|
package/common/layers/Layer.js
CHANGED
|
@@ -13,7 +13,9 @@ import { v4 as uuid } from 'uuid';
|
|
|
13
13
|
* @classproperty {string} key - Identifier of the layer. Must be unique.
|
|
14
14
|
* @classproperty {string[]} copyrights - Array of copyrights.
|
|
15
15
|
* @classproperty {boolean} isBaseLayer - Define if the layer is a base layer. Read-only.
|
|
16
|
-
* @classproperty {boolean} isQueryable - Define if the layer can be queried. Read-only.
|
|
16
|
+
* @classproperty {boolean} isQueryable - Define if the layer can be queried. If false, it will set isHoverActive and isClickActive to false. Read-only.
|
|
17
|
+
* @classproperty {boolean} isClickActive - If true feature information will be queried on user click event. See inherited layers for more informations. Read-only.
|
|
18
|
+
* @classproperty {boolean} isHoverActive - If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
|
|
17
19
|
* @classproperty {boolean} isReactSpatialLayer - Custom property for duck typing since `instanceof` is not working when the instance was created on different bundles. Read-only.
|
|
18
20
|
* @classproperty {Layer[]} children - List of children.
|
|
19
21
|
* @classproperty {boolean} visible - Define if the layer is visible or not.
|
|
@@ -33,9 +35,9 @@ export default class Layer extends Observable {
|
|
|
33
35
|
* @param {Object} [options.properties={}] Application-specific layer properties.
|
|
34
36
|
* @param {boolean} [options.visible=true] If true this layer is visible on the map.
|
|
35
37
|
* @param {boolean} [options.isBaseLayer=false] If true this layer is a baseLayer.
|
|
36
|
-
* @param {boolean} [options.isQueryable=true]
|
|
37
|
-
* @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations.
|
|
38
|
-
* @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations.
|
|
38
|
+
* @param {boolean} [options.isQueryable=true] Define if the layer can be queried. If false, it will also set isHoverActive and isClickActive to false. Read-only.
|
|
39
|
+
* @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations. Read-only.
|
|
40
|
+
* @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
|
|
39
41
|
* @param {number} [options.hitTolerance=5] Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.
|
|
40
42
|
*/
|
|
41
43
|
constructor(options = {}) {
|
|
@@ -109,10 +111,10 @@ export default class Layer extends Observable {
|
|
|
109
111
|
value: !!isQueryable,
|
|
110
112
|
},
|
|
111
113
|
isClickActive: {
|
|
112
|
-
value: !!isClickActive,
|
|
114
|
+
value: !!isQueryable && !!isClickActive,
|
|
113
115
|
},
|
|
114
116
|
isHoverActive: {
|
|
115
|
-
value: !!isHoverActive,
|
|
117
|
+
value: !!isQueryable && !!isHoverActive,
|
|
116
118
|
},
|
|
117
119
|
hitTolerance: {
|
|
118
120
|
value: hitTolerance || 5,
|
|
@@ -307,6 +309,7 @@ export default class Layer extends Observable {
|
|
|
307
309
|
// eslint-disable-next-line no-console
|
|
308
310
|
console.error(
|
|
309
311
|
'getFeatureInfoAtCoordinate must be implemented by inheriting layers',
|
|
312
|
+
this.key,
|
|
310
313
|
);
|
|
311
314
|
|
|
312
315
|
// This layer returns no feature info.
|
|
@@ -427,7 +430,7 @@ export default class Layer extends Observable {
|
|
|
427
430
|
.then((featureInfo) => {
|
|
428
431
|
const { features, layer, coordinate } = featureInfo;
|
|
429
432
|
this.hoverCallbacks.forEach((callback) =>
|
|
430
|
-
callback(
|
|
433
|
+
callback(features, layer, coordinate),
|
|
431
434
|
);
|
|
432
435
|
return featureInfo;
|
|
433
436
|
})
|
|
@@ -63,6 +63,21 @@ describe('Layer', () => {
|
|
|
63
63
|
expect(layer.name).toEqual('Layer');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
test('should set isClickActive and isHoverActive to false if isQueryable is set to false.', () => {
|
|
67
|
+
const options = {
|
|
68
|
+
name: 'Layer',
|
|
69
|
+
isQueryable: false,
|
|
70
|
+
isClickActive: true,
|
|
71
|
+
isHoverActive: true,
|
|
72
|
+
olLayer,
|
|
73
|
+
};
|
|
74
|
+
const layer = new Layer(options);
|
|
75
|
+
expect(layer).toBeInstanceOf(Layer);
|
|
76
|
+
expect(layer.isQueryable).toBe(false);
|
|
77
|
+
expect(layer.isClickActive).toBe(false);
|
|
78
|
+
expect(layer.isHoverActive).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
66
81
|
test('should called terminate on initialization.', () => {
|
|
67
82
|
const layer = new Layer({ name: 'Layer', olLayer });
|
|
68
83
|
const spy = jest.spyOn(layer, 'terminate');
|
|
@@ -465,7 +480,13 @@ describe('Layer', () => {
|
|
|
465
480
|
expect(spy).toHaveBeenCalledWith(evt.coordinate);
|
|
466
481
|
expect(featureInfo).toBe(goodFeatureInfo);
|
|
467
482
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
483
|
+
expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
484
|
+
expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
485
|
+
expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
468
486
|
expect(fn2).toHaveBeenCalledTimes(1);
|
|
487
|
+
expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
488
|
+
expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
489
|
+
expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
469
490
|
done();
|
|
470
491
|
});
|
|
471
492
|
});
|
|
@@ -599,7 +620,13 @@ describe('Layer', () => {
|
|
|
599
620
|
expect(spy).toHaveBeenCalledWith(evt.coordinate);
|
|
600
621
|
expect(featureInfo).toBe(goodFeatureInfo);
|
|
601
622
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
623
|
+
expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
624
|
+
expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
625
|
+
expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
602
626
|
expect(fn2).toHaveBeenCalledTimes(1);
|
|
627
|
+
expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
628
|
+
expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
629
|
+
expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
603
630
|
done();
|
|
604
631
|
});
|
|
605
632
|
});
|