@dra2020/baseclient 1.0.60 → 1.0.63

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.
@@ -48,6 +48,18 @@ export interface PolyDescription {
48
48
  export declare function polyDescribe(poly: any): PolyDescription;
49
49
  export declare function npoints(poly: any): number;
50
50
  export declare function polyTransform(poly: any, transformFn: any): any;
51
- export declare function featureRewind(poly: any): any;
51
+ export interface RewindOptions {
52
+ validateHoles?: boolean;
53
+ validateClose?: boolean;
54
+ canonical?: boolean;
55
+ }
56
+ interface Winding {
57
+ iPoly: number;
58
+ iRing: number;
59
+ direction: number;
60
+ }
61
+ export declare function polyRingWindings(poly: any): Winding[];
62
+ export declare function featureRewind(f: any, options?: RewindOptions): any;
52
63
  export declare function polyRewindRings(pp: any, bLog?: boolean): any;
53
64
  export declare function polyBadCoordinates(f: any): boolean;
65
+ export {};
package/lib/poly/poly.ts CHANGED
@@ -667,30 +667,156 @@ function closePoly(poly: any): any
667
667
  return poly;
668
668
  }
669
669
 
670
+ function closeFeature(f: any): void
671
+ {
672
+ let d = Util.depthof(f.geometry.coordinates);
673
+ if (d === 4) closePoly(f.geometry.coordinates);
674
+ if (d === 5) f.geometry.coordinates.forEach(closePoly);
675
+ }
676
+
677
+ export interface RewindOptions
678
+ {
679
+ validateHoles?: boolean,
680
+ validateClose?: boolean,
681
+ canonical?: boolean,
682
+ }
683
+
684
+ function canonicalPoint(f: any, options: RewindOptions): any
685
+ {
686
+ if (options.canonical)
687
+ if (f && f.geometry && f.geometry.type === 'Point' && f.geometry.coordinates)
688
+ while (Array.isArray(f.geometry.coordinates[0]))
689
+ f.geometry.coordinates = f.geometry.coordinates[0];
690
+ return f;
691
+ }
692
+
693
+ //
694
+ // polyRingWindings: Return depth-first array of winding of each ring in the feature.
695
+ // value < 0: CW
696
+ // value > 0: CCW
697
+ //
698
+
699
+ interface Winding { iPoly: number, iRing: number, direction: number };
700
+
701
+ function misWound(w: Winding): boolean
702
+ {
703
+ return (((w.iRing == 0) && (w.direction > 0)) || ((w.iRing > 0) && (w.direction < 0)));
704
+ }
705
+
706
+ export function polyRingWindings(poly: any): Winding[]
707
+ {
708
+ let windings: Winding[] = [];
709
+
710
+ let pp = polyNormalize(poly);
711
+ if (pp == null || pp.buffer == null) return windings;
712
+
713
+ PP.polyPackEachRing(pp, (b: Float64Array, iPoly: number, iRing: number, iOffset: number, nPoints: number) =>
714
+ {
715
+ const iStart = iOffset;
716
+ const iEnd = iStart + (nPoints * 2) - 2;
717
+
718
+ // Determine the winding order of the ring
719
+ let direction = 0;
720
+
721
+ // Start at the second point
722
+ iOffset += 2;
723
+ for (; iOffset <= iEnd; iOffset += 2)
724
+ {
725
+ // The previous point
726
+ let jOffset = iOffset - 2;
727
+
728
+ // Sum over the edges
729
+ direction += twoTimesArea(b[iOffset], b[jOffset], b[iOffset+1], b[jOffset+1]);
730
+ }
731
+
732
+ // Implicitly close the ring, if necessary
733
+ if (nPoints > 2 && (b[iStart] != b[iEnd] || b[iStart + 1] != b[iEnd + 1]))
734
+ direction += twoTimesArea(b[iStart], b[iEnd], b[iStart + 1], b[iEnd + 1]);
735
+
736
+ windings.push({ iPoly: iPoly, iRing: iRing, direction: direction });
737
+ });
738
+ return windings;
739
+ }
740
+
741
+
670
742
  // This mutates the passed in feature to ensure it is correctly wound
671
743
  // For convenience, passed back the value provided.
