@konfirm/geojson 1.0.0

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.
Files changed (122) hide show
  1. package/.editorconfig +16 -0
  2. package/.github/workflows/release.yml +21 -0
  3. package/.github/workflows/tests.yml +56 -0
  4. package/CHANGELOG.md +21 -0
  5. package/LICENSE +21 -0
  6. package/README.md +260 -0
  7. package/dist/Domain/Constants.d.ts +5 -0
  8. package/dist/Domain/GeoJSON/Concept/Altitude.d.ts +3 -0
  9. package/dist/Domain/GeoJSON/Concept/BoundingBox.d.ts +6 -0
  10. package/dist/Domain/GeoJSON/Concept/ExteriorRing.d.ts +4 -0
  11. package/dist/Domain/GeoJSON/Concept/GeoJSONObject.d.ts +11 -0
  12. package/dist/Domain/GeoJSON/Concept/InteriorRing.d.ts +4 -0
  13. package/dist/Domain/GeoJSON/Concept/Latitude.d.ts +3 -0
  14. package/dist/Domain/GeoJSON/Concept/LinearRing.d.ts +4 -0
  15. package/dist/Domain/GeoJSON/Concept/Longitude.d.ts +3 -0
  16. package/dist/Domain/GeoJSON/Concept/Position.d.ts +7 -0
  17. package/dist/Domain/GeoJSON/Feature.d.ts +12 -0
  18. package/dist/Domain/GeoJSON/FeatureCollection.d.ts +8 -0
  19. package/dist/Domain/GeoJSON/GeoJSON.d.ts +7 -0
  20. package/dist/Domain/GeoJSON/Geometry/LineString.d.ts +11 -0
  21. package/dist/Domain/GeoJSON/Geometry/MultiLineString.d.ts +7 -0
  22. package/dist/Domain/GeoJSON/Geometry/MultiPoint.d.ts +8 -0
  23. package/dist/Domain/GeoJSON/Geometry/MultiPolygon.d.ts +7 -0
  24. package/dist/Domain/GeoJSON/Geometry/Point.d.ts +11 -0
  25. package/dist/Domain/GeoJSON/Geometry/Polygon.d.ts +11 -0
  26. package/dist/Domain/GeoJSON/Geometry.d.ts +9 -0
  27. package/dist/Domain/GeoJSON/GeometryCollection.d.ts +8 -0
  28. package/dist/Domain/GeoJSON/GeometryObject.d.ts +12 -0
  29. package/dist/Domain/Guards/Number.d.ts +3 -0
  30. package/dist/Domain/Guards/Tuple.d.ts +3 -0
  31. package/dist/Domain/Iterator/IterablePair.d.ts +32 -0
  32. package/dist/Domain/Iterator/SimpleGeometry.d.ts +90 -0
  33. package/dist/Domain/Utility/Calculate.d.ts +9 -0
  34. package/dist/Domain/Utility/Distance.d.ts +3 -0
  35. package/dist/Domain/Utility/Intersect.d.ts +2 -0
  36. package/dist/Domain/Utility/Segments.d.ts +2 -0
  37. package/dist/Domain/Utility/Winding.d.ts +3 -0
  38. package/dist/geojson.cjs.js +655 -0
  39. package/dist/geojson.cjs.min.js +1 -0
  40. package/dist/geojson.d.ts +198 -0
  41. package/dist/geojson.es.js +627 -0
  42. package/dist/geojson.es.min.js +1 -0
  43. package/dist/geojson.js +660 -0
  44. package/dist/geojson.min.js +1 -0
  45. package/dist/main.d.ts +15 -0
  46. package/package.json +63 -0
  47. package/rollup.config.mjs +43 -0
  48. package/source/Domain/Constants.ts +5 -0
  49. package/source/Domain/GeoJSON/Concept/Altitude.ts +9 -0
  50. package/source/Domain/GeoJSON/Concept/BoundingBox.ts +31 -0
  51. package/source/Domain/GeoJSON/Concept/ExteriorRing.ts +12 -0
  52. package/source/Domain/GeoJSON/Concept/GeoJSONObject.ts +23 -0
  53. package/source/Domain/GeoJSON/Concept/InteriorRing.ts +12 -0
  54. package/source/Domain/GeoJSON/Concept/Latitude.ts +7 -0
  55. package/source/Domain/GeoJSON/Concept/LinearRing.ts +14 -0
  56. package/source/Domain/GeoJSON/Concept/Longitude.ts +7 -0
  57. package/source/Domain/GeoJSON/Concept/Position.ts +18 -0
  58. package/source/Domain/GeoJSON/Feature.ts +24 -0
  59. package/source/Domain/GeoJSON/FeatureCollection.ts +16 -0
  60. package/source/Domain/GeoJSON/GeoJSON.ts +9 -0
  61. package/source/Domain/GeoJSON/Geometry/LineString.ts +12 -0
  62. package/source/Domain/GeoJSON/Geometry/MultiLineString.ts +9 -0
  63. package/source/Domain/GeoJSON/Geometry/MultiPoint.ts +10 -0
  64. package/source/Domain/GeoJSON/Geometry/MultiPolygon.ts +9 -0
  65. package/source/Domain/GeoJSON/Geometry/Point.ts +12 -0
  66. package/source/Domain/GeoJSON/Geometry/Polygon.ts +20 -0
  67. package/source/Domain/GeoJSON/Geometry.ts +11 -0
  68. package/source/Domain/GeoJSON/GeometryCollection.ts +23 -0
  69. package/source/Domain/GeoJSON/GeometryObject.ts +20 -0
  70. package/source/Domain/Guards/Number.ts +13 -0
  71. package/source/Domain/Guards/Tuple.ts +6 -0
  72. package/source/Domain/Guards/Utility.ts +1 -0
  73. package/source/Domain/Iterator/IterablePair.ts +47 -0
  74. package/source/Domain/Iterator/SimpleGeometry.ts +137 -0
  75. package/source/Domain/Utility/Calculate.ts +143 -0
  76. package/source/Domain/Utility/Distance.ts +56 -0
  77. package/source/Domain/Utility/Intersect.ts +42 -0
  78. package/source/Domain/Utility/Numeric.ts +8 -0
  79. package/source/Domain/Utility/Segments.ts +5 -0
  80. package/source/Domain/Utility/Winding.ts +19 -0
  81. package/source/main.ts +20 -0
  82. package/test/Domain/GeoJSON/Concept/Altitude.ts +58 -0
  83. package/test/Domain/GeoJSON/Concept/BoundingBox.ts +77 -0
  84. package/test/Domain/GeoJSON/Concept/ExteriorRing.ts +65 -0
  85. package/test/Domain/GeoJSON/Concept/GeoJSONObject.ts +56 -0
  86. package/test/Domain/GeoJSON/Concept/InteriorRing.ts +65 -0
  87. package/test/Domain/GeoJSON/Concept/Latitude.ts +56 -0
  88. package/test/Domain/GeoJSON/Concept/LinearRing.ts +63 -0
  89. package/test/Domain/GeoJSON/Concept/Longitude.ts +56 -0
  90. package/test/Domain/GeoJSON/Concept/Position.ts +56 -0
  91. package/test/Domain/GeoJSON/Feature.ts +9 -0
  92. package/test/Domain/GeoJSON/FeatureCollection.ts +9 -0
  93. package/test/Domain/GeoJSON/GeoJSON.ts +21 -0
  94. package/test/Domain/GeoJSON/Geometry/LineString.ts +11 -0
  95. package/test/Domain/GeoJSON/Geometry/MultiLineString.ts +11 -0
  96. package/test/Domain/GeoJSON/Geometry/MultiPoint.ts +11 -0
  97. package/test/Domain/GeoJSON/Geometry/MultiPolygon.ts +11 -0
  98. package/test/Domain/GeoJSON/Geometry/Point.ts +11 -0
  99. package/test/Domain/GeoJSON/Geometry/Polygon.ts +11 -0
  100. package/test/Domain/GeoJSON/Geometry.ts +9 -0
  101. package/test/Domain/GeoJSON/GeometryCollection.ts +9 -0
  102. package/test/Domain/GeoJSON/GeometryObject.ts +4 -0
  103. package/test/Domain/Guards/Number.ts +49 -0
  104. package/test/Domain/Guards/Tuple.ts +27 -0
  105. package/test/Domain/Iterator/IterablePair.ts +203 -0
  106. package/test/Domain/Iterator/SimpleGeometry.ts +195 -0
  107. package/test/Domain/Utility/Calculate.ts +178 -0
  108. package/test/Domain/Utility/Distance.ts +19 -0
  109. package/test/Domain/Utility/Intersect.ts +54 -0
  110. package/test/Domain/Utility/Numeric.ts +30 -0
  111. package/test/Domain/Utility/Winding.ts +52 -0
  112. package/test/README.ts +123 -0
  113. package/test/data/Distance.ts +51 -0
  114. package/test/data/HolySee.ts +74 -0
  115. package/test/data/Intersect.ts +300 -0
  116. package/test/data/Italy.ts +2883 -0
  117. package/test/data/SanMarino.ts +83 -0
  118. package/test/data/Shapes.ts +107 -0
  119. package/test/helper/geometry.ts +76 -0
  120. package/test/helper/malform.ts +82 -0
  121. package/test/main.ts +4 -0
  122. package/tsconfig.json +12 -0
