1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tools_1 = require("@toba/tools");
|
4 | const piDeg = Math.PI / 180.0;
|
5 | const radiusMiles = 3958.756;
|
6 | const earthRadius = radiusMiles;
|
7 | var Unit;
|
8 | (function (Unit) {
|
9 | Unit[Unit["English"] = 0] = "English";
|
10 | Unit[Unit["Metric"] = 1] = "Metric";
|
11 | })(Unit = exports.Unit || (exports.Unit = {}));
|
12 | const toRadians = (deg) => deg * piDeg;
|
13 | const toDegrees = (rad) => (rad * 180) / Math.PI;
|
14 | const sameLocation = (p1, p2) => tools_1.is.array(p1) &&
|
15 | tools_1.is.array(p2) &&
|
16 | p1[1] == p2[1] &&
|
17 | p1[0] == p2[0];
|
18 | function pointDistance(p1, p2) {
|
19 | if (sameLocation(p1, p2) || !tools_1.is.array(p1) || !tools_1.is.array(p2)) {
|
20 | return 0;
|
21 | }
|
22 | const radLat1 = toRadians(p1[1]);
|
23 | const radLat2 = toRadians(p2[1]);
|
24 | const latDistance = toRadians(p2[1] - p1[1]);
|
25 | const lonDistance = toRadians(p2[0] - p1[0]);
|
26 | const a = Math.sin(latDistance / 2) ** 2 +
|
27 | Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(lonDistance / 2) ** 2;
|
28 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
29 | return earthRadius * c;
|
30 | }
|
31 | const length = (points) => points.reduce((total, p, i) => total + (i > 0 ? pointDistance(points[i - 1], p) : 0), 0);
|
32 | function speed(p1, p2) {
|
33 | const t = Math.abs(p1[3] - p2[3]);
|
34 | const d = pointDistance(p1, p2);
|
35 | return t > 0 && d > 0 ? d / (t / 3600000) : 0;
|
36 | }
|
37 | function duration(line) {
|
38 | const firstPoint = line[0];
|
39 | const lastPoint = line[line.length - 1];
|
40 | return (lastPoint[3] - firstPoint[3]) / 3600000;
|
41 | }
|
42 | function pointLineDistance(p, p1, p2) {
|
43 | let x = p1[0];
|
44 | let y = p1[1];
|
45 | let Δx = p2[0] - x;
|
46 | let Δy = p2[1] - y;
|
47 | if (Δx !== 0 || Δy !== 0) {
|
48 | const t = ((p[0] - x) * Δx + (p[1] - y) * Δy) /
|
49 | (Δx * Δx + Δy * Δy);
|
50 | if (t > 1) {
|
51 | x = p2[0];
|
52 | y = p2[1];
|
53 | }
|
54 | else if (t > 0) {
|
55 | x += Δx * t;
|
56 | y += Δy * t;
|
57 | }
|
58 | }
|
59 | Δx = p[0] - x;
|
60 | Δy = p[1] - y;
|
61 | return Δx * Δx + Δy * Δy;
|
62 | }
|
63 | function centroid(points) {
|
64 | const count = points.length;
|
65 | if (count == 0) {
|
66 | return null;
|
67 | }
|
68 | if (count == 1) {
|
69 | return {
|
70 | lon: points[0][0],
|
71 | lat: points[0][1]
|
72 | };
|
73 | }
|
74 | const location = { lon: 0, lat: 0 };
|
75 | let x = 0;
|
76 | let y = 0;
|
77 | let z = 0;
|
78 | points.forEach(p => {
|
79 | const radLat = toRadians(p[1]);
|
80 | const radLon = toRadians(p[0]);
|
81 | x += Math.cos(radLat) * Math.cos(radLon);
|
82 | y += Math.cos(radLat) * Math.sin(radLon);
|
83 | z += Math.sin(radLat);
|
84 | });
|
85 | x /= count;
|
86 | y /= count;
|
87 | z /= count;
|
88 | const lon = Math.atan2(y, x);
|
89 | const hyp = Math.sqrt(x * x + y * y);
|
90 | const lat = Math.atan2(z, hyp);
|
91 | location.lat = toDegrees(lat);
|
92 | location.lon = toDegrees(lon);
|
93 | return location;
|
94 | }
|
95 | function simplify(points, maxPointDeviationFeet = 0) {
|
96 | if (maxPointDeviationFeet <= 0) {
|
97 | return points;
|
98 | }
|
99 | const yard = 3;
|
100 | const mile = yard * 1760;
|
101 | const equatorFeet = mile * radiusMiles;
|
102 | const len = points.length;
|
103 | const keep = new Uint8Array(len);
|
104 | const tolerance = maxPointDeviationFeet / equatorFeet;
|
105 | let first = 0;
|
106 | let last = len - 1;
|
107 | const stack = [];
|
108 | let maxDistance = 0;
|
109 | let distance = 0;
|
110 | let index = 0;
|
111 | keep[first] = 1;
|
112 | keep[last] = 1;
|
113 | while (last > 0) {
|
114 | maxDistance = 0;
|
115 | for (let i = first + 1; i < last; i++) {
|
116 | distance = pointLineDistance(points[i], points[first], points[last]);
|
117 | if (distance > maxDistance) {
|
118 | index = i;
|
119 | maxDistance = distance;
|
120 | }
|
121 | }
|
122 | if (maxDistance > tolerance) {
|
123 | keep[index] = 1;
|
124 | stack.push(first, index, index, last);
|
125 | }
|
126 | let i = stack.pop();
|
127 | if (i !== undefined) {
|
128 | last = i;
|
129 | i = stack.pop();
|
130 | if (i !== undefined) {
|
131 | first = i;
|
132 | }
|
133 | }
|
134 | }
|
135 | return points.filter((_p, i) => keep[i] == 1);
|
136 | }
|
137 | exports.measure = {
|
138 | speed,
|
139 | length,
|
140 | centroid,
|
141 | duration,
|
142 | toRadians,
|
143 | toDegrees,
|
144 | sameLocation,
|
145 | pointDistance,
|
146 | simplify
|
147 | };
|