@geospatial-sdk/core 0.0.5-alpha.1

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.
Files changed (47) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +11 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/model/index.d.ts +3 -0
  7. package/dist/model/index.d.ts.map +1 -0
  8. package/dist/model/index.js +2 -0
  9. package/dist/model/map-context-diff.d.ts +35 -0
  10. package/dist/model/map-context-diff.d.ts.map +1 -0
  11. package/dist/model/map-context-diff.js +1 -0
  12. package/dist/model/map-context.d.ts +78 -0
  13. package/dist/model/map-context.d.ts.map +1 -0
  14. package/dist/model/map-context.js +1 -0
  15. package/dist/utils/freeze.d.ts +2 -0
  16. package/dist/utils/freeze.d.ts.map +1 -0
  17. package/dist/utils/freeze.js +15 -0
  18. package/dist/utils/freeze.test.d.ts +2 -0
  19. package/dist/utils/freeze.test.d.ts.map +1 -0
  20. package/dist/utils/freeze.test.js +51 -0
  21. package/dist/utils/index.d.ts +4 -0
  22. package/dist/utils/index.d.ts.map +1 -0
  23. package/dist/utils/index.js +3 -0
  24. package/dist/utils/map-context-diff.d.ts +25 -0
  25. package/dist/utils/map-context-diff.d.ts.map +1 -0
  26. package/dist/utils/map-context-diff.js +111 -0
  27. package/dist/utils/map-context-diff.test.d.ts +2 -0
  28. package/dist/utils/map-context-diff.test.d.ts.map +1 -0
  29. package/dist/utils/map-context-diff.test.js +370 -0
  30. package/dist/utils/url.d.ts +7 -0
  31. package/dist/utils/url.d.ts.map +1 -0
  32. package/dist/utils/url.js +17 -0
  33. package/dist/utils/url.test.d.ts +2 -0
  34. package/dist/utils/url.test.d.ts.map +1 -0
  35. package/dist/utils/url.test.js +8 -0
  36. package/lib/index.ts +3 -0
  37. package/lib/model/index.ts +2 -0
  38. package/lib/model/map-context-diff.ts +37 -0
  39. package/lib/model/map-context.ts +96 -0
  40. package/lib/utils/freeze.test.ts +52 -0
  41. package/lib/utils/freeze.ts +14 -0
  42. package/lib/utils/index.ts +3 -0
  43. package/lib/utils/map-context-diff.test.ts +577 -0
  44. package/lib/utils/map-context-diff.ts +144 -0
  45. package/lib/utils/url.test.ts +14 -0
  46. package/lib/utils/url.ts +20 -0
  47. package/package.json +26 -0
