@wemap/geo 9.0.0 → 9.0.2
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/dist/wemap-geo.es.js +405 -304
- package/dist/wemap-geo.es.js.map +1 -1
- package/package.json +2 -2
- package/src/coordinates/Coordinates.js +1 -1
- package/src/coordinates/Level.js +21 -1
- package/src/coordinates/Level.spec.js +46 -22
- package/src/graph/GraphNode.js +9 -9
- package/src/graph/MapMatching.js +49 -33
- package/src/graph/MapMatching.spec.js +125 -20
- package/src/graph/Network.js +1 -1
- package/tests/CommonTest.js +35 -24
package/dist/wemap-geo.es.js
CHANGED
|
@@ -31,309 +31,351 @@ Constants.CIRCUMFERENCE = Constants.R_MAJOR * 2 * Math.PI;
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* A Level is the representation of a building floor number
|
|
34
|
-
* A level can be a simple number
|
|
35
|
-
*
|
|
34
|
+
* A level can be a simple number or a range (low, up)
|
|
35
|
+
* The range is an array of two numbers
|
|
36
36
|
*/
|
|
37
37
|
class Level {
|
|
38
38
|
|
|
39
|
-
/** @type {number?} */
|
|
40
|
-
val = null;
|
|
41
|
-
|
|
42
39
|
/** @type {boolean} */
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/** @type {number?} */
|
|
46
|
-
low = null;
|
|
47
|
-
|
|
48
|
-
/** @type {number?} */
|
|
49
|
-
up = null;
|
|
40
|
+
VERIFY_TYPING = false;
|
|
50
41
|
|
|
51
42
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* 2 arguments: level is a range
|
|
55
|
-
* @param {number} arg1 if arg2: low value, otherwise: level
|
|
56
|
-
* @param {number} arg2 (optional) up value
|
|
43
|
+
* @param {null|number|[number, number]} level
|
|
44
|
+
* @throws {Error}
|
|
57
45
|
*/
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
46
|
+
static checkType(level) {
|
|
47
|
+
if (level === null) {
|
|
48
|
+
return;
|
|
61
49
|
}
|
|
62
|
-
if (typeof
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
50
|
+
if (typeof level === 'number' && !isNaN(level)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(level) && level.length === 2) {
|
|
54
|
+
const [low, up] = level;
|
|
55
|
+
if (typeof low === 'number' && !isNaN(low)
|
|
56
|
+
&& typeof up === 'number' && !isNaN(up)) {
|
|
57
|
+
if (low > up || low === up) {
|
|
58
|
+
throw Error(`Invalid level range: [${low}, ${up}]`);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
70
61
|
}
|
|
71
|
-
} else if (typeof arg2 === 'undefined') {
|
|
72
|
-
this.isRange = false;
|
|
73
|
-
this.val = arg1;
|
|
74
|
-
} else {
|
|
75
|
-
throw new Error('second argument is not a number');
|
|
76
62
|
}
|
|
63
|
+
throw Error(`Unknown level format: ${level}`);
|
|
77
64
|
}
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Return true if the level is a range, false otherwise
|
|
68
|
+
* @param {null|number|[number, number]} level
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
static isRange(level) {
|
|
72
|
+
if (this.VERIFY_TYPING) {
|
|
73
|
+
this.checkType(level);
|
|
82
74
|
}
|
|
83
|
-
return
|
|
75
|
+
return Array.isArray(level);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {null|number|[number, number]} level
|
|
80
|
+
* @returns {null|number|[number, number]}
|
|
81
|
+
* @throws {Error}
|
|
82
|
+
*/
|
|
83
|
+
static clone(level) {
|
|
84
|
+
if (this.VERIFY_TYPING) {
|
|
85
|
+
this.checkType(level);
|
|
86
|
+
}
|
|
87
|
+
if (level === null) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (typeof level === 'number') {
|
|
91
|
+
return level;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [level[0], level[1]];
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
/**
|
|
87
98
|
* Create a level from a string
|
|
88
99
|
* @param {string} str level in str format (eg. 1, -2, 1;2, -2;3, 2;-1, 0.5;1 ...)
|
|
100
|
+
* @returns {null|number|[number, number]}
|
|
101
|
+
* @throws {Error}
|
|
89
102
|
*/
|
|
90
103
|
static fromString(str) {
|
|
91
104
|
|
|
92
|
-
if (
|
|
105
|
+
if (str === null) {
|
|
93
106
|
return null;
|
|
94
107
|
}
|
|
95
108
|
|
|
109
|
+
if (typeof str !== 'string') {
|
|
110
|
+
throw Error(`argument must be a string, got ${typeof str}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
96
113
|
if (!isNaN(Number(str))) {
|
|
97
|
-
return
|
|
114
|
+
return parseFloat(str);
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
const splited = str.split(';');
|
|
101
118
|
if (splited.length === 2) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
const low = Number(splited[0]);
|
|
120
|
+
const up = Number(splited[1]);
|
|
121
|
+
this.checkType([low, up]);
|
|
122
|
+
return [parseFloat(splited[0]), parseFloat(splited[1])];
|
|
105
123
|
}
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Returns if the level is inside the given level
|
|
113
|
-
* @param {Level} level the container level
|
|
114
|
-
*/
|
|
115
|
-
isInside(level) {
|
|
116
|
-
return Level.contains(level, this);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Returns if the level is inside the given level
|
|
121
|
-
* @param {Level} level the container level
|
|
122
|
-
*/
|
|
123
|
-
contains(level) {
|
|
124
|
-
return Level.contains(this, level);
|
|
125
|
+
throw Error(`Cannot parse following level: ${str}`);
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
130
|
* Returns if a level is contained in another
|
|
130
|
-
* @param {
|
|
131
|
-
* @param {
|
|
131
|
+
* @param {null|number|[number, number]} container The container level
|
|
132
|
+
* @param {null|number|[number, number]} targeted The targeted level
|
|
132
133
|
*/
|
|
133
134
|
static contains(container, targeted) {
|
|
135
|
+
if (this.VERIFY_TYPING) {
|
|
136
|
+
this.checkType(container);
|
|
137
|
+
this.checkType(targeted);
|
|
138
|
+
}
|
|
134
139
|
|
|
140
|
+
// Covers null and number
|
|
135
141
|
if (container === targeted) {
|
|
136
142
|
return true;
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!container.isRange) {
|
|
144
|
-
if (targeted.isRange) {
|
|
145
|
-
return false;
|
|
145
|
+
if (Array.isArray(container)) {
|
|
146
|
+
if (Array.isArray(targeted)) {
|
|
147
|
+
return container[0] <= targeted[0] && container[1] >= targeted[1];
|
|
146
148
|
}
|
|
147
|
-
return container
|
|
149
|
+
return container[0] <= targeted && container[1] >= targeted;
|
|
148
150
|
}
|
|
149
|
-
|
|
150
|
-
return container.up >= (targeted.isRange ? targeted.up : targeted.val)
|
|
151
|
-
&& container.low <= (targeted.isRange ? targeted.low : targeted.val);
|
|
151
|
+
return container <= targeted[0] && container >= targeted[1];
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
155
|
* Retrieve the intersection of two levels
|
|
156
|
-
* @param {
|
|
156
|
+
* @param {null|number|[number, number]} first The first level
|
|
157
|
+
* @param {null|number|[number, number]} second The second level
|
|
158
|
+
* @returns {null|number|[number, number]}
|
|
157
159
|
*/
|
|
158
|
-
|
|
159
|
-
return Level.intersect(this, other);
|
|
160
|
-
}
|
|
160
|
+
static intersection(first, second) {
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
*/
|
|
167
|
-
static intersect(first, second) {
|
|
162
|
+
if (this.VERIFY_TYPING) {
|
|
163
|
+
this.checkType(first);
|
|
164
|
+
this.checkType(second);
|
|
165
|
+
}
|
|
168
166
|
|
|
169
|
-
if (first === second) {
|
|
170
|
-
if (first instanceof Level) {
|
|
171
|
-
return first.clone();
|
|
172
|
-
}
|
|
167
|
+
if (first === null || second === null) {
|
|
173
168
|
return null;
|
|
174
169
|
}
|
|
175
170
|
|
|
176
|
-
if (
|
|
177
|
-
return
|
|
171
|
+
if (this.equals(first, second)) {
|
|
172
|
+
return this.clone(first);
|
|
178
173
|
}
|
|
179
174
|
|
|
180
|
-
if (
|
|
181
|
-
return null;
|
|
175
|
+
if (typeof first === 'number' && typeof second === 'number') {
|
|
176
|
+
return first === second ? first : null;
|
|
182
177
|
}
|
|
183
178
|
|
|
184
|
-
if (first
|
|
185
|
-
if (
|
|
186
|
-
return second
|
|
179
|
+
if (Array.isArray(first) && !Array.isArray(second)) {
|
|
180
|
+
if (this.contains(first, second)) {
|
|
181
|
+
return second;
|
|
187
182
|
}
|
|
188
183
|
return null;
|
|
189
184
|
}
|
|
190
|
-
if (!first
|
|
191
|
-
if (
|
|
192
|
-
return first
|
|
185
|
+
if (!Array.isArray(first) && Array.isArray(second)) {
|
|
186
|
+
if (this.contains(second, first)) {
|
|
187
|
+
return first;
|
|
193
188
|
}
|
|
194
189
|
return null;
|
|
195
190
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return new Level(low, up);
|
|
191
|
+
|
|
192
|
+
// There are two ranges
|
|
193
|
+
const low = Math.max(first[0], second[0]);
|
|
194
|
+
const up = Math.min(first[1], second[1]);
|
|
195
|
+
if (up === low) {
|
|
196
|
+
return up;
|
|
203
197
|
}
|
|
204
|
-
return
|
|
198
|
+
return up < low ? null : [low, up];
|
|
205
199
|
}
|
|
206
200
|
|
|
207
201
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
202
|
+
* Retrieve the intersection of two levels
|
|
203
|
+
* @param {null|number|[number, number]} first The first level
|
|
204
|
+
* @param {null|number|[number, number]} second The second level
|
|
205
|
+
* @returns {boolean}
|
|
206
|
+
*/
|
|
207
|
+
static intersect(first, second) {
|
|
208
|
+
|
|
209
|
+
if (this.VERIFY_TYPING) {
|
|
210
|
+
this.checkType(first);
|
|
211
|
+
this.checkType(second);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (first === null && second === null) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return this.intersection(first, second) !== null;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
/**
|
|
216
222
|
* Retrieve the union of two levels
|
|
217
|
-
* @param {
|
|
223
|
+
* @param {null|number|[number, number]} first The first level
|
|
224
|
+
* @param {null|number|[number, number]} second The scond level
|
|
225
|
+
* @returns {null|number|[number, number]}
|
|
218
226
|
*/
|
|
219
227
|
static union(first, second) {
|
|
220
228
|
|
|
229
|
+
if (this.VERIFY_TYPING) {
|
|
230
|
+
this.checkType(first);
|
|
231
|
+
this.checkType(second);
|
|
232
|
+
}
|
|
233
|
+
|
|
221
234
|
if (first === second) {
|
|
222
|
-
|
|
223
|
-
return first.clone();
|
|
224
|
-
}
|
|
225
|
-
return null;
|
|
235
|
+
return this.clone(first);
|
|
226
236
|
}
|
|
227
237
|
|
|
228
|
-
if (
|
|
229
|
-
return
|
|
238
|
+
if (second === null) {
|
|
239
|
+
return this.clone(first);
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
if (
|
|
233
|
-
return
|
|
242
|
+
if (first === null) {
|
|
243
|
+
return this.clone(second);
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
let low, up;
|
|
237
|
-
if (!first
|
|
238
|
-
low = Math.min(first
|
|
239
|
-
up = Math.max(first
|
|
240
|
-
} else if (first
|
|
241
|
-
low = Math.min(first
|
|
242
|
-
up = Math.max(first
|
|
243
|
-
} else if (!first
|
|
244
|
-
low = Math.min(second
|
|
245
|
-
up = Math.max(second
|
|
247
|
+
if (!Array.isArray(first) && !Array.isArray(second)) {
|
|
248
|
+
low = Math.min(first, second);
|
|
249
|
+
up = Math.max(first, second);
|
|
250
|
+
} else if (Array.isArray(first) && !Array.isArray(second)) {
|
|
251
|
+
low = Math.min(first[0], second);
|
|
252
|
+
up = Math.max(first[1], second);
|
|
253
|
+
} else if (!Array.isArray(first) && Array.isArray(second)) {
|
|
254
|
+
low = Math.min(second[0], first);
|
|
255
|
+
up = Math.max(second[1], first);
|
|
246
256
|
} else {
|
|
247
|
-
/* if (first
|
|
248
|
-
low = Math.min(second
|
|
249
|
-
up = Math.max(second
|
|
257
|
+
/* if (Array.isArray(first) && Array.isArray(second)) */
|
|
258
|
+
low = Math.min(second[0], first[0]);
|
|
259
|
+
up = Math.max(second[1], first[1]);
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
if (low === up) {
|
|
253
|
-
return
|
|
263
|
+
return low;
|
|
254
264
|
}
|
|
255
|
-
return
|
|
265
|
+
return [low, up];
|
|
256
266
|
}
|
|
257
267
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Multiply a level by a factor
|
|
270
|
+
* @param {null|number|[number, number]} level the level to multiply
|
|
271
|
+
* @param {number} factor
|
|
272
|
+
* @returns {null|number|[number, number]}
|
|
273
|
+
*/
|
|
274
|
+
static multiplyBy(level, factor) {
|
|
275
|
+
if (this.VERIFY_TYPING) {
|
|
276
|
+
this.checkType(level);
|
|
264
277
|
}
|
|
265
|
-
|
|
278
|
+
|
|
279
|
+
return Array.isArray(level) ? [level[0] * factor, level[1] * factor] : level * factor;
|
|
266
280
|
}
|
|
267
281
|
|
|
268
|
-
|
|
269
|
-
|
|
282
|
+
/**
|
|
283
|
+
* @param {null|number|[number, number]} level
|
|
284
|
+
* @returns {string|null}
|
|
285
|
+
*/
|
|
286
|
+
static toString(level) {
|
|
287
|
+
if (this.VERIFY_TYPING) {
|
|
288
|
+
this.checkType(level);
|
|
289
|
+
}
|
|
290
|
+
if (level === null) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return Array.isArray(level) ? level[0] + ';' + level[1] : String(level);
|
|
270
294
|
}
|
|
271
295
|
|
|
272
|
-
|
|
296
|
+
/**
|
|
297
|
+
* @param {null|number|[number, number]} first
|
|
298
|
+
* @param {null|number|[number, number]} second
|
|
299
|
+
* @returns {boolean}
|
|
300
|
+
*/
|
|
301
|
+
static equals(first, second) {
|
|
302
|
+
|
|
303
|
+
if (this.VERIFY_TYPING) {
|
|
304
|
+
this.checkType(first);
|
|
305
|
+
this.checkType(second);
|
|
306
|
+
}
|
|
273
307
|
|
|
274
308
|
if (first === second) {
|
|
275
309
|
return true;
|
|
276
310
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (!first && second) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
if (first && !second) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
if (!first.isRange && second.isRange) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
if (first.isRange && !second.isRange) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
if (first.isRange && second.isRange) {
|
|
293
|
-
return first.low === second.low && first.up === second.up;
|
|
311
|
+
|
|
312
|
+
if (Array.isArray(first) && Array.isArray(second)) {
|
|
313
|
+
return first[0] === second[0] && first[1] === second[1];
|
|
294
314
|
}
|
|
295
|
-
// !first.isRange && !second.isRange
|
|
296
|
-
return first.val === second.val;
|
|
297
315
|
|
|
316
|
+
return false;
|
|
298
317
|
}
|
|
299
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @param {null|number|[number, number]} first
|
|
321
|
+
* @param {null|number|[number, number]} second
|
|
322
|
+
* @returns {null|number}
|
|
323
|
+
*/
|
|
300
324
|
static diff(first, second) {
|
|
301
325
|
|
|
326
|
+
if (this.VERIFY_TYPING) {
|
|
327
|
+
this.checkType(first);
|
|
328
|
+
this.checkType(second);
|
|
329
|
+
}
|
|
330
|
+
|
|
302
331
|
if (first === null || second === null) {
|
|
303
332
|
return null;
|
|
304
333
|
}
|
|
305
334
|
|
|
306
|
-
if (!first
|
|
307
|
-
return second
|
|
335
|
+
if (!Array.isArray(first) && !Array.isArray(second)) {
|
|
336
|
+
return second - first;
|
|
308
337
|
}
|
|
309
338
|
|
|
310
|
-
if (first
|
|
311
|
-
if (first
|
|
312
|
-
return second
|
|
339
|
+
if (Array.isArray(first) && !Array.isArray(second)) {
|
|
340
|
+
if (first[0] === second) {
|
|
341
|
+
return second - first[1];
|
|
313
342
|
}
|
|
314
|
-
if (first
|
|
315
|
-
return second
|
|
343
|
+
if (first[1] === second) {
|
|
344
|
+
return second - first[0];
|
|
316
345
|
}
|
|
317
346
|
return null;
|
|
318
347
|
}
|
|
319
348
|
|
|
320
|
-
if (second
|
|
321
|
-
if (first
|
|
322
|
-
return second
|
|
349
|
+
if (Array.isArray(second) && !Array.isArray(first)) {
|
|
350
|
+
if (first === second[0]) {
|
|
351
|
+
return second[1] - first;
|
|
323
352
|
}
|
|
324
|
-
if (first
|
|
325
|
-
return second
|
|
353
|
+
if (first === second[1]) {
|
|
354
|
+
return second[0] - first;
|
|
326
355
|
}
|
|
327
356
|
return null;
|
|
328
357
|
}
|
|
329
358
|
|
|
330
|
-
if (Level.
|
|
359
|
+
if (Level.equals(first, second)) {
|
|
331
360
|
return 0;
|
|
332
361
|
}
|
|
333
362
|
|
|
334
363
|
return null;
|
|
335
364
|
}
|
|
336
365
|
|
|
366
|
+
/**
|
|
367
|
+
* @param {null|{isRange: boolean, val?: number, low?: number, up?: number}} legacyLevel
|
|
368
|
+
* @deprecated
|
|
369
|
+
*/
|
|
370
|
+
static fromLegacy(legacyLevel) {
|
|
371
|
+
if (legacyLevel === null) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
if (legacyLevel.isRange) {
|
|
375
|
+
return [legacyLevel.low, legacyLevel.up];
|
|
376
|
+
}
|
|
377
|
+
return legacyLevel.val;
|
|
378
|
+
}
|
|
337
379
|
}
|
|
338
380
|
|
|
339
381
|
/**
|
|
@@ -358,7 +400,7 @@ class Coordinates {
|
|
|
358
400
|
/** @type {Number|null} */
|
|
359
401
|
_alt = null;
|
|
360
402
|
|
|
361
|
-
/** @type {
|
|
403
|
+
/** @type {null|number|[number, number]} */
|
|
362
404
|
_level = null;
|
|
363
405
|
|
|
364
406
|
/** @type {[Number, Number, Number]|null} */
|
|
@@ -370,7 +412,7 @@ class Coordinates {
|
|
|
370
412
|
* @param {Number} lat
|
|
371
413
|
* @param {Number} lng
|
|
372
414
|
* @param {?(Number|null)} alt
|
|
373
|
-
* @param {?(
|
|
415
|
+
* @param {?(null|number|[number, number])} level
|
|
374
416
|
*/
|
|
375
417
|
constructor(lat, lng, alt = null, level = null) {
|
|
376
418
|
this.lat = lat;
|
|
@@ -409,7 +451,7 @@ class Coordinates {
|
|
|
409
451
|
return this._alt;
|
|
410
452
|
}
|
|
411
453
|
|
|
412
|
-
/** @type {
|
|
454
|
+
/** @type {null|number|[number, number]} */
|
|
413
455
|
get level() {
|
|
414
456
|
return this._level;
|
|
415
457
|
}
|
|
@@ -460,16 +502,10 @@ class Coordinates {
|
|
|
460
502
|
this._ecef = null;
|
|
461
503
|
}
|
|
462
504
|
|
|
463
|
-
/** @type {
|
|
505
|
+
/** @type {null|number|[number, number]} */
|
|
464
506
|
set level(level) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
} else {
|
|
468
|
-
if (typeof level !== 'undefined' && level !== null) {
|
|
469
|
-
throw new Error('level argument is not a Level object');
|
|
470
|
-
}
|
|
471
|
-
this._level = null;
|
|
472
|
-
}
|
|
507
|
+
Level.checkType(level);
|
|
508
|
+
this._level = level;
|
|
473
509
|
}
|
|
474
510
|
|
|
475
511
|
/**
|
|
@@ -478,8 +514,8 @@ class Coordinates {
|
|
|
478
514
|
*/
|
|
479
515
|
clone() {
|
|
480
516
|
const output = new Coordinates(this.lat, this.lng, this.alt);
|
|
481
|
-
if (this.level) {
|
|
482
|
-
output.level = this.level
|
|
517
|
+
if (this.level !== null) {
|
|
518
|
+
output.level = Level.clone(this.level);
|
|
483
519
|
}
|
|
484
520
|
return output;
|
|
485
521
|
}
|
|
@@ -497,7 +533,7 @@ class Coordinates {
|
|
|
497
533
|
* @param {Number} eps latitude and longitude epsilon in degrees (default: 1e-8 [~1mm at lat=0])
|
|
498
534
|
* @param {Number} epsAlt altitude epsilon in meters (default: 1e-3 [= 1mm])
|
|
499
535
|
*/
|
|
500
|
-
static
|
|
536
|
+
static equals(pos1, pos2, eps = Constants.EPS_DEG_MM, epsAlt = Constants.EPS_MM) {
|
|
501
537
|
|
|
502
538
|
// Handle null comparison
|
|
503
539
|
if (pos1 === null && pos1 === pos2) {
|
|
@@ -513,15 +549,15 @@ class Coordinates {
|
|
|
513
549
|
&& (pos1.alt === pos2.alt
|
|
514
550
|
|| pos1.alt !== null && pos2.alt !== null
|
|
515
551
|
&& Math.abs(pos2.alt - pos1.alt) < epsAlt)
|
|
516
|
-
&& Level.
|
|
552
|
+
&& Level.equals(pos1.level, pos2.level);
|
|
517
553
|
}
|
|
518
554
|
|
|
519
555
|
/**
|
|
520
556
|
* @param {Coordinates} other
|
|
521
557
|
* @returns {!Boolean}
|
|
522
558
|
*/
|
|
523
|
-
|
|
524
|
-
return Coordinates.
|
|
559
|
+
equals(other) {
|
|
560
|
+
return Coordinates.equals(this, other);
|
|
525
561
|
}
|
|
526
562
|
|
|
527
563
|
/**
|
|
@@ -738,7 +774,7 @@ class Coordinates {
|
|
|
738
774
|
alt = (p1.alt + p2.alt) / 2;
|
|
739
775
|
}
|
|
740
776
|
const projection = new Coordinates(poseCoordinates.lat, poseCoordinates.lng,
|
|
741
|
-
alt, Level.
|
|
777
|
+
alt, Level.intersection(p1.level, p2.level));
|
|
742
778
|
|
|
743
779
|
if (Math.abs((p1.distanceTo(p2) - p1.distanceTo(projection) - p2.distanceTo(projection))) > Constants.EPS_MM) {
|
|
744
780
|
return null;
|
|
@@ -760,7 +796,7 @@ class Coordinates {
|
|
|
760
796
|
str += ', ' + this._alt.toFixed(2);
|
|
761
797
|
}
|
|
762
798
|
if (this._level !== null) {
|
|
763
|
-
str += ', [' + this._level
|
|
799
|
+
str += ', [' + Level.toString(this._level) + ']';
|
|
764
800
|
}
|
|
765
801
|
str += ']';
|
|
766
802
|
return str;
|
|
@@ -778,7 +814,24 @@ class Coordinates {
|
|
|
778
814
|
output.alt = this.alt;
|
|
779
815
|
}
|
|
780
816
|
if (this.level !== null) {
|
|
781
|
-
output.level = this.level
|
|
817
|
+
output.level = this.level;
|
|
818
|
+
}
|
|
819
|
+
return output;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* @returns {!Object}
|
|
824
|
+
*/
|
|
825
|
+
toLegacyJson() {
|
|
826
|
+
const output = {
|
|
827
|
+
lat: this.lat,
|
|
828
|
+
lng: this.lng
|
|
829
|
+
};
|
|
830
|
+
if (this.alt !== null) {
|
|
831
|
+
output.alt = this.alt;
|
|
832
|
+
}
|
|
833
|
+
if (this.level !== null) {
|
|
834
|
+
output.level = Level.toString(this.level);
|
|
782
835
|
}
|
|
783
836
|
return output;
|
|
784
837
|
}
|
|
@@ -788,7 +841,11 @@ class Coordinates {
|
|
|
788
841
|
* @returns {!Coordinates}
|
|
789
842
|
*/
|
|
790
843
|
static fromJson(json) {
|
|
791
|
-
|
|
844
|
+
if (typeof json.level === 'string') {
|
|
845
|
+
Logger.warn('Still using legacy level format. Please update your project.');
|
|
846
|
+
return new Coordinates(json.lat, json.lng, json.alt, Level.fromString(json.level));
|
|
847
|
+
}
|
|
848
|
+
return new Coordinates(json.lat, json.lng, json.alt, json.level);
|
|
792
849
|
}
|
|
793
850
|
|
|
794
851
|
/**
|
|
@@ -800,7 +857,22 @@ class Coordinates {
|
|
|
800
857
|
output.push(this.alt);
|
|
801
858
|
}
|
|
802
859
|
if (this.level !== null) {
|
|
803
|
-
output.push(this.level
|
|
860
|
+
output.push(this.level);
|
|
861
|
+
}
|
|
862
|
+
return output;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* @returns {!Object}
|
|
867
|
+
* @deprecated
|
|
868
|
+
*/
|
|
869
|
+
toLegacyCompressedJson() {
|
|
870
|
+
const output = [this.lat, this.lng];
|
|
871
|
+
if (this.alt !== null || this.level !== null) {
|
|
872
|
+
output.push(this.alt);
|
|
873
|
+
}
|
|
874
|
+
if (this.level !== null) {
|
|
875
|
+
output.push(Level.toString(this.level));
|
|
804
876
|
}
|
|
805
877
|
return output;
|
|
806
878
|
}
|
|
@@ -810,6 +882,26 @@ class Coordinates {
|
|
|
810
882
|
* @returns {!Coordinates}
|
|
811
883
|
*/
|
|
812
884
|
static fromCompressedJson(json) {
|
|
885
|
+
const coords = new Coordinates(json[0], json[1]);
|
|
886
|
+
if (json.length > 2) {
|
|
887
|
+
coords.alt = json[2];
|
|
888
|
+
}
|
|
889
|
+
if (json.length > 3) {
|
|
890
|
+
if (typeof json[3] === 'string') {
|
|
891
|
+
Logger.warn('Still using legacy level format. Please update your project.');
|
|
892
|
+
coords.level = Level.fromString(json[3]);
|
|
893
|
+
} else {
|
|
894
|
+
coords.level = json[3];
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return coords;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* @param {!Object} json
|
|
902
|
+
* @returns {!Coordinates}
|
|
903
|
+
*/
|
|
904
|
+
static fromLegacyCompressedJson(json) {
|
|
813
905
|
const coords = new Coordinates(json[0], json[1]);
|
|
814
906
|
if (json.length > 2) {
|
|
815
907
|
coords.alt = json[2];
|
|
@@ -908,7 +1000,7 @@ class UserPosition extends Coordinates {
|
|
|
908
1000
|
* @param {Number} eps latitude and longitude epsilon in degrees (default: 1e-8 [~1mm at lat=0])
|
|
909
1001
|
* @param {Number} epsAlt altitude epsilon in meters (default: 1e-3 [= 1mm])
|
|
910
1002
|
*/
|
|
911
|
-
static
|
|
1003
|
+
static equals(pos1, pos2, eps = Constants.EPS_DEG_MM, epsAlt = Constants.EPS_MM) {
|
|
912
1004
|
|
|
913
1005
|
// Handle null comparison
|
|
914
1006
|
if (pos1 === null && pos1 === pos2) {
|
|
@@ -919,7 +1011,7 @@ class UserPosition extends Coordinates {
|
|
|
919
1011
|
return false;
|
|
920
1012
|
}
|
|
921
1013
|
|
|
922
|
-
if (!super.
|
|
1014
|
+
if (!super.equals(pos1, pos2, eps, epsAlt)) {
|
|
923
1015
|
return false;
|
|
924
1016
|
}
|
|
925
1017
|
|
|
@@ -928,8 +1020,8 @@ class UserPosition extends Coordinates {
|
|
|
928
1020
|
&& pos1.bearing === pos2.bearing;
|
|
929
1021
|
}
|
|
930
1022
|
|
|
931
|
-
|
|
932
|
-
return UserPosition.
|
|
1023
|
+
equals(other) {
|
|
1024
|
+
return UserPosition.equals(this, other);
|
|
933
1025
|
}
|
|
934
1026
|
|
|
935
1027
|
|
|
@@ -1044,14 +1136,14 @@ function trimRoute(route, startPosition = route[0], length = Number.MAX_VALUE) {
|
|
|
1044
1136
|
const p1 = route[currentPointIndex - 1];
|
|
1045
1137
|
const p2 = route[currentPointIndex];
|
|
1046
1138
|
|
|
1047
|
-
if (Coordinates.
|
|
1139
|
+
if (Coordinates.equals(startPosition, p1)) {
|
|
1048
1140
|
newRoute.push(p1);
|
|
1049
1141
|
previousPoint = p1;
|
|
1050
1142
|
break;
|
|
1051
1143
|
}
|
|
1052
1144
|
|
|
1053
1145
|
const proj = startPosition.getSegmentProjection(p1, p2);
|
|
1054
|
-
if (proj && Coordinates.
|
|
1146
|
+
if (proj && Coordinates.equals(startPosition, proj) && !proj.equals(p2)) {
|
|
1055
1147
|
newRoute.push(proj);
|
|
1056
1148
|
previousPoint = proj;
|
|
1057
1149
|
break;
|
|
@@ -1089,7 +1181,7 @@ function trimRoute(route, startPosition = route[0], length = Number.MAX_VALUE) {
|
|
|
1089
1181
|
*/
|
|
1090
1182
|
function simplifyRoute(coords, precisionAngle = deg2rad(5)) {
|
|
1091
1183
|
|
|
1092
|
-
const isClosed = (coords[0].
|
|
1184
|
+
const isClosed = (coords[0].equals(coords[coords.length - 1]));
|
|
1093
1185
|
|
|
1094
1186
|
let newRoute = coords.slice(0, coords.length - (isClosed ? 1 : 0));
|
|
1095
1187
|
|
|
@@ -1336,13 +1428,13 @@ class BoundingBox {
|
|
|
1336
1428
|
return this.northEast.lat;
|
|
1337
1429
|
}
|
|
1338
1430
|
|
|
1339
|
-
static
|
|
1340
|
-
return Coordinates.
|
|
1341
|
-
&& Coordinates.
|
|
1431
|
+
static equals(bb1, bb2) {
|
|
1432
|
+
return Coordinates.equals(bb1.northEast, bb2.northEast)
|
|
1433
|
+
&& Coordinates.equals(bb1.southWest, bb2.southWest);
|
|
1342
1434
|
}
|
|
1343
1435
|
|
|
1344
|
-
|
|
1345
|
-
return BoundingBox.
|
|
1436
|
+
equals(other) {
|
|
1437
|
+
return BoundingBox.equals(this, other);
|
|
1346
1438
|
}
|
|
1347
1439
|
|
|
1348
1440
|
/**
|
|
@@ -1478,7 +1570,7 @@ class RelativePosition {
|
|
|
1478
1570
|
* @param {RelativePosition} pos2 position 2
|
|
1479
1571
|
* @param {Number} eps x, y, z epsilon in meters (default: 1e-3 [= 1mm])
|
|
1480
1572
|
*/
|
|
1481
|
-
static
|
|
1573
|
+
static equals(pos1, pos2, eps = Constants.EPS_MM) {
|
|
1482
1574
|
|
|
1483
1575
|
// Handle null comparison
|
|
1484
1576
|
if (pos1 === null && pos1 === pos2) {
|
|
@@ -1497,8 +1589,8 @@ class RelativePosition {
|
|
|
1497
1589
|
&& pos1.bearing === pos2.bearing;
|
|
1498
1590
|
}
|
|
1499
1591
|
|
|
1500
|
-
|
|
1501
|
-
return RelativePosition.
|
|
1592
|
+
equals(other) {
|
|
1593
|
+
return RelativePosition.equals(this, other);
|
|
1502
1594
|
}
|
|
1503
1595
|
|
|
1504
1596
|
|
|
@@ -1715,7 +1807,7 @@ class Attitude {
|
|
|
1715
1807
|
* @param {Attitude} att1 attitude 1
|
|
1716
1808
|
* @param {Attitude} att2 attitude 2
|
|
1717
1809
|
*/
|
|
1718
|
-
static
|
|
1810
|
+
static equals(att1, att2) {
|
|
1719
1811
|
|
|
1720
1812
|
// Handle null comparison
|
|
1721
1813
|
if (att1 === null && att1 === att2) {
|
|
@@ -1731,15 +1823,15 @@ class Attitude {
|
|
|
1731
1823
|
return true;
|
|
1732
1824
|
}
|
|
1733
1825
|
|
|
1734
|
-
return Quaternion.
|
|
1826
|
+
return Quaternion.equals(att1.quaternion, att2.quaternion);
|
|
1735
1827
|
}
|
|
1736
1828
|
|
|
1737
1829
|
/**
|
|
1738
1830
|
* @param {Attitude} other
|
|
1739
1831
|
* @returns {boolean}
|
|
1740
1832
|
*/
|
|
1741
|
-
|
|
1742
|
-
return Attitude.
|
|
1833
|
+
equals(other) {
|
|
1834
|
+
return Attitude.equals(this, other);
|
|
1743
1835
|
}
|
|
1744
1836
|
|
|
1745
1837
|
/**
|
|
@@ -1864,7 +1956,7 @@ class AbsoluteHeading {
|
|
|
1864
1956
|
* @param {AbsoluteHeading} heading1 heading 1
|
|
1865
1957
|
* @param {AbsoluteHeading} heading2 heading 2
|
|
1866
1958
|
*/
|
|
1867
|
-
static
|
|
1959
|
+
static equals(heading1, heading2) {
|
|
1868
1960
|
|
|
1869
1961
|
// Handle null comparison
|
|
1870
1962
|
if (heading1 === null && heading1 === heading2) {
|
|
@@ -1878,8 +1970,8 @@ class AbsoluteHeading {
|
|
|
1878
1970
|
return Math.abs(heading1.heading - heading2.heading) < 1e-8;
|
|
1879
1971
|
}
|
|
1880
1972
|
|
|
1881
|
-
|
|
1882
|
-
return AbsoluteHeading.
|
|
1973
|
+
equals(other) {
|
|
1974
|
+
return AbsoluteHeading.equals(this, other);
|
|
1883
1975
|
}
|
|
1884
1976
|
|
|
1885
1977
|
toJson() {
|
|
@@ -1964,8 +2056,8 @@ class GraphNode {
|
|
|
1964
2056
|
* @param {GraphNode} other
|
|
1965
2057
|
* @returns {boolean}
|
|
1966
2058
|
*/
|
|
1967
|
-
|
|
1968
|
-
return this.coords.
|
|
2059
|
+
equals(other) {
|
|
2060
|
+
return this.coords.equals(other.coords)
|
|
1969
2061
|
&& this.builtFrom === other.builtFrom;
|
|
1970
2062
|
}
|
|
1971
2063
|
|
|
@@ -1996,16 +2088,16 @@ class GraphNode {
|
|
|
1996
2088
|
return new GraphNode(Coordinates.fromCompressedJson(json));
|
|
1997
2089
|
}
|
|
1998
2090
|
|
|
1999
|
-
|
|
2091
|
+
_generateLevelFromEdges() {
|
|
2000
2092
|
let tmpLevel = null;
|
|
2001
2093
|
for (let i = 0; i < this.edges.length; i++) {
|
|
2002
2094
|
const edge = this.edges[i];
|
|
2003
|
-
if (edge.level) {
|
|
2004
|
-
if (
|
|
2005
|
-
tmpLevel = edge.level
|
|
2095
|
+
if (edge.level !== null) {
|
|
2096
|
+
if (tmpLevel === null) {
|
|
2097
|
+
tmpLevel = Level.clone(edge.level);
|
|
2006
2098
|
} else {
|
|
2007
|
-
tmpLevel =
|
|
2008
|
-
if (
|
|
2099
|
+
tmpLevel = Level.intersection(tmpLevel, edge.level);
|
|
2100
|
+
if (tmpLevel === null) {
|
|
2009
2101
|
Logger.error('Error: Something bad happend during parsing: We cannot retrieve node level from adjacent ways: ' + this.coords);
|
|
2010
2102
|
return false;
|
|
2011
2103
|
}
|
|
@@ -2020,9 +2112,9 @@ class GraphNode {
|
|
|
2020
2112
|
/**
|
|
2021
2113
|
* We suppose generateLevelFromEdges() was called before
|
|
2022
2114
|
*/
|
|
2023
|
-
|
|
2115
|
+
_inferNodeLevelByRecursion() {
|
|
2024
2116
|
const { level } = this.coords;
|
|
2025
|
-
if (
|
|
2117
|
+
if (level === null || !Level.isRange(level)) {
|
|
2026
2118
|
return true;
|
|
2027
2119
|
}
|
|
2028
2120
|
|
|
@@ -2038,16 +2130,17 @@ class GraphNode {
|
|
|
2038
2130
|
* The result of this method is an union of all single level nodes found.
|
|
2039
2131
|
* @param {GraphNode} node node to explore
|
|
2040
2132
|
* @param {GraphNode[]} visitedNodes list of visited nodes
|
|
2133
|
+
* @returns {null|number|[number, number]}
|
|
2041
2134
|
*/
|
|
2042
2135
|
const lookForLevel = (node, visitedNodes) => {
|
|
2043
2136
|
|
|
2044
2137
|
visitedNodes.push(node);
|
|
2045
2138
|
|
|
2046
|
-
if (
|
|
2139
|
+
if (node.coords.level === null) {
|
|
2047
2140
|
return null;
|
|
2048
2141
|
}
|
|
2049
2142
|
|
|
2050
|
-
if (!node.coords.level
|
|
2143
|
+
if (!Level.isRange(node.coords.level)) {
|
|
2051
2144
|
return node.coords.level;
|
|
2052
2145
|
}
|
|
2053
2146
|
|
|
@@ -2065,8 +2158,8 @@ class GraphNode {
|
|
|
2065
2158
|
const othersLevels = lookForLevel(this, []);
|
|
2066
2159
|
|
|
2067
2160
|
if (othersLevels !== null) {
|
|
2068
|
-
if (!
|
|
2069
|
-
this.coords.level =
|
|
2161
|
+
if (!Level.isRange(othersLevels)) {
|
|
2162
|
+
this.coords.level = othersLevels === level[0] ? level[1] : level[0];
|
|
2070
2163
|
return true;
|
|
2071
2164
|
}
|
|
2072
2165
|
Logger.warn('Level of: ' + this.coords.toString() + ' cannot be decided');
|
|
@@ -2079,9 +2172,9 @@ class GraphNode {
|
|
|
2079
2172
|
/**
|
|
2080
2173
|
* We suppose generateLevelFromEdges() was called before
|
|
2081
2174
|
*/
|
|
2082
|
-
|
|
2175
|
+
_inferNodeLevelByNeighboors() {
|
|
2083
2176
|
const { level } = this.coords;
|
|
2084
|
-
if (
|
|
2177
|
+
if (level === null || !Level.isRange(level)) {
|
|
2085
2178
|
return true;
|
|
2086
2179
|
}
|
|
2087
2180
|
|
|
@@ -2092,8 +2185,8 @@ class GraphNode {
|
|
|
2092
2185
|
tmpLevel = Level.union(otherNode.coords.level, tmpLevel);
|
|
2093
2186
|
}
|
|
2094
2187
|
|
|
2095
|
-
if (tmpLevel === null || !
|
|
2096
|
-
this.coords.level =
|
|
2188
|
+
if (tmpLevel === null || !Level.isRange(tmpLevel)) {
|
|
2189
|
+
this.coords.level = tmpLevel === level[0] ? level[1] : level[0];
|
|
2097
2190
|
}
|
|
2098
2191
|
|
|
2099
2192
|
return true;
|
|
@@ -2103,7 +2196,7 @@ class GraphNode {
|
|
|
2103
2196
|
* Set node.io to true for nodes that make the link between
|
|
2104
2197
|
* indoor and outdoor edges
|
|
2105
2198
|
*/
|
|
2106
|
-
|
|
2199
|
+
_checkIO() {
|
|
2107
2200
|
this.io = this._coords.level !== null
|
|
2108
2201
|
&& this.edges.some(edge => edge.level === null);
|
|
2109
2202
|
return true;
|
|
@@ -2113,7 +2206,7 @@ class GraphNode {
|
|
|
2113
2206
|
* @param {GraphNode[]} nodes
|
|
2114
2207
|
*/
|
|
2115
2208
|
static generateNodesLevels(nodes) {
|
|
2116
|
-
const success = nodes.reduce((acc, node) => acc && node.
|
|
2209
|
+
const success = nodes.reduce((acc, node) => acc && node._generateLevelFromEdges(), true);
|
|
2117
2210
|
if (!success) {
|
|
2118
2211
|
return false;
|
|
2119
2212
|
}
|
|
@@ -2122,12 +2215,12 @@ class GraphNode {
|
|
|
2122
2215
|
// (e.g stairs without network at one of its bounds)
|
|
2123
2216
|
// To infer this node level, we use inferNodeLevelByRecursion()
|
|
2124
2217
|
const res = nodes.reduce((acc, node) => acc
|
|
2125
|
-
&& node.
|
|
2126
|
-
&& node.
|
|
2218
|
+
&& node._inferNodeLevelByNeighboors()
|
|
2219
|
+
&& node._inferNodeLevelByRecursion()
|
|
2127
2220
|
, true);
|
|
2128
2221
|
|
|
2129
2222
|
// Finally define nodes that are links between indoor and outdoor
|
|
2130
|
-
nodes.forEach(node => node.
|
|
2223
|
+
nodes.forEach(node => node._checkIO());
|
|
2131
2224
|
|
|
2132
2225
|
return res;
|
|
2133
2226
|
}
|
|
@@ -2148,7 +2241,7 @@ class GraphEdge {
|
|
|
2148
2241
|
/** @type {GraphNode<T>} */
|
|
2149
2242
|
_node2 = null;
|
|
2150
2243
|
|
|
2151
|
-
/** @type {
|
|
2244
|
+
/** @type {null|number|[number, number]} */
|
|
2152
2245
|
_level = null;
|
|
2153
2246
|
|
|
2154
2247
|
/** @type {?number} */
|
|
@@ -2169,7 +2262,7 @@ class GraphEdge {
|
|
|
2169
2262
|
/**
|
|
2170
2263
|
* @param {!GraphNode} node1
|
|
2171
2264
|
* @param {!GraphNode} node2
|
|
2172
|
-
* @param {?
|
|
2265
|
+
* @param {?(null|number|[number, number])} level
|
|
2173
2266
|
* @param {?T} builtFrom
|
|
2174
2267
|
* @param {?string} name
|
|
2175
2268
|
*/
|
|
@@ -2224,21 +2317,15 @@ class GraphEdge {
|
|
|
2224
2317
|
this._computedSizeAndBearing = false;
|
|
2225
2318
|
}
|
|
2226
2319
|
|
|
2227
|
-
/** @type {
|
|
2320
|
+
/** @type {null|number|[number, number]} */
|
|
2228
2321
|
get level() {
|
|
2229
2322
|
return this._level;
|
|
2230
2323
|
}
|
|
2231
2324
|
|
|
2232
|
-
/** @type {
|
|
2325
|
+
/** @type {null|number|[number, number]} */
|
|
2233
2326
|
set level(level) {
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
} else {
|
|
2237
|
-
if (typeof level !== 'undefined' && level !== null) {
|
|
2238
|
-
throw new Error('level argument is not a Level object');
|
|
2239
|
-
}
|
|
2240
|
-
this._level = null;
|
|
2241
|
-
}
|
|
2327
|
+
Level.checkType(level);
|
|
2328
|
+
this._level = level;
|
|
2242
2329
|
}
|
|
2243
2330
|
|
|
2244
2331
|
/**
|
|
@@ -2273,7 +2360,7 @@ class GraphEdge {
|
|
|
2273
2360
|
* @param {GraphEdge<T>} other
|
|
2274
2361
|
* @returns {boolean}
|
|
2275
2362
|
*/
|
|
2276
|
-
|
|
2363
|
+
equals(other) {
|
|
2277
2364
|
|
|
2278
2365
|
if (this === other) {
|
|
2279
2366
|
return true;
|
|
@@ -2283,9 +2370,9 @@ class GraphEdge {
|
|
|
2283
2370
|
return false;
|
|
2284
2371
|
}
|
|
2285
2372
|
|
|
2286
|
-
return other.node1.
|
|
2287
|
-
&& other.node2.
|
|
2288
|
-
&& Level.
|
|
2373
|
+
return other.node1.equals(this.node1)
|
|
2374
|
+
&& other.node2.equals(this.node2)
|
|
2375
|
+
&& Level.equals(other.level, this.level)
|
|
2289
2376
|
&& other.isOneway === this.isOneway
|
|
2290
2377
|
&& other.builtFrom === this.builtFrom;
|
|
2291
2378
|
}
|
|
@@ -2351,7 +2438,7 @@ class Network {
|
|
|
2351
2438
|
* @returns {?GraphNode<T>}
|
|
2352
2439
|
*/
|
|
2353
2440
|
getNodeByCoords(coords) {
|
|
2354
|
-
return this.nodes.find(node => node.coords.
|
|
2441
|
+
return this.nodes.find(node => node.coords.equals(coords));
|
|
2355
2442
|
}
|
|
2356
2443
|
|
|
2357
2444
|
/**
|
|
@@ -2427,7 +2514,7 @@ class Network {
|
|
|
2427
2514
|
this.nodes.indexOf(edge.node2)
|
|
2428
2515
|
];
|
|
2429
2516
|
if (edge.level !== null) {
|
|
2430
|
-
output.push(edge.level
|
|
2517
|
+
output.push(edge.level);
|
|
2431
2518
|
}
|
|
2432
2519
|
if (edge.isOneway) {
|
|
2433
2520
|
if (edge.level === null) {
|
|
@@ -2456,7 +2543,7 @@ class Network {
|
|
|
2456
2543
|
network.nodes[jsonEdge[1]]
|
|
2457
2544
|
);
|
|
2458
2545
|
if (jsonEdge.length > 2 && jsonEdge[2] !== null) {
|
|
2459
|
-
edge.level =
|
|
2546
|
+
edge.level = jsonEdge[2];
|
|
2460
2547
|
}
|
|
2461
2548
|
if (jsonEdge.length > 3 && jsonEdge[3]) {
|
|
2462
2549
|
edge.isOneway = true;
|
|
@@ -2478,7 +2565,7 @@ class Network {
|
|
|
2478
2565
|
const network = new Network();
|
|
2479
2566
|
|
|
2480
2567
|
const getOrCreateNode = coords =>
|
|
2481
|
-
network.nodes.find(_coords => _coords.
|
|
2568
|
+
network.nodes.find(_coords => _coords.equals(coords)) || new GraphNode(coords);
|
|
2482
2569
|
|
|
2483
2570
|
|
|
2484
2571
|
const createEdgeFromNodes = (node1, node2) =>
|
|
@@ -2512,7 +2599,7 @@ class Network {
|
|
|
2512
2599
|
getEdgesAtLevel(targetLevel, useMultiLevelEdges = true) {
|
|
2513
2600
|
return this.edges.filter(
|
|
2514
2601
|
({ level }) => useMultiLevelEdges
|
|
2515
|
-
? Level.intersect(targetLevel, level)
|
|
2602
|
+
? Level.intersect(targetLevel, level)
|
|
2516
2603
|
: Level.contains(targetLevel, level)
|
|
2517
2604
|
);
|
|
2518
2605
|
}
|
|
@@ -2607,37 +2694,29 @@ class MapMatching {
|
|
|
2607
2694
|
*/
|
|
2608
2695
|
_shouldProjectOnEdgeAndNodes(edge, location, useBearing, useMultiLevelSegments, acceptEdgeFn) {
|
|
2609
2696
|
|
|
2697
|
+
// ignore projections if edge is not accepted
|
|
2610
2698
|
if (!acceptEdgeFn(edge)) {
|
|
2611
|
-
// if edge selection is not verified
|
|
2612
2699
|
return [false, false, false];
|
|
2613
2700
|
}
|
|
2614
2701
|
|
|
2615
|
-
|
|
2616
|
-
let
|
|
2617
|
-
let
|
|
2702
|
+
// First, check if levels intersects
|
|
2703
|
+
let checkEdge = Level.intersect(location.level, edge.level);
|
|
2704
|
+
let checkNode1 = Level.intersect(location.level, edge.node1.coords.level);
|
|
2705
|
+
let checkNode2 = Level.intersect(location.level, edge.node2.coords.level);
|
|
2618
2706
|
|
|
2619
|
-
if
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
&& (
|
|
2623
|
-
// if edge level intersect location level
|
|
2624
|
-
!Level.intersect(location.level, edge.level)
|
|
2625
|
-
// ignore MultiLevelSegments if option used
|
|
2626
|
-
|| (!useMultiLevelSegments && edge.level && edge.level.isRange)
|
|
2627
|
-
)) {
|
|
2628
|
-
checkEdge = false;
|
|
2629
|
-
}
|
|
2707
|
+
// Second, in case of IO nodes, accept matching if location's level is null
|
|
2708
|
+
checkNode1 ||= edge.node1.io && location.level === null;
|
|
2709
|
+
checkNode2 ||= edge.node2.io && location.level === null;
|
|
2630
2710
|
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
checkNode2 = false;
|
|
2711
|
+
// Third, check if level is a range if useMultiLevelSegments is false
|
|
2712
|
+
if (!useMultiLevelSegments) {
|
|
2713
|
+
checkEdge &&= !Level.isRange(edge.level);
|
|
2714
|
+
checkNode1 &&= !Level.isRange(edge.node1.coords.level);
|
|
2715
|
+
checkNode2 &&= !Level.isRange(edge.node2.coords.level);
|
|
2637
2716
|
}
|
|
2638
2717
|
|
|
2718
|
+
// Finally, check edges bearing if option is used
|
|
2639
2719
|
if (useBearing) {
|
|
2640
|
-
// if mapmatching bearing is enabled do not use nodes matching
|
|
2641
2720
|
if (checkEdge) {
|
|
2642
2721
|
// condition for optimisation
|
|
2643
2722
|
const diffAngle = diffAngleLines(edge.bearing, location.bearing);
|
|
@@ -2646,6 +2725,7 @@ class MapMatching {
|
|
|
2646
2725
|
checkEdge = false;
|
|
2647
2726
|
}
|
|
2648
2727
|
}
|
|
2728
|
+
// if mapmatching bearing is enabled do not use nodes matching
|
|
2649
2729
|
checkNode1 = false;
|
|
2650
2730
|
checkNode2 = false;
|
|
2651
2731
|
}
|
|
@@ -2661,7 +2741,22 @@ class MapMatching {
|
|
|
2661
2741
|
static _assignLatLngLevel(fromCoordinates, toCoordinates) {
|
|
2662
2742
|
toCoordinates.lat = fromCoordinates.lat;
|
|
2663
2743
|
toCoordinates.lng = fromCoordinates.lng;
|
|
2664
|
-
toCoordinates.level = fromCoordinates.level;
|
|
2744
|
+
toCoordinates.level = Level.clone(fromCoordinates.level);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/**
|
|
2748
|
+
* IO Nodes are typical because they have a non-null level but projection car works on them.
|
|
2749
|
+
* This function handles the case where the projection is on an IO node and a location with
|
|
2750
|
+
* a null level is required.
|
|
2751
|
+
*
|
|
2752
|
+
* @param {Coordinates} projection
|
|
2753
|
+
* @param {Coordinates} location
|
|
2754
|
+
* @param {GraphNode} projectionNode
|
|
2755
|
+
*/
|
|
2756
|
+
static _handleLevelsWithIONodes(projection, location, projectionNode) {
|
|
2757
|
+
if (location.level === null && projectionNode.io) {
|
|
2758
|
+
projection.level = null;
|
|
2759
|
+
}
|
|
2665
2760
|
}
|
|
2666
2761
|
|
|
2667
2762
|
/**
|
|
@@ -2669,18 +2764,20 @@ class MapMatching {
|
|
|
2669
2764
|
* @param {Coordinates} _projection
|
|
2670
2765
|
*/
|
|
2671
2766
|
static _updateProjectionLevelFromEdge = (_edge, _projection) => {
|
|
2672
|
-
|
|
2673
|
-
_projection.level = _edge.level.clone();
|
|
2674
|
-
}
|
|
2767
|
+
_projection.level = Level.clone(_edge.level);
|
|
2675
2768
|
};
|
|
2676
2769
|
|
|
2677
2770
|
/**
|
|
2771
|
+
* Main function for map-matching, the networks have to be set before calling this function
|
|
2772
|
+
* The function will returns a GraphProjection object given a coordinates object and a set
|
|
2773
|
+
* of options (useDistance, useBearing, useMultiLevelSegments, acceptEdgeFn).
|
|
2774
|
+
*
|
|
2678
2775
|
* @param {!Coordinates} location
|
|
2679
2776
|
* @param {boolean} useDistance
|
|
2680
2777
|
* @param {boolean} useBearing
|
|
2681
2778
|
* @param {boolean} useMultiLevelSegments
|
|
2682
2779
|
* @param {function} acceptEdgeFn
|
|
2683
|
-
* @returns {GraphProjection}
|
|
2780
|
+
* @returns {?GraphProjection}
|
|
2684
2781
|
*/
|
|
2685
2782
|
getProjection(location, useDistance = false, useBearing = false,
|
|
2686
2783
|
useMultiLevelSegments = true, acceptEdgeFn = () => true) {
|
|
@@ -2694,22 +2791,29 @@ class MapMatching {
|
|
|
2694
2791
|
}
|
|
2695
2792
|
|
|
2696
2793
|
if (useBearing && (!location.bearing || !this._maxAngleBearing)) {
|
|
2794
|
+
// If useBearing is true and bearing is not set in coordinates, return null
|
|
2697
2795
|
return null;
|
|
2698
2796
|
}
|
|
2699
2797
|
|
|
2798
|
+
// Build a new GraphProjection object from parameters
|
|
2700
2799
|
const projection = new GraphProjection();
|
|
2701
2800
|
projection.origin = location;
|
|
2702
2801
|
projection.distanceFromNearestElement = Number.MAX_VALUE;
|
|
2703
2802
|
projection.projection = location.clone();
|
|
2704
2803
|
|
|
2804
|
+
// Define a function to know if a projection is better than the current one
|
|
2705
2805
|
const isProjectionBetter = (distanceOfNewProjection) => {
|
|
2706
2806
|
return distanceOfNewProjection < projection.distanceFromNearestElement
|
|
2707
2807
|
&& (!useDistance || distanceOfNewProjection <= this._maxDistance);
|
|
2708
2808
|
};
|
|
2709
2809
|
|
|
2710
|
-
|
|
2711
|
-
|
|
2810
|
+
// Loop on all the network edges
|
|
2811
|
+
// Each time a better projection is found (see isProjectionBetter()),
|
|
2812
|
+
// the current projection is replaced
|
|
2813
|
+
for (const edge of this.network.edges) {
|
|
2712
2814
|
|
|
2815
|
+
// Check if the specified edge and its nodes can be used for projection. See the
|
|
2816
|
+
// documentation of the corresponding function for more information.
|
|
2713
2817
|
const [checkEdge, checkNode1, checkNode2] = this._shouldProjectOnEdgeAndNodes(
|
|
2714
2818
|
edge, location, useBearing, useMultiLevelSegments, acceptEdgeFn);
|
|
2715
2819
|
|
|
@@ -2720,10 +2824,9 @@ class MapMatching {
|
|
|
2720
2824
|
projection.distanceFromNearestElement = distNode1;
|
|
2721
2825
|
projection.nearestElement = edge.node1;
|
|
2722
2826
|
MapMatching._assignLatLngLevel(edge.node1.coords, projection.projection);
|
|
2723
|
-
MapMatching.
|
|
2827
|
+
MapMatching._handleLevelsWithIONodes(projection.projection, location, edge.node1);
|
|
2724
2828
|
|
|
2725
|
-
if (distNode1 <= Constants.EPS_MM
|
|
2726
|
-
&& location.level === edge.node1.coords.level) {
|
|
2829
|
+
if (distNode1 <= Constants.EPS_MM) {
|
|
2727
2830
|
break;
|
|
2728
2831
|
}
|
|
2729
2832
|
}
|
|
@@ -2733,14 +2836,12 @@ class MapMatching {
|
|
|
2733
2836
|
|
|
2734
2837
|
const distNode2 = location.distanceTo(edge.node2.coords);
|
|
2735
2838
|
if (isProjectionBetter(distNode2) || distNode2 <= Constants.EPS_MM) {
|
|
2736
|
-
|
|
2737
2839
|
projection.distanceFromNearestElement = distNode2;
|
|
2738
2840
|
projection.nearestElement = edge.node2;
|
|
2739
2841
|
MapMatching._assignLatLngLevel(edge.node2.coords, projection.projection);
|
|
2740
|
-
MapMatching.
|
|
2842
|
+
MapMatching._handleLevelsWithIONodes(projection.projection, location, edge.node2);
|
|
2741
2843
|
|
|
2742
|
-
if (distNode2 <= Constants.EPS_MM
|
|
2743
|
-
&& location.level === edge.node2.coords.level) {
|
|
2844
|
+
if (distNode2 <= Constants.EPS_MM) {
|
|
2744
2845
|
break;
|
|
2745
2846
|
}
|
|
2746
2847
|
}
|
|
@@ -2903,11 +3004,11 @@ class GraphRouterOptions {
|
|
|
2903
3004
|
*/
|
|
2904
3005
|
class GraphRouter {
|
|
2905
3006
|
|
|
2906
|
-
/** @type {!Network<T>} */
|
|
3007
|
+
/** @type {!(Network<T>)} */
|
|
2907
3008
|
_network;
|
|
2908
3009
|
|
|
2909
3010
|
/**
|
|
2910
|
-
* @param {!Network<T>} network
|
|
3011
|
+
* @param {!(Network<T>)} network
|
|
2911
3012
|
*/
|
|
2912
3013
|
constructor(network) {
|
|
2913
3014
|
this._network = network;
|
|
@@ -2954,9 +3055,9 @@ class GraphRouter {
|
|
|
2954
3055
|
if (!proj) {
|
|
2955
3056
|
let message = `Point ${point.toString()} is too far from the network `
|
|
2956
3057
|
+ `> ${this._mapMatching.maxDistance.toFixed(0)} meters.`;
|
|
2957
|
-
if (point.level) {
|
|
3058
|
+
if (point.level !== null) {
|
|
2958
3059
|
message += ' If it is a multi-level map, please verify if you have'
|
|
2959
|
-
+ ` a network at level ${point.level
|
|
3060
|
+
+ ` a network at level ${Level.toString(point.level)}.`;
|
|
2960
3061
|
}
|
|
2961
3062
|
throw new NoRouteFoundError(start, end, message);
|
|
2962
3063
|
}
|