@dra2020/baseclient 1.0.15 → 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.
- package/README.md +5 -16
- package/dist/all/all.d.ts +2 -18
- package/dist/baseclient.js +1297 -167
- package/dist/baseclient.js.map +1 -1
- package/dist/filterexpr/filterexpr.d.ts +3 -0
- package/dist/fsm/fsm.d.ts +1 -0
- package/dist/geo/all.d.ts +2 -0
- package/dist/geo/geo.d.ts +67 -0
- package/dist/geo/vfeature.d.ts +4 -0
- package/dist/ot-js/otsession.d.ts +3 -0
- package/dist/poly/union.d.ts +1 -0
- package/docs/filterexpr.md +22 -0
- package/lib/all/all.ts +2 -22
- package/lib/filterexpr/filterexpr.ts +79 -5
- package/lib/fsm/fsm.ts +12 -2
- package/lib/geo/all.ts +2 -0
- package/lib/geo/geo.ts +452 -0
- package/lib/geo/vfeature.ts +34 -0
- package/lib/ot-js/otsession.ts +4 -1
- package/lib/poly/hash.ts +1 -1
- package/lib/poly/polypack.ts +12 -2
- package/lib/poly/topo.ts +26 -41
- package/lib/poly/union.ts +17 -0
- package/package.json +17 -5
- package/dist/all/allclient.d.ts +0 -18
- package/dist/base.js +0 -33010
- package/dist/base.js.map +0 -1
- package/dist/dbabstract/all.d.ts +0 -1
- package/dist/dbabstract/db.d.ts +0 -83
- package/dist/dbdynamo/all.d.ts +0 -1
- package/dist/dbdynamo/dbdynamo.d.ts +0 -190
- package/dist/fsmfile/all.d.ts +0 -1
- package/dist/fsmfile/fsmfile.d.ts +0 -47
- package/dist/jsonstream/all.d.ts +0 -1
- package/dist/jsonstream/jsonstream.d.ts +0 -130
- package/dist/lambda/all.d.ts +0 -1
- package/dist/lambda/env.d.ts +0 -10
- package/dist/lambda/lambda.d.ts +0 -18
- package/dist/logserver/all.d.ts +0 -5
- package/dist/logserver/log.d.ts +0 -11
- package/dist/logserver/logaccum.d.ts +0 -154
- package/dist/logserver/logblob.d.ts +0 -24
- package/dist/logserver/logconcat.d.ts +0 -55
- package/dist/logserver/logkey.d.ts +0 -28
- package/dist/memsqs/all.d.ts +0 -4
- package/dist/memsqs/client.d.ts +0 -13
- package/dist/memsqs/loopback.d.ts +0 -11
- package/dist/memsqs/orderedlist.d.ts +0 -19
- package/dist/memsqs/queue.d.ts +0 -84
- package/dist/memsqs/server.d.ts +0 -37
- package/dist/storage/all.d.ts +0 -4
- package/dist/storage/datablob.d.ts +0 -9
- package/dist/storage/env.d.ts +0 -10
- package/dist/storage/splitsblob.d.ts +0 -13
- package/dist/storage/storage.d.ts +0 -166
- package/dist/storages3/all.d.ts +0 -1
- package/dist/storages3/s3.d.ts +0 -62
- package/docs/dbabstract.md +0 -2
- package/docs/dbdynamo.md +0 -2
- package/docs/fsmfile.md +0 -2
- package/docs/jsonstream.md +0 -44
- package/docs/lambda.md +0 -2
- package/docs/logserver.md +0 -2
- package/docs/storage.md +0 -2
- package/docs/storages3.md +0 -2
- package/lib/all/allclient.ts +0 -19
- package/lib/dbabstract/all.ts +0 -1
- package/lib/dbabstract/db.ts +0 -246
- package/lib/dbdynamo/all.ts +0 -1
- package/lib/dbdynamo/dbdynamo.ts +0 -1551
- package/lib/fsmfile/all.ts +0 -1
- package/lib/fsmfile/fsmfile.ts +0 -236
- package/lib/jsonstream/all.ts +0 -1
- package/lib/jsonstream/jsonstream.ts +0 -940
- package/lib/lambda/all.ts +0 -1
- package/lib/lambda/env.ts +0 -13
- package/lib/lambda/lambda.ts +0 -120
- package/lib/logserver/all.ts +0 -5
- package/lib/logserver/log.ts +0 -565
- package/lib/logserver/logaccum.ts +0 -1445
- package/lib/logserver/logblob.ts +0 -84
- package/lib/logserver/logconcat.ts +0 -313
- package/lib/logserver/logkey.ts +0 -125
- package/lib/memsqs/all.ts +0 -4
- package/lib/memsqs/client.ts +0 -268
- package/lib/memsqs/loopback.ts +0 -64
- package/lib/memsqs/orderedlist.ts +0 -74
- package/lib/memsqs/queue.ts +0 -395
- package/lib/memsqs/server.ts +0 -262
- package/lib/storage/all.ts +0 -4
- package/lib/storage/datablob.ts +0 -36
- package/lib/storage/env.ts +0 -14
- package/lib/storage/splitsblob.ts +0 -63
- package/lib/storage/storage.ts +0 -604
- package/lib/storages3/all.ts +0 -1
- package/lib/storages3/s3.ts +0 -576
package/lib/geo/geo.ts
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import * as geojson from 'geojson';
|
|
2
|
+
import * as Util from '../util/all'
|
|
3
|
+
import * as Poly from '../poly/all'
|
|
4
|
+
|
|
5
|
+
export type GeoProperties = geojson.GeoJsonProperties;
|
|
6
|
+
export type GeoFeature = geojson.Feature;
|
|
7
|
+
export type GeoFeatureArray = GeoFeature[];
|
|
8
|
+
export type GeoFeatureCollection = geojson.FeatureCollection;
|
|
9
|
+
|
|
10
|
+
export function geoCollectionToMap(col: GeoFeatureCollection): GeoFeatureMap
|
|
11
|
+
{
|
|
12
|
+
if (col == null) return null;
|
|
13
|
+
let map: GeoFeatureMap = {};
|
|
14
|
+
col.features.forEach((f: GeoFeature) => { map[String(f.properties.id)] = f; });
|
|
15
|
+
return map;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function geoMapToCollection(map: GeoFeatureMap): GeoFeatureCollection
|
|
19
|
+
{
|
|
20
|
+
if (map == null || Util.countKeys(map) == 0) return null;
|
|
21
|
+
let col: GeoFeatureCollection = { type: 'FeatureCollection', features: [] };
|
|
22
|
+
Object.keys(map).forEach((geoid: string) => { col.features.push(map[geoid]) });
|
|
23
|
+
return col;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function geoCollectionToTopo(col: GeoFeatureCollection): Poly.Topo
|
|
27
|
+
{
|
|
28
|
+
let topo = Poly.topoFromCollection(col);
|
|
29
|
+
Poly.topoPack(topo);
|
|
30
|
+
return topo;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function geoTopoToCollection(topo: Poly.Topo): GeoFeatureCollection
|
|
34
|
+
{
|
|
35
|
+
let col = Poly.topoToCollection(topo);
|
|
36
|
+
Poly.featurePack(col);
|
|
37
|
+
return col;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GeoFeatureMap
|
|
41
|
+
{
|
|
42
|
+
[id: string]: GeoFeature; // Maps id to GeoFeature
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type FeatureFunc = (f: GeoFeature) => void;
|
|
46
|
+
|
|
47
|
+
interface GeoEntry
|
|
48
|
+
{
|
|
49
|
+
tag: string;
|
|
50
|
+
col?: GeoFeatureCollection;
|
|
51
|
+
map?: GeoFeatureMap;
|
|
52
|
+
topo?: Poly.Topo;
|
|
53
|
+
}
|
|
54
|
+
type GeoEntryMap = { [tag: string]: GeoEntry };
|
|
55
|
+
|
|
56
|
+
export function geoEqual(m1: GeoMultiCollection, m2: GeoMultiCollection): boolean
|
|
57
|
+
{
|
|
58
|
+
let n1 = m1 ? m1.length : 0;
|
|
59
|
+
let n2 = m2 ? m2.length : 0;
|
|
60
|
+
|
|
61
|
+
if (n1 != n2) return false;
|
|
62
|
+
if (n1 == 0) return true;
|
|
63
|
+
|
|
64
|
+
let n = 0;
|
|
65
|
+
let eq = true;
|
|
66
|
+
m1.forEach(f => { if (eq && !m2.find(f.properties.id)) eq = false });
|
|
67
|
+
return eq;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function geoMapEqual(m1: GeoFeatureMap, m2: GeoFeatureMap): boolean
|
|
71
|
+
{
|
|
72
|
+
if (m1 == null) return Util.isEmpty(m2);
|
|
73
|
+
if (m2 == null) return Util.isEmpty(m1);
|
|
74
|
+
let p: string;
|
|
75
|
+
for (p in m1) if (m1.hasOwnProperty(p))
|
|
76
|
+
if (m1[p] !== m2[p])
|
|
77
|
+
return false;
|
|
78
|
+
for (p in m2) if (m2.hasOwnProperty(p))
|
|
79
|
+
if (m1[p] === undefined)
|
|
80
|
+
return false;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class GeoMultiCollection
|
|
85
|
+
{
|
|
86
|
+
entries: GeoEntryMap;
|
|
87
|
+
all: GeoEntry;
|
|
88
|
+
hidden: any;
|
|
89
|
+
stamp: number;
|
|
90
|
+
|
|
91
|
+
constructor(tag?: string, topo?: Poly.Topo, col?: GeoFeatureCollection, map?: GeoFeatureMap)
|
|
92
|
+
{
|
|
93
|
+
this.stamp = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER / 2);
|
|
94
|
+
this.empty();
|
|
95
|
+
if (tag)
|
|
96
|
+
this.add(tag, topo, col, map);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
empty()
|
|
100
|
+
{
|
|
101
|
+
this.entries = {};
|
|
102
|
+
this.hidden = {};
|
|
103
|
+
this._onChange();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get nEntries(): number { return Util.countKeys(this.entries) }
|
|
107
|
+
|
|
108
|
+
nthEntry(n: number): GeoEntry
|
|
109
|
+
{
|
|
110
|
+
return Util.nthProperty(this.entries, n) as GeoEntry;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
add(tag: string, topo: Poly.Topo, col: GeoFeatureCollection, map: GeoFeatureMap): void
|
|
114
|
+
{
|
|
115
|
+
let entry = this.entries[tag];
|
|
116
|
+
if (entry === undefined) entry = { tag: tag }, this.entries[tag] = entry;
|
|
117
|
+
if ((topo && entry.topo !== topo) || (col && entry.col !== col) || (map && entry.map !== map))
|
|
118
|
+
{
|
|
119
|
+
entry.topo = topo;
|
|
120
|
+
entry.col = col;
|
|
121
|
+
entry.map = map;
|
|
122
|
+
this._onChange();
|
|
123
|
+
}
|
|
124
|
+
else if (topo == null && col == null && map == null)
|
|
125
|
+
this.remove(tag);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
addMulti(multi: GeoMultiCollection): void
|
|
129
|
+
{
|
|
130
|
+
multi.forEachEntry(e => {
|
|
131
|
+
this.add(e.tag, e.topo, e.col, e.map);
|
|
132
|
+
});
|
|
133
|
+
for (let p in multi.hidden) if (multi.hidden.hasOwnProperty(p))
|
|
134
|
+
{
|
|
135
|
+
this.hidden[p] = true;
|
|
136
|
+
this._onChange();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
remove(tag: string): void
|
|
141
|
+
{
|
|
142
|
+
let entry = this.entries[tag];
|
|
143
|
+
if (entry)
|
|
144
|
+
{
|
|
145
|
+
if (entry.topo || entry.col || entry.map)
|
|
146
|
+
this._onChange();
|
|
147
|
+
delete this.entries[tag];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_onChange(): void
|
|
152
|
+
{
|
|
153
|
+
this.all = { tag: 'all' };
|
|
154
|
+
this.stamp++;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_col(e: GeoEntry): GeoFeatureCollection
|
|
158
|
+
{
|
|
159
|
+
if (e == null) return null;
|
|
160
|
+
if (! e.col)
|
|
161
|
+
{
|
|
162
|
+
if (e.map)
|
|
163
|
+
e.col = geoMapToCollection(e.map);
|
|
164
|
+
else if (e.topo)
|
|
165
|
+
e.col = geoTopoToCollection(e.topo);
|
|
166
|
+
}
|
|
167
|
+
return e.col;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_map(e: GeoEntry): GeoFeatureMap
|
|
171
|
+
{
|
|
172
|
+
if (e == null) return null;
|
|
173
|
+
if (! e.map)
|
|
174
|
+
{
|
|
175
|
+
if (e.col)
|
|
176
|
+
e.map = geoCollectionToMap(e.col);
|
|
177
|
+
else if (e.topo)
|
|
178
|
+
{
|
|
179
|
+
e.col = geoTopoToCollection(e.topo);
|
|
180
|
+
e.map = geoCollectionToMap(e.col);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return e.map;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_topo(e: GeoEntry): Poly.Topo
|
|
187
|
+
{
|
|
188
|
+
if (e == null) return null;
|
|
189
|
+
if (! e.topo)
|
|
190
|
+
{
|
|
191
|
+
if (e.col)
|
|
192
|
+
e.topo = geoCollectionToTopo(e.col);
|
|
193
|
+
else if (e.map)
|
|
194
|
+
{
|
|
195
|
+
e.col = geoMapToCollection(e.map);
|
|
196
|
+
e.topo = geoCollectionToTopo(e.col);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return e.topo;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
colOf(tag: string): GeoFeatureCollection { return this._col(this.entries[tag]); }
|
|
203
|
+
mapOf(tag: string): GeoFeatureMap { return this._map(this.entries[tag]); }
|
|
204
|
+
topoOf(tag: string): Poly.Topo { return this._topo(this.entries[tag]); }
|
|
205
|
+
|
|
206
|
+
forEachEntry(cb: (e: GeoEntry) => void): void
|
|
207
|
+
{
|
|
208
|
+
Object.values(this.entries).forEach(cb);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
allCol(): GeoFeatureCollection
|
|
212
|
+
{
|
|
213
|
+
if (this.nEntries == 0) return null;
|
|
214
|
+
if (! this.all.col)
|
|
215
|
+
{
|
|
216
|
+
// optimise case where one entry
|
|
217
|
+
let n = this.nEntries;
|
|
218
|
+
if (n == 1)
|
|
219
|
+
this.all.col = this._col(this.nthEntry(0));
|
|
220
|
+
else
|
|
221
|
+
// Going from map to collection guarantees that any duplicates are removed
|
|
222
|
+
this.all.col = geoMapToCollection(this.allMap());
|
|
223
|
+
}
|
|
224
|
+
return this.all.col;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
allMap(): GeoFeatureMap
|
|
228
|
+
{
|
|
229
|
+
if (this.nEntries == 0) return null;
|
|
230
|
+
if (! this.all.map)
|
|
231
|
+
{
|
|
232
|
+
// optimise case where one entry
|
|
233
|
+
let n = this.nEntries;
|
|
234
|
+
if (n == 1)
|
|
235
|
+
this.all.map = this._map(this.nthEntry(0));
|
|
236
|
+
else
|
|
237
|
+
{
|
|
238
|
+
let map: GeoFeatureMap = {};
|
|
239
|
+
this.all.map = map;
|
|
240
|
+
this.forEach(f => { map[String(f.properties.id)] = f });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return this.all.map;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
allTopo(): Poly.Topo
|
|
247
|
+
{
|
|
248
|
+
if (this.nEntries == 0) return null;
|
|
249
|
+
if (! this.all.topo)
|
|
250
|
+
{
|
|
251
|
+
// optimise case where one entry
|
|
252
|
+
let n = this.nEntries;
|
|
253
|
+
if (n == 1)
|
|
254
|
+
this.all.topo = this._topo(this.nthEntry(0));
|
|
255
|
+
else
|
|
256
|
+
this.all.topo = geoCollectionToTopo(this.allCol());
|
|
257
|
+
}
|
|
258
|
+
return this.all.topo;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
hide(id: any): void
|
|
262
|
+
{
|
|
263
|
+
if (id)
|
|
264
|
+
{
|
|
265
|
+
if (typeof id === 'string')
|
|
266
|
+
this.hidden[id] = true;
|
|
267
|
+
else if (Array.isArray(id))
|
|
268
|
+
id.forEach((i: string) => { this.hidden[i] = true })
|
|
269
|
+
else if (typeof id === 'object')
|
|
270
|
+
for (let p in id) if (id.hasOwnProperty(p)) this.hidden[p] = true;
|
|
271
|
+
this._onChange();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
show(id: any): void
|
|
276
|
+
{
|
|
277
|
+
if (id)
|
|
278
|
+
{
|
|
279
|
+
if (typeof id === 'string')
|
|
280
|
+
delete this.hidden[id];
|
|
281
|
+
else if (Array.isArray(id))
|
|
282
|
+
id.forEach((i: string) => { delete this.hidden[i] })
|
|
283
|
+
else if (typeof id === 'object')
|
|
284
|
+
for (let p in id) if (id.hasOwnProperty(p)) delete this.hidden[p];
|
|
285
|
+
this._onChange();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
showAll(): void
|
|
290
|
+
{
|
|
291
|
+
if (! Util.isEmpty(this.hidden))
|
|
292
|
+
{
|
|
293
|
+
this.hidden = {};
|
|
294
|
+
this._onChange();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
get length(): number
|
|
299
|
+
{
|
|
300
|
+
let n = 0;
|
|
301
|
+
this.forEachEntry(e => {
|
|
302
|
+
if (e.col)
|
|
303
|
+
n += e.col.features.length;
|
|
304
|
+
else if (e.map)
|
|
305
|
+
n += Util.countKeys(e.map);
|
|
306
|
+
else if (e.topo)
|
|
307
|
+
n += Util.countKeys(e.topo.objects);
|
|
308
|
+
});
|
|
309
|
+
return n;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Use forEach in preference to iteration using this function
|
|
313
|
+
nthFeature(n: number): GeoFeature
|
|
314
|
+
{
|
|
315
|
+
let found: GeoFeature;
|
|
316
|
+
|
|
317
|
+
if (n >= 0)
|
|
318
|
+
this.forEachEntry(e => {
|
|
319
|
+
if (found) return;
|
|
320
|
+
let col = this._col(e);
|
|
321
|
+
if (col)
|
|
322
|
+
if (n > col.features.length)
|
|
323
|
+
n -= col.features.length;
|
|
324
|
+
else
|
|
325
|
+
found = col.features[n];
|
|
326
|
+
});
|
|
327
|
+
return found;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
nthFilteredFeature(n: number, cb: (f: GeoFeature) => boolean)
|
|
331
|
+
{
|
|
332
|
+
let found: GeoFeature;
|
|
333
|
+
|
|
334
|
+
this.forEachEntry(e => {
|
|
335
|
+
if (found) return;
|
|
336
|
+
let col = this._col(e);
|
|
337
|
+
if (col)
|
|
338
|
+
for (let i = 0; !found && i < col.features.length; i++)
|
|
339
|
+
{
|
|
340
|
+
let f = col.features[i];
|
|
341
|
+
if (this.hidden[f.properties.id] === undefined && cb(f))
|
|
342
|
+
{
|
|
343
|
+
if (n === 0)
|
|
344
|
+
{
|
|
345
|
+
found = f;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
n--;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return found;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
forEach(cb: FeatureFunc): void
|
|
356
|
+
{
|
|
357
|
+
this.forEachEntry(e => {
|
|
358
|
+
let col = this._col(e);
|
|
359
|
+
if (e.col)
|
|
360
|
+
e.col.features.forEach(f => { if (this.hidden[f.properties.id] === undefined) cb(f) })
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
map(cb: (f: GeoFeature) => GeoFeature): GeoFeature[]
|
|
365
|
+
{
|
|
366
|
+
let features: GeoFeature[] = [];
|
|
367
|
+
this.forEach((f: GeoFeature) => { features.push(cb(f)) });
|
|
368
|
+
return features;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
isHidden(id: string): boolean
|
|
372
|
+
{
|
|
373
|
+
return this.hidden[id] !== undefined;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
find(id: string): GeoFeature
|
|
377
|
+
{
|
|
378
|
+
if (this.hidden[id] !== undefined)
|
|
379
|
+
return undefined;
|
|
380
|
+
|
|
381
|
+
let entries = Object.values(this.entries);
|
|
382
|
+
for (let i = 0; i < entries.length; i++)
|
|
383
|
+
{
|
|
384
|
+
let map = this._map(entries[i]);
|
|
385
|
+
if (map[id])
|
|
386
|
+
return map[id];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
filter(test: (f: GeoFeature) => boolean): GeoMultiCollection
|
|
393
|
+
{
|
|
394
|
+
let m = new GeoMultiCollection();
|
|
395
|
+
this.forEachEntry(e => {
|
|
396
|
+
let col = this._col(e);
|
|
397
|
+
let features = col ? col.features.filter(test) : null;
|
|
398
|
+
if (features && features.length)
|
|
399
|
+
m.add(e.tag, null, { type: 'FeatureCollection', features: features }, null);
|
|
400
|
+
});
|
|
401
|
+
return m;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export enum geoIntersectOptions { Intersects, Bounds, BoundsCenter };
|
|
406
|
+
|
|
407
|
+
function geoBoxIntersect(x1: Poly.BoundBox, x2: Poly.BoundBox, opt: geoIntersectOptions): boolean
|
|
408
|
+
{
|
|
409
|
+
if (x1.left === undefined || x2.left === undefined) return false;
|
|
410
|
+
|
|
411
|
+
let l1 = x1.left;
|
|
412
|
+
let l2 = x2.left;
|
|
413
|
+
let r1 = x1.right;
|
|
414
|
+
let r2 = x2.right;
|
|
415
|
+
let b1 = x1.top; // flip
|
|
416
|
+
let b2 = x2.top; // flip
|
|
417
|
+
let t1 = x1.bottom; // flip
|
|
418
|
+
let t2 = x2.bottom; // flip
|
|
419
|
+
let cx2 = l2 + (r2 - l2) / 2;
|
|
420
|
+
let cy2 = t2 + (b2 - t2) / 2;
|
|
421
|
+
|
|
422
|
+
// Note I flipped top and bottom above when extracting,
|
|
423
|
+
// in order to make below logic work for normal y axis alignment (0 at top).
|
|
424
|
+
switch (opt)
|
|
425
|
+
{
|
|
426
|
+
case geoIntersectOptions.Intersects:
|
|
427
|
+
return !(l2 > r1 || r2 < l1 || t2 > b1 || b2 < t1);
|
|
428
|
+
case geoIntersectOptions.Bounds:
|
|
429
|
+
return l1 <= l2 && t1 <= t2 && r1 >= r2 && b1 >= b2;
|
|
430
|
+
case geoIntersectOptions.BoundsCenter:
|
|
431
|
+
return l1 <= cx2 && t1 <= cy2 && r1 >= cx2 && b1 >= cy2;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function geoIntersect(multi: GeoMultiCollection, bbox: Poly.BoundBox, opt: geoIntersectOptions): GeoMultiCollection
|
|
436
|
+
{
|
|
437
|
+
let m: GeoFeatureMap = {};
|
|
438
|
+
let bboxPoly = Poly.boundboxPoly(bbox);
|
|
439
|
+
|
|
440
|
+
multi.forEach((f: GeoFeature) => {
|
|
441
|
+
let box = Poly.boundbox(f);
|
|
442
|
+
if (geoBoxIntersect(bbox, box, opt))
|
|
443
|
+
{
|
|
444
|
+
if (opt !== geoIntersectOptions.Intersects || Poly.polyIntersects(bboxPoly, f))
|
|
445
|
+
m[f.properties.id] = f;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
let result = new GeoMultiCollection();
|
|
450
|
+
result.add('result', null, null, m);
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as geojson from 'geojson';
|
|
2
|
+
import * as Util from '../util/all'
|
|
3
|
+
import * as Poly from '../poly/all'
|
|
4
|
+
import * as G from './geo';
|
|
5
|
+
|
|
6
|
+
// Given the topology for a precinct, the bintrie mapping and the list of blocks, construct the
|
|
7
|
+
// feature data for the virtual feature.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
export function computeVFeature(topoPrecinct: Poly.Topo, bintrie: Util.BinTrie, blocks: string[]): G.GeoFeature
|
|
11
|
+
{
|
|
12
|
+
let contiguity = new Util.IndexedArray();
|
|
13
|
+
let block_contiguity = new Util.IndexedArray();
|
|
14
|
+
let f = Poly.topoMerge(topoPrecinct, blocks);
|
|
15
|
+
f.properties.datasets = {};
|
|
16
|
+
blocks.forEach(blockid => {
|
|
17
|
+
let b = topoPrecinct.objects[blockid];
|
|
18
|
+
if (b.properties.datasets)
|
|
19
|
+
Util.deepAccum(f.properties.datasets, b.properties.datasets);
|
|
20
|
+
if (b.properties.contiguity)
|
|
21
|
+
{
|
|
22
|
+
b.properties.contiguity.forEach((id: string) => {
|
|
23
|
+
contiguity.set(id === 'OUT_OF_STATE' ? id : bintrie.get(id));
|
|
24
|
+
});
|
|
25
|
+
b.properties.contiguity.forEach((id: string) => {
|
|
26
|
+
block_contiguity.set(id);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
f.properties.contiguity = contiguity.asArray();
|
|
31
|
+
f.properties.block_contiguity = block_contiguity.asArray();
|
|
32
|
+
f.properties.blocks = blocks;
|
|
33
|
+
return f;
|
|
34
|
+
}
|
package/lib/ot-js/otsession.ts
CHANGED
|
@@ -23,7 +23,8 @@ export const FilterRecent: number = 3;
|
|
|
23
23
|
export const FilterTrash: number = 4;
|
|
24
24
|
export const FilterPublic: number = 5;
|
|
25
25
|
export const FilterOfficial: number = 6;
|
|
26
|
-
export const
|
|
26
|
+
export const FilterCOI: number = 7;
|
|
27
|
+
export const FilterCount: number = 8;
|
|
27
28
|
export type Filter = number;
|
|
28
29
|
|
|
29
30
|
// Permissions
|
|
@@ -111,6 +112,8 @@ export interface SessionProps
|
|
|
111
112
|
loadFailed: boolean;
|
|
112
113
|
accessMap: AccessMap;
|
|
113
114
|
revisions: RevisionList;
|
|
115
|
+
expunged?: boolean;
|
|
116
|
+
expungeDate?: string;
|
|
114
117
|
xprops?: { [prop: string]: string };
|
|
115
118
|
}
|
|
116
119
|
|
package/lib/poly/hash.ts
CHANGED
package/lib/poly/polypack.ts
CHANGED
|
@@ -221,7 +221,8 @@ export function polyPack(coords: any, prepack?: PolyPack): PolyPack
|
|
|
221
221
|
|
|
222
222
|
// Transparently handle polygon or multi-polygon
|
|
223
223
|
let depth = Util.depthof(coords);
|
|
224
|
-
if (depth ==
|
|
224
|
+
if (depth == 2) coords = [ [ [ coords ] ] ];
|
|
225
|
+
else if (depth == 3) coords = [ [ coords ] ];
|
|
225
226
|
else if (depth == 4) coords = [ coords ];
|
|
226
227
|
|
|
227
228
|
let nFloats = polyPackSize(coords);
|
|
@@ -374,9 +375,18 @@ export function featureUnpack(f: any): any
|
|
|
374
375
|
if (f && f.geometry && f.geometry.packed !== undefined)
|
|
375
376
|
{
|
|
376
377
|
f.geometry.coordinates = polyUnpack(f.geometry.packed);
|
|
378
|
+
let depth = Util.depthof(f.geometry.coordinates);
|
|
377
379
|
// Check for oops, optimized away the multipolygon in polyUnpack
|
|
378
|
-
if (f.geometry.type === 'MultiPolygon' &&
|
|
380
|
+
if (f.geometry.type === 'MultiPolygon' && depth === 4)
|
|
379
381
|
f.geometry.coordinates = [ f.geometry.coordinates ];
|
|
382
|
+
else if (f.geometry.type === 'Point' && depth != 2)
|
|
383
|
+
{
|
|
384
|
+
while (depth > 2)
|
|
385
|
+
{
|
|
386
|
+
f.geometry.coordinates = f.geometry.coordinates[0];
|
|
387
|
+
depth = Util.depthof(f.geometry.coordinates);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
380
390
|
delete f.geometry.packed;
|
|
381
391
|
}
|
|
382
392
|
else if (f.type && f.type === 'FeatureCollection' && f.features)
|
package/lib/poly/topo.ts
CHANGED
|
@@ -59,19 +59,6 @@ function correctGeometry(f: any): any
|
|
|
59
59
|
{
|
|
60
60
|
let multiPoly = f.geometry.coordinates;
|
|
61
61
|
|
|
62
|
-
/* Comment this out right now - might have been do to not rewinding merge output
|
|
63
|
-
// topojson will under certain circumstances return a MultiPolygon with the first Polygon containing holes
|
|
64
|
-
// that are precisely filled by a subsequent polygon. We make a secondary union pass to try to correct for this.
|
|
65
|
-
// If we have a really degenerate multipolygon (test above some number of polygons) omit this expensive pass
|
|
66
|
-
// since cleanup is unlikely.
|
|
67
|
-
if (multiPoly.length > 1 && multiPoly.length < 50)
|
|
68
|
-
{
|
|
69
|
-
let result = Q.unionPolys(multiPoly);
|
|
70
|
-
if (Util.depthof(result) == 4) result = [ result ];
|
|
71
|
-
multiPoly = result;
|
|
72
|
-
}
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
62
|
// Convert degenerate MultiPolygon to Polygon
|
|
76
63
|
if (multiPoly.length == 1)
|
|
77
64
|
{
|
|
@@ -80,6 +67,15 @@ function correctGeometry(f: any): any
|
|
|
80
67
|
}
|
|
81
68
|
}
|
|
82
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
|
+
|
|
83
79
|
return f;
|
|
84
80
|
}
|
|
85
81
|
|
|
@@ -194,6 +190,11 @@ export interface SimplifyOptions
|
|
|
194
190
|
|
|
195
191
|
const DefaultSimplifyOptions: SimplifyOptions = { minArea: 500 };
|
|
196
192
|
|
|
193
|
+
function log(s: string): void
|
|
194
|
+
{
|
|
195
|
+
//console.log(s);
|
|
196
|
+
}
|
|
197
|
+
|
|
197
198
|
//
|
|
198
199
|
// topoSimplifyCollection:
|
|
199
200
|
// This implements our simplification strategy for block/precinct level shapes. The basic idea is to
|
|
@@ -218,10 +219,10 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
218
219
|
let elapsedTotal = new Util.Elapsed();
|
|
219
220
|
let elapsed = new Util.Elapsed();
|
|
220
221
|
let topo = topoFromCollection(col);
|
|
221
|
-
|
|
222
|
+
log(`topoSimplifyCollection: fromCollection: ${Math.round(elapsed.ms())}ms`);
|
|
222
223
|
elapsed.start();
|
|
223
224
|
topo = TopoSimplify.presimplify(topo, TopoSimplify['sphericalTriangleArea']);
|
|
224
|
-
|
|
225
|
+
log(`topoSimplifyCollection: presimplify: ${Math.round(elapsed.ms())}ms`);
|
|
225
226
|
elapsed.start();
|
|
226
227
|
|
|
227
228
|
// Keep iterating on removing simplification from degenerate shapes
|
|
@@ -240,6 +241,11 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
240
241
|
col.features.forEach((f: any) => {
|
|
241
242
|
let oOld: any = topo.objects[f.properties.id];
|
|
242
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
|
+
|
|
243
249
|
if (! keepTiny.has(f))
|
|
244
250
|
{
|
|
245
251
|
// Walk through each polygon of a multipolygon separately since I may have a large non-degenerate
|
|
@@ -286,13 +292,13 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
294
|
});
|
|
289
|
-
|
|
295
|
+
log(`topoSimplifyCollection: pass ${nTries}: ${nBad} (${nTiny} tiny) of ${col.features.length} features are degenerate`);
|
|
290
296
|
|
|
291
297
|
// If not making progress, keep more points
|
|
292
298
|
if (nBad >= nBadLast)
|
|
293
299
|
{
|
|
294
300
|
keepweight /= 10;
|
|
295
|
-
|
|
301
|
+
log(`topoSimplifyCollection: pass ${nTries}: reducing weight limit to ${keepweight}`);
|
|
296
302
|
}
|
|
297
303
|
nBadLast = nBad;
|
|
298
304
|
|
|
@@ -308,7 +314,7 @@ export function topoSimplifyCollection(col: any, options?: SimplifyOptions): any
|
|
|
308
314
|
nTries++;
|
|
309
315
|
}
|
|
310
316
|
|
|
311
|
-
|
|
317
|
+
log(`topoSimplifyCollection: total elapsed time: ${bigTimeString(elapsedTotal.ms())}`);
|
|
312
318
|
|
|
313
319
|
return col;
|
|
314
320
|
}
|
|
@@ -318,28 +324,7 @@ export function topoMerge(topo: Topo, geoids: string[]): any
|
|
|
318
324
|
if (geoids == null || geoids.length == 0) return null;
|
|
319
325
|
let objects: any[] = [];
|
|
320
326
|
geoids.forEach((geoid) => objects.push(topo.objects[geoid]));
|
|
321
|
-
|
|
322
|
-
P.featureRewind(f);
|
|
323
|
-
|
|
324
|
-
/* Comment out for now - may be due to merge output needing to be rewound
|
|
325
|
-
// If I get a bad output from topoMerge, just do more expensive poly union. This can happen if input polygons
|
|
326
|
-
// are a little funky (in particular, if they double back along the same edge.
|
|
327
|
-
if (selfIntersectFast(f))
|
|
328
|
-
{
|
|
329
|
-
//console.log('topoMerge: patching selfIntersect');
|
|
330
|
-
let polys: any[] = [];
|
|
331
|
-
geoids.forEach((geoid) => polys.push(topoToFeature(topo, geoid).geometry.coordinates));
|
|
332
|
-
let result = Q.unionPolys(polys);
|
|
333
|
-
let depth = Util.depthof(result);
|
|
334
|
-
if (depth === 5 && result.length === 1)
|
|
335
|
-
{
|
|
336
|
-
depth = 4;
|
|
337
|
-
result = result[0];
|
|
338
|
-
}
|
|
339
|
-
f = { type: 'feature', properties: {}, geometry: { type: depth == 4 ? 'Polygon' : 'MultiPolygon', coordinates: result } };
|
|
340
|
-
}
|
|
341
|
-
*/
|
|
342
|
-
return f;
|
|
327
|
+
return correctGeometry({ type: 'Feature', properties: {}, geometry: TopoClient.merge(topo, objects) });
|
|
343
328
|
}
|
|
344
329
|
|
|
345
330
|
export function topoMergeFeatures(topo: Topo, features: any[]): any
|
|
@@ -392,7 +377,7 @@ class FsmIncrementalUnion extends FSM.Fsm
|
|
|
392
377
|
let values = Object.values(map);
|
|
393
378
|
this.work = { nUnion: values.length, nDifference: 0, ms: 0 };
|
|
394
379
|
let elapsed = new Util.Elapsed();
|
|
395
|
-
this.result = topoMergeFeatures(this.key.
|
|
380
|
+
this.result = topoMergeFeatures(this.key.multi.allTopo(), values);
|
|
396
381
|
this.work.ms = elapsed.ms();
|
|
397
382
|
this.map = map;
|
|
398
383
|
}
|
package/lib/poly/union.ts
CHANGED
|
@@ -59,6 +59,23 @@ export function polyIntersects(p1: any, p2: any): boolean
|
|
|
59
59
|
return bIntersects;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export function polyDifference(main: any, parts: any[]): any
|
|
63
|
+
{
|
|
64
|
+
main = PP.polyUnpack(coords(main));
|
|
65
|
+
|
|
66
|
+
// need to explode multipolygon so that "exploded" is a valid multipolygon input to underlying difference routine
|
|
67
|
+
let exploded: any[] = [];
|
|
68
|
+
parts.forEach((p: any) => {
|
|
69
|
+
p = PP.polyUnpack(coords(p));
|
|
70
|
+
if (Util.depthof(p) == 5)
|
|
71
|
+
p.forEach((poly: any) => { exploded.push(poly) });
|
|
72
|
+
else
|
|
73
|
+
exploded.push(p);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return PR.polyRound(_difference(main, exploded));
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
class FsmDifference extends FSM.Fsm
|
|
63
80
|
{
|
|
64
81
|
accum: any;
|