@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.
- package/.editorconfig +16 -0
- package/.github/workflows/release.yml +21 -0
- package/.github/workflows/tests.yml +56 -0
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/Domain/Constants.d.ts +5 -0
- package/dist/Domain/GeoJSON/Concept/Altitude.d.ts +3 -0
- package/dist/Domain/GeoJSON/Concept/BoundingBox.d.ts +6 -0
- package/dist/Domain/GeoJSON/Concept/ExteriorRing.d.ts +4 -0
- package/dist/Domain/GeoJSON/Concept/GeoJSONObject.d.ts +11 -0
- package/dist/Domain/GeoJSON/Concept/InteriorRing.d.ts +4 -0
- package/dist/Domain/GeoJSON/Concept/Latitude.d.ts +3 -0
- package/dist/Domain/GeoJSON/Concept/LinearRing.d.ts +4 -0
- package/dist/Domain/GeoJSON/Concept/Longitude.d.ts +3 -0
- package/dist/Domain/GeoJSON/Concept/Position.d.ts +7 -0
- package/dist/Domain/GeoJSON/Feature.d.ts +12 -0
- package/dist/Domain/GeoJSON/FeatureCollection.d.ts +8 -0
- package/dist/Domain/GeoJSON/GeoJSON.d.ts +7 -0
- package/dist/Domain/GeoJSON/Geometry/LineString.d.ts +11 -0
- package/dist/Domain/GeoJSON/Geometry/MultiLineString.d.ts +7 -0
- package/dist/Domain/GeoJSON/Geometry/MultiPoint.d.ts +8 -0
- package/dist/Domain/GeoJSON/Geometry/MultiPolygon.d.ts +7 -0
- package/dist/Domain/GeoJSON/Geometry/Point.d.ts +11 -0
- package/dist/Domain/GeoJSON/Geometry/Polygon.d.ts +11 -0
- package/dist/Domain/GeoJSON/Geometry.d.ts +9 -0
- package/dist/Domain/GeoJSON/GeometryCollection.d.ts +8 -0
- package/dist/Domain/GeoJSON/GeometryObject.d.ts +12 -0
- package/dist/Domain/Guards/Number.d.ts +3 -0
- package/dist/Domain/Guards/Tuple.d.ts +3 -0
- package/dist/Domain/Iterator/IterablePair.d.ts +32 -0
- package/dist/Domain/Iterator/SimpleGeometry.d.ts +90 -0
- package/dist/Domain/Utility/Calculate.d.ts +9 -0
- package/dist/Domain/Utility/Distance.d.ts +3 -0
- package/dist/Domain/Utility/Intersect.d.ts +2 -0
- package/dist/Domain/Utility/Segments.d.ts +2 -0
- package/dist/Domain/Utility/Winding.d.ts +3 -0
- package/dist/geojson.cjs.js +655 -0
- package/dist/geojson.cjs.min.js +1 -0
- package/dist/geojson.d.ts +198 -0
- package/dist/geojson.es.js +627 -0
- package/dist/geojson.es.min.js +1 -0
- package/dist/geojson.js +660 -0
- package/dist/geojson.min.js +1 -0
- package/dist/main.d.ts +15 -0
- package/package.json +63 -0
- package/rollup.config.mjs +43 -0
- package/source/Domain/Constants.ts +5 -0
- package/source/Domain/GeoJSON/Concept/Altitude.ts +9 -0
- package/source/Domain/GeoJSON/Concept/BoundingBox.ts +31 -0
- package/source/Domain/GeoJSON/Concept/ExteriorRing.ts +12 -0
- package/source/Domain/GeoJSON/Concept/GeoJSONObject.ts +23 -0
- package/source/Domain/GeoJSON/Concept/InteriorRing.ts +12 -0
- package/source/Domain/GeoJSON/Concept/Latitude.ts +7 -0
- package/source/Domain/GeoJSON/Concept/LinearRing.ts +14 -0
- package/source/Domain/GeoJSON/Concept/Longitude.ts +7 -0
- package/source/Domain/GeoJSON/Concept/Position.ts +18 -0
- package/source/Domain/GeoJSON/Feature.ts +24 -0
- package/source/Domain/GeoJSON/FeatureCollection.ts +16 -0
- package/source/Domain/GeoJSON/GeoJSON.ts +9 -0
- package/source/Domain/GeoJSON/Geometry/LineString.ts +12 -0
- package/source/Domain/GeoJSON/Geometry/MultiLineString.ts +9 -0
- package/source/Domain/GeoJSON/Geometry/MultiPoint.ts +10 -0
- package/source/Domain/GeoJSON/Geometry/MultiPolygon.ts +9 -0
- package/source/Domain/GeoJSON/Geometry/Point.ts +12 -0
- package/source/Domain/GeoJSON/Geometry/Polygon.ts +20 -0
- package/source/Domain/GeoJSON/Geometry.ts +11 -0
- package/source/Domain/GeoJSON/GeometryCollection.ts +23 -0
- package/source/Domain/GeoJSON/GeometryObject.ts +20 -0
- package/source/Domain/Guards/Number.ts +13 -0
- package/source/Domain/Guards/Tuple.ts +6 -0
- package/source/Domain/Guards/Utility.ts +1 -0
- package/source/Domain/Iterator/IterablePair.ts +47 -0
- package/source/Domain/Iterator/SimpleGeometry.ts +137 -0
- package/source/Domain/Utility/Calculate.ts +143 -0
- package/source/Domain/Utility/Distance.ts +56 -0
- package/source/Domain/Utility/Intersect.ts +42 -0
- package/source/Domain/Utility/Numeric.ts +8 -0
- package/source/Domain/Utility/Segments.ts +5 -0
- package/source/Domain/Utility/Winding.ts +19 -0
- package/source/main.ts +20 -0
- package/test/Domain/GeoJSON/Concept/Altitude.ts +58 -0
- package/test/Domain/GeoJSON/Concept/BoundingBox.ts +77 -0
- package/test/Domain/GeoJSON/Concept/ExteriorRing.ts +65 -0
- package/test/Domain/GeoJSON/Concept/GeoJSONObject.ts +56 -0
- package/test/Domain/GeoJSON/Concept/InteriorRing.ts +65 -0
- package/test/Domain/GeoJSON/Concept/Latitude.ts +56 -0
- package/test/Domain/GeoJSON/Concept/LinearRing.ts +63 -0
- package/test/Domain/GeoJSON/Concept/Longitude.ts +56 -0
- package/test/Domain/GeoJSON/Concept/Position.ts +56 -0
- package/test/Domain/GeoJSON/Feature.ts +9 -0
- package/test/Domain/GeoJSON/FeatureCollection.ts +9 -0
- package/test/Domain/GeoJSON/GeoJSON.ts +21 -0
- package/test/Domain/GeoJSON/Geometry/LineString.ts +11 -0
- package/test/Domain/GeoJSON/Geometry/MultiLineString.ts +11 -0
- package/test/Domain/GeoJSON/Geometry/MultiPoint.ts +11 -0
- package/test/Domain/GeoJSON/Geometry/MultiPolygon.ts +11 -0
- package/test/Domain/GeoJSON/Geometry/Point.ts +11 -0
- package/test/Domain/GeoJSON/Geometry/Polygon.ts +11 -0
- package/test/Domain/GeoJSON/Geometry.ts +9 -0
- package/test/Domain/GeoJSON/GeometryCollection.ts +9 -0
- package/test/Domain/GeoJSON/GeometryObject.ts +4 -0
- package/test/Domain/Guards/Number.ts +49 -0
- package/test/Domain/Guards/Tuple.ts +27 -0
- package/test/Domain/Iterator/IterablePair.ts +203 -0
- package/test/Domain/Iterator/SimpleGeometry.ts +195 -0
- package/test/Domain/Utility/Calculate.ts +178 -0
- package/test/Domain/Utility/Distance.ts +19 -0
- package/test/Domain/Utility/Intersect.ts +54 -0
- package/test/Domain/Utility/Numeric.ts +30 -0
- package/test/Domain/Utility/Winding.ts +52 -0
- package/test/README.ts +123 -0
- package/test/data/Distance.ts +51 -0
- package/test/data/HolySee.ts +74 -0
- package/test/data/Intersect.ts +300 -0
- package/test/data/Italy.ts +2883 -0
- package/test/data/SanMarino.ts +83 -0
- package/test/data/Shapes.ts +107 -0
- package/test/helper/geometry.ts +76 -0
- package/test/helper/malform.ts +82 -0
- package/test/main.ts +4 -0
- 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,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
|
+
});
|