@dra2020/baseclient 1.0.101 → 1.0.103
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/dist/baseclient.js +124 -67
- package/dist/baseclient.js.map +1 -1
- package/dist/poly/all.d.ts +1 -0
- package/dist/poly/featurecleanholes.d.ts +1 -0
- package/dist/poly/topo.d.ts +10 -7
- package/lib/poly/all.ts +1 -0
- package/lib/poly/featurecleanholes.ts +89 -0
- package/lib/poly/poly.ts +19 -50
- package/lib/poly/topo.ts +27 -32
- package/package.json +1 -1
package/dist/poly/all.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function featureCleanHoles(f: any): any;
|
package/dist/poly/topo.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as FSM from '../fsm/all';
|
|
2
|
+
import * as G from '../geo/all';
|
|
2
3
|
import * as P from './poly';
|
|
3
4
|
import * as Q from './quad';
|
|
4
5
|
export declare type Topo = any;
|
|
@@ -15,30 +16,32 @@ export declare function topoMerge(topo: Topo, geoids: string[]): any;
|
|
|
15
16
|
export declare function topoMergeFeatures(topo: Topo, features: any[]): any;
|
|
16
17
|
declare class FsmIncrementalUnion extends FSM.Fsm {
|
|
17
18
|
options: P.TickOptions;
|
|
18
|
-
key:
|
|
19
|
+
key: number;
|
|
20
|
+
multi: G.GeoMultiCollection;
|
|
19
21
|
map: any;
|
|
20
22
|
result: any;
|
|
21
23
|
work: Q.WorkDone;
|
|
22
|
-
constructor(env: FSM.FsmEnvironment, options: P.TickOptions, key:
|
|
23
|
-
matches(key: any): boolean;
|
|
24
|
+
constructor(env: FSM.FsmEnvironment, options: P.TickOptions, multi: G.GeoMultiCollection, key: number, map?: any);
|
|
24
25
|
recompute(map: any): void;
|
|
25
26
|
cancel(): void;
|
|
26
27
|
tick(): void;
|
|
27
28
|
}
|
|
28
29
|
export interface TopoUnionResult {
|
|
29
|
-
key:
|
|
30
|
+
key: number;
|
|
30
31
|
poly: any;
|
|
31
32
|
work: Q.WorkDone;
|
|
32
33
|
}
|
|
33
34
|
export declare class FsmTopoUnion extends FSM.Fsm {
|
|
34
35
|
options: P.TickOptions;
|
|
35
|
-
unions:
|
|
36
|
+
unions: {
|
|
37
|
+
[index: number]: FsmIncrementalUnion;
|
|
38
|
+
};
|
|
36
39
|
work: Q.WorkDone;
|
|
37
40
|
constructor(env: FSM.FsmEnvironment, options?: P.TickOptions);
|
|
38
41
|
get result(): TopoUnionResult[];
|
|
39
42
|
cancel(): void;
|
|
40
|
-
cancelOne(key:
|
|
41
|
-
recompute(key:
|
|
43
|
+
cancelOne(key: number): void;
|
|
44
|
+
recompute(multi: G.GeoMultiCollection, key: number, map: any): void;
|
|
42
45
|
tick(): void;
|
|
43
46
|
}
|
|
44
47
|
export declare function topoPacked(topo: any): boolean;
|
package/lib/poly/all.ts
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { polyIntersects } from './union';
|
|
2
|
+
import { polyArea } from './poly';
|
|
3
|
+
|
|
4
|
+
function flattenMultiPoly(polys: any): any
|
|
5
|
+
{
|
|
6
|
+
let c: any[] = [];
|
|
7
|
+
polys.forEach((poly: any) => {
|
|
8
|
+
poly.forEach((ring: any) => {
|
|
9
|
+
c.push([ring]);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
return c;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Handed a multipolygon that may not have been properly structured as outer rings and inner holes, fix it up.
|
|
16
|
+
// This is relatively expensive since we compute intersection areas to test for holes.
|
|
17
|
+
//
|
|
18
|
+
export function featureCleanHoles(f: any): any
|
|
19
|
+
{
|
|
20
|
+
if (!f
|
|
21
|
+
|| f.type !== 'Feature'
|
|
22
|
+
|| !f.geometry
|
|
23
|
+
|| f.geometry.type !== 'MultiPolygon'
|
|
24
|
+
|| !Array.isArray(f.geometry.coordinates))
|
|
25
|
+
return f;
|
|
26
|
+
|
|
27
|
+
// Normalize to flattened polygon
|
|
28
|
+
f.geometry.coordinates = flattenMultiPoly(f.geometry.coordinates);
|
|
29
|
+
|
|
30
|
+
let polys = f.geometry.coordinates;
|
|
31
|
+
let areas: number[] = polys.map(polyArea);
|
|
32
|
+
// Compute 'top triangle' of overlap grid, then reflect
|
|
33
|
+
let overlaps: boolean[][] = polys.map((p1: any, i: number) => {
|
|
34
|
+
return polys.map((p2: any, j: number) => { return j <= i ? false : polyIntersects(p1, p2) });
|
|
35
|
+
});
|
|
36
|
+
// Now reflect
|
|
37
|
+
let n = polys.length;
|
|
38
|
+
for (let i = 0; i < n-1; i++)
|
|
39
|
+
for (let j = i+1; j < n; j++)
|
|
40
|
+
overlaps[j][i] = overlaps[i][j];
|
|
41
|
+
|
|
42
|
+
// The outer polygon is one that
|
|
43
|
+
// 1. Overlaps with this one
|
|
44
|
+
// 2. Has a larger area than this one
|
|
45
|
+
// 3. Has the smallest area of any that overlap
|
|
46
|
+
// 4. There are an odd number of polygons that satisfy 1 and 2. (This allows nesting of polygons inside holes.)
|
|
47
|
+
//
|
|
48
|
+
function findOuter(i: number): number
|
|
49
|
+
{
|
|
50
|
+
let jOuter = -1;
|
|
51
|
+
let aOuter = 0;
|
|
52
|
+
let nOverlap = 0;
|
|
53
|
+
|
|
54
|
+
for (let j = 0; j < n; j++)
|
|
55
|
+
if (overlaps[i][j] && areas[j] > areas[i])
|
|
56
|
+
{
|
|
57
|
+
nOverlap++;
|
|
58
|
+
if (jOuter < 0 || areas[j] < aOuter)
|
|
59
|
+
{
|
|
60
|
+
jOuter = j;
|
|
61
|
+
aOuter = areas[j];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return (nOverlap % 2) == 1 ? jOuter : -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Walk through and match up holes with their containers
|
|
68
|
+
for (let i = 0; i < n; i++)
|
|
69
|
+
{
|
|
70
|
+
let j = findOuter(i);
|
|
71
|
+
if (j >= 0)
|
|
72
|
+
{
|
|
73
|
+
polys[j].push(polys[i][0]);
|
|
74
|
+
polys[i] = [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Delete outer shell of hole rings that were moved
|
|
79
|
+
f.geometry.coordinates = polys.filter((p: any[]) => p.length > 0);
|
|
80
|
+
|
|
81
|
+
// Degenerate multi-polygon
|
|
82
|
+
if (f.geometry.coordinates.length == 1)
|
|
83
|
+
{
|
|
84
|
+
f.geometry.type = 'Polygon';
|
|
85
|
+
f.geometry.coordinates = f.geometry.coordinates[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return f;
|
|
89
|
+
}
|
package/lib/poly/poly.ts
CHANGED
|
@@ -2,6 +2,8 @@ import * as Util from '../util/all';
|
|
|
2
2
|
|
|
3
3
|
import * as PP from './polypack';
|
|
4
4
|
import { makeConvexHullGrahamScan } from './graham-scan';
|
|
5
|
+
import { polyIntersects } from './union';
|
|
6
|
+
import { featureCleanHoles } from './featurecleanholes';
|
|
5
7
|
|
|
6
8
|
// For incremental poly interface
|
|
7
9
|
export interface TickOptions
|
|
@@ -667,7 +669,7 @@ function closePoly(poly: any): any
|
|
|
667
669
|
return poly;
|
|
668
670
|
}
|
|
669
671
|
|
|
670
|
-
function
|
|
672
|
+
function featureClose(f: any): void
|
|
671
673
|
{
|
|
672
674
|
let d = Util.depthof(f.geometry.coordinates);
|
|
673
675
|
if (d === 4) closePoly(f.geometry.coordinates);
|
|
@@ -738,6 +740,16 @@ export function polyRingWindings(poly: any): Winding[]
|
|
|
738
740
|
return windings;
|
|
739
741
|
}
|
|
740
742
|
|
|
743
|
+
function flattenMultiPoly(polys: any): any
|
|
744
|
+
{
|
|
745
|
+
let c: any[] = [];
|
|
746
|
+
polys.forEach((poly: any) => {
|
|
747
|
+
poly.forEach((ring: any) => {
|
|
748
|
+
c.push([ring]);
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
return c;
|
|
752
|
+
}
|
|
741
753
|
|
|
742
754
|
// This mutates the passed in feature to ensure it is correctly wound
|
|
743
755
|
// For convenience, passed back the value provided.
|
|
@@ -756,61 +768,18 @@ export function featureRewind(f: any, options?: RewindOptions): any
|
|
|
756
768
|
if (f.geometry.type !== 'Polygon' && f.geometry.type !== 'MultiPolygon') return canonicalPoint(f, options);
|
|
757
769
|
|
|
758
770
|
// Make sure polygon is closed (first === last)
|
|
759
|
-
if (options.validateClose)
|
|
771
|
+
if (options.validateClose)
|
|
772
|
+
featureClose(f);
|
|
760
773
|
|
|
761
774
|
// Check if multi-polygon is really polygon with holes
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
let windings = polyRingWindings(f);
|
|
765
|
-
if (options.validateHoles && d === 5 && windings.length > 1)
|
|
766
|
-
{
|
|
767
|
-
// First winding needs to be correct so we have a poly to add holes to
|
|
768
|
-
if (!misWound(windings[0]))
|
|
769
|
-
{
|
|
770
|
-
// Flatten everything out and then rebuild hole structure based on windings
|
|
771
|
-
let nHoles = 0; windings.forEach(w => { nHoles += w.iRing ? 1 : 0 });
|
|
772
|
-
if (nHoles)
|
|
773
|
-
{
|
|
774
|
-
let c: any[] = [];
|
|
775
|
-
f.geometry.coordinates.forEach((poly: any) => {
|
|
776
|
-
poly.forEach((ring: any) => {
|
|
777
|
-
c.push([ring]);
|
|
778
|
-
});
|
|
779
|
-
});
|
|
780
|
-
f.geometry.coordinates = c;
|
|
781
|
-
windings = polyRingWindings(f);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
let polys = f.geometry.coordinates;
|
|
785
|
-
let iPoly = 0;
|
|
786
|
-
for (let iWinding = 0; iWinding < windings.length; iWinding++)
|
|
787
|
-
{
|
|
788
|
-
let good = !misWound(windings[iWinding]);
|
|
789
|
-
if (good)
|
|
790
|
-
iPoly = iWinding;
|
|
791
|
-
else
|
|
792
|
-
{
|
|
793
|
-
// If hole, add to previous poly
|
|
794
|
-
polys[iPoly].push(polys[iWinding][0]);
|
|
795
|
-
polys[iWinding] = null;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
f.geometry.coordinates = polys.filter((p: any) => p != null);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Degenerate multi-polygon
|
|
802
|
-
if (f.geometry.coordinates.length == 1)
|
|
803
|
-
{
|
|
804
|
-
f.geometry.type = 'Polygon';
|
|
805
|
-
f.geometry.coordinates = f.geometry.coordinates[0];
|
|
806
|
-
d = 4;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
775
|
+
if (options.validateHoles)
|
|
776
|
+
featureCleanHoles(f);
|
|
809
777
|
|
|
810
778
|
// OK, now go through each ring
|
|
811
779
|
let polys = f.geometry.coordinates;
|
|
780
|
+
let d = Util.depthof(f.geometry.coordinates);
|
|
812
781
|
if (d == 4) polys = [polys];
|
|
813
|
-
windings = polyRingWindings(f);
|
|
782
|
+
let windings = polyRingWindings(f);
|
|
814
783
|
windings.forEach((w: Winding) => {
|
|
815
784
|
let good = !misWound(w);
|
|
816
785
|
if (!good)
|
package/lib/poly/topo.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as TopoSimplify from '@dra2020/topojson-simplify';
|
|
|
8
8
|
|
|
9
9
|
import * as Util from '../util/all';
|
|
10
10
|
import * as FSM from '../fsm/all';
|
|
11
|
+
import * as G from '../geo/all';
|
|
11
12
|
|
|
12
13
|
import * as P from './poly';
|
|
13
14
|
import * as Q from './quad';
|
|
@@ -362,26 +363,23 @@ let FSM_COMPUTING = UniqueState++;
|
|
|
362
363
|
class FsmIncrementalUnion extends FSM.Fsm
|
|
363
364
|
{
|
|
364
365
|
options: P.TickOptions;
|
|
365
|
-
key:
|
|
366
|
+
key: number;
|
|
367
|
+
multi: G.GeoMultiCollection;
|
|
366
368
|
map: any; // { [geoid: string]: Feature }
|
|
367
369
|
result: any;
|
|
368
370
|
work: Q.WorkDone;
|
|
369
371
|
|
|
370
|
-
constructor(env: FSM.FsmEnvironment, options: P.TickOptions, key:
|
|
372
|
+
constructor(env: FSM.FsmEnvironment, options: P.TickOptions, multi: G.GeoMultiCollection, key: number, map?: any)
|
|
371
373
|
{
|
|
372
374
|
super(env);
|
|
373
375
|
this.options = options;
|
|
376
|
+
this.multi = multi;
|
|
374
377
|
this.key = key;
|
|
375
378
|
this.result = null;
|
|
376
379
|
this.map = null;
|
|
377
380
|
if (map) this.recompute(map);
|
|
378
381
|
}
|
|
379
382
|
|
|
380
|
-
matches(key: any): boolean
|
|
381
|
-
{
|
|
382
|
-
return Util.shallowEqual(this.key, key);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
383
|
recompute(map: any): void
|
|
386
384
|
{
|
|
387
385
|
if (this.map != null && map != null && Util.shallowEqual(map, this.map))
|
|
@@ -399,7 +397,7 @@ class FsmIncrementalUnion extends FSM.Fsm
|
|
|
399
397
|
let values = Object.values(map);
|
|
400
398
|
this.work = { nUnion: values.length, nDifference: 0, ms: 0 };
|
|
401
399
|
let elapsed = new Util.Elapsed();
|
|
402
|
-
this.result = topoMergeFeatures(this.
|
|
400
|
+
this.result = topoMergeFeatures(this.multi.allTopo(), values);
|
|
403
401
|
this.work.ms = elapsed.ms();
|
|
404
402
|
this.map = map;
|
|
405
403
|
}
|
|
@@ -430,7 +428,7 @@ class FsmIncrementalUnion extends FSM.Fsm
|
|
|
430
428
|
|
|
431
429
|
export interface TopoUnionResult
|
|
432
430
|
{
|
|
433
|
-
key:
|
|
431
|
+
key: number;
|
|
434
432
|
poly: any;
|
|
435
433
|
work: Q.WorkDone;
|
|
436
434
|
}
|
|
@@ -438,59 +436,51 @@ export interface TopoUnionResult
|
|
|
438
436
|
export class FsmTopoUnion extends FSM.Fsm
|
|
439
437
|
{
|
|
440
438
|
options: P.TickOptions;
|
|
441
|
-
unions:
|
|
439
|
+
unions: { [index: number]: FsmIncrementalUnion };
|
|
442
440
|
work: Q.WorkDone;
|
|
443
441
|
|
|
444
442
|
constructor(env: FSM.FsmEnvironment, options?: P.TickOptions)
|
|
445
443
|
{
|
|
446
444
|
super(env);
|
|
447
445
|
this.options = Util.shallowAssignImmutable(P.DefaultTickOptions, options);
|
|
448
|
-
this.unions =
|
|
446
|
+
this.unions = {};
|
|
449
447
|
this.work = { nUnion: 0, nDifference: 0, ms: 0 };
|
|
450
448
|
}
|
|
451
449
|
|
|
452
450
|
get result(): TopoUnionResult[]
|
|
453
451
|
{
|
|
454
|
-
if (this.unions
|
|
455
|
-
return this.unions.map((i: FsmIncrementalUnion) => ({ key: i.key, poly: i.result, work: i.work }) );
|
|
452
|
+
if (Util.countKeys(this.unions) > 0 && this.state === FSM.FSM_DONE)
|
|
453
|
+
return Object.values(this.unions).map((i: FsmIncrementalUnion) => ({ key: i.key, poly: i.result, work: i.work }) );
|
|
456
454
|
else
|
|
457
455
|
return null;
|
|
458
456
|
}
|
|
459
457
|
|
|
460
458
|
cancel(): void
|
|
461
459
|
{
|
|
462
|
-
this.unions.forEach((i: FsmIncrementalUnion) => {
|
|
460
|
+
Object.values(this.unions).forEach((i: FsmIncrementalUnion) => {
|
|
463
461
|
i.cancel();
|
|
464
462
|
});
|
|
465
|
-
this.unions =
|
|
463
|
+
this.unions = {};
|
|
466
464
|
this.setState(FSM.FSM_DONE);
|
|
467
465
|
}
|
|
468
466
|
|
|
469
|
-
cancelOne(key:
|
|
467
|
+
cancelOne(key: number): void
|
|
470
468
|
{
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
let u = this.unions[i];
|
|
474
|
-
if (u.matches(key))
|
|
475
|
-
{
|
|
476
|
-
u.cancel();
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
469
|
+
let u = this.unions[key];
|
|
470
|
+
if (u) u.cancel();
|
|
480
471
|
}
|
|
481
472
|
|
|
482
|
-
recompute(key:
|
|
473
|
+
recompute(multi: G.GeoMultiCollection, key: number, map: any): void
|
|
483
474
|
{
|
|
484
|
-
let fsm
|
|
485
|
-
if (fsm == null)
|
|
475
|
+
let fsm = this.unions[key];
|
|
476
|
+
if (fsm == null || fsm.multi !== multi)
|
|
486
477
|
{
|
|
487
|
-
fsm = new FsmIncrementalUnion(this.env, this.options, key, map);
|
|
488
|
-
this.unions
|
|
478
|
+
fsm = new FsmIncrementalUnion(this.env, this.options, multi, key, map);
|
|
479
|
+
this.unions[key] = fsm;
|
|
489
480
|
}
|
|
490
481
|
else
|
|
491
482
|
fsm.recompute(map);
|
|
492
483
|
this.work = { nUnion: 0, nDifference: 0, ms: 0 };
|
|
493
|
-
this.unions.forEach((u) => { this.work.nUnion += u.work.nUnion; this.work.nDifference += u.work.nDifference });
|
|
494
484
|
this.waitOn(fsm);
|
|
495
485
|
this.setState(FSM_COMPUTING);
|
|
496
486
|
}
|
|
@@ -503,7 +493,12 @@ export class FsmTopoUnion extends FSM.Fsm
|
|
|
503
493
|
{
|
|
504
494
|
case FSM.FSM_STARTING:
|
|
505
495
|
case FSM_COMPUTING:
|
|
506
|
-
|
|
496
|
+
this.work = { nUnion: 0, nDifference: 0, ms: 0 };
|
|
497
|
+
if (this.unions) Object.values(this.unions).forEach((i: FsmIncrementalUnion) => {
|
|
498
|
+
this.work.ms += i.work.ms;
|
|
499
|
+
this.work.nUnion += i.work.nUnion;
|
|
500
|
+
this.work.nDifference += i.work.nDifference;
|
|
501
|
+
});
|
|
507
502
|
this.setState(FSM.FSM_DONE);
|
|
508
503
|
break;
|
|
509
504
|
}
|