@dra2020/baseclient 1.0.13 → 1.0.16

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/all/all.d.ts +20 -0
  4. package/dist/baseclient.js +10121 -0
  5. package/dist/baseclient.js.map +1 -0
  6. package/dist/context/all.d.ts +1 -0
  7. package/dist/context/context.d.ts +13 -0
  8. package/dist/filterexpr/all.d.ts +1 -0
  9. package/dist/filterexpr/filterexpr.d.ts +67 -0
  10. package/dist/fsm/all.d.ts +1 -0
  11. package/dist/fsm/fsm.d.ts +119 -0
  12. package/dist/geo/all.d.ts +2 -0
  13. package/dist/geo/geo.d.ts +67 -0
  14. package/dist/geo/vfeature.d.ts +4 -0
  15. package/dist/logabstract/all.d.ts +1 -0
  16. package/dist/logabstract/log.d.ts +26 -0
  17. package/dist/logclient/all.d.ts +1 -0
  18. package/dist/logclient/log.d.ts +6 -0
  19. package/dist/ot-editutil/all.d.ts +2 -0
  20. package/dist/ot-editutil/oteditutil.d.ts +14 -0
  21. package/dist/ot-editutil/otmaputil.d.ts +21 -0
  22. package/dist/ot-js/all.d.ts +9 -0
  23. package/dist/ot-js/otarray.d.ts +111 -0
  24. package/dist/ot-js/otclientengine.d.ts +38 -0
  25. package/dist/ot-js/otcomposite.d.ts +37 -0
  26. package/dist/ot-js/otcounter.d.ts +17 -0
  27. package/dist/ot-js/otengine.d.ts +22 -0
  28. package/dist/ot-js/otmap.d.ts +19 -0
  29. package/dist/ot-js/otserverengine.d.ts +38 -0
  30. package/dist/ot-js/otsession.d.ts +114 -0
  31. package/dist/ot-js/ottypes.d.ts +29 -0
  32. package/dist/poly/all.d.ts +15 -0
  33. package/dist/poly/blend.d.ts +1 -0
  34. package/dist/poly/boundbox.d.ts +16 -0
  35. package/dist/poly/cartesian.d.ts +5 -0
  36. package/dist/poly/graham-scan.d.ts +8 -0
  37. package/dist/poly/hash.d.ts +1 -0
  38. package/dist/poly/matrix.d.ts +24 -0
  39. package/dist/poly/minbound.d.ts +1 -0
  40. package/dist/poly/poly.d.ts +52 -0
  41. package/dist/poly/polybin.d.ts +5 -0
  42. package/dist/poly/polylabel.d.ts +7 -0
  43. package/dist/poly/polypack.d.ts +30 -0
  44. package/dist/poly/polyround.d.ts +1 -0
  45. package/dist/poly/polysimplify.d.ts +1 -0
  46. package/dist/poly/quad.d.ts +48 -0
  47. package/dist/poly/selfintersect.d.ts +1 -0
  48. package/dist/poly/shamos.d.ts +1 -0
  49. package/dist/poly/simplify.d.ts +2 -0
  50. package/dist/poly/topo.d.ts +46 -0
  51. package/dist/poly/union.d.ts +49 -0
  52. package/dist/util/all.d.ts +5 -0
  53. package/dist/util/bintrie.d.ts +93 -0
  54. package/dist/util/countedhash.d.ts +19 -0
  55. package/dist/util/gradient.d.ts +15 -0
  56. package/dist/util/indexedarray.d.ts +15 -0
  57. package/dist/util/util.d.ts +68 -0
  58. package/docs/context.md +2 -0
  59. package/docs/filterexpr.md +22 -0
  60. package/docs/fsm.md +243 -0
  61. package/docs/logabstract.md +2 -0
  62. package/docs/logclient.md +2 -0
  63. package/docs/ot-editutil.md +2 -0
  64. package/docs/ot-js.md +95 -0
  65. package/docs/poly.md +103 -0
  66. package/docs/util.md +2 -0
  67. package/lib/all/all.ts +21 -0
  68. package/lib/context/all.ts +1 -0
  69. package/lib/context/context.ts +82 -0
  70. package/lib/filterexpr/all.ts +1 -0
  71. package/lib/filterexpr/filterexpr.ts +699 -0
  72. package/lib/fsm/all.ts +1 -0
  73. package/lib/fsm/fsm.ts +559 -0
  74. package/lib/geo/all.ts +2 -0
  75. package/lib/geo/geo.ts +452 -0
  76. package/lib/geo/vfeature.ts +34 -0
  77. package/lib/logabstract/all.ts +1 -0
  78. package/lib/logabstract/log.ts +55 -0
  79. package/lib/logclient/all.ts +1 -0
  80. package/lib/logclient/log.ts +105 -0
  81. package/lib/ot-editutil/all.ts +2 -0
  82. package/lib/ot-editutil/oteditutil.ts +180 -0
  83. package/lib/ot-editutil/otmaputil.ts +209 -0
  84. package/lib/ot-js/all.ts +9 -0
  85. package/lib/ot-js/otarray.ts +1168 -0
  86. package/lib/ot-js/otclientengine.ts +327 -0
  87. package/lib/ot-js/otcomposite.ts +247 -0
  88. package/lib/ot-js/otcounter.ts +145 -0
  89. package/lib/ot-js/otengine.ts +71 -0
  90. package/lib/ot-js/otmap.ts +144 -0
  91. package/lib/ot-js/otserverengine.ts +329 -0
  92. package/lib/ot-js/otsession.ts +202 -0
  93. package/lib/ot-js/ottypes.ts +98 -0
  94. package/lib/poly/all.ts +15 -0
  95. package/lib/poly/blend.ts +27 -0
  96. package/lib/poly/boundbox.ts +102 -0
  97. package/lib/poly/cartesian.ts +130 -0
  98. package/lib/poly/graham-scan.ts +401 -0
  99. package/lib/poly/hash.ts +15 -0
  100. package/lib/poly/matrix.ts +309 -0
  101. package/lib/poly/minbound.ts +211 -0
  102. package/lib/poly/poly.ts +767 -0
  103. package/lib/poly/polybin.ts +218 -0
  104. package/lib/poly/polylabel.ts +204 -0
  105. package/lib/poly/polypack.ts +468 -0
  106. package/lib/poly/polyround.ts +30 -0
  107. package/lib/poly/polysimplify.ts +24 -0
  108. package/lib/poly/quad.ts +272 -0
  109. package/lib/poly/selfintersect.ts +87 -0
  110. package/lib/poly/shamos.ts +297 -0
  111. package/lib/poly/simplify.ts +119 -0
  112. package/lib/poly/topo.ts +510 -0
  113. package/lib/poly/union.ts +388 -0
  114. package/lib/util/all.ts +5 -0
  115. package/lib/util/bintrie.ts +603 -0
  116. package/lib/util/countedhash.ts +83 -0
  117. package/lib/util/gradient.ts +108 -0
  118. package/lib/util/indexedarray.ts +80 -0
  119. package/lib/util/util.ts +695 -0
  120. package/package.json +15 -16
