@dra2020/baseclient 1.0.101 → 1.0.102

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;
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.101",
3
+ "version": "1.0.102",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",