@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.
@@ -15,3 +15,4 @@ export * from './selfintersect';
15
15
  export * from './shamos';
16
16
  export * from './pointinpoly';
17
17
  export * from './mapto';
18
+ export * from './featurecleanholes';
@@ -0,0 +1 @@
1
+ export declare function featureCleanHoles(f: any): any;
@@ -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: any;
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: any, map?: any);
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: any;
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: FsmIncrementalUnion[];
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: any): void;
41
- recompute(key: any, map: any): void;
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
@@ -15,3 +15,4 @@ export * from './selfintersect';
15
15
  export * from './shamos';
16
16
  export * from './pointinpoly';
17
17
  export * from './mapto';
18
+ export * from './featurecleanholes';
@@ -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 closeFeature(f: any): void
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) closeFeature(f);
771
+ if (options.validateClose)
772
+ featureClose(f);
760
773
 
761
774
  // Check if multi-polygon is really polygon with holes
762
- // Only applies to multi-polygons with no holes
763
- let d = Util.depthof(f.geometry.coordinates);
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: any;
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: any, map?: any)
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.key.multi.allTopo(), values);
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: any;
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: FsmIncrementalUnion[];
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.length > 0 && this.state === FSM.FSM_DONE)
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: any): void
467
+ cancelOne(key: number): void
470
468
  {
471
- for (let i = 0; i < this.unions.length; i++)
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: any, map: any): void
473
+ recompute(multi: G.GeoMultiCollection, key: number, map: any): void
483
474
  {
484
- let fsm: FsmIncrementalUnion = this.unions.find((i: FsmIncrementalUnion) => i.matches(key));
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.push(fsm);
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
- if (this.unions) this.unions.forEach((u) => { this.work.ms += u.work.ms });
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.101",
3
+ "version": "1.0.103",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",