672
- export function featureRewind(poly: any): any
744
+ //
745
+ export function featureRewind(f: any, options?: RewindOptions): any
673
746
  {
674
- let pp = polyNormalize(poly);
675
- if (pp == null) return null;
676
- polyRewindRings(pp);
677
- if (poly.type === 'Feature')
747
+ options = Util.shallowAssignImmutable({ validateHoles: true, validateClose: true, canonical: true }, options);
748
+
749
+ if (!f) return null;
750
+
751
+ // Has to be an unpacked feature
752
+ if (f.type !== 'Feature') throw 'featureRewind: must be a valid GeoJSON feature';
753
+ if (!f.geometry || f.geometry.packed) throw 'featureRewind: only valid on unpacked features';
754
+
755
+ // Non polygons are simpler
756
+ if (f.geometry.type !== 'Polygon' && f.geometry.type !== 'MultiPolygon') return canonicalPoint(f, options);
757
+
758
+ // Make sure polygon is closed (first === last)
759
+ if (options.validateClose) closeFeature(f);
760
+
761
+ // 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)
678
766
  {
679
- if (poly.geometry.packed !== pp)
767
+ // If there are explicit holes, don't look for implicit ones
768
+ let nHoles = 0; windings.forEach(w => { if (w.iRing) nHoles++ });
769
+ if (nHoles == 0 && !misWound(windings[0]))
770
+ {
771
+ // Looking for pattern: of (G (B*))*
772
+ // To convert to ((GH*))*
773
+ let iWinding = 0;
774
+ let polys = f.geometry.coordinates;
775
+ let iPoly: number;
776
+ for (; iWinding < windings.length; iWinding++)
777
+ {
778
+ let good = !misWound(windings[iWinding]);
779
+ if (!good && iWinding == 0) // First is miswound - no good
780
+ break;
781
+ if (good)
782
+ iPoly = iWinding;
783
+ else
784
+ {
785
+ polys[iPoly].push(polys[iWinding][0]);
786
+ polys[iWinding] = null;
787
+ }
788
+ }
789
+ f.geometry.coordinates = polys.filter((p: any) => p != null);
790
+ }
791
+
792
+ // Degenerate multi-polygon
793
+ if (f.geometry.coordinates.length == 1)
680
794
  {
681
- poly.geometry.coordinates = PP.polyUnpack(pp);
682
- // Also make sure first === last coordinate
683
- let d = Util.depthof(poly.geometry.coordinates);
684
- if (d === 4) closePoly(poly.geometry.coordinates);
685
- else if (d === 5) poly.geometry.coordinates.forEach(closePoly);
686
- poly.geometry.type = d === 4 ? 'Polygon' : 'MultiPolygon';
795
+ f.geometry.type = 'Polygon';
796
+ f.geometry.coordinates = f.geometry.coordinates[0];
687
797
  }
688
- return poly;
689
798
  }
690
- else if (poly === pp)
691
- return pp;
692
- else
693
- return PP.polyUnpack(pp);
799
+
800
+ // OK, now go through each ring
801
+ let polys = f.geometry.coordinates;
802
+ windings = polyRingWindings(f);
803
+ windings.forEach((w: Winding) => {
804
+ let good = !misWound(w);
805
+ if (!good)
806
+ {
807
+ let ring = polys[w.iPoly][w.iRing];
808
+ let iFront = 0;
809
+ let iBack = ring.length-1;
810
+ for (; iFront < iBack; iFront++, iBack--)
811
+ {
812
+ let tmp = ring[iFront];
813
+ ring[iFront] = ring[iBack];
814
+ ring[iBack] = tmp;
815
+ }
816
+ }
817
+ });
818
+
819
+ return f;
694
820
  }
695
821
 
696
822
  //
package/lib/poly/topo.ts CHANGED
@@ -65,28 +65,7 @@ function ringsCancel(outerPoly: any, innerRing: any): boolean
65
65
 
66
66
  function correctGeometry(f: any): any
67
67
  {
68
- if (f && f.geometry && f.geometry.type === 'MultiPolygon' && f.geometry.coordinates)
69
- {
70
- let multiPoly = f.geometry.coordinates;
71
-
72
- // Convert degenerate MultiPolygon to Polygon
73
- if (multiPoly.length == 1)
74
- {
75
- f.geometry.type = 'Polygon';
76
- f.geometry.coordinates = multiPoly[0];
77
- }
78
- }
79
-
80
- if (f && f.geometry && f.geometry.type === 'Point' && f.geometry.coordinates)
81
- {
82
- while (Array.isArray(f.geometry.coordinates[0]))
83
- f.geometry.coordinates = f.geometry.coordinates[0];
84
- }
85
- else
86
- // TopoJSON does not guarantee proper winding order which messes up later processing. Fix it.
87
- P.featureRewind(f);
88
-
89
- return f;
68
+ return P.featureRewind(f, { validateHoles: false } );
90
69
  }
91
70
 
92
71
  export function topoContiguity(topo: Topo): any
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.60",
3
+ "version": "1.0.63",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",