@@ -0,0 +1,119 @@
1
+ /*
2
+ Adapted from:
3
+ (c) 2017, Vladimir Agafonkin
4
+ Simplify.js, a high-performance JS polyline simplification library
5
+ mourner.github.io/simplify-js
6
+ */
7
+
8
+ export type Point = [ number, number];
9
+
10
+ // square distance between 2 points
11
+ function getSqDist(p1: Point, p2: Point): number
12
+ {
13
+ var dx = p1[0] - p2[0],
14
+ dy = p1[1] - p2[1];
15
+
16
+ return dx * dx + dy * dy;
17
+ }
18
+
19
+ // square distance from a point to a segment
20
+ function getSqSegDist(p: Point, p1: Point, p2: Point): number
21
+ {
22
+ var x = p1[0],
23
+ y = p1[1],
24
+ dx = p2[0] - x,
25
+ dy = p2[1] - y;
26
+
27
+ if (dx !== 0 || dy !== 0) {
28
+
29
+ var t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);
30
+
31
+ if (t > 1) {
32
+ x = p2[0];
33
+ y = p2[1];
34
+
35
+ } else if (t > 0) {
36
+ x += dx * t;
37
+ y += dy * t;
38
+ }
39
+ }
40
+
41
+ dx = p[0] - x;
42
+ dy = p[1] - y;
43
+
44
+ return dx * dx + dy * dy;
45
+ }
46
+
47
+
48
+ // rest of the code doesn't care about point format
49
+
50
+ // basic distance-based simplification
51
+ function simplifyRadialDist(points: Point[], sqTolerance: number): Point[]
52
+ {
53
+ let prevPoint: Point = points[0],
54
+ newPoints: Point[] = [prevPoint],
55
+ point: Point;
56
+
57
+ for (let i = 1, len = points.length; i < len; i++) {
58
+ point = points[i];
59
+
60
+ if (getSqDist(point, prevPoint) > sqTolerance) {
61
+ newPoints.push(point);
62
+ prevPoint = point;
63
+ }
64
+ }
65
+
66
+ if (prevPoint !== point) newPoints.push(point);
67
+
68
+ return newPoints;
69
+ }
70
+
71
+ function simplifyDPStep(points: Point[], first: number, last: number, sqTolerance: number, simplified: Point[]): void
72
+ {
73
+ let maxSqDist: number = sqTolerance,
74
+ index: number;
75
+
76
+ for (let i = first + 1; i < last; i++)
77
+ {
78
+ let sqDist = getSqSegDist(points[i], points[first], points[last]);
79
+
80
+ if (sqDist > maxSqDist)
81
+ {
82
+ index = i;
83
+ maxSqDist = sqDist;
84
+ }
85
+ }
86
+
87
+ if (maxSqDist > sqTolerance)
88
+ {
89
+ if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
90
+ simplified.push(points[index]);
91
+ if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
92
+ }
93
+ }
94
+
95
+ // simplification using Ramer-Douglas-Peucker algorithm
96
+ function simplifyDouglasPeucker(points: Point[], sqTolerance: number): Point[]
97
+ {
98
+ let last: number = points.length - 1;
99
+
100
+ let simplified: Point[] = [points[0]];
101
+ simplifyDPStep(points, 0, last, sqTolerance, simplified);
102
+ simplified.push(points[last]);
103
+
104
+ return simplified;
105
+ }
106
+
107
+ // both algorithms combined for awesome performance
108
+ export function simplify(points: Point[], tolerance: number = 1, highestQuality: boolean = false): Point[]
109
+ {
110
+ if (tolerance == 0 || points.length <= 2)
111
+ return points;
112
+
113
+ let sqTolerance: number = tolerance * tolerance;
114
+
115
+ points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
116
+ points = simplifyDouglasPeucker(points, sqTolerance);
117
+
118
+ return points;
119
+ }
@@ -0,0 +1,510 @@
1
+
2
+ // Forked version that supports packing
3
+ import * as TopoClient from '@dra2020/topojson-client';
4
+ // Forked version that fixes self-looping hole problem
5
+ import * as TopoServer from '@dra2020/topojson-server';
6
+ // Forked version that fixes performance problem
7
+ import * as TopoSimplify from '@dra2020/topojson-simplify';
8
+
9
+ import * as Util from '../util/all';
10
+ import * as FSM from '../fsm/all';
11
+
12
+ import * as P from './poly';
13
+ import * as Q from './quad';
14
+ import * as PP from './polypack';
15
+ import * as PL from './polylabel';
16
+ import { selfIntersectFast } from './shamos';
17
+
18
+ export type Topo = any;
19
+
20
+ function getGEOID(f: any): string
21
+ {
22
+ if (f.features && f.features.length) f = f.features[0];
23
+ else if (Array.isArray(f)) f = f[0];
24
+ if (f.properties.id !== undefined) return 'id';
25
+ if (f.properties.GEOID !== undefined) return 'GEOID';
26
+ if (f.properties.GEOID10 !== undefined) return 'GEOID10';
27
+ }
28
+
29
+ export function topoFromCollection(col: any): Topo
30
+ {
31
+ if (col == null) return null;
32
+ let save = PP.featureUnpackTemporarily(col);
33
+ let prop = getGEOID(col);
34
+ let objects: any = {};
35
+ col.features.forEach((f: any) => objects[f.properties[prop]] = f);
36
+ let topo = TopoServer.topology(objects);
37
+ PP.featureRepack(col, save);
38
+ if (col.datasets)
39
+ (topo as any).datasets = col.datasets;
40
+ return topo;
41
+ }
42
+
43
+ function ringsCancel(outerPoly: any, innerRing: any): boolean
44
+ {
45
+ if (outerPoly.length != 1) return false;
46
+ let outerRing = outerPoly[0];
47
+ if (outerRing.length !== innerRing.length) return false;
48
+ let n = outerRing.length-1;
49
+ let i = 0;
50
+ for (; i <= n; i++, n--)
51
+ if (! Util.shallowEqual(outerRing[i], innerRing[n]))
52
+ return false;
53
+ return true;
54
+ }
55
+
56
+ function correctGeometry(f: any): any
57
+ {
58
+ if (f && f.geometry && f.geometry.type === 'MultiPolygon' && f.geometry.coordinates)
59
+ {
60
+ let multiPoly = f.geometry.coordinates;
61
+
62
+ // Convert degenerate MultiPolygon to Polygon
63
+ if (multiPoly.length == 1)
64
+ {
65
+ f.geometry.type = 'Polygon';
66
+ f.geometry.coordinates = multiPoly[0];
67
+ }
68
+ }
69
+
70
+ if (f && f.geometry && f.geometry.type === 'Point' && f.geometry.coordinates)
71
+ {
72
+ while (Array.isArray(f.geometry.coordinates[0]))
73
+ f.geometry.coordinates = f.geometry.coordinates[0];
74
+ }
75
+ else
76
+ // TopoJSON does not guarantee proper winding order which messes up later processing. Fix it.
77
+ P.featureRewind(f);
78
+
79
+ return f;
80
+ }
81
+
82
+ export function topoContiguity(topo: Topo): any
83
+ {
84
+ let objects: any[] = Object.values(topo.objects);
85
+ let geoid = getGEOID(objects);
86
+ objects.forEach((o: any) => { o.properties.id = o.properties[geoid] });
87
+ let neighbors = TopoClient.neighbors(objects, true);
88
+ let result: any = {};
89
+ result['OUT_OF_STATE'] = [];
90
+ objects.forEach((o: any, i: number) => {
91
+ result[o.properties.id] = neighbors[i].map((j: any) => {
92
+ if (j >= 0)
93
+ return objects[j].properties.id;
94
+ else
95
+ {
96
+ result['OUT_OF_STATE'].push(o.properties.id);
97
+ return 'OUT_OF_STATE';
98
+ }
99
+ });
100
+ });
101
+ return result;
102
+ }
103
+
104
+ export function topoToFeature(topo: Topo, geoid: string): any
105
+ {
106
+ return correctGeometry(TopoClient.feature(topo, topo.objects[geoid]));
107
+ }
108
+
109
+ export function topoToCollection(topo: Topo): any
110
+ {
111
+ let col: any = { type: 'FeatureCollection', features: [] };
112
+ Object.keys(topo.objects).forEach((geoid: string) => {
113
+ col.features.push(topoToFeature(topo, geoid));
114
+ });
115
+ if (topo.datasets) col.datasets = topo.datasets;
116
+ return col;
117
+ }
118
+
119
+ function keepArcs(topo: any, arcs: any, keepweight: number): void
120
+ {
121
+ arcs.forEach((a: any) => {
122
+ if (Array.isArray(a))
123
+ keepArcs(topo, a, keepweight);
124
+ else
125
+ {
126
+ let arc = topo.arcs[a < 0 ? ~a : a];
127
+ arc.forEach((pt: any) => {
128
+ if (pt[2] >= keepweight)
129
+ pt[2] = Infinity;
130
+ });
131
+ }
132
+ });
133
+ }
134
+
135
+ function fullFidelity(topo: any, arcs: any): boolean
136
+ {
137
+ let bFull = true;
138
+
139
+ arcs.forEach((a: any) => {
140
+ if (Array.isArray(a))
141
+ bFull = bFull && fullFidelity(topo, a);
142
+ else
143
+ {
144
+ let arc = topo.arcs[a < 0 ? ~a : a];
145
+ arc.forEach((pt: any) => {
146
+ if (bFull && pt[2] !== Infinity)
147
+ bFull = false;
148
+ });
149
+ }
150
+ });
151
+ return bFull;
152
+ }
153
+
154
+ function misMatchPoly(p1: any, p2: any): boolean
155
+ {
156
+ if (p1 == null || p2 == null || p1.length != p2.length) return true;
157
+ for (let i = 0; i < p1.length; i++)
158
+ if (p1[i] == null || p2[i] == null) return true;
159
+ return false;
160
+ }
161
+
162
+ function misMatchMulti(m1: any, m2: any): boolean
163
+ {
164
+ if (m1 == null || m2 == null || m1.length != m2.length) return true;
165
+ for (let i = 0; i < m1.length; i++)
166
+ if (misMatchPoly(m1[i], m2[i])) return true;
167
+ return false;
168
+ }
169
+
170
+ function misMatchObject(o1: any, o2: any): boolean
171
+ {
172
+ if (o1 == null || o2 == null || o1.type !== o2.type) return true;
173
+ return (o1.type === 'MultiPolygon') ? misMatchMulti(o1.arcs, o2.arcs) : misMatchPoly(o1.arcs, o2.arcs);
174
+ }
175
+
176
+ const MAX_TRIES = 25;
177
+
178
+ function bigTimeString(ms: number): string
179
+ {
180
+ let seconds = Math.trunc(ms / 1000);
181
+ let minutes = Math.trunc(seconds / 60);
182
+ seconds -= minutes * 60;
183
+ return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
184
+ }
185
+
186
+ export interface SimplifyOptions
187
+ {
188
+ minArea?: number,
189
+ }
190
+
191
+ const DefaultSimplifyOptions: SimplifyOptions = { minArea: 500 };
192
+
193
+ function log(s: string): void
194
+ {
195
+ //console.log(s);
196
+ }
197
+
198
+ //
199
+ // topoSimplifyCollection:
200
+ // This implements our simplification strategy for block/precinct level shapes. The basic idea is to
201
+ // ensure that all shapes generated at the block level have non-zero area and do not introduce self-crossing
202
+ // edges. Since topological simplification happens at the edge level, self-crossing is a risk when opposite
203
+ // edges make simplification decisions that cause the edges to cross. Crossing edges result in visual anomalies.
204
+ // In addition, our basic strategy is that if we have well-formed block shapes, all other shape processing
205
+ // (virtual features, precincts, counties, districts) can be done by fast merges that generate well-formed
206
+ // shapes without holes, overlaps or crossing edges.
207
+ // The strategy here is to first just do topological simplification at the default simplification level. We then
208
+ // scan for problematic shapes (zero area, too thin and narrow or self-crossing) and keep additional points on
209
+ // the edges (arcs) for those shapes, adding additional points on each iteration step until we have a good set.
210
+ // For tiny shapes (zero area or short and thin) we just immediately say keep all points since these are mostly
211
+ // shapes with a small number of points anyway.
212
+ //
213
+
214
+ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
215
+ {
216
+ options = Util.shallowAssignImmutable(DefaultSimplifyOptions, options);
217
+
218
+ if (col == null) return null;
219
+ let elapsedTotal = new Util.Elapsed();
220
+ let elapsed = new Util.Elapsed();
221
+ let topo = topoFromCollection(col);
222
+ log(`topoSimplifyCollection: fromCollection: ${Math.round(elapsed.ms())}ms`);
223
+ elapsed.start();
224
+ topo = TopoSimplify.presimplify(topo, TopoSimplify['sphericalTriangleArea']);
225
+ log(`topoSimplifyCollection: presimplify: ${Math.round(elapsed.ms())}ms`);
226
+ elapsed.start();
227
+
228
+ // Keep iterating on removing simplification from degenerate shapes
229
+ let nTries = 1;
230
+ let nBadLast = Number.MAX_VALUE;
231
+ let keepweight = 1E-11;
232
+ let nLabelTolerance = 1E-4;
233
+ let minArea = options.minArea;
234
+ let keepTiny = new WeakMap<any, any>();
235
+ while (true)
236
+ {
237
+ let testtopo = TopoSimplify.simplify(topo, 1E-10);
238
+ elapsed.start();
239
+ let nBad = 0;
240
+ let nTiny = 0;
241
+ col.features.forEach((f: any) => {
242
+ let oOld: any = topo.objects[f.properties.id];
243
+ let oNew: any = testtopo.objects[f.properties.id];
244
+
245
+ // Ignore points
246
+ if (f.geometry && f.geometry.type === 'Point')
247
+ keepTiny.set(f, f);
248
+
249
+ if (! keepTiny.has(f))
250
+ {
251
+ // Walk through each polygon of a multipolygon separately since I may have a large non-degenerate
252
+ // shape combined with degenerate smaller shapes that I want to identify. I do not examine holes
253
+ // separately under the assumption that holes will have matching polygons that would be identified
254
+ // and would cause those shared edges (between the hole and the filling polygon) to be preserved if necessary.
255
+ //
256
+ // I do make a final pass of testing for self-intersection that looks at both polygon and hole edges.
257
+ //
258
+ let arcs = PP.normalizeArcs(oNew.arcs);
259
+ let npoly = PP.countArcPolygons(arcs);
260
+ let bDecided = false;
261
+ let iPoly = 0;
262
+ for (; !bDecided && iPoly < npoly; iPoly++)
263
+ {
264
+ let pp = PP.polyPackTopoArcs(testtopo, arcs, iPoly);
265
+ P.polyRewindRings(pp);
266
+ let a = P.polyArea(pp);
267
+ if (a <= 0)
268
+ {
269
+ keepTiny.set(f, f);
270
+ keepArcs(topo, oOld.arcs, 0); // keeps all points to avoid reprocessing these tiny shapes
271
+ nBad++, nTiny++, bDecided = true;
272
+ }
273
+ else if (a < minArea)
274
+ {
275
+ let d = PL.polyLabel(pp);
276
+ if (d.d < nLabelTolerance)
277
+ {
278
+ keepTiny.set(f, f);
279
+ keepArcs(topo, oOld.arcs, 0); // keeps all points to avoid reprocessing these tiny shapes
280
+ nBad++, nTiny++, bDecided = true;
281
+ }
282
+ }
283
+ }
284
+ if (! bDecided)
285
+ {
286
+ let pp = PP.polyPackTopoArcs(testtopo, arcs);
287
+ if (selfIntersectFast(pp))
288
+ {
289
+ keepArcs(topo, oOld.arcs, keepweight);
290
+ nBad++;
291
+ }
292
+ }
293
+ }
294
+ });
295
+ log(`topoSimplifyCollection: pass ${nTries}: ${nBad} (${nTiny} tiny) of ${col.features.length} features are degenerate`);
296
+
297
+ // If not making progress, keep more points
298
+ if (nBad >= nBadLast)
299
+ {
300
+ keepweight /= 10;
301
+ log(`topoSimplifyCollection: pass ${nTries}: reducing weight limit to ${keepweight}`);
302
+ }
303
+ nBadLast = nBad;
304
+
305
+ if (nBad && nTries > MAX_TRIES)
306
+ console.error(`topoSimplifyCollection: failed to finalize simplify down to zero degenerate features`);
307
+ // If no bad block shapes, or finished trying, just return result
308
+ if (nBad == 0 || nTries > MAX_TRIES)
309
+ {
310
+ col = topoToCollection(testtopo);
311
+ break;
312
+ }
313
+
314
+ nTries++;
315
+ }
316
+
317
+ log(`topoSimplifyCollection: total elapsed time: ${bigTimeString(elapsedTotal.ms())}`);
318
+
319
+ return col;
320
+ }
321
+
322
+ export function topoMerge(topo: Topo, geoids: string[]): any
323
+ {
324
+ if (geoids == null || geoids.length == 0) return null;
325
+ let objects: any[] = [];
326
+ geoids.forEach((geoid) => objects.push(topo.objects[geoid]));
327
+ return correctGeometry({ type: 'Feature', properties: {}, geometry: TopoClient.merge(topo, objects) });
328
+ }
329
+
330
+ export function topoMergeFeatures(topo: Topo, features: any[]): any
331
+ {
332
+ if (features == null || features.length == 0) return null;
333
+ let prop = getGEOID(features);
334
+ return topoMerge(topo, features.map(f => f.properties[prop]));
335
+ }
336
+
337
+ let UniqueState = FSM.FSM_CUSTOM1;
338
+ let FSM_COMPUTING = UniqueState++;
339
+
340
+ class FsmIncrementalUnion extends FSM.Fsm
341
+ {
342
+ options: P.TickOptions;
343
+ key: any;
344
+ map: any; // { [geoid: string]: Feature }
345
+ result: any;
346
+ work: Q.WorkDone;
347
+
348
+ constructor(env: FSM.FsmEnvironment, options: P.TickOptions, key: any, map?: any)
349
+ {
350
+ super(env);
351
+ this.options = options;
352
+ this.key = key;
353
+ this.result = null;
354
+ this.map = null;
355
+ if (map) this.recompute(map);
356
+ }
357
+
358
+ matches(key: any): boolean
359
+ {
360
+ return Util.shallowEqual(this.key, key);
361
+ }
362
+
363
+ recompute(map: any): void
364
+ {
365
+ if (this.map != null && map != null && Util.shallowEqual(map, this.map))
366
+ {
367
+ this.work = { nUnion: 0, nDifference: 0, ms: 0 };
368
+ }
369
+ else if (map == null || Util.isEmpty(map))
370
+ {
371
+ this.work = { nUnion: 0, nDifference: 0, ms: 0 };
372
+ this.result = null;
373
+ this.map = map;
374
+ }
375
+ else
376
+ {
377
+ let values = Object.values(map);
378
+ this.work = { nUnion: values.length, nDifference: 0, ms: 0 };
379
+ let elapsed = new Util.Elapsed();
380
+ this.result = topoMergeFeatures(this.key.multi.allTopo(), values);
381
+ this.work.ms = elapsed.ms();
382
+ this.map = map;
383
+ }
384
+ this.setState(FSM.FSM_DONE);
385
+ }
386
+
387
+ cancel(): void
388
+ {
389
+ this.result = null;
390
+ this.map = null;
391
+ this.setState(FSM.FSM_DONE);
392
+ }
393
+
394
+ tick(): void
395
+ {
396
+ if (this.ready)
397
+ {
398
+ switch (this.state)
399
+ {
400
+ case FSM.FSM_STARTING:
401
+ // never initialized to do work (see recompute())
402
+ this.setState(FSM.FSM_DONE);
403
+ break;
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ export interface TopoUnionResult
410
+ {
411
+ key: any;
412
+ poly: any;
413
+ work: Q.WorkDone;
414
+ }
415
+
416
+ export class FsmTopoUnion extends FSM.Fsm
417
+ {
418
+ options: P.TickOptions;
419
+ unions: FsmIncrementalUnion[];
420
+ work: Q.WorkDone;
421
+
422
+ constructor(env: FSM.FsmEnvironment, options?: P.TickOptions)
423
+ {
424
+ super(env);
425
+ this.options = Util.shallowAssignImmutable(P.DefaultTickOptions, options);
426
+ this.unions = [];
427
+ this.work = { nUnion: 0, nDifference: 0, ms: 0 };
428
+ }
429
+
430
+ get result(): TopoUnionResult[]
431
+ {
432
+ if (this.unions.length > 0 && this.state === FSM.FSM_DONE)
433
+ return this.unions.map((i: FsmIncrementalUnion) => ({ key: i.key, poly: i.result, work: i.work }) );
434
+ else
435
+ return null;
436
+ }
437
+
438
+ cancel(): void
439
+ {
440
+ this.unions.forEach((i: FsmIncrementalUnion) => {
441
+ i.cancel();
442
+ });
443
+ this.unions = [];
444
+ this.setState(FSM.FSM_DONE);
445
+ }
446
+
447
+ cancelOne(key: any): void
448
+ {
449
+ for (let i = 0; i < this.unions.length; i++)
450
+ {
451
+ let u = this.unions[i];
452
+ if (u.matches(key))
453
+ {
454
+ u.cancel();
455
+ return;
456
+ }
457
+ }
458
+ }
459
+
460
+ recompute(key: any, map: any): void
461
+ {
462
+ let fsm: FsmIncrementalUnion = this.unions.find((i: FsmIncrementalUnion) => i.matches(key));
463
+ if (fsm == null)
464
+ {
465
+ fsm = new FsmIncrementalUnion(this.env, this.options, key, map);
466
+ this.unions.push(fsm);
467
+ }
468
+ else
469
+ fsm.recompute(map);
470
+ this.work = { nUnion: 0, nDifference: 0, ms: 0 };
471
+ this.unions.forEach((u) => { this.work.nUnion += u.work.nUnion; this.work.nDifference += u.work.nDifference });
472
+ this.waitOn(fsm);
473
+ this.setState(FSM_COMPUTING);
474
+ }
475
+
476
+ tick(): void
477
+ {
478
+ if (this.ready)
479
+ {
480
+ switch (this.state)
481
+ {
482
+ case FSM.FSM_STARTING:
483
+ case FSM_COMPUTING:
484
+ if (this.unions) this.unions.forEach((u) => { this.work.ms += u.work.ms });
485
+ this.setState(FSM.FSM_DONE);
486
+ break;
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ export function topoPacked(topo: any): boolean
493
+ {
494
+ return topo.packed !== undefined;
495
+ }
496
+
497
+ export function topoPack(topo: any): any
498
+ {
499
+ let tc = TopoClient;
500
+ TopoClient.packArcs(topo);
501
+ TopoClient.packArcIndices(topo);
502
+ return topo;
503
+ }
504
+
505
+ export function topoUnpack(topo: any): any
506
+ {
507
+ TopoClient.unpackArcs(topo);
508
+ TopoClient.unpackArcIndices(topo);
509
+ return topo;
510
+ }