@@ -0,0 +1,577 @@
1
+ import { MapContext, MapContextDiff, MapContextLayer } from "../model";
2
+ import { deepFreeze } from "./freeze";
3
+ import {
4
+ computeMapContextDiff,
5
+ getLayerHash,
6
+ isLayerSame,
7
+ isLayerSameAndUnchanged,
8
+ } from "./map-context-diff";
9
+
10
+ const SAMPLE_CONTEXT: MapContext = deepFreeze({
11
+ view: {
12
+ center: [10, 20],
13
+ zoom: 3,
14
+ extent: [40, 50, 60, 70],
15
+ },
16
+ layers: [],
17
+ });
18
+
19
+ const SAMPLE_LAYER1: MapContextLayer = deepFreeze({
20
+ type: "wms",
21
+ url: "http://abc.org/wms",
22
+ name: "myLayer",
23
+ extras: { myField: "abc" },
24
+ });
25
+ const SAMPLE_LAYER2: MapContextLayer = deepFreeze({
26
+ type: "xyz",
27
+ url: "http://abc.org/tiles",
28
+ extras: { myField2: "123" },
29
+ });
30
+ const SAMPLE_LAYER3: MapContextLayer = deepFreeze({
31
+ type: "geojson",
32
+ data: '{ "type": "Feature", "properties": {}}',
33
+ extras: { myField3: "000" },
34
+ });
35
+ const SAMPLE_LAYER4: MapContextLayer = deepFreeze({
36
+ type: "wfs",
37
+ url: "http://abc.org/wfs",
38
+ featureType: "myFeatureType",
39
+ extras: { myField4: "aaa" },
40
+ });
41
+ const SAMPLE_LAYER5: MapContextLayer = deepFreeze({
42
+ type: "xyz",
43
+ url: "http://my.tiles/server",
44
+ });
45
+
46
+ describe("Context diff utils", () => {
47
+ describe("isLayerSame", () => {
48
+ describe("layers with id", () => {
49
+ it("compares non-strictly by id", () => {
50
+ expect(
51
+ isLayerSame(
52
+ { ...SAMPLE_LAYER1, id: "a" },
53
+ { ...SAMPLE_LAYER1, id: "b" },
54
+ ),
55
+ ).toBe(false);
56
+ expect(
57
+ isLayerSame(
58
+ { ...SAMPLE_LAYER1, id: "ab" },
59
+ { ...SAMPLE_LAYER2, id: "ab" },
60
+ ),
61
+ ).toBe(true);
62
+ expect(
63
+ isLayerSame(
64
+ { ...SAMPLE_LAYER1, id: 1 },
65
+ { ...SAMPLE_LAYER2, id: "01" },
66
+ ),
67
+ ).toBe(true);
68
+ expect(
69
+ isLayerSame({ ...SAMPLE_LAYER1, id: 1 }, { ...SAMPLE_LAYER2, id: 1 }),
70
+ ).toBe(true);
71
+ expect(
72
+ isLayerSame({ ...SAMPLE_LAYER1, id: 1 }, { ...SAMPLE_LAYER1, id: 2 }),
73
+ ).toBe(false);
74
+ });
75
+ });
76
+ describe("layers without id", () => {
77
+ it("compares by properties", () => {
78
+ expect(isLayerSame(SAMPLE_LAYER1, SAMPLE_LAYER1)).toBe(true);
79
+ expect(
80
+ isLayerSame(SAMPLE_LAYER1, { ...SAMPLE_LAYER1, name: "abc" }),
81
+ ).toBe(false);
82
+ expect(
83
+ isLayerSame(
84
+ {
85
+ ...SAMPLE_LAYER1,
86
+ url: "http://abc.org",
87
+ name: SAMPLE_LAYER1.name,
88
+ },
89
+ { ...SAMPLE_LAYER1, url: "http://abc.org" },
90
+ ),
91
+ ).toBe(true);
92
+ expect(isLayerSame(SAMPLE_LAYER1, SAMPLE_LAYER2)).toBe(false);
93
+ });
94
+ it("ignores extras prop", () => {
95
+ expect(
96
+ isLayerSame(SAMPLE_LAYER1, {
97
+ ...SAMPLE_LAYER1,
98
+ extras: { otherProp: "abc" },
99
+ }),
100
+ ).toBe(true);
101
+ expect(
102
+ isLayerSame(SAMPLE_LAYER1, {
103
+ ...SAMPLE_LAYER1,
104
+ extras: undefined,
105
+ }),
106
+ ).toBe(true);
107
+ const { extras, ...layer } = SAMPLE_LAYER1;
108
+ expect(isLayerSame(SAMPLE_LAYER1, layer)).toBe(true);
109
+ });
110
+ });
111
+ describe("layers with and without id", () => {
112
+ it("compares by properties", () => {
113
+ expect(
114
+ isLayerSame(SAMPLE_LAYER1, { ...SAMPLE_LAYER1, id: "123" }),
115
+ ).toBe(false);
116
+ expect(
117
+ isLayerSame(
118
+ { ...SAMPLE_LAYER1, id: "123" },
119
+ { id: "123", ...SAMPLE_LAYER1, name: SAMPLE_LAYER1.name },
120
+ ),
121
+ ).toBe(true);
122
+ });
123
+ });
124
+ });
125
+
126
+ describe("isLayerSameAndUnchanged", () => {
127
+ describe("layers with id", () => {
128
+ it("compares non-strictly by id and version field", () => {
129
+ expect(
130
+ isLayerSameAndUnchanged(
131
+ { ...SAMPLE_LAYER1, id: "a" },
132
+ { ...SAMPLE_LAYER1, id: "b" },
133
+ ),
134
+ ).toBe(false);
135
+ expect(
136
+ isLayerSameAndUnchanged(
137
+ { ...SAMPLE_LAYER1, id: "a" },
138
+ { ...SAMPLE_LAYER2, id: "a" },
139
+ ),
140
+ ).toBe(true);
141
+ expect(
142
+ isLayerSameAndUnchanged(
143
+ { ...SAMPLE_LAYER1, id: "ab", version: 2 },
144
+ { ...SAMPLE_LAYER2, id: "ab", version: 2 },
145
+ ),
146
+ ).toBe(true);
147
+ expect(
148
+ isLayerSameAndUnchanged(
149
+ { ...SAMPLE_LAYER1, id: "ab", version: 2 },
150
+ { ...SAMPLE_LAYER1, id: "ab", version: 1 },
151
+ ),
152
+ ).toBe(false);
153
+ expect(
154
+ isLayerSameAndUnchanged(
155
+ { ...SAMPLE_LAYER1, id: 1 },
156
+ { ...SAMPLE_LAYER1, id: "01", version: 3 },
157
+ ),
158
+ ).toBe(false);
159
+ });
160
+ });
161
+ describe("layers without id", () => {
162
+ it("compares by properties, including extras", () => {
163
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, SAMPLE_LAYER1)).toBe(
164
+ true,
165
+ );
166
+ expect(
167
+ isLayerSameAndUnchanged(SAMPLE_LAYER1, {
168
+ ...SAMPLE_LAYER1,
169
+ name: "abc",
170
+ }),
171
+ ).toBe(false);
172
+ expect(
173
+ isLayerSameAndUnchanged(
174
+ {
175
+ ...SAMPLE_LAYER1,
176
+ url: "http://abc.org",
177
+ name: SAMPLE_LAYER1.name,
178
+ },
179
+ { ...SAMPLE_LAYER1, url: "http://abc.org" },
180
+ ),
181
+ ).toBe(true);
182
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, SAMPLE_LAYER2)).toBe(
183
+ false,
184
+ );
185
+ });
186
+ it("takes into account extras prop", () => {
187
+ expect(
188
+ isLayerSameAndUnchanged(SAMPLE_LAYER1, {
189
+ ...SAMPLE_LAYER1,
190
+ extras: { otherProp: "abc" },
191
+ }),
192
+ ).toBe(false);
193
+ expect(
194
+ isLayerSameAndUnchanged(SAMPLE_LAYER1, {
195
+ ...SAMPLE_LAYER1,
196
+ extras: undefined,
197
+ }),
198
+ ).toBe(false);
199
+ const { extras, ...layer } = SAMPLE_LAYER1;
200
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, layer)).toBe(false);
201
+ });
202
+ });
203
+ describe("layers with and without id", () => {
204
+ it("compares by properties", () => {
205
+ expect(
206
+ isLayerSameAndUnchanged(SAMPLE_LAYER1, {
207
+ ...SAMPLE_LAYER1,
208
+ id: "123",
209
+ }),
210
+ ).toBe(false);
211
+ expect(
212
+ isLayerSameAndUnchanged(
213
+ { ...SAMPLE_LAYER1, id: "123" },
214
+ { id: "123", ...SAMPLE_LAYER1, name: SAMPLE_LAYER1.name },
215
+ ),
216
+ ).toBe(true);
217
+ });
218
+ });
219
+ });
220
+
221
+ describe("computeMapContextDiff", () => {
222
+ let contextOld: MapContext;
223
+ let contextNew: MapContext;
224
+ let diff: MapContextDiff;
225
+
226
+ describe("no change", () => {
227
+ beforeEach(() => {
228
+ contextOld = {
229
+ ...SAMPLE_CONTEXT,
230
+ layers: [SAMPLE_LAYER2, SAMPLE_LAYER1],
231
+ };
232
+ contextNew = {
233
+ ...SAMPLE_CONTEXT,
234
+ layers: [SAMPLE_LAYER2, SAMPLE_LAYER1],
235
+ };
236
+ });
237
+ it("outputs the correct diff", () => {
238
+ diff = computeMapContextDiff(contextNew, contextOld);
239
+ expect(diff).toEqual({
240
+ layersAdded: [],
241
+ layersChanged: [],
242
+ layersRemoved: [],
243
+ layersReordered: [],
244
+ viewChanges: {},
245
+ });
246
+ });
247
+ });
248
+
249
+ describe("layers added", () => {
250
+ beforeEach(() => {
251
+ contextOld = {
252
+ ...SAMPLE_CONTEXT,
253
+ layers: [SAMPLE_LAYER1],
254
+ };
255
+ contextNew = {
256
+ ...SAMPLE_CONTEXT,
257
+ layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER4],
258
+ };
259
+ });
260
+ it("outputs the correct diff", () => {
261
+ diff = computeMapContextDiff(contextNew, contextOld);
262
+ expect(diff).toEqual({
263
+ layersAdded: [
264
+ {
265
+ layer: SAMPLE_LAYER2,
266
+ position: 0,
267
+ },
268
+ {
269
+ layer: SAMPLE_LAYER4,
270
+ position: 2,
271
+ },
272
+ ],
273
+ layersChanged: [],
274
+ layersRemoved: [],
275
+ layersReordered: [],
276
+ viewChanges: {},
277
+ });
278
+ });
279
+ });
280
+
281
+ describe("layers removed", () => {
282
+ beforeEach(() => {
283
+ contextOld = {
284
+ ...SAMPLE_CONTEXT,
285
+ layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER4],
286
+ };
287
+ contextNew = {
288
+ ...SAMPLE_CONTEXT,
289
+ layers: [SAMPLE_LAYER4],
290
+ };
291
+ });
292
+ it("outputs the correct diff", () => {
293
+ diff = computeMapContextDiff(contextNew, contextOld);
294
+ expect(diff).toEqual({
295
+ layersAdded: [],
296
+ layersChanged: [],
297
+ layersRemoved: [
298
+ {
299
+ layer: SAMPLE_LAYER2,
300
+ position: 0,
301
+ },
302
+ {
303
+ layer: SAMPLE_LAYER1,
304
+ position: 1,
305
+ },
306
+ ],
307
+ layersReordered: [],
308
+ viewChanges: {},
309
+ });
310
+ });
311
+ });
312
+
313
+ describe("layers changed", () => {
314
+ beforeEach(() => {
315
+ contextOld = {
316
+ ...SAMPLE_CONTEXT,
317
+ layers: [SAMPLE_LAYER2, { ...SAMPLE_LAYER1, id: 123, version: 3 }],
318
+ };
319
+ contextNew = {
320
+ ...SAMPLE_CONTEXT,
321
+ layers: [
322
+ { ...SAMPLE_LAYER2, extras: { prop: true } },
323
+ { ...SAMPLE_LAYER1, id: 123, version: 10 },
324
+ ],
325
+ };
326
+ });
327
+ it("outputs the correct diff", () => {
328
+ diff = computeMapContextDiff(contextNew, contextOld);
329
+ expect(diff).toEqual({
330
+ layersAdded: [],
331
+ layersChanged: [
332
+ {
333
+ layer: contextNew.layers[0],
334
+ position: 0,
335
+ },
336
+ {
337
+ layer: contextNew.layers[1],
338
+ position: 1,
339
+ },
340
+ ],
341
+ layersRemoved: [],
342
+ layersReordered: [],
343
+ viewChanges: {},
344
+ });
345
+ });
346
+ });
347
+
348
+ describe("reordering", () => {
349
+ describe("three layers reordered", () => {
350
+ beforeEach(() => {
351
+ contextOld = {
352
+ ...SAMPLE_CONTEXT,
353
+ layers: [SAMPLE_LAYER1, SAMPLE_LAYER2, SAMPLE_LAYER3],
354
+ };
355
+ contextNew = {
356
+ ...SAMPLE_CONTEXT,
357
+ layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER3],
358
+ };
359
+ });
360
+ it("outputs the correct diff", () => {
361
+ diff = computeMapContextDiff(contextNew, contextOld);
362
+ expect(diff).toEqual({
363
+ layersAdded: [],
364
+ layersChanged: [],
365
+ layersRemoved: [],
366
+ layersReordered: [
367
+ {
368
+ layer: SAMPLE_LAYER2,
369
+ newPosition: 0,
370
+ previousPosition: 1,
371
+ },
372
+ {
373
+ layer: SAMPLE_LAYER1,
374
+ newPosition: 1,
375
+ previousPosition: 0,
376
+ },
377
+ ],
378
+ viewChanges: {},
379
+ });
380
+ });
381
+ });
382
+
383
+ describe("four layers reordered", () => {
384
+ beforeEach(() => {
385
+ contextOld = {
386
+ ...SAMPLE_CONTEXT,
387
+ layers: [
388
+ SAMPLE_LAYER1,
389
+ SAMPLE_LAYER3,
390
+ SAMPLE_LAYER4,
391
+ SAMPLE_LAYER2,
392
+ ],
393
+ };
394
+ contextNew = {
395
+ ...SAMPLE_CONTEXT,
396
+ layers: [
397
+ SAMPLE_LAYER4,
398
+ SAMPLE_LAYER3,
399
+ SAMPLE_LAYER1,
400
+ SAMPLE_LAYER2,
401
+ ],
402
+ };
403
+ });
404
+ it("outputs the correct diff", () => {
405
+ diff = computeMapContextDiff(contextNew, contextOld);
406
+ expect(diff).toEqual({
407
+ layersAdded: [],
408
+ layersChanged: [],
409
+ layersRemoved: [],
410
+ layersReordered: [
411
+ {
412
+ layer: SAMPLE_LAYER4,
413
+ newPosition: 0,
414
+ previousPosition: 2,
415
+ },
416
+ {
417
+ layer: SAMPLE_LAYER1,
418
+ newPosition: 2,
419
+ previousPosition: 0,
420
+ },
421
+ ],
422
+ viewChanges: {},
423
+ });
424
+ });
425
+ });
426
+ });
427
+
428
+ describe("combined changes", () => {
429
+ let changedLayer: MapContextLayer;
430
+ beforeEach(() => {
431
+ changedLayer = { ...SAMPLE_LAYER3, extras: { prop: true } };
432
+ contextOld = {
433
+ ...SAMPLE_CONTEXT,
434
+ layers: [SAMPLE_LAYER1, SAMPLE_LAYER5, SAMPLE_LAYER3, SAMPLE_LAYER4],
435
+ };
436
+ contextNew = {
437
+ ...SAMPLE_CONTEXT,
438
+ layers: [SAMPLE_LAYER2, changedLayer, SAMPLE_LAYER5],
439
+ };
440
+ });
441
+ it("outputs the correct diff", () => {
442
+ diff = computeMapContextDiff(contextNew, contextOld);
443
+ expect(diff).toEqual({
444
+ layersAdded: [
445
+ {
446
+ layer: SAMPLE_LAYER2,
447
+ position: 0,
448
+ },
449
+ ],
450
+ layersChanged: [
451
+ {
452
+ layer: changedLayer,
453
+ position: 1,
454
+ },
455
+ ],
456
+ layersRemoved: [
457
+ {
458
+ layer: SAMPLE_LAYER1,
459
+ position: 0,
460
+ },
461
+ {
462
+ layer: SAMPLE_LAYER4,
463
+ position: 3,
464
+ },
465
+ ],
466
+ layersReordered: [
467
+ {
468
+ layer: changedLayer,
469
+ newPosition: 1,
470
+ previousPosition: 2,
471
+ },
472
+ {
473
+ layer: SAMPLE_LAYER5,
474
+ newPosition: 2,
475
+ previousPosition: 1,
476
+ },
477
+ ],
478
+ viewChanges: {},
479
+ });
480
+ });
481
+ });
482
+ });
483
+
484
+ describe("getLayerHash", () => {
485
+ it("works with serializable entities", () => {
486
+ expect(
487
+ getLayerHash({
488
+ ...SAMPLE_LAYER1,
489
+ extras: {
490
+ array: [11, 22, null, undefined],
491
+ object: {},
492
+ undef: undefined,
493
+ },
494
+ }),
495
+ ).toBeTypeOf("string");
496
+ });
497
+ it("ignores extra property by default", () => {
498
+ expect(
499
+ getLayerHash({
500
+ ...SAMPLE_LAYER1,
501
+ extras: {
502
+ array: [11, 22, null, undefined],
503
+ object: {},
504
+ },
505
+ }),
506
+ ).toEqual(
507
+ getLayerHash({
508
+ ...SAMPLE_LAYER1,
509
+ extras: { hello: "world" },
510
+ }),
511
+ );
512
+ });
513
+ it("works with non-serializable entities (but they are ignored)", () => {
514
+ const hash = getLayerHash(
515
+ {
516
+ ...SAMPLE_LAYER1,
517
+ extras: {
518
+ func: () => 123,
519
+ canvas: document.createElement("canvas"),
520
+ },
521
+ },
522
+ true,
523
+ );
524
+ expect(hash).toBeTypeOf("string");
525
+ expect(hash).toEqual(
526
+ getLayerHash(
527
+ {
528
+ ...SAMPLE_LAYER1,
529
+ extras: {
530
+ func: () => 456,
531
+ canvas: document.createElement("div"),
532
+ },
533
+ },
534
+ true,
535
+ ),
536
+ );
537
+ expect(hash).not.toEqual(
538
+ getLayerHash(
539
+ {
540
+ ...SAMPLE_LAYER1,
541
+ extras: {},
542
+ },
543
+ true,
544
+ ),
545
+ );
546
+ });
547
+ it("does not take into account properties order", () => {
548
+ expect(
549
+ getLayerHash(
550
+ {
551
+ type: "wms",
552
+ url: "http://abc.org/wms",
553
+ name: "myLayer",
554
+ extras: {
555
+ array: [1, 2, 3],
556
+ object: {},
557
+ },
558
+ },
559
+ true,
560
+ ),
561
+ ).toEqual(
562
+ getLayerHash(
563
+ {
564
+ extras: {
565
+ array: [1, 2, 3],
566
+ object: {},
567
+ },
568
+ url: "http://abc.org/wms",
569
+ type: "wms",
570
+ name: "myLayer",
571
+ },
572
+ true,
573
+ ),
574
+ );
575
+ });
576
+ });
577
+ });
@@ -0,0 +1,144 @@
1
+ import {
2
+ MapContext,
3
+ MapContextDiff,
4
+ MapContextLayer,
5
+ MapContextLayerPositioned,
6
+ MapContextLayerReordered,
7
+ MapContextView,
8
+ } from "../model";
9
+
10
+ export function getLayerHash(
11
+ layer: MapContextLayer,
12
+ includeExtras = false,
13
+ ): string {
14
+ function getHash(input: unknown): string {
15
+ if (input instanceof Object) {
16
+ const obj: Record<string, string> = {};
17
+ const keys = Object.keys(input).sort();
18
+ for (const key of keys) {
19
+ if (!includeExtras && key === "extras") continue;
20
+ obj[key] = getHash(input[key as keyof typeof input]);
21
+ }
22
+ const hash = JSON.stringify(obj)
23
+ .split("")
24
+ .reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
25
+ return (hash >>> 0).toString();
26
+ } else {
27
+ return JSON.stringify(input);
28
+ }
29
+ }
30
+ return getHash(layer);
31
+ }
32
+
33
+ export function isLayerSame(
34
+ layerA: MapContextLayer,
35
+ layerB: MapContextLayer,
36
+ ): boolean {
37
+ if ("id" in layerA && "id" in layerB) {
38
+ return layerA.id == layerB.id;
39
+ }
40
+ return getLayerHash(layerA) === getLayerHash(layerB);
41
+ }
42
+
43
+ export function isLayerSameAndUnchanged(
44
+ layerA: MapContextLayer,
45
+ layerB: MapContextLayer,
46
+ ): boolean {
47
+ if ("id" in layerA && "id" in layerB) {
48
+ return layerA.id == layerB.id && layerA.version == layerB.version;
49
+ }
50
+ return getLayerHash(layerA, true) === getLayerHash(layerB, true);
51
+ }
52
+
53
+ /**
54
+ * The following logic is produced by identifying layers in both context
55
+ * and determining whether they have been added, removed, changed or reordered.
56
+ *
57
+ * Identifying layers to determine if they have been added/removed/reordered is done like so:
58
+ * 1. For layers with an `id` property, use non-strict equality on it (e.g. '2' and 2 are equivalent);
59
+ * 2. For layers without `id`, compute a hash of their base properties _excluding the `extras` property_
60
+ *
61
+ * Determining whether layers have changed is done like so:
62
+ * 1. For layers with an `id` property, the value of the `version` field is compared;
63
+ * if values are different (using non-strict equality), then the layer is considered to have changed; otherwise
64
+ * it is considered to have remained the same
65
+ * 2. For layers without `id`, a full hash is computed _including the `extras` property_;
66
+ * this means that a layer which only had changes in its `extras` object will not be considered added/removed,
67
+ * but only changed
68
+ *
69
+ * @param nextContext
70
+ * @param previousContext
71
+ */
72
+ export function computeMapContextDiff(
73
+ nextContext: MapContext,
74
+ previousContext: MapContext,
75
+ ): MapContextDiff {
76
+ function getLayerPosition(
77
+ layer: MapContextLayer,
78
+ layers: MapContextLayer[],
79
+ ): number {
80
+ for (let i = 0; i < layers.length; i++) {
81
+ if (isLayerSame(layers[i], layer)) {
82
+ return i;
83
+ }
84
+ }
85
+ return -1;
86
+ }
87
+
88
+ const layersChanged: MapContextLayerPositioned[] = [];
89
+ const layersReordered: MapContextLayerReordered[] = [];
90
+ const layersRemoved: MapContextLayerPositioned[] = [];
91
+ const layersAdded: MapContextLayerPositioned[] = [];
92
+ const viewChanges: MapContextView = {};
93
+
94
+ // loop on prev context layers (for removed layers)
95
+ for (let i = 0; i < previousContext.layers.length; i++) {
96
+ const layer = previousContext.layers[i];
97
+ const nextPosition = getLayerPosition(layer, nextContext.layers);
98
+ const prevPosition = getLayerPosition(layer, previousContext.layers);
99
+ if (nextPosition === -1) {
100
+ layersRemoved.push({ layer, position: prevPosition });
101
+ }
102
+ }
103
+
104
+ // loop on next context layers (for added & updated)
105
+ for (let i = 0; i < nextContext.layers.length; i++) {
106
+ const layer = nextContext.layers[i];
107
+ const prevPosition = getLayerPosition(layer, previousContext.layers);
108
+ if (prevPosition === -1) {
109
+ layersAdded.push({ layer, position: i });
110
+ } else {
111
+ const prevLayer = previousContext.layers[prevPosition];
112
+ if (!isLayerSameAndUnchanged(layer, prevLayer)) {
113
+ layersChanged.push({ layer, position: i });
114
+ }
115
+ }
116
+ }
117
+
118
+ // look for moved layers
119
+ const prevLayersFiltered = previousContext.layers.filter(
120
+ (l) => !layersRemoved.find(({ layer }) => l === layer),
121
+ );
122
+ const nextLayersFiltered = nextContext.layers.filter(
123
+ (l) => !layersAdded.find(({ layer }) => l === layer),
124
+ );
125
+ for (let i = 0; i < nextLayersFiltered.length; i++) {
126
+ const layer = nextLayersFiltered[i];
127
+ const prevPosition = getLayerPosition(layer, prevLayersFiltered);
128
+ if (i !== prevPosition) {
129
+ layersReordered.push({
130
+ layer,
131
+ newPosition: getLayerPosition(layer, nextContext.layers),
132
+ previousPosition: getLayerPosition(layer, previousContext.layers),
133
+ });
134
+ }
135
+ }
136
+
137
+ return {
138
+ layersAdded,
139
+ layersChanged,
140
+ layersRemoved,
141
+ layersReordered,
142
+ viewChanges,
143
+ };
144
+ }