@@ -0,0 +1,137 @@
1
+ import { LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from "../../main";
2
+ import { Feature } from "../GeoJSON/Feature";
3
+ import { FeatureCollection } from "../GeoJSON/FeatureCollection";
4
+ import { GeoJSON } from "../GeoJSON/GeoJSON";
5
+ import { GeometryCollection } from "../GeoJSON/GeometryCollection";
6
+
7
+ type SimpleGeometry = Point | LineString | Polygon;
8
+
9
+ /**
10
+ * Iterator class to turn any GeoJSON structure into one or more SimpleGeometry (Point | LineString | Polygon) objects
11
+ *
12
+ * @export
13
+ * @class SimpleGeometryIterator
14
+ */
15
+ export class SimpleGeometryIterator {
16
+ private readonly inputs: Array<GeoJSON> = [];
17
+
18
+ /**
19
+ * Creates an instance of SimpleGeometryIterator
20
+ *
21
+ * @param {...[GeoJSON, ...Array<GeoJSON>]} inputs
22
+ * @memberof SimpleGeometryIterator
23
+ */
24
+ constructor(...inputs: [GeoJSON, ...Array<GeoJSON>]) {
25
+ this.inputs = inputs;
26
+ }
27
+
28
+ /**
29
+ * Generator yielding all SimpleGeometry objects from the provided GeoJSON structure(s)
30
+ *
31
+ * @return {*} {Iterator<SimpleGeometry>}
32
+ * @memberof SimpleGeometryIterator
33
+ */
34
+ *[Symbol.iterator](): Iterator<SimpleGeometry> {
35
+ for (const input of this.inputs) {
36
+ for (const simple of this.unwrap(input)) {
37
+ yield simple;
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * unwrap any GeoJSON object into one or more SimpleGeometry object
44
+ *
45
+ * @private
46
+ * @param {GeoJSON} geo
47
+ * @return {*} {Iterable<SimpleGeometry>}
48
+ * @memberof SimpleGeometryIterator
49
+ */
50
+ private *unwrap(geo: GeoJSON): Iterable<SimpleGeometry> {
51
+ geo.type in this
52
+ ? yield* this[geo.type](geo)
53
+ : yield geo as SimpleGeometry;
54
+ }
55
+
56
+ /**
57
+ * Generate the geometries in a GeometryCollection
58
+ *
59
+ * @private
60
+ * @param {GeometryCollection} { geometries }
61
+ * @return {*} {Iterable<SimpleGeometry>}
62
+ * @memberof SimpleGeometryIterator
63
+ */
64
+ private *GeometryCollection({ geometries }: GeometryCollection): Iterable<SimpleGeometry> {
65
+ for (const geometry of geometries) {
66
+ yield* this.unwrap(geometry);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Generate the geometries of the Feature object(s) in a FeatureCollection
72
+ *
73
+ * @private
74
+ * @param {FeatureCollection} { features }
75
+ * @return {*} {Iterable<SimpleGeometry>}
76
+ * @memberof SimpleGeometryIterator
77
+ */
78
+ private *FeatureCollection({ features }: FeatureCollection): Iterable<SimpleGeometry> {
79
+ for (const { geometry } of features) {
80
+ yield* this.unwrap(geometry);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Generate the geometry of a Feature object
86
+ *
87
+ * @private
88
+ * @param {Feature} { geometry }
89
+ * @return {*} {Iterable<SimpleGeometry>}
90
+ * @memberof SimpleGeometryIterator
91
+ */
92
+ private *Feature({ geometry }: Feature): Iterable<SimpleGeometry> {
93
+ yield* this.unwrap(geometry);
94
+ }
95
+
96
+ /**
97
+ * Generate Point objects from a MultiPoint object
98
+ *
99
+ * @private
100
+ * @param {MultiPoint} multi
101
+ * @return {*} {Iterable<Point>}
102
+ * @memberof SimpleGeometryIterator
103
+ */
104
+ private *MultiPoint(multi: MultiPoint): Iterable<Point> {
105
+ for (const coordinates of multi.coordinates) {
106
+ yield { type: 'Point', coordinates };
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Generate LineString objects from a MultiLineString object
112
+ *
113
+ * @private
114
+ * @param {MultiLineString} multi
115
+ * @return {*} {Iterable<LineString>}
116
+ * @memberof SimpleGeometryIterator
117
+ */
118
+ private *MultiLineString(multi: MultiLineString): Iterable<LineString> {
119
+ for (const coordinates of multi.coordinates) {
120
+ yield { type: 'LineString', coordinates };
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Generate Polygon objects from a MultiPolygon object
126
+ *
127
+ * @private
128
+ * @param {MultiPolygon} multi
129
+ * @return {*} {Iterable<Polygon>}
130
+ * @memberof SimpleGeometryIterator
131
+ */
132
+ private *MultiPolygon(multi: MultiPolygon): Iterable<Polygon> {
133
+ for (const coordinates of multi.coordinates) {
134
+ yield { type: 'Polygon', coordinates };
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,143 @@
1
+ import { Point } from "../GeoJSON/Geometry/Point";
2
+ import { EARTH_RADIUS, EARTH_RADIUS_MAJOR, EARTH_RADIUS_MINOR, EARTH_FLATTENING } from '../Constants';
3
+
4
+ const D2R = Math.PI / 180;
5
+ const π = Math.PI;
6
+
7
+ function constrain(value: number, min: number, max: number): number {
8
+ return Math.max(Math.min(value, max), min);
9
+ }
10
+
11
+ function squared(n: number): number {
12
+ return n * n;
13
+ }
14
+
15
+ function rad(n: number): number {
16
+ return n * D2R;
17
+ }
18
+
19
+ const EARTH_RADIUS_MAJOR_SQUARED = squared(EARTH_RADIUS_MAJOR);
20
+ const EARTH_RADIUS_MINOR_SQUARED = squared(EARTH_RADIUS_MINOR);
21
+ const EARTH_RADIUS_FACTOR = (EARTH_RADIUS_MAJOR_SQUARED - EARTH_RADIUS_MINOR_SQUARED) / EARTH_RADIUS_MINOR_SQUARED;
22
+ const EARTH_INVERSE_FLATTENING = 1 / EARTH_FLATTENING;
23
+
24
+
25
+ const PointToPoint: { [key: string]: (...positions: [Point['coordinates'], Point['coordinates']]) => number } = {
26
+ cartesian([λa, φa], [λb, φb]) {
27
+ return EARTH_RADIUS * rad(Math.sqrt(squared(λb - λa) + squared(φb - φa)));
28
+ },
29
+ haversine([λa, φa], [λb, φb]) {
30
+ //https://www.movable-type.co.uk/scripts/latlong.html
31
+ const Δ = squared(Math.sin(rad(φb - φa) / 2)) + Math.cos(rad(φa)) * Math.cos(rad(φb)) * squared(Math.sin(rad(λb - λa) / 2));
32
+
33
+ return EARTH_RADIUS * Math.atan2(Math.sqrt(Δ), Math.sqrt(1 - Δ)) * 2;
34
+ },
35
+ vincenty(...points) {
36
+ //https://www.movable-type.co.uk/scripts/latlong-vincenty.html
37
+ const [[λ1, φ1], [λ2, φ2]] = points.map((p) => p.map(rad));
38
+ const L = λ2 - λ1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanφ.
39
+ const tanU1 = (1 - EARTH_INVERSE_FLATTENING) * Math.tan(φ1), cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1;
40
+ const tanU2 = (1 - EARTH_INVERSE_FLATTENING) * Math.tan(φ2), cosU2 = 1 / Math.sqrt((1 + tanU2 * tanU2)), sinU2 = tanU2 * cosU2;
41
+
42
+ const antipodal = Math.abs(L) > π / 2 || Math.abs(φ2 - φ1) > π / 2;
43
+
44
+ let λ = L;
45
+ let sinλ = null;
46
+ let cosλ = null; // λ = difference in longitude on an auxiliary sphere
47
+ let σ = antipodal ? π : 0;
48
+ let sinσ = 0;
49
+ let cosσ = antipodal ? -1 : 1
50
+ let sinSqσ = null; // σ = angular distance P₁ P₂ on the sphere
51
+ let cos2σₘ = 1; // σₘ = angular distance on the sphere from the equator to the midpoint of the line
52
+ let cosSqα = 1; // α = azimuth of the geodesic at the equator
53
+ let λʹ = null;
54
+ let iterations = 0;
55
+
56
+ do {
57
+ sinλ = Math.sin(λ);
58
+ cosλ = Math.cos(λ);
59
+ sinSqσ = (cosU2 * sinλ) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosλ) ** 2;
60
+ if (Math.abs(sinSqσ) < 1e-24) break; // co-incident/antipodal points (σ < ≈0.006mm)
61
+ sinσ = Math.sqrt(sinSqσ);
62
+ cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
63
+ σ = Math.atan2(sinσ, cosσ);
64
+ const sinα = cosU1 * cosU2 * sinλ / sinσ;
65
+ cosSqα = 1 - sinα * sinα;
66
+ cos2σₘ = (cosSqα != 0) ? (cosσ - 2 * sinU1 * sinU2 / cosSqα) : 0; // on equatorial line cos²α = 0 (§6)
67
+ const C = EARTH_INVERSE_FLATTENING / 16 * cosSqα * (4 + EARTH_INVERSE_FLATTENING * (4 - 3 * cosSqα));
68
+ λʹ = λ;
69
+ λ = L + (1 - C) * EARTH_INVERSE_FLATTENING * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));
70
+ // TODO: add tests
71
+ // const iterationCheck = antipodal ? Math.abs(λ) - π : Math.abs(λ);
72
+ // if (iterationCheck > π) throw new EvalError('λ > π');
73
+ } while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < 1000); // TV: 'iterate until negligible change in λ' (≈0.006mm)
74
+ // TODO: add tests
75
+ // if (iterations >= 1000) throw new EvalError('Vincenty formula failed to converge');
76
+
77
+ const uSq = cosSqα * EARTH_RADIUS_FACTOR;
78
+ const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
79
+ const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
80
+ const Δσ = B * sinσ * (cos2σₘ + B / 4 * (cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) - B / 6 * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));
81
+
82
+ return EARTH_RADIUS_MINOR * A * (σ - Δσ);
83
+ },
84
+ };
85
+
86
+ export type PointToPointCalculation = 'cartesian' | 'haversine' | 'vincenty' | ((a: Point['coordinates'], b: Point['coordinates']) => number);
87
+
88
+ export function getClosestPointOnLineByPoint(point: Point['coordinates'], line: [Point['coordinates'], Point['coordinates']]): Point['coordinates'] {
89
+ const [[px, py], [ax, ay], [bx, by]] = [point, ...line];
90
+ const [abx, aby] = [bx - ax, by - ay];
91
+ const [apx, apy] = [px - ax, py - ay];
92
+ const t = constrain((apx * abx + apy * aby) / (abx * abx + aby * aby), 0, 1);
93
+
94
+ return t === 0 || t === 1 ? line[t] : [ax + abx * t, ay + aby * t];
95
+ }
96
+
97
+ export function getDistanceOfPointToPoint(a: Point['coordinates'], b: Point['coordinates'], calculation: PointToPointCalculation): number {
98
+ const calc = typeof calculation === 'function' ? calculation : PointToPoint[calculation];
99
+
100
+ if (typeof calc === 'function') {
101
+ return calc(a, b);
102
+ }
103
+
104
+ throw new Error(`Not a PointToPoint calculation function ${calculation}`);
105
+ }
106
+
107
+ export function getDistanceOfPointToLine(point: Point['coordinates'], line: [Point['coordinates'], Point['coordinates']], calculation: PointToPointCalculation): number {
108
+ return getDistanceOfPointToPoint(point, getClosestPointOnLineByPoint(point, line), calculation);
109
+ }
110
+
111
+ export function getDistanceOfLineToLine(a: [Point['coordinates'], Point['coordinates']], b: [Point['coordinates'], Point['coordinates']], calculation: PointToPointCalculation): number {
112
+ return isLinesCrossing(a, b)
113
+ ? 0
114
+ : Math.min(...a.map((a) => getDistanceOfPointToLine(a, b, calculation)), ...b.map((b) => getDistanceOfPointToLine(b, a, calculation)));
115
+ }
116
+
117
+ export function isLinesCrossing(a: [Point['coordinates'], Point['coordinates']], b: [Point['coordinates'], Point['coordinates']]): boolean {
118
+ const [[a1x, a1y], [a2x, a2y]] = a;
119
+ const [[b1x, b1y], [b2x, b2y]] = b;
120
+ const [s1x, s1y, s2x, s2y] = [a2x - a1x, a2y - a1y, b2x - b1x, b2y - b1y];
121
+ const s = (-s1y * (a1x - b1x) + s1x * (a1y - b1y)) / (-s2x * s1y + s1x * s2y);
122
+ const t = (s2x * (a1y - b1y) - s2y * (a1x - b1x)) / (-s2x * s1y + s1x * s2y);
123
+
124
+ return s >= 0 && s <= 1 && t >= 0 && t <= 1;
125
+ }
126
+
127
+ export function isPointOnLine(point: Point['coordinates'], line: [Point['coordinates'], Point['coordinates']], threshold: number = 1e-14): boolean {
128
+ return getDistanceOfPointToLine(point, line, 'cartesian') < threshold;
129
+ }
130
+
131
+ export function isPointInRing(p: Point['coordinates'], ring: Array<Point['coordinates']>): boolean {
132
+ const { length } = ring;
133
+ const odd = ring
134
+ .reduce((odd, a, i) => {
135
+ const b = ring[(length + i - 1) % length];
136
+
137
+ return (a[1] < p[1] && b[1] >= p[1] || b[1] < p[1] && a[1] >= p[1]) && (a[0] <= p[0] || b[0] <= p[0])
138
+ ? odd ^ Number(a[0] + (p[1] - a[1]) / (b[1] - a[1]) * (b[0] - a[0]) < p[0])
139
+ : odd;
140
+ }, 0);
141
+
142
+ return odd !== 0 || ring.slice(1).some((a, index) => isPointOnLine(p, [ring[index], a]));
143
+ }
@@ -0,0 +1,56 @@
1
+ import { GeoJSON } from '../GeoJSON/GeoJSON';
2
+ import { Point } from '../GeoJSON/Geometry/Point';
3
+ import { LineString } from '../GeoJSON/Geometry/LineString';
4
+ import { Polygon } from '../GeoJSON/Geometry/Polygon';
5
+ import { segments } from './Segments';
6
+ import { SimpleGeometryIterator } from '../Iterator/SimpleGeometry';
7
+ import { IterablePairIterator } from '../Iterator/IterablePair';
8
+ import { getDistanceOfLineToLine, getDistanceOfPointToLine, getDistanceOfPointToPoint, isPointInRing, PointToPointCalculation } from './Calculate';
9
+
10
+ const geometries = {
11
+ PointPoint(a: Point['coordinates'], b: Point['coordinates'], calculation: PointToPointCalculation): number {
12
+ return getDistanceOfPointToPoint(a, b, calculation);
13
+ },
14
+ LineStringPoint(a: LineString['coordinates'], b: Point['coordinates'], calculation: PointToPointCalculation): number {
15
+ return Math.min(...segments(a).map((line) => getDistanceOfPointToLine(b, line, calculation)));
16
+ },
17
+ LineStringLineString(a: LineString['coordinates'], b: LineString['coordinates'], calculation: PointToPointCalculation): number {
18
+ const sa = segments(a);
19
+ const sb = segments(b);
20
+
21
+ return sa.reduce((carry, a) => sb.reduce((carry, b) => carry > 0 ? Math.min(carry, getDistanceOfLineToLine(a, b, calculation)) : carry, carry), Infinity);
22
+ },
23
+ PolygonPoint([exterior, ...interior]: Polygon['coordinates'], b: Point['coordinates'], calculation: PointToPointCalculation): number {
24
+ if (isPointInRing(b, exterior)) {
25
+ const [excluded] = interior.filter((ring) => isPointInRing(b, ring));
26
+
27
+ return excluded ? this.LineStringPoint(excluded, b, calculation) : 0;
28
+ }
29
+
30
+ return this.LineStringPoint(exterior, b, calculation);
31
+ },
32
+ PolygonLineString(a: Polygon['coordinates'], b: LineString['coordinates'], calculation: PointToPointCalculation): number {
33
+ const [exterior, ...interior] = a;
34
+ const line = segments(b);
35
+ const ring = b.some((b) => isPointInRing(b, exterior))
36
+ ? interior.find((a) => b.every((b) => isPointInRing(b, a)))
37
+ : exterior;
38
+
39
+ return ring
40
+ ? Math.min(...segments(ring).map((a) => Math.min(...line.map((b) => getDistanceOfLineToLine(a, b, calculation)))))
41
+ : 0;
42
+ },
43
+ PolygonPolygon(a: Polygon['coordinates'], b: Polygon['coordinates'], calculation: PointToPointCalculation): number {
44
+ return Math.min(this.PolygonLineString(a, b[0], calculation), this.PolygonLineString(b, a[0], calculation));
45
+ },
46
+ };
47
+
48
+ export function distance(a: GeoJSON, b: GeoJSON, calculation: PointToPointCalculation = 'cartesian'): number {
49
+ return Math.min(...[...new IterablePairIterator(new SimpleGeometryIterator(a), new SimpleGeometryIterator(b))].map(([a, b]) => {
50
+ return a.type + b.type in geometries
51
+ ? geometries[a.type + b.type](a.coordinates, b.coordinates, calculation)
52
+ : b.type + a.type in geometries
53
+ ? geometries[b.type + a.type](b.coordinates, a.coordinates, calculation)
54
+ : Infinity
55
+ }));
56
+ }
@@ -0,0 +1,42 @@
1
+ import { Point } from '../GeoJSON/Geometry/Point';
2
+ import { LineString } from '../GeoJSON/Geometry/LineString';
3
+ import { Polygon } from '../GeoJSON/Geometry/Polygon';
4
+ import { GeoJSON } from '../GeoJSON/GeoJSON';
5
+ import { segments } from './Segments';
6
+ import { isLinesCrossing, isPointInRing, isPointOnLine } from "./Calculate";
7
+ import { SimpleGeometryIterator } from "../Iterator/SimpleGeometry";
8
+ import { IterablePairIterator } from "../Iterator/IterablePair";
9
+
10
+ const geometries = {
11
+ PointPoint(a: Point['coordinates'], b: Point['coordinates']): boolean {
12
+ return a.length >= 2 && b.length >= 2 && a.slice(0, 2).every((v, i) => v === b[i]);
13
+ },
14
+ LineStringPoint(a: LineString['coordinates'], b: Point['coordinates']): boolean {
15
+ return a.some((a) => this.PointPoint(a, b)) || segments(a).some((line) => isPointOnLine(b, line));
16
+ },
17
+ LineStringLineString(a: LineString['coordinates'], b: LineString['coordinates']): boolean {
18
+ const lines = segments(b);
19
+
20
+ return segments(a).some((a) => lines.some((b) => isLinesCrossing(a, b)));
21
+ },
22
+ PolygonPoint([exterior, ...interior]: Polygon['coordinates'], b: Point['coordinates']): boolean {
23
+ return (this.LineStringPoint(exterior, b) || isPointInRing(b, exterior)) && (!interior.length || interior.every((ring) => !isPointInRing(b, ring)));
24
+ },
25
+ PolygonLineString(a: Polygon['coordinates'], b: LineString['coordinates']): boolean {
26
+ return a.some((ring) => this.LineStringLineString(ring, b)) || b.some((point) => this.PolygonPoint(a, point));
27
+ },
28
+ PolygonPolygon(a: Polygon['coordinates'], b: Polygon['coordinates']): boolean {
29
+ return b.some((b1) => this.PolygonLineString(a, b1) || b1.some((b2) => this.PolygonPoint(a, b2)))
30
+ || a.some((a1) => this.PolygonLineString(b, a1) || a1.some((a2) => this.PolygonPoint(b, a2)))
31
+ },
32
+ };
33
+
34
+ export function intersect(a: GeoJSON, b: GeoJSON): boolean {
35
+ for (const [itA, itB] of new IterablePairIterator(new SimpleGeometryIterator(a), new SimpleGeometryIterator(b))) {
36
+ if ((itA.type + itB.type in geometries && geometries[itA.type + itB.type](itA.coordinates, itB.coordinates)) || (itB.type + itA.type in geometries && geometries[itB.type + itA.type](itB.coordinates, itA.coordinates))) {
37
+ return true;
38
+ }
39
+ }
40
+
41
+ return false;
42
+ }
@@ -0,0 +1,8 @@
1
+ export function bounds<T extends number>(a: number, b: number): (value: number) => T {
2
+ const min = Math.min(a, b);
3
+ const max = Math.max(a, b);
4
+ const range = max - min;
5
+ const mod = (value: number): T => value < min ? mod(value + range) : value > max ? mod(value - range) : <T>value;
6
+
7
+ return (value: number): T => mod(value) as T;
8
+ }
@@ -0,0 +1,5 @@
1
+ import { Point } from '../GeoJSON/Geometry/Point';
2
+
3
+ export function segments(line: Array<Point['coordinates']>): Array<[Point['coordinates'], Point['coordinates']]> {
4
+ return line.slice(1).map((point, index) => [line[index], point]);
5
+ }
@@ -0,0 +1,19 @@
1
+ import { isArrayOfType } from "@konfirm/guard";
2
+ import { isPosition, Position } from "../GeoJSON/Concept/Position";
3
+
4
+ function winding(positions: Array<Position>): number {
5
+ return positions.reduce((carry, [x, y], i, a) => {
6
+ const [nx, ny] = a[(i + 1) % a.length];
7
+
8
+ return carry + ((nx - x) * (ny + y));
9
+ }, 0);
10
+ }
11
+
12
+ const isPositionArray = isArrayOfType<Array<Position>>(isPosition);
13
+
14
+ export function isClockwiseWinding<T extends Array<Position>>(value: any): value is T {
15
+ return isPositionArray(value) && winding(value) <= 0;
16
+ }
17
+ export function isCounterClockwiseWinding<T extends Array<Position>>(value: any): value is T {
18
+ return isPositionArray(value) && winding(value) >= 0;
19
+ }
package/source/main.ts ADDED
@@ -0,0 +1,20 @@
1
+ // intersect and distance functions
2
+ export { intersect } from './Domain/Utility/Intersect';
3
+ export { distance } from './Domain/Utility/Distance';
4
+
5
+ // SimpleGeometryIterator
6
+ export { SimpleGeometryIterator } from './Domain/Iterator/SimpleGeometry';
7
+
8
+ // the individual GeoJSON types and type guards
9
+ export { Position, isPosition, isStrictPosition } from './Domain/GeoJSON/Concept/Position';
10
+ export { Point, isPoint, isStrictPoint } from './Domain/GeoJSON/Geometry/Point';
11
+ export { MultiPoint, isMultiPoint, isStrictMultiPoint } from './Domain/GeoJSON/Geometry/MultiPoint';
12
+ export { LineString, isLineString, isStrictLineString } from './Domain/GeoJSON/Geometry/LineString';
13
+ export { MultiLineString, isMultiLineString, isStrictMultiLineString } from './Domain/GeoJSON/Geometry/MultiLineString';
14
+ export { Polygon, isPolygon, isStrictPolygon } from './Domain/GeoJSON/Geometry/Polygon';
15
+ export { MultiPolygon, isMultiPolygon, isStrictMultiPolygon } from './Domain/GeoJSON/Geometry/MultiPolygon';
16
+ export { GeometryCollection, isGeometryCollection, isStrictGeometryCollection } from './Domain/GeoJSON/GeometryCollection';
17
+ export { Geometry, isGeometry, isStrictGeometry } from './Domain/GeoJSON/Geometry';
18
+ export { Feature, isFeature, isStrictFeature } from './Domain/GeoJSON/Feature';
19
+ export { FeatureCollection, isFeatureCollection, isStrictFeatureCollection } from './Domain/GeoJSON/FeatureCollection';
20
+ export { GeoJSON, isGeoJSON, isStrictGeoJSON } from './Domain/GeoJSON/GeoJSON';
@@ -0,0 +1,58 @@
1
+ import test from 'tape';
2
+ import { each } from 'template-literal-each';
3
+ import * as Export from '../../../../source/Domain/GeoJSON/Concept/Altitude';
4
+ import { exported } from '../../../helper/geometry';
5
+
6
+ exported('Domain/GeoJSON/Concept/Altitude', Export, 'isAltitude', 'isStrictAltitude');
7
+
8
+ const { isAltitude, isStrictAltitude } = Export;
9
+
10
+ test('Domain/GeoJSON/Concept/Altitude - isAltitude', (t) => {
11
+ each`
12
+ input | valid
13
+ -------------------|-------
14
+ ${0} | yes
15
+ ${0.0000000001} | yes
16
+ ${1_234_567} | yes
17
+ ${-6_371_008.7714} | yes
18
+ ${-6_378_137} | yes
19
+ ${20_180_000} | yes
20
+ ${20_180_000.1} | yes
21
+ ${-Infinity} | no
22
+ ${Infinity} | no
23
+ ${NaN} | no
24
+ ${'1234'} | no
25
+ ${false} | no
26
+ ${true} | no
27
+ `(({ input, valid }) => {
28
+ t.equal(isAltitude(input), valid === 'yes', `${input} ${valid}`);
29
+ });
30
+
31
+ t.end();
32
+ });
33
+
34
+ test('Domain/GeoJSON/Concept/Altitude - isStrictAltitude', (t) => {
35
+ each`
36
+ input | valid
37
+ -------------------|-------
38
+ ${0} | yes
39
+ ${0.0000000001} | yes
40
+ ${1_234_567} | yes
41
+ ${-6_371_008.7714} | yes
42
+ ${-6_371_008.7715} | no
43
+ ${-6_378_137} | no
44
+ ${-6_378_137.1} | no
45
+ ${20_180_000} | yes
46
+ ${20_180_000.1} | no
47
+ ${-Infinity} | no
48
+ ${Infinity} | no
49
+ ${NaN} | no
50
+ ${'1234'} | no
51
+ ${false} | no
52
+ ${true} | no
53
+ `(({ input, valid }) => {
54
+ t.equal(isStrictAltitude(input), valid === 'yes', `${input} ${valid}`);
55
+ });
56
+
57
+ t.end();
58
+ });
@@ -0,0 +1,77 @@
1
+ import test from 'tape';
2
+ import { each } from 'template-literal-each';
3
+ import * as Export from '../../../../source/Domain/GeoJSON/Concept/BoundingBox';
4
+ import { exported } from '../../../helper/geometry';
5
+
6
+ exported('Domain/GeoJSON/Concept/BoundingBox', Export, 'isBoundingBox', 'isStrictBoundingBox');
7
+
8
+ const { isBoundingBox, isStrictBoundingBox } = Export;
9
+
10
+ test('Domain/GeoJSON/Concept/BoundingBox - isBoundingBox', (t) => {
11
+ each`
12
+ input | valid
13
+ -----------------------------------------|-------
14
+ ${[-10.0, -10.0, 10.0, 10.0]} | yes
15
+ ${[100.0, 0.0, 105.0, 1.0]} | yes
16
+ ${[100.0, 0.0, -100.0, 105.0, 1.0, 0.0]} | yes
17
+ ${[177.0, -20.0, -178.0, -16.0]} | yes
18
+ ${[-178.0, -20.0, 177.0, -16.0]} | yes
19
+ ${[-370.0, -190.0, 190.0, 370.0]} | yes
20
+ ${[]} | no
21
+ ${[0]} | no
22
+ ${[-180]} | no
23
+ ${[0, 0]} | no
24
+ ${[-180, -90]} | no
25
+ ${[0, 0, 0]} | no
26
+ ${[-180, -90, 180]} | no
27
+ ${[0, 0, 0, 0]} | yes
28
+ ${[-180, -90, 180, 90]} | yes
29
+ ${[0, 0, 0, 0, 0]} | no
30
+ ${[-180, -90, 0, 180, 90]} | no
31
+ ${[0, 0, 0, 0, 0, 0]} | yes
32
+ ${[-180, -90, 0, 180, 90, 0]} | yes
33
+ ${[180, 90, 0, -180, -90, 0]} | no
34
+ ${[-180, 90, 0, 180, -90, 0]} | no
35
+ ${[180, -90, 0, -180, 90, 0]} | yes
36
+ ${[-181, -91, 0, 181, 91, 0]} | yes
37
+ `(({ input, valid }) => {
38
+ t.equal(isBoundingBox(input), valid === 'yes', `[${input}] isBoudingBox ${valid}`)
39
+ });
40
+
41
+ t.end();
42
+ });
43
+
44
+ test('Domain/GeoJSON/Concept/BoundingBox - isStrictBoundingBox', (t) => {
45
+ each`
46
+ input | valid
47
+ -----------------------------------------|-------
48
+ ${[-10.0, -10.0, 10.0, 10.0]} | yes
49
+ ${[100.0, 0.0, 105.0, 1.0]} | yes
50
+ ${[100.0, 0.0, -100.0, 105.0, 1.0, 0.0]} | yes
51
+ ${[177.0, -20.0, -178.0, -16.0]} | yes
52
+ ${[-178.0, -20.0, 177.0, -16.0]} | yes
53
+ ${[-370.0, -190.0, 190.0, 370.0]} | no
54
+ ${[]} | no
55
+ ${[0]} | no
56
+ ${[-180]} | no
57
+ ${[0, 0]} | no
58
+ ${[-180, -90]} | no
59
+ ${[0, 0, 0]} | no
60
+ ${[-180, -90, 180]} | no
61
+ ${[0, 0, 0, 0]} | yes
62
+ ${[-180, -90, 180, 90]} | yes
63
+ ${[0, 0, 0, 0, 0]} | no
64
+ ${[-180, -90, 0, 180, 90]} | no
65
+ ${[0, 0, 0, 0, 0, 0]} | yes
66
+ ${[-180, -90, 0, 180, 90, 0]} | yes
67
+ ${[180, 90, 0, -180, -90, 0]} | no
68
+ ${[180, 90, 0, -180, -90, 0]} | no
69
+ ${[-180, 90, 0, 180, -90, 0]} | no
70
+ ${[180, -90, 0, -180, 90, 0]} | yes
71
+ ${[-181, -91, 0, 181, 91, 0]} | no
72
+ `(({ input, valid }) => {
73
+ t.equal(isStrictBoundingBox(input), valid === 'yes', `[${input}] isStrictBoudingBox ${valid}`)
74
+ });
75
+
76
+ t.end();
77
+ });
@@ -0,0 +1,65 @@
1
+ import test from 'tape';
2
+ import { each } from 'template-literal-each';
3
+ import * as Export from '../../../../source/Domain/GeoJSON/Concept/ExteriorRing';
4
+ import { exported } from '../../../helper/geometry';
5
+ import { coordinates as Italy } from '../../../data/Italy';
6
+ import { coordinates as SanMarino, polygon } from '../../../data/SanMarino';
7
+ import { coordinates as HolySee } from '../../../data/HolySee';
8
+
9
+ exported('Domain/GeoJSON/Concept/ExteriorRing', Export, 'isExteriorRing', 'isStrictExteriorRing');
10
+
11
+ const { isExteriorRing, isStrictExteriorRing } = Export;
12
+
13
+ test('Domain/GeoJSON/Concept/ExteriorRing - isExteriorRing', (t) => {
14
+ each`
15
+ input | valid
16
+ -------------------------------------------------|----
17
+ ${undefined} | no
18
+ ${null} | no
19
+ ${'[[1,0],[1,1],[0,1],[1,0]]'} | no
20
+ ${[[1, 0], [1, 1], [0, 1], [0, 0.5], [1, 0]]} | yes
21
+ ${[[1, 0], [0, 0.5], [0, 1], [1, 1], [1, 0]]} | yes
22
+ ${[[1, 0], [1, 1], [0, 1], [1, 0]]} | yes
23
+ ${[[1, 0], [1, 1], [0, 1], [0, 0]]} | no
24
+ ${[[1, 0], [1, 1], [1, 0]]} | no
25
+ ${[[1, 0], [1, 0]]} | no
26
+ ${[[1, 0]]} | no
27
+ ${[[-181, -91], [0, -91], [0, 91], [-181, -91]]} | yes
28
+ `(({ input, valid }) => {
29
+ t.equal(isExteriorRing(input), valid === 'yes', `[${input}] isExteriorRing ${valid}`);
30
+ });
31
+
32
+ t.ok(Italy.every((polygon) => polygon.every(isExteriorRing)), 'coordinates for Italy polygon all match isExteriorRing');
33
+ t.notOk(SanMarino.every(isExteriorRing), 'coordinates for SanMarino does not match isExteriorRing');
34
+ t.notOk(HolySee.every(isExteriorRing), 'coordinates for HolySee does not match isExteriorRing');
35
+
36
+ t.end();
37
+ });
38
+
39
+ test('Domain/GeoJSON/Concept/ExteriorRing - isStrictExteriorRing', (t) => {
40
+ each`
41
+ input | valid
42
+ -------------------------------------------------|----
43
+ ${undefined} | no
44
+ ${null} | no
45
+ ${'[[1,0],[1,1],[0,1],[1,0]]'} | no
46
+ ${[[1, 0], [1, 1], [0, 1], [0, 0.5], [1, 0]]} | no
47
+ ${[[1, 0], [0, 0.5], [0, 1], [1, 1], [1, 0]]} | yes
48
+ ${[[1, 0], [1, 1], [0, 1], [1, 0]]} | no
49
+ ${[[1, 0], [1, 1], [0, 1], [0, 0]]} | no
50
+ ${[[1, 0], [1, 1], [1, 0]]} | no
51
+ ${[[1, 0], [1, 0]]} | no
52
+ ${[[1, 0]]} | no
53
+ ${[[-181, -91], [0, -91], [0, 91], [-181, -91]]} | no
54
+ `(({ input, valid }) => {
55
+ t.equal(isStrictExteriorRing(input), valid === 'yes', `[${input}] isStrictExteriorRing ${valid}`);
56
+ });
57
+
58
+ t.notOk(Italy.every((polygon) => polygon.every(isStrictExteriorRing)), 'coordinates for Italy polygon do not match isStrictExteriorRing');
59
+ t.ok(Italy.every((polygon) => isStrictExteriorRing(polygon[0])), 'first polygon coordinates for Italy all match isStrictExteriorRing');
60
+ t.ok(Italy.filter((polygon) => polygon.length > 1).every((polygon) => !polygon.slice(1).some(isStrictExteriorRing)), 'second+ polygon coordinates for Italy none match isStrictExteriorRing')
61
+ t.notOk(SanMarino.every(isStrictExteriorRing), 'coordinates for SanMarino does not match isStrictExteriorRing');
62
+ t.notOk(HolySee.every(isStrictExteriorRing), 'coordinates for HolySee does not match isStrictExteriorRing');
63
+
64
+ t.end();
65
+ });