terra-draw-route-snap-mode 0.1.3 → 0.2.0
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/package.json +2 -2
- package/src/routing.spec.ts +384 -63
- package/src/routing.ts +26 -3
- package/src/terra-draw-route-snap.mode.spec.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terra-draw-route-snap-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A mode for Terra Draw to provide snapping to a route network",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"docs": "typedoc",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"knip": "5.30.2",
|
|
61
61
|
"microbundle": "0.15.0",
|
|
62
62
|
"serve": "^14.2.4",
|
|
63
|
-
"terra-route": "^0.0.
|
|
63
|
+
"terra-route": "^0.0.14",
|
|
64
64
|
"ts-jest": "^29.3.1",
|
|
65
65
|
"ts-loader": "9.5.1",
|
|
66
66
|
"tsx": "^4.19.3",
|
package/src/routing.spec.ts
CHANGED
|
@@ -35,102 +35,423 @@ describe("Routing", () => {
|
|
|
35
35
|
const mockRouteFinder = {
|
|
36
36
|
getRoute: jest.fn().mockReturnValue(mockRoute),
|
|
37
37
|
setNetwork: jest.fn(),
|
|
38
|
+
expandNetwork: jest.fn(),
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe("constructor", () => {
|
|
46
|
+
it("should not be affected if the original network is mutated after construction", () => {
|
|
47
|
+
const inputNetwork: FeatureCollection<LineString> = {
|
|
43
48
|
type: "FeatureCollection",
|
|
44
49
|
features: [
|
|
50
|
+
{
|
|
51
|
+
type: "Feature",
|
|
52
|
+
geometry: {
|
|
53
|
+
type: "LineString",
|
|
54
|
+
coordinates: [
|
|
55
|
+
[0, 0],
|
|
56
|
+
[1, 1],
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
properties: {},
|
|
60
|
+
},
|
|
45
61
|
],
|
|
46
|
-
}
|
|
47
|
-
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const routing = new Routing({ network: inputNetwork, routeFinder: mockRouteFinder });
|
|
48
65
|
|
|
49
|
-
|
|
66
|
+
// Mutate the original object after construction
|
|
67
|
+
inputNetwork.features.pop();
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
// Routing should still behave as if the original feature exists
|
|
70
|
+
expect(routing.getClosestNetworkCoordinate([0, 0])).toEqual([0, 0]);
|
|
71
|
+
expect(routing.getClosestNetworkCoordinate([1, 1])).toEqual([1, 1]);
|
|
72
|
+
|
|
73
|
+
expect(routing.getRoute([0, 0], [1, 1])).toEqual({
|
|
74
|
+
type: "Feature",
|
|
75
|
+
geometry: {
|
|
76
|
+
type: "LineString",
|
|
77
|
+
coordinates: [
|
|
78
|
+
[0, 0],
|
|
79
|
+
[1, 1],
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
properties: {},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
52
85
|
});
|
|
53
86
|
|
|
54
|
-
|
|
55
|
-
|
|
87
|
+
describe("getClosestNetworkCoordinate", () => {
|
|
88
|
+
it("should return null for empty network", () => {
|
|
89
|
+
const routing = new Routing({
|
|
90
|
+
network: {
|
|
91
|
+
type: "FeatureCollection",
|
|
92
|
+
features: [
|
|
93
|
+
],
|
|
94
|
+
}, routeFinder: mockRouteFinder
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const closest = routing.getClosestNetworkCoordinate([0, 0]);
|
|
98
|
+
|
|
99
|
+
expect(closest).toEqual(null);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should find the closest network coordinate with exact match", () => {
|
|
103
|
+
const routing = new Routing({ network, routeFinder: mockRouteFinder });
|
|
104
|
+
|
|
105
|
+
const closest = routing.getClosestNetworkCoordinate([0, 0]);
|
|
106
|
+
|
|
107
|
+
expect(closest).toEqual([0, 0]);
|
|
108
|
+
});
|
|
56
109
|
|
|
57
|
-
|
|
110
|
+
it("should find the closest network coordinate if not exact match", () => {
|
|
111
|
+
const routing = new Routing({ network, routeFinder: mockRouteFinder });
|
|
112
|
+
|
|
113
|
+
const closest = routing.getClosestNetworkCoordinate([0.1, 0.1]);
|
|
114
|
+
|
|
115
|
+
expect(closest).toEqual([0, 0]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should still return a closest coordinate when a network repeats points", () => {
|
|
119
|
+
const networkWithDuplicates: FeatureCollection<LineString> = {
|
|
120
|
+
type: "FeatureCollection",
|
|
121
|
+
features: [
|
|
122
|
+
{
|
|
123
|
+
type: "Feature",
|
|
124
|
+
geometry: {
|
|
125
|
+
type: "LineString",
|
|
126
|
+
coordinates: [
|
|
127
|
+
[0, 0],
|
|
128
|
+
[1, 1],
|
|
129
|
+
[1, 1],
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
properties: {},
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
58
136
|
|
|
59
|
-
|
|
137
|
+
const routing = new Routing({ network: networkWithDuplicates, routeFinder: mockRouteFinder });
|
|
138
|
+
expect(routing.getClosestNetworkCoordinate([1, 1])).toEqual([1, 1]);
|
|
139
|
+
});
|
|
60
140
|
});
|
|
61
141
|
|
|
62
|
-
|
|
63
|
-
|
|
142
|
+
describe("getRoute", () => {
|
|
143
|
+
it("should return a route and cache it if enabled", () => {
|
|
144
|
+
const routing = new Routing({ network, routeFinder: mockRouteFinder, useCache: true });
|
|
145
|
+
|
|
146
|
+
const route = routing.getRoute([0, 0], [1, 1]);
|
|
147
|
+
|
|
148
|
+
expect(route).toEqual(mockRoute);
|
|
149
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(1);
|
|
150
|
+
|
|
151
|
+
// Call again to hit cache
|
|
152
|
+
const cachedRoute = routing.getRoute([0, 0], [1, 1]);
|
|
153
|
+
|
|
154
|
+
expect(cachedRoute).toEqual(mockRoute);
|
|
155
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(1); // Still 1 because cache used
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should call the route finder again when the previous result was null", () => {
|
|
159
|
+
const nullRouteFinder = {
|
|
160
|
+
getRoute: jest.fn().mockReturnValue(null),
|
|
161
|
+
setNetwork: jest.fn(),
|
|
162
|
+
expandNetwork: jest.fn(),
|
|
163
|
+
};
|
|
64
164
|
|
|
65
|
-
|
|
165
|
+
const routing = new Routing({ network, routeFinder: nullRouteFinder, useCache: true });
|
|
66
166
|
|
|
67
|
-
|
|
167
|
+
expect(routing.getRoute([0, 0], [1, 1])).toBeNull();
|
|
168
|
+
expect(routing.getRoute([0, 0], [1, 1])).toBeNull();
|
|
169
|
+
expect(nullRouteFinder.getRoute).toHaveBeenCalledTimes(2);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should return a new route if cache is disabled", () => {
|
|
173
|
+
const routing = new Routing({ network, routeFinder: mockRouteFinder, useCache: false });
|
|
174
|
+
|
|
175
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
176
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
177
|
+
|
|
178
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(2);
|
|
179
|
+
});
|
|
68
180
|
});
|
|
69
181
|
|
|
70
|
-
|
|
71
|
-
|
|
182
|
+
describe("setNetwork", () => {
|
|
183
|
+
it("should clear the route cache when the network is replaced", () => {
|
|
184
|
+
const routing = new Routing({ network, routeFinder: mockRouteFinder, useCache: true });
|
|
185
|
+
|
|
186
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
187
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(1);
|
|
188
|
+
|
|
189
|
+
// Warm cache
|
|
190
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
191
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(1);
|
|
72
192
|
|
|
73
|
-
|
|
193
|
+
const newNetwork: FeatureCollection<LineString> = {
|
|
194
|
+
type: "FeatureCollection",
|
|
195
|
+
features: [
|
|
196
|
+
{
|
|
197
|
+
type: "Feature",
|
|
198
|
+
geometry: {
|
|
199
|
+
type: "LineString",
|
|
200
|
+
coordinates: [
|
|
201
|
+
[10, 10],
|
|
202
|
+
[11, 11],
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
properties: {},
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
routing.setNetwork(newNetwork);
|
|
211
|
+
|
|
212
|
+
// Cache should be cleared, so it calls through again.
|
|
213
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
214
|
+
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(2);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should not be affected if the original network is mutated after setNetwork", () => {
|
|
218
|
+
const terraRoute = new TerraRoute();
|
|
219
|
+
terraRoute.buildRouteGraph(network);
|
|
220
|
+
|
|
221
|
+
const routing = new Routing({
|
|
222
|
+
network,
|
|
223
|
+
routeFinder: {
|
|
224
|
+
getRoute: terraRoute.getRoute.bind(terraRoute),
|
|
225
|
+
setNetwork: terraRoute.buildRouteGraph.bind(terraRoute),
|
|
226
|
+
expandNetwork: terraRoute.expandRouteGraph.bind(terraRoute),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const replacementNetwork: FeatureCollection<LineString> = {
|
|
231
|
+
type: "FeatureCollection",
|
|
232
|
+
features: [
|
|
233
|
+
{
|
|
234
|
+
type: "Feature",
|
|
235
|
+
geometry: {
|
|
236
|
+
type: "LineString",
|
|
237
|
+
coordinates: [
|
|
238
|
+
[10, 10],
|
|
239
|
+
[11, 11],
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
properties: {},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
74
246
|
|
|
75
|
-
|
|
76
|
-
expect(mockRouteFinder.getRoute).toHaveBeenCalledTimes(1);
|
|
247
|
+
routing.setNetwork(replacementNetwork);
|
|
77
248
|
|
|
78
|
-
|
|
79
|
-
|
|
249
|
+
// Mutate the original object after setNetwork
|
|
250
|
+
replacementNetwork.features.pop();
|
|
80
251
|
|
|
81
|
-
|
|
82
|
-
|
|
252
|
+
// Routing should still behave as if the replacement feature exists
|
|
253
|
+
expect(routing.getClosestNetworkCoordinate([10, 10])).toEqual([10, 10]);
|
|
254
|
+
expect(routing.getClosestNetworkCoordinate([11, 11])).toEqual([11, 11]);
|
|
255
|
+
|
|
256
|
+
expect(routing.getRoute([10, 10], [11, 11])).toEqual({
|
|
257
|
+
type: "Feature",
|
|
258
|
+
geometry: {
|
|
259
|
+
type: "LineString",
|
|
260
|
+
coordinates: [
|
|
261
|
+
[10, 10],
|
|
262
|
+
[11, 11],
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
properties: {},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
});
|
|
83
269
|
});
|
|
84
270
|
|
|
85
|
-
|
|
86
|
-
|
|
271
|
+
describe("expandRouteNetwork", () => {
|
|
272
|
+
it("should clear the route cache when the network is expanded", () => {
|
|
273
|
+
const routing = new Routing({
|
|
274
|
+
network,
|
|
275
|
+
routeFinder: mockRouteFinder,
|
|
276
|
+
useCache: true,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const additionalNetwork: FeatureCollection<LineString> = {
|
|
280
|
+
type: "FeatureCollection",
|
|
281
|
+
features: [
|
|
282
|
+
{
|
|
283
|
+
type: "Feature",
|
|
284
|
+
geometry: {
|
|
285
|
+
type: "LineString",
|
|
286
|
+
coordinates: [
|
|
287
|
+
[2, 2],
|
|
288
|
+
[3, 3],
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
properties: {},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const getRouteSpy = jest.spyOn(
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
298
|
+
(routing as any).routeFinder,
|
|
299
|
+
"getRoute"
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
303
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
304
|
+
expect(getRouteSpy).toHaveBeenCalledTimes(1);
|
|
305
|
+
|
|
306
|
+
routing.expandRouteNetwork(additionalNetwork);
|
|
307
|
+
|
|
308
|
+
// Cache should be cleared after expansion.
|
|
309
|
+
routing.getRoute([0, 0], [1, 1]);
|
|
310
|
+
expect(getRouteSpy).toHaveBeenCalledTimes(2);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should expand the route network and re-index points", () => {
|
|
314
|
+
const routing = new Routing({
|
|
315
|
+
network,
|
|
316
|
+
routeFinder: mockRouteFinder,
|
|
317
|
+
useCache: true,
|
|
318
|
+
});
|
|
87
319
|
|
|
88
|
-
|
|
89
|
-
routing.getRoute([0, 0], [1, 1]);
|
|
320
|
+
expect(routing.getClosestNetworkCoordinate([2, 2])).toEqual([1, 1]);
|
|
90
321
|
|
|
91
|
-
|
|
322
|
+
const additionalNetwork: FeatureCollection<LineString> = {
|
|
323
|
+
type: "FeatureCollection",
|
|
324
|
+
features: [
|
|
325
|
+
{
|
|
326
|
+
type: "Feature",
|
|
327
|
+
geometry: {
|
|
328
|
+
type: "LineString",
|
|
329
|
+
coordinates: [
|
|
330
|
+
[2, 2],
|
|
331
|
+
[3, 3],
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
properties: {},
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const expandSpy = jest.spyOn(
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
341
|
+
(routing as any).routeFinder,
|
|
342
|
+
"expandNetwork"
|
|
343
|
+
);
|
|
344
|
+
routing.expandRouteNetwork(additionalNetwork);
|
|
345
|
+
|
|
346
|
+
expect(expandSpy).toHaveBeenCalledWith(additionalNetwork);
|
|
347
|
+
|
|
348
|
+
expect(routing.getClosestNetworkCoordinate([1, 1])).toEqual([1, 1]);
|
|
349
|
+
expect(routing.getClosestNetworkCoordinate([2, 2])).toEqual([2, 2]);
|
|
350
|
+
expect(routing.getClosestNetworkCoordinate([3, 3])).toEqual([3, 3]);
|
|
351
|
+
|
|
352
|
+
});
|
|
92
353
|
});
|
|
93
354
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
355
|
+
describe("integration (TerraRoute)", () => {
|
|
356
|
+
it('should update the network correctly with real a route finder', () => {
|
|
357
|
+
const terraRoute = new TerraRoute();
|
|
358
|
+
terraRoute.buildRouteGraph(network);
|
|
97
359
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
360
|
+
const routing = new Routing({
|
|
361
|
+
network, routeFinder: {
|
|
362
|
+
getRoute: terraRoute.getRoute.bind(terraRoute),
|
|
363
|
+
setNetwork: terraRoute.buildRouteGraph.bind(terraRoute),
|
|
364
|
+
expandNetwork: terraRoute.expandRouteGraph.bind(terraRoute)
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
jest.spyOn(routing, "setNetwork");
|
|
369
|
+
|
|
370
|
+
const existingRoute = routing.getRoute([0, 0], [1, 1]);
|
|
371
|
+
expect(existingRoute?.geometry.coordinates).toEqual([[0, 0], [1, 1]]);
|
|
372
|
+
expect(routing.getRoute([2, 2], [3, 3])).toBeNull();
|
|
373
|
+
|
|
374
|
+
const newNetwork: FeatureCollection<LineString> = {
|
|
375
|
+
type: "FeatureCollection",
|
|
376
|
+
features: [
|
|
377
|
+
{
|
|
378
|
+
type: "Feature",
|
|
379
|
+
geometry: {
|
|
380
|
+
type: "LineString",
|
|
381
|
+
coordinates: [
|
|
382
|
+
[2, 2],
|
|
383
|
+
[3, 3],
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
properties: {},
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
routing.setNetwork(newNetwork);
|
|
392
|
+
|
|
393
|
+
expect(routing.setNetwork).toHaveBeenCalledWith(newNetwork);
|
|
394
|
+
|
|
395
|
+
const newRoute = routing.getRoute([2, 2], [3, 3]);
|
|
396
|
+
expect(newRoute?.geometry.coordinates).toEqual([[2, 2], [3, 3]]);
|
|
397
|
+
expect(routing.getRoute([0, 0], [1, 1])).toBeNull();
|
|
103
398
|
});
|
|
104
399
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
400
|
+
it('should be able to route after expanding the network', () => {
|
|
401
|
+
const terraRoute = new TerraRoute();
|
|
402
|
+
terraRoute.buildRouteGraph(network);
|
|
403
|
+
|
|
404
|
+
const routing = new Routing({
|
|
405
|
+
network, routeFinder: {
|
|
406
|
+
getRoute: terraRoute.getRoute.bind(terraRoute),
|
|
407
|
+
setNetwork: terraRoute.buildRouteGraph.bind(terraRoute),
|
|
408
|
+
expandNetwork: terraRoute.expandRouteGraph.bind(terraRoute)
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
jest.spyOn(routing, "expandRouteNetwork");
|
|
413
|
+
|
|
414
|
+
const existingRoute = routing.getRoute([0, 0], [1, 1]);
|
|
415
|
+
expect(existingRoute?.geometry.coordinates).toEqual([[0, 0], [1, 1]]);
|
|
416
|
+
expect(routing.getRoute([2, 2], [3, 3])).toBeNull();
|
|
417
|
+
|
|
418
|
+
const newNetwork: FeatureCollection<LineString> = {
|
|
419
|
+
type: "FeatureCollection",
|
|
420
|
+
features: [
|
|
421
|
+
{
|
|
422
|
+
type: "Feature",
|
|
423
|
+
geometry: {
|
|
424
|
+
type: "LineString",
|
|
425
|
+
coordinates: [
|
|
426
|
+
[2, 2],
|
|
427
|
+
[3, 3],
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
properties: {},
|
|
122
431
|
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
432
|
+
],
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
routing.expandRouteNetwork(newNetwork);
|
|
127
436
|
|
|
128
|
-
|
|
437
|
+
expect(routing.expandRouteNetwork).toHaveBeenCalledWith(newNetwork);
|
|
129
438
|
|
|
130
|
-
|
|
439
|
+
const newRoute = routing.getRoute([2, 2], [3, 3]);
|
|
440
|
+
expect(newRoute?.geometry.coordinates).toEqual([[2, 2], [3, 3]]);
|
|
441
|
+
expect(routing.getRoute([0, 0], [1, 1])).toEqual({
|
|
442
|
+
type: "Feature",
|
|
443
|
+
geometry: {
|
|
444
|
+
type: "LineString",
|
|
445
|
+
coordinates: [
|
|
446
|
+
[0, 0],
|
|
447
|
+
[1, 1],
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
properties: {},
|
|
451
|
+
});
|
|
131
452
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
453
|
+
// 1, 1 is not connected to 2, 2
|
|
454
|
+
expect(routing.getRoute([0, 0], [3, 3])).toBeNull();
|
|
455
|
+
});
|
|
135
456
|
});
|
|
136
457
|
});
|
package/src/routing.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
export type RouteFinder = {
|
|
12
12
|
getRoute: (positionA: Feature<Point>, positionB: Feature<Point>) => Feature<LineString> | null
|
|
13
13
|
setNetwork: (network: FeatureCollection<LineString>) => void
|
|
14
|
+
expandNetwork: (additionalNetwork: FeatureCollection<LineString>) => void
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface RoutingInterface {
|
|
@@ -33,8 +34,8 @@ export class Routing implements RoutingInterface {
|
|
|
33
34
|
network: FeatureCollection<LineString>, useCache?: boolean,
|
|
34
35
|
routeFinder: RouteFinder
|
|
35
36
|
}) {
|
|
36
|
-
this.useCache = options.useCache
|
|
37
|
-
this.network = options.network;
|
|
37
|
+
this.useCache = options.useCache !== undefined ? options.useCache : true;
|
|
38
|
+
this.network = this.clone(options.network);
|
|
38
39
|
this.routeFinder = options.routeFinder;
|
|
39
40
|
|
|
40
41
|
this.initialise();
|
|
@@ -96,7 +97,7 @@ export class Routing implements RoutingInterface {
|
|
|
96
97
|
* @param network The network to use
|
|
97
98
|
*/
|
|
98
99
|
public setNetwork(network: FeatureCollection<LineString>) {
|
|
99
|
-
this.network = network;
|
|
100
|
+
this.network = this.clone(network);
|
|
100
101
|
|
|
101
102
|
// Ensure the network is updated correctly for the router finder
|
|
102
103
|
this.routeFinder.setNetwork(network);
|
|
@@ -105,6 +106,24 @@ export class Routing implements RoutingInterface {
|
|
|
105
106
|
this.initialise();
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
public expandRouteNetwork(additionalNetwork: FeatureCollection<LineString>) {
|
|
110
|
+
const clonedNetwork = this.clone(additionalNetwork);
|
|
111
|
+
|
|
112
|
+
// Ensure the network is updated correctly for the router finder
|
|
113
|
+
this.routeFinder.expandNetwork(clonedNetwork);
|
|
114
|
+
|
|
115
|
+
const mergedNetwork = {
|
|
116
|
+
type: "FeatureCollection",
|
|
117
|
+
features: [...clonedNetwork.features, ...this.network.features]
|
|
118
|
+
} as FeatureCollection<LineString>;
|
|
119
|
+
|
|
120
|
+
this.network = mergedNetwork;
|
|
121
|
+
|
|
122
|
+
// Re-initialize all internal data structures for this class
|
|
123
|
+
// TODO: Is there a way to avoid re-initialising here?
|
|
124
|
+
this.initialise();
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
/**
|
|
109
128
|
* Get the route between two coordinates returned as a GeoJSON LineString
|
|
110
129
|
* @param startCoord start coordinate
|
|
@@ -152,4 +171,8 @@ export class Routing implements RoutingInterface {
|
|
|
152
171
|
return route;
|
|
153
172
|
|
|
154
173
|
}
|
|
174
|
+
|
|
175
|
+
private clone(network: FeatureCollection<LineString>) {
|
|
176
|
+
return JSON.parse(JSON.stringify(network)) as FeatureCollection<LineString>;
|
|
177
|
+
}
|
|
155
178
|
}
|