@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,370 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { deepFreeze } from "./freeze";
13
+ import { computeMapContextDiff, getLayerHash, isLayerSame, isLayerSameAndUnchanged, } from "./map-context-diff";
14
+ const SAMPLE_CONTEXT = deepFreeze({
15
+ view: {
16
+ center: [10, 20],
17
+ zoom: 3,
18
+ extent: [40, 50, 60, 70],
19
+ },
20
+ layers: [],
21
+ });
22
+ const SAMPLE_LAYER1 = deepFreeze({
23
+ type: "wms",
24
+ url: "http://abc.org/wms",
25
+ name: "myLayer",
26
+ extras: { myField: "abc" },
27
+ });
28
+ const SAMPLE_LAYER2 = deepFreeze({
29
+ type: "xyz",
30
+ url: "http://abc.org/tiles",
31
+ extras: { myField2: "123" },
32
+ });
33
+ const SAMPLE_LAYER3 = deepFreeze({
34
+ type: "geojson",
35
+ data: '{ "type": "Feature", "properties": {}}',
36
+ extras: { myField3: "000" },
37
+ });
38
+ const SAMPLE_LAYER4 = deepFreeze({
39
+ type: "wfs",
40
+ url: "http://abc.org/wfs",
41
+ featureType: "myFeatureType",
42
+ extras: { myField4: "aaa" },
43
+ });
44
+ const SAMPLE_LAYER5 = deepFreeze({
45
+ type: "xyz",
46
+ url: "http://my.tiles/server",
47
+ });
48
+ describe("Context diff utils", () => {
49
+ describe("isLayerSame", () => {
50
+ describe("layers with id", () => {
51
+ it("compares non-strictly by id", () => {
52
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "a" }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "b" }))).toBe(false);
53
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "ab" }), Object.assign(Object.assign({}, SAMPLE_LAYER2), { id: "ab" }))).toBe(true);
54
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 1 }), Object.assign(Object.assign({}, SAMPLE_LAYER2), { id: "01" }))).toBe(true);
55
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 1 }), Object.assign(Object.assign({}, SAMPLE_LAYER2), { id: 1 }))).toBe(true);
56
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 1 }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 2 }))).toBe(false);
57
+ });
58
+ });
59
+ describe("layers without id", () => {
60
+ it("compares by properties", () => {
61
+ expect(isLayerSame(SAMPLE_LAYER1, SAMPLE_LAYER1)).toBe(true);
62
+ expect(isLayerSame(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { name: "abc" }))).toBe(false);
63
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { url: "http://abc.org", name: SAMPLE_LAYER1.name }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { url: "http://abc.org" }))).toBe(true);
64
+ expect(isLayerSame(SAMPLE_LAYER1, SAMPLE_LAYER2)).toBe(false);
65
+ });
66
+ it("ignores extras prop", () => {
67
+ expect(isLayerSame(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: { otherProp: "abc" } }))).toBe(true);
68
+ expect(isLayerSame(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: undefined }))).toBe(true);
69
+ const { extras } = SAMPLE_LAYER1, layer = __rest(SAMPLE_LAYER1, ["extras"]);
70
+ expect(isLayerSame(SAMPLE_LAYER1, layer)).toBe(true);
71
+ });
72
+ });
73
+ describe("layers with and without id", () => {
74
+ it("compares by properties", () => {
75
+ expect(isLayerSame(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "123" }))).toBe(false);
76
+ expect(isLayerSame(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "123" }), Object.assign(Object.assign({ id: "123" }, SAMPLE_LAYER1), { name: SAMPLE_LAYER1.name }))).toBe(true);
77
+ });
78
+ });
79
+ });
80
+ describe("isLayerSameAndUnchanged", () => {
81
+ describe("layers with id", () => {
82
+ it("compares non-strictly by id and version field", () => {
83
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "a" }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "b" }))).toBe(false);
84
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "a" }), Object.assign(Object.assign({}, SAMPLE_LAYER2), { id: "a" }))).toBe(true);
85
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "ab", version: 2 }), Object.assign(Object.assign({}, SAMPLE_LAYER2), { id: "ab", version: 2 }))).toBe(true);
86
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "ab", version: 2 }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "ab", version: 1 }))).toBe(false);
87
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 1 }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "01", version: 3 }))).toBe(false);
88
+ });
89
+ });
90
+ describe("layers without id", () => {
91
+ it("compares by properties, including extras", () => {
92
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, SAMPLE_LAYER1)).toBe(true);
93
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { name: "abc" }))).toBe(false);
94
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { url: "http://abc.org", name: SAMPLE_LAYER1.name }), Object.assign(Object.assign({}, SAMPLE_LAYER1), { url: "http://abc.org" }))).toBe(true);
95
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, SAMPLE_LAYER2)).toBe(false);
96
+ });
97
+ it("takes into account extras prop", () => {
98
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: { otherProp: "abc" } }))).toBe(false);
99
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: undefined }))).toBe(false);
100
+ const { extras } = SAMPLE_LAYER1, layer = __rest(SAMPLE_LAYER1, ["extras"]);
101
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, layer)).toBe(false);
102
+ });
103
+ });
104
+ describe("layers with and without id", () => {
105
+ it("compares by properties", () => {
106
+ expect(isLayerSameAndUnchanged(SAMPLE_LAYER1, Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "123" }))).toBe(false);
107
+ expect(isLayerSameAndUnchanged(Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: "123" }), Object.assign(Object.assign({ id: "123" }, SAMPLE_LAYER1), { name: SAMPLE_LAYER1.name }))).toBe(true);
108
+ });
109
+ });
110
+ });
111
+ describe("computeMapContextDiff", () => {
112
+ let contextOld;
113
+ let contextNew;
114
+ let diff;
115
+ describe("no change", () => {
116
+ beforeEach(() => {
117
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, SAMPLE_LAYER1] });
118
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, SAMPLE_LAYER1] });
119
+ });
120
+ it("outputs the correct diff", () => {
121
+ diff = computeMapContextDiff(contextNew, contextOld);
122
+ expect(diff).toEqual({
123
+ layersAdded: [],
124
+ layersChanged: [],
125
+ layersRemoved: [],
126
+ layersReordered: [],
127
+ viewChanges: {},
128
+ });
129
+ });
130
+ });
131
+ describe("layers added", () => {
132
+ beforeEach(() => {
133
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER1] });
134
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER4] });
135
+ });
136
+ it("outputs the correct diff", () => {
137
+ diff = computeMapContextDiff(contextNew, contextOld);
138
+ expect(diff).toEqual({
139
+ layersAdded: [
140
+ {
141
+ layer: SAMPLE_LAYER2,
142
+ position: 0,
143
+ },
144
+ {
145
+ layer: SAMPLE_LAYER4,
146
+ position: 2,
147
+ },
148
+ ],
149
+ layersChanged: [],
150
+ layersRemoved: [],
151
+ layersReordered: [],
152
+ viewChanges: {},
153
+ });
154
+ });
155
+ });
156
+ describe("layers removed", () => {
157
+ beforeEach(() => {
158
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER4] });
159
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER4] });
160
+ });
161
+ it("outputs the correct diff", () => {
162
+ diff = computeMapContextDiff(contextNew, contextOld);
163
+ expect(diff).toEqual({
164
+ layersAdded: [],
165
+ layersChanged: [],
166
+ layersRemoved: [
167
+ {
168
+ layer: SAMPLE_LAYER2,
169
+ position: 0,
170
+ },
171
+ {
172
+ layer: SAMPLE_LAYER1,
173
+ position: 1,
174
+ },
175
+ ],
176
+ layersReordered: [],
177
+ viewChanges: {},
178
+ });
179
+ });
180
+ });
181
+ describe("layers changed", () => {
182
+ beforeEach(() => {
183
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 123, version: 3 })] });
184
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [
185
+ Object.assign(Object.assign({}, SAMPLE_LAYER2), { extras: { prop: true } }),
186
+ Object.assign(Object.assign({}, SAMPLE_LAYER1), { id: 123, version: 10 }),
187
+ ] });
188
+ });
189
+ it("outputs the correct diff", () => {
190
+ diff = computeMapContextDiff(contextNew, contextOld);
191
+ expect(diff).toEqual({
192
+ layersAdded: [],
193
+ layersChanged: [
194
+ {
195
+ layer: contextNew.layers[0],
196
+ position: 0,
197
+ },
198
+ {
199
+ layer: contextNew.layers[1],
200
+ position: 1,
201
+ },
202
+ ],
203
+ layersRemoved: [],
204
+ layersReordered: [],
205
+ viewChanges: {},
206
+ });
207
+ });
208
+ });
209
+ describe("reordering", () => {
210
+ describe("three layers reordered", () => {
211
+ beforeEach(() => {
212
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER1, SAMPLE_LAYER2, SAMPLE_LAYER3] });
213
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, SAMPLE_LAYER1, SAMPLE_LAYER3] });
214
+ });
215
+ it("outputs the correct diff", () => {
216
+ diff = computeMapContextDiff(contextNew, contextOld);
217
+ expect(diff).toEqual({
218
+ layersAdded: [],
219
+ layersChanged: [],
220
+ layersRemoved: [],
221
+ layersReordered: [
222
+ {
223
+ layer: SAMPLE_LAYER2,
224
+ newPosition: 0,
225
+ previousPosition: 1,
226
+ },
227
+ {
228
+ layer: SAMPLE_LAYER1,
229
+ newPosition: 1,
230
+ previousPosition: 0,
231
+ },
232
+ ],
233
+ viewChanges: {},
234
+ });
235
+ });
236
+ });
237
+ describe("four layers reordered", () => {
238
+ beforeEach(() => {
239
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [
240
+ SAMPLE_LAYER1,
241
+ SAMPLE_LAYER3,
242
+ SAMPLE_LAYER4,
243
+ SAMPLE_LAYER2,
244
+ ] });
245
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [
246
+ SAMPLE_LAYER4,
247
+ SAMPLE_LAYER3,
248
+ SAMPLE_LAYER1,
249
+ SAMPLE_LAYER2,
250
+ ] });
251
+ });
252
+ it("outputs the correct diff", () => {
253
+ diff = computeMapContextDiff(contextNew, contextOld);
254
+ expect(diff).toEqual({
255
+ layersAdded: [],
256
+ layersChanged: [],
257
+ layersRemoved: [],
258
+ layersReordered: [
259
+ {
260
+ layer: SAMPLE_LAYER4,
261
+ newPosition: 0,
262
+ previousPosition: 2,
263
+ },
264
+ {
265
+ layer: SAMPLE_LAYER1,
266
+ newPosition: 2,
267
+ previousPosition: 0,
268
+ },
269
+ ],
270
+ viewChanges: {},
271
+ });
272
+ });
273
+ });
274
+ });
275
+ describe("combined changes", () => {
276
+ let changedLayer;
277
+ beforeEach(() => {
278
+ changedLayer = Object.assign(Object.assign({}, SAMPLE_LAYER3), { extras: { prop: true } });
279
+ contextOld = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER1, SAMPLE_LAYER5, SAMPLE_LAYER3, SAMPLE_LAYER4] });
280
+ contextNew = Object.assign(Object.assign({}, SAMPLE_CONTEXT), { layers: [SAMPLE_LAYER2, changedLayer, SAMPLE_LAYER5] });
281
+ });
282
+ it("outputs the correct diff", () => {
283
+ diff = computeMapContextDiff(contextNew, contextOld);
284
+ expect(diff).toEqual({
285
+ layersAdded: [
286
+ {
287
+ layer: SAMPLE_LAYER2,
288
+ position: 0,
289
+ },
290
+ ],
291
+ layersChanged: [
292
+ {
293
+ layer: changedLayer,
294
+ position: 1,
295
+ },
296
+ ],
297
+ layersRemoved: [
298
+ {
299
+ layer: SAMPLE_LAYER1,
300
+ position: 0,
301
+ },
302
+ {
303
+ layer: SAMPLE_LAYER4,
304
+ position: 3,
305
+ },
306
+ ],
307
+ layersReordered: [
308
+ {
309
+ layer: changedLayer,
310
+ newPosition: 1,
311
+ previousPosition: 2,
312
+ },
313
+ {
314
+ layer: SAMPLE_LAYER5,
315
+ newPosition: 2,
316
+ previousPosition: 1,
317
+ },
318
+ ],
319
+ viewChanges: {},
320
+ });
321
+ });
322
+ });
323
+ });
324
+ describe("getLayerHash", () => {
325
+ it("works with serializable entities", () => {
326
+ expect(getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: {
327
+ array: [11, 22, null, undefined],
328
+ object: {},
329
+ undef: undefined,
330
+ } }))).toBeTypeOf("string");
331
+ });
332
+ it("ignores extra property by default", () => {
333
+ expect(getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: {
334
+ array: [11, 22, null, undefined],
335
+ object: {},
336
+ } }))).toEqual(getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: { hello: "world" } })));
337
+ });
338
+ it("works with non-serializable entities (but they are ignored)", () => {
339
+ const hash = getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: {
340
+ func: () => 123,
341
+ canvas: document.createElement("canvas"),
342
+ } }), true);
343
+ expect(hash).toBeTypeOf("string");
344
+ expect(hash).toEqual(getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: {
345
+ func: () => 456,
346
+ canvas: document.createElement("div"),
347
+ } }), true));
348
+ expect(hash).not.toEqual(getLayerHash(Object.assign(Object.assign({}, SAMPLE_LAYER1), { extras: {} }), true));
349
+ });
350
+ it("does not take into account properties order", () => {
351
+ expect(getLayerHash({
352
+ type: "wms",
353
+ url: "http://abc.org/wms",
354
+ name: "myLayer",
355
+ extras: {
356
+ array: [1, 2, 3],
357
+ object: {},
358
+ },
359
+ }, true)).toEqual(getLayerHash({
360
+ extras: {
361
+ array: [1, 2, 3],
362
+ object: {},
363
+ },
364
+ url: "http://abc.org/wms",
365
+ type: "wms",
366
+ name: "myLayer",
367
+ }, true));
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Removes the given search params from the URL completely; this is case-insensitive
3
+ * @param url
4
+ * @param searchParams
5
+ */
6
+ export declare function removeSearchParams(url: string, searchParams: string[]): string;
7
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../lib/utils/url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EAAE,GACrB,MAAM,CAWR"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Removes the given search params from the URL completely; this is case-insensitive
3
+ * @param url
4
+ * @param searchParams
5
+ */
6
+ export function removeSearchParams(url, searchParams) {
7
+ const toDelete = [];
8
+ const urlObj = new URL(url, window.location.toString());
9
+ const keysLower = searchParams.map((p) => p.toLowerCase());
10
+ for (const param of urlObj.searchParams.keys()) {
11
+ if (keysLower.indexOf(param.toLowerCase()) > -1) {
12
+ toDelete.push(param);
13
+ }
14
+ }
15
+ toDelete.map((param) => urlObj.searchParams.delete(param));
16
+ return urlObj.toString();
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=url.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.test.d.ts","sourceRoot":"","sources":["../../lib/utils/url.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import { removeSearchParams } from "./url";
2
+ describe("URL utils", () => {
3
+ describe("removeSearchParams", () => {
4
+ it("removes given search params in a case insensitive way", () => {
5
+ expect(removeSearchParams("http://my.org/abc/?arg0=1234&arg1=aaa&Arg1=111&ARG2=&aRG3=fff&arg4=5678", ["ARG1", "arg2", "arg3"])).toEqual("http://my.org/abc/?arg0=1234&arg4=5678");
6
+ });
7
+ });
8
+ });
package/lib/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ // PUBLIC API
2
+ export * from "./utils";
3
+ export * from "./model";
@@ -0,0 +1,2 @@
1
+ export * from "./map-context";
2
+ export * from "./map-context-diff";
@@ -0,0 +1,37 @@
1
+ import { MapContextLayer, MapContextView } from "./map-context";
2
+
3
+ /**
4
+ * Associates a position to a layer; the position is the index of
5
+ * the layer in the layers array
6
+ */
7
+ export interface MapContextLayerPositioned {
8
+ layer: MapContextLayer;
9
+ position: number;
10
+ }
11
+
12
+ /**
13
+ * Describes a layer being moved to a different position
14
+ */
15
+ export interface MapContextLayerReordered {
16
+ layer: MapContextLayer;
17
+ newPosition: number;
18
+ previousPosition: number;
19
+ }
20
+
21
+ /**
22
+ * Describes a delta between two contexts, in order to be
23
+ * applied to an existing map.
24
+ *
25
+ * For positions to be correct the order of operations should be:
26
+ * 1. change layers
27
+ * 2. remove layers
28
+ * 3. add layers
29
+ * 4. move layers
30
+ */
31
+ export interface MapContextDiff {
32
+ layersChanged: MapContextLayerPositioned[];
33
+ layersReordered: MapContextLayerReordered[];
34
+ layersRemoved: MapContextLayerPositioned[];
35
+ layersAdded: MapContextLayerPositioned[];
36
+ viewChanges: MapContextView;
37
+ }
@@ -0,0 +1,96 @@
1
+ import { FeatureCollection, Geometry } from "geojson";
2
+
3
+ export type LayerDimensions = Record<string, string>;
4
+
5
+ export type LayerExtras = Record<string, unknown>;
6
+
7
+ export interface MapContextBaseLayer {
8
+ id?: string | number;
9
+ version?: number;
10
+
11
+ /**
12
+ * This property can be used to store anything application-specific on layers; as its content may occasionally
13
+ * be serialized to JSON for change detection purposes, it is not recommended to store Functions or other
14
+ * non-serializable entities
15
+ */
16
+ extras?: LayerExtras;
17
+ }
18
+
19
+ export interface MapContextLayerWms extends MapContextBaseLayer {
20
+ type: "wms";
21
+ url: string;
22
+ name: string;
23
+ dimensions?: LayerDimensions;
24
+ style?: string;
25
+ }
26
+
27
+ export interface MapContextLayerWmts extends MapContextBaseLayer {
28
+ type: "wmts";
29
+ url: string;
30
+ name: string;
31
+ dimensions?: LayerDimensions;
32
+ style?: string;
33
+ }
34
+
35
+ export interface MapContextLayerWfs extends MapContextBaseLayer {
36
+ type: "wfs";
37
+ url: string;
38
+ featureType: string;
39
+ }
40
+
41
+ export interface MapContextLayerXyz extends MapContextBaseLayer {
42
+ type: "xyz";
43
+ url: string;
44
+ }
45
+
46
+ interface LayerGeojson extends MapContextBaseLayer {
47
+ type: "geojson";
48
+ }
49
+ interface LayerGeojsonWithUrl extends LayerGeojson {
50
+ url: string;
51
+ data?: never;
52
+ }
53
+ interface LayerGeojsonWithData extends LayerGeojson {
54
+ data: FeatureCollection<Geometry | null> | string;
55
+ url?: never;
56
+ }
57
+
58
+ /**
59
+ * @interface
60
+ */
61
+ export type MapContextLayerGeojson = LayerGeojsonWithUrl | LayerGeojsonWithData;
62
+
63
+ /**
64
+ * @interface
65
+ */
66
+ export type MapContextLayer =
67
+ | MapContextLayerWms
68
+ | MapContextLayerWmts
69
+ | MapContextLayerWfs
70
+ | MapContextLayerXyz
71
+ | MapContextLayerGeojson;
72
+
73
+ export type Coordinate = [number, number];
74
+
75
+ /**
76
+ * Min X, min Y, max X, max Y
77
+ */
78
+ export type Extent = [number, number, number, number];
79
+
80
+ /**
81
+ * @property center Expressed in longitude/latitude
82
+ * @property extent Expressed in longitude/latitude
83
+ * @property maxExtent Expressed in longitude/latitude
84
+ */
85
+ export interface MapContextView {
86
+ center?: Coordinate;
87
+ zoom?: number;
88
+ extent?: Extent;
89
+ maxZoom?: number;
90
+ maxExtent?: Extent;
91
+ }
92
+
93
+ export interface MapContext {
94
+ layers: MapContextLayer[];
95
+ view: MapContextView;
96
+ }
@@ -0,0 +1,52 @@
1
+ import { deepFreeze } from "./freeze";
2
+
3
+ describe("freeze util", () => {
4
+ describe("deepFreeze", () => {
5
+ let obj: any;
6
+ beforeEach(() => {
7
+ obj = deepFreeze({
8
+ ab: "cde",
9
+ fg: {
10
+ h: true,
11
+ ij: 12,
12
+ },
13
+ k: [
14
+ {
15
+ l: 456,
16
+ mn: ["opqr"],
17
+ },
18
+ ],
19
+ });
20
+ });
21
+ it("prevents mutation of root properties", () => {
22
+ expect(() => {
23
+ obj.ab = "AA";
24
+ }).toThrow();
25
+ });
26
+ it("prevents mutation of nested properties", () => {
27
+ expect(() => {
28
+ obj.fg.h = "AA";
29
+ }).toThrow();
30
+ });
31
+ it("prevents mutation of objects inside nested arrays", () => {
32
+ expect(() => {
33
+ obj.k[0].l = "AA";
34
+ }).toThrow();
35
+ });
36
+ describe("starting with array", () => {
37
+ beforeEach(() => {
38
+ obj = deepFreeze([
39
+ {
40
+ l: 456,
41
+ mn: ["opqr"],
42
+ },
43
+ ]);
44
+ });
45
+ it("prevents mutation of nested objects", () => {
46
+ expect(() => {
47
+ obj[0].l = "AA";
48
+ }).toThrow();
49
+ });
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,14 @@
1
+ export function deepFreeze<U>(obj: U): U {
2
+ if (Array.isArray(obj)) {
3
+ for (const elt of obj) {
4
+ deepFreeze(elt);
5
+ }
6
+ return obj;
7
+ } else if (obj instanceof Object) {
8
+ for (const prop in obj) {
9
+ deepFreeze((obj as Record<string, unknown>)[prop]);
10
+ }
11
+ return Object.freeze(obj);
12
+ }
13
+ return obj;
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./url";
2
+ export * from "./freeze";
3
+ export { computeMapContextDiff } from "./map-context-diff";