UNPKG

40.3 kBJavaScriptView Raw
1/*! Leaflet.Geodesic 2.5.5-0 - (c) Henry Thasler - https://github.com/henrythasler/Leaflet.Geodesic */
2import { LatLng, GeoJSON, Polyline, Util } from 'leaflet';
3
4/*! *****************************************************************************
5Copyright (c) Microsoft Corporation. All rights reserved.
6Licensed under the Apache License, Version 2.0 (the "License"); you may not use
7this file except in compliance with the License. You may obtain a copy of the
8License at http://www.apache.org/licenses/LICENSE-2.0
9
10THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
12WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
13MERCHANTABLITY OR NON-INFRINGEMENT.
14
15See the Apache Version 2.0 License for specific language governing permissions
16and limitations under the License.
17***************************************************************************** */
18/* global Reflect, Promise */
19
20var extendStatics = function(d, b) {
21 extendStatics = Object.setPrototypeOf ||
22 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
23 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
24 return extendStatics(d, b);
25};
26
27function __extends(d, b) {
28 extendStatics(d, b);
29 function __() { this.constructor = d; }
30 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
31}
32
33var __assign = function() {
34 __assign = Object.assign || function __assign(t) {
35 for (var s, i = 1, n = arguments.length; i < n; i++) {
36 s = arguments[i];
37 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
38 }
39 return t;
40 };
41 return __assign.apply(this, arguments);
42};
43
44function __spreadArrays() {
45 for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
46 for (var r = Array(s), k = 0, i = 0; i < il; i++)
47 for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
48 r[k] = a[j];
49 return r;
50}
51
52var GeodesicCore = /** @class */ (function () {
53 function GeodesicCore(options) {
54 this.options = { wrap: true, steps: 3 };
55 this.ellipsoid = {
56 a: 6378137,
57 b: 6356752.3142,
58 f: 1 / 298.257223563
59 }; // WGS-84
60 this.options = __assign(__assign({}, this.options), options);
61 }
62 GeodesicCore.prototype.toRadians = function (degree) {
63 return degree * Math.PI / 180;
64 };
65 GeodesicCore.prototype.toDegrees = function (radians) {
66 return radians * 180 / Math.PI;
67 };
68 /**
69 * implements scientific modulus
70 * source: http://www.codeavenger.com/2017/05/19/JavaScript-Modulo-operation-and-the-Caesar-Cipher.html
71 * @param n
72 * @param p
73 * @return
74 */
75 GeodesicCore.prototype.mod = function (n, p) {
76 var r = n % p;
77 return r < 0 ? r + p : r;
78 };
79 /**
80 * source: https://github.com/chrisveness/geodesy/blob/master/dms.js
81 * @param degrees arbitrary value
82 * @return degrees between 0..360
83 */
84 GeodesicCore.prototype.wrap360 = function (degrees) {
85 if (0 <= degrees && degrees < 360) {
86 return degrees; // avoid rounding due to arithmetic ops if within range
87 }
88 else {
89 return this.mod(degrees, 360);
90 }
91 };
92 /**
93 * general wrap function with arbitrary max value
94 * @param degrees arbitrary value
95 * @param max
96 * @return degrees between `-max`..`+max`
97 */
98 GeodesicCore.prototype.wrap = function (degrees, max) {
99 if (max === void 0) { max = 360; }
100 if (-max <= degrees && degrees <= max) {
101 return degrees;
102 }
103 else {
104 return this.mod((degrees + max), 2 * max) - max;
105 }
106 };
107 /**
108 * Vincenty direct calculation.
109 * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
110 * source: https://github.com/chrisveness/geodesy/blob/master/latlon-ellipsoidal-vincenty.js
111 *
112 * @param start starting point
113 * @param bearing initial bearing (in degrees)
114 * @param distance distance from starting point to calculate along given bearing in meters.
115 * @param maxInterations How many iterations can be made to reach the allowed deviation (`ε`), before an error will be thrown.
116 * @return Final point (destination point) and bearing (in degrees)
117 */
118 GeodesicCore.prototype.direct = function (start, bearing, distance, maxInterations) {
119 if (maxInterations === void 0) { maxInterations = 100; }
120 var φ1 = this.toRadians(start.lat);
121 var λ1 = this.toRadians(start.lng);
122 var α1 = this.toRadians(bearing);
123 var s = distance;
124 var ε = Number.EPSILON * 1000;
125 var _a = this.ellipsoid, a = _a.a, b = _a.b, f = _a.f;
126 var sinα1 = Math.sin(α1);
127 var cosα1 = Math.cos(α1);
128 var tanU1 = (1 - f) * Math.tan(φ1), cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1;
129 var σ1 = Math.atan2(tanU1, cosα1); // σ1 = angular distance on the sphere from the equator to P1
130 var sinα = cosU1 * sinα1; // α = azimuth of the geodesic at the equator
131 var cosSqα = 1 - sinα * sinα;
132 var uSq = cosSqα * (a * a - b * b) / (b * b);
133 var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
134 var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
135 var σ = s / (b * A), sinσ = null, cosσ = null, Δσ = null; // σ = angular distance P₁ P₂ on the sphere
136 var cos2σₘ = null; // σₘ = angular distance on the sphere from the equator to the midpoint of the line
137 var σʹ = null, iterations = 0;
138 do {
139 cos2σₘ = Math.cos(2 * σ1 + σ);
140 sinσ = Math.sin(σ);
141 cosσ = Math.cos(σ);
142 Δσ = B * sinσ * (cos2σₘ + B / 4 * (cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) -
143 B / 6 * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));
144 σʹ = σ;
145 σ = s / (b * A) + Δσ;
146 } while (Math.abs(σ - σʹ) > ε && ++iterations < maxInterations);
147 if (iterations >= maxInterations) {
148 throw new EvalError("Direct vincenty formula failed to converge after " + maxInterations + " iterations \n (start=" + start.lat + "/" + start.lng + "; bearing=" + bearing + "; distance=" + distance + ")"); // not possible?
149 }
150 var x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
151 var φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - f) * Math.sqrt(sinα * sinα + x * x));
152 var λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
153 var C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
154 var dL = λ - (1 - C) * f * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));
155 var λ2 = λ1 + dL;
156 var α2 = Math.atan2(sinα, -x);
157 return {
158 lat: this.toDegrees(φ2),
159 lng: this.toDegrees(λ2),
160 bearing: this.wrap360(this.toDegrees(α2))
161 };
162 };
163 /**
164 * Vincenty inverse calculation.
165 * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
166 * source: https://github.com/chrisveness/geodesy/blob/master/latlon-ellipsoidal-vincenty.js
167 *
168 * @param start Latitude/longitude of starting point.
169 * @param dest Latitude/longitude of destination point.
170 * @return Object including distance, initialBearing, finalBearing.
171 */
172 GeodesicCore.prototype.inverse = function (start, dest, maxInterations, mitigateConvergenceError) {
173 if (maxInterations === void 0) { maxInterations = 100; }
174 if (mitigateConvergenceError === void 0) { mitigateConvergenceError = true; }
175 var p1 = start, p2 = dest;
176 var φ1 = this.toRadians(p1.lat), λ1 = this.toRadians(p1.lng);
177 var φ2 = this.toRadians(p2.lat), λ2 = this.toRadians(p2.lng);
178 var π = Math.PI;
179 var ε = Number.EPSILON;
180 // allow alternative ellipsoid to be specified
181 var _a = this.ellipsoid, a = _a.a, b = _a.b, f = _a.f;
182 var dL = λ2 - λ1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanφ.
183 var tanU1 = (1 - f) * Math.tan(φ1), cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1;
184 var tanU2 = (1 - f) * Math.tan(φ2), cosU2 = 1 / Math.sqrt((1 + tanU2 * tanU2)), sinU2 = tanU2 * cosU2;
185 var antipodal = Math.abs(dL) > π / 2 || Math.abs(φ2 - φ1) > π / 2;
186 var λ = dL, sinλ = null, cosλ = null; // λ = difference in longitude on an auxiliary sphere
187 var σ = antipodal ? π : 0, sinσ = 0, cosσ = antipodal ? -1 : 1, sinSqσ = null; // σ = angular distance P₁ P₂ on the sphere
188 var cos2σₘ = 1; // σₘ = angular distance on the sphere from the equator to the midpoint of the line
189 var sinα = null, cosSqα = 1; // α = azimuth of the geodesic at the equator
190 var C = null;
191 var λʹ = null, iterations = 0;
192 do {
193 sinλ = Math.sin(λ);
194 cosλ = Math.cos(λ);
195 sinSqσ = (cosU2 * sinλ) * (cosU2 * sinλ) + (cosU1 * sinU2 - sinU1 * cosU2 * cosλ) * (cosU1 * sinU2 - sinU1 * cosU2 * cosλ);
196 if (Math.abs(sinSqσ) < ε) {
197 break; // co-incident/antipodal points (falls back on λ/σ = L)
198 }
199 sinσ = Math.sqrt(sinSqσ);
200 cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
201 σ = Math.atan2(sinσ, cosσ);
202 sinα = cosU1 * cosU2 * sinλ / sinσ;
203 cosSqα = 1 - sinα * sinα;
204 cos2σₘ = (cosSqα !== 0) ? (cosσ - 2 * sinU1 * sinU2 / cosSqα) : 0; // on equatorial line cos²α = 0 (§6)
205 C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
206 λʹ = λ;
207 λ = dL + (1 - C) * f * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));
208 var iterationCheck = antipodal ? Math.abs(λ) - π : Math.abs(λ);
209 if (iterationCheck > π) {
210 throw new EvalError('λ > π');
211 }
212 } while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < maxInterations);
213 if (iterations >= maxInterations) {
214 if (mitigateConvergenceError) {
215 return this.inverse(start, new LatLng(dest.lat, dest.lng - 0.01), maxInterations, mitigateConvergenceError);
216 }
217 else {
218 throw new EvalError("Inverse vincenty formula failed to converge after " + maxInterations + " iterations \n (start=" + start.lat + "/" + start.lng + "; dest=" + dest.lat + "/" + dest.lng + ")");
219 }
220 }
221 var uSq = cosSqα * (a * a - b * b) / (b * b);
222 var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
223 var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
224 var Δσ = B * sinσ * (cos2σₘ + B / 4 * (cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) -
225 B / 6 * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));
226 var s = b * A * (σ - Δσ); // s = length of the geodesic
227 // note special handling of exactly antipodal points where sin²σ = 0 (due to discontinuity
228 // atan2(0, 0) = 0 but atan2(this.ε, 0) = π/2 / 90°) - in which case bearing is always meridional,
229 // due north (or due south!)
230 // α = azimuths of the geodesic; α2 the direction P₁ P₂ produced
231 var α1 = Math.abs(sinSqσ) < ε ? 0 : Math.atan2(cosU2 * sinλ, cosU1 * sinU2 - sinU1 * cosU2 * cosλ);
232 var α2 = Math.abs(sinSqσ) < ε ? π : Math.atan2(cosU1 * sinλ, -sinU1 * cosU2 + cosU1 * sinU2 * cosλ);
233 return {
234 distance: s,
235 initialBearing: Math.abs(s) < ε ? NaN : this.wrap360(this.toDegrees(α1)),
236 finalBearing: Math.abs(s) < ε ? NaN : this.wrap360(this.toDegrees(α2))
237 };
238 };
239 /**
240 * Returns the point of intersection of two paths defined by position and bearing.
241 * This calculation uses a spherical model of the earth. This will lead to small errors compared to an ellipsiod model.
242 * based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
243 * source: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js
244 *
245 * @param firstPos 1st path: position and bearing
246 * @param firstBearing
247 * @param secondPos 2nd path: position and bearing
248 * @param secondBearing
249 */
250 GeodesicCore.prototype.intersection = function (firstPos, firstBearing, secondPos, secondBearing) {
251 var φ1 = this.toRadians(firstPos.lat);
252 var λ1 = this.toRadians(firstPos.lng);
253 var φ2 = this.toRadians(secondPos.lat);
254 var λ2 = this.toRadians(secondPos.lng);
255 var θ13 = this.toRadians(firstBearing);
256 var θ23 = this.toRadians(secondBearing);
257 var Δφ = φ2 - φ1, Δλ = λ2 - λ1;
258 var π = Math.PI;
259 var ε = Number.EPSILON;
260 // angular distance p1-p2
261 var δ12 = 2 * Math.asin(Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2)
262 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)));
263 if (Math.abs(δ12) < ε) {
264 return firstPos; // coincident points
265 }
266 // initial/final bearings between points
267 var cosθa = (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ1));
268 var cosθb = (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ2));
269 var θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors
270 var θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors
271 var θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * π - θa;
272 var θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * π - θb : θb;
273 var α1 = θ13 - θ12; // angle 2-1-3
274 var α2 = θ21 - θ23; // angle 1-2-3
275 if (Math.sin(α1) === 0 && Math.sin(α2) === 0) {
276 return null; // infinite intersections
277 }
278 if (Math.sin(α1) * Math.sin(α2) < 0) {
279 return null; // ambiguous intersection (antipodal?)
280 }
281 var cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
282 var δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2), Math.cos(α2) + Math.cos(α1) * cosα3);
283 var φ3 = Math.asin(Math.min(Math.max(Math.sin(φ1) * Math.cos(δ13) + Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13), -1), 1));
284 var Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1), Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
285 var λ3 = λ1 + Δλ13;
286 return new LatLng(this.toDegrees(φ3), this.toDegrees(λ3));
287 };
288 GeodesicCore.prototype.midpoint = function (start, dest) {
289 // φm = atan2( sinφ1 + sinφ2, √( (cosφ1 + cosφ2⋅cosΔλ)² + cos²φ2⋅sin²Δλ ) )
290 // λm = λ1 + atan2(cosφ2⋅sinΔλ, cosφ1 + cosφ2⋅cosΔλ)
291 // midpoint is sum of vectors to two points: mathforum.org/library/drmath/view/51822.html
292 var φ1 = this.toRadians(start.lat);
293 var λ1 = this.toRadians(start.lng);
294 var φ2 = this.toRadians(dest.lat);
295 var Δλ = this.toRadians(dest.lng - start.lng);
296 // get cartesian coordinates for the two points
297 var A = { x: Math.cos(φ1), y: 0, z: Math.sin(φ1) }; // place point A on prime meridian y=0
298 var B = { x: Math.cos(φ2) * Math.cos(Δλ), y: Math.cos(φ2) * Math.sin(Δλ), z: Math.sin(φ2) };
299 // vector to midpoint is sum of vectors to two points (no need to normalise)
300 var C = { x: A.x + B.x, y: A.y + B.y, z: A.z + B.z };
301 var φm = Math.atan2(C.z, Math.sqrt(C.x * C.x + C.y * C.y));
302 var λm = λ1 + Math.atan2(C.y, C.x);
303 return new LatLng(this.toDegrees(φm), this.toDegrees(λm));
304 };
305 return GeodesicCore;
306}());
307
308var GeodesicGeometry = /** @class */ (function () {
309 function GeodesicGeometry(options) {
310 this.geodesic = new GeodesicCore();
311 this.steps = (options && options.steps !== undefined) ? options.steps : 3;
312 }
313 /**
314 * A geodesic line between `start` and `dest` is created with this recursive function.
315 * It calculates the geodesic midpoint between `start` and `dest` and uses this midpoint to call itself again (twice!).
316 * The results are then merged into one continuous linestring.
317 *
318 * The number of resulting vertices (incl. `start` and `dest`) depends on the initial value for `iterations`
319 * and can be calculated with: vertices == 1 + 2 ** (initialIterations + 1)
320 *
321 * As this is an exponential function, be extra careful to limit the initial value for `iterations` (8 results in 513 vertices).
322 *
323 * @param start start position
324 * @param dest destination
325 * @param iterations
326 * @return resulting linestring
327 */
328 GeodesicGeometry.prototype.recursiveMidpoint = function (start, dest, iterations) {
329 var geom = [start, dest];
330 var midpoint = this.geodesic.midpoint(start, dest);
331 if (iterations > 0) {
332 geom.splice.apply(geom, __spreadArrays([0, 1], this.recursiveMidpoint(start, midpoint, iterations - 1)));
333 geom.splice.apply(geom, __spreadArrays([geom.length - 2, 2], this.recursiveMidpoint(midpoint, dest, iterations - 1)));
334 }
335 else {
336 geom.splice(1, 0, midpoint);
337 }
338 return geom;
339 };
340 /**
341 * This is the wrapper-function to generate a geodesic line. It's just for future backwards-compatibility
342 * if there is another algorithm used to create the actual line.
343 *
344 * The `steps`-property is used to define the number of resulting vertices of the linestring: vertices == 1 + 2 ** (steps + 1)
345 * The value for `steps` is currently limited to 8 (513 vertices) for performance reasons until another algorithm is found.
346 *
347 * @param start start position
348 * @param dest destination
349 * @return resulting linestring
350 */
351 GeodesicGeometry.prototype.line = function (start, dest) {
352 return this.recursiveMidpoint(start, dest, Math.min(8, this.steps));
353 };
354 GeodesicGeometry.prototype.multiLineString = function (latlngs) {
355 var _this = this;
356 var multiLineString = [];
357 latlngs.forEach(function (linestring) {
358 var segment = [];
359 for (var j = 1; j < linestring.length; j++) {
360 segment.splice.apply(segment, __spreadArrays([segment.length - 1, 1], _this.line(linestring[j - 1], linestring[j])));
361 }
362 multiLineString.push(segment);
363 });
364 return multiLineString;
365 };
366 GeodesicGeometry.prototype.lineString = function (latlngs) {
367 return this.multiLineString([latlngs])[0];
368 };
369 /**
370 *
371 * Is much (10x) faster than the previous implementation:
372 *
373 * ```
374 * Benchmark (no split): splitLine x 459,044 ops/sec ±0.53% (95 runs sampled)
375 * Benchmark (split): splitLine x 42,999 ops/sec ±0.51% (97 runs sampled)
376 * ```
377 *
378 * @param startPosition
379 * @param destPosition
380 */
381 GeodesicGeometry.prototype.splitLine = function (startPosition, destPosition) {
382 var antimeridianWest = {
383 point: new LatLng(89.9, -180.0000001),
384 bearing: 180
385 };
386 var antimeridianEast = {
387 point: new LatLng(89.9, 180.0000001),
388 bearing: 180
389 };
390 // make a copy to work with
391 var start = new LatLng(startPosition.lat, startPosition.lng);
392 var dest = new LatLng(destPosition.lat, destPosition.lng);
393 start.lng = this.geodesic.wrap(start.lng, 360);
394 dest.lng = this.geodesic.wrap(dest.lng, 360);
395 if ((dest.lng - start.lng) > 180) {
396 dest.lng = dest.lng - 360;
397 }
398 else if ((dest.lng - start.lng) < -180) {
399 dest.lng = dest.lng + 360;
400 }
401 var result = [[new LatLng(start.lat, this.geodesic.wrap(start.lng, 180)), new LatLng(dest.lat, this.geodesic.wrap(dest.lng, 180))]];
402 // crossing antimeridian from "this" side?
403 if ((start.lng >= -180) && (start.lng <= 180)) {
404 // crossing the "western" antimeridian
405 if (dest.lng < -180) {
406 var bearing = this.geodesic.inverse(start, dest).initialBearing;
407 var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);
408 if (intersection) {
409 result = [[start, intersection], [new LatLng(intersection.lat, intersection.lng + 360), new LatLng(dest.lat, dest.lng + 360)]];
410 }
411 }
412 // crossing the "eastern" antimeridian
413 else if (dest.lng > 180) {
414 var bearing = this.geodesic.inverse(start, dest).initialBearing;
415 var intersection = this.geodesic.intersection(start, bearing, antimeridianEast.point, antimeridianEast.bearing);
416 if (intersection) {
417 result = [[start, intersection], [new LatLng(intersection.lat, intersection.lng - 360), new LatLng(dest.lat, dest.lng - 360)]];
418 }
419 }
420 }
421 // coming back over the antimeridian from the "other" side?
422 else if ((dest.lng >= -180) && (dest.lng <= 180)) {
423 // crossing the "western" antimeridian
424 if (start.lng < -180) {
425 var bearing = this.geodesic.inverse(start, dest).initialBearing;
426 var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);
427 if (intersection) {
428 result = [[new LatLng(start.lat, start.lng + 360), new LatLng(intersection.lat, intersection.lng + 360)], [intersection, dest]];
429 }
430 }
431 // crossing the "eastern" antimeridian
432 else if (start.lng > 180) {
433 var bearing = this.geodesic.inverse(start, dest).initialBearing;
434 var intersection = this.geodesic.intersection(start, bearing, antimeridianWest.point, antimeridianWest.bearing);
435 if (intersection) {
436 result = [[new LatLng(start.lat, start.lng - 360), new LatLng(intersection.lat, intersection.lng - 360)], [intersection, dest]];
437 }
438 }
439 }
440 return result;
441 };
442 /**
443 * Linestrings of a given multilinestring that cross the antimeridian will be split in two separate linestrings.
444 * This function is used to wrap lines around when they cross the antimeridian
445 * It iterates over all linestrings and reconstructs the step-by-step if no split is needed.
446 * In case the line was split, the linestring ends at the antimeridian and a new linestring is created for the
447 * remaining points of the original linestring.
448 *
449 * @param multilinestring
450 * @return another multilinestring where segments crossing the antimeridian are split
451 */
452 GeodesicGeometry.prototype.splitMultiLineString = function (multilinestring) {
453 var _this = this;
454 var result = [];
455 multilinestring.forEach(function (linestring) {
456 if (linestring.length === 1) {
457 result.push(linestring); // just a single point in linestring, no need to split
458 }
459 else {
460 var segment = [];
461 for (var j = 1; j < linestring.length; j++) {
462 var split = _this.splitLine(linestring[j - 1], linestring[j]);
463 segment.pop();
464 segment = segment.concat(split[0]);
465 if (split.length > 1) {
466 result.push(segment); // the line was split, so we end the linestring right here
467 segment = split[1]; // begin the new linestring with the second part of the split line
468 }
469 }
470 result.push(segment);
471 }
472 });
473 return result;
474 };
475 GeodesicGeometry.prototype.wrapMultiLineString = function (multilinestring) {
476 var result = [];
477 multilinestring.forEach(function (linestring) {
478 var resultLine = [];
479 var previous = null;
480 // let temp: number[][] = [];
481 linestring.forEach(function (point) {
482 if (previous === null) {
483 resultLine.push(point);
484 previous = point;
485 }
486 else {
487 var diff = point.lng - previous.lng;
488 var offset = Math.sign(diff / 180) * Math.ceil(Math.abs(diff / 180));
489 if (Math.abs(diff) > 180) {
490 resultLine.push(new LatLng(point.lat, point.lng - offset * 180));
491 }
492 else {
493 resultLine.push(new LatLng(point.lat, point.lng));
494 }
495 // temp.push([diff, offset]);
496 }
497 });
498 result.push(resultLine);
499 // console.log(temp);
500 });
501 return result;
502 };
503 /**
504 * Creates a circular (constant radius), closed (1st pos == last pos) geodesic linestring.
505 * The number of vertices is calculated with: `vertices == steps + 1` (where 1st == last)
506 *
507 * @param center
508 * @param radius
509 * @return resulting linestring
510 */
511 GeodesicGeometry.prototype.circle = function (center, radius) {
512 var vertices = [];
513 for (var i = 0; i < this.steps; i++) {
514 var point = this.geodesic.direct(center, 360 / this.steps * i, radius);
515 vertices.push(new LatLng(point.lat, point.lng));
516 }
517 // append first vertice to the end to close the linestring
518 vertices.push(new LatLng(vertices[0].lat, vertices[0].lng));
519 return vertices;
520 };
521 /**
522 * Handles splitting of circles at the antimeridian.
523 * @param linestring a linestring that resembles the geodesic circle
524 * @return a multilinestring that consist of one or two linestrings
525 */
526 GeodesicGeometry.prototype.splitCircle = function (linestring) {
527 var result = [];
528 result = this.splitMultiLineString([linestring]);
529 // If the circle was split, it results in exactly three linestrings where first and last
530 // must be re-assembled because they belong to the same "side" of the split circle.
531 if (result.length === 3) {
532 result[2] = __spreadArrays(result[2], result[0]);
533 result.shift();
534 }
535 return result;
536 };
537 /**
538 * Calculates the distance between two positions on the earths surface
539 * @param start 1st position
540 * @param dest 2nd position
541 * @return the distance in **meters**
542 */
543 GeodesicGeometry.prototype.distance = function (start, dest) {
544 return this.geodesic.inverse(new LatLng(start.lat, this.geodesic.wrap(start.lng, 180)), new LatLng(dest.lat, this.geodesic.wrap(dest.lng, 180))).distance;
545 };
546 GeodesicGeometry.prototype.multilineDistance = function (multilinestring) {
547 var _this = this;
548 var dist = [];
549 multilinestring.forEach(function (linestring) {
550 var segmentDistance = 0;
551 for (var j = 1; j < linestring.length; j++) {
552 segmentDistance += _this.distance(linestring[j - 1], linestring[j]);
553 }
554 dist.push(segmentDistance);
555 });
556 return dist;
557 };
558 GeodesicGeometry.prototype.updateStatistics = function (points, vertices) {
559 var stats = {};
560 stats.distanceArray = this.multilineDistance(points);
561 stats.totalDistance = stats.distanceArray.reduce(function (x, y) { return x + y; }, 0);
562 stats.points = 0;
563 points.forEach(function (item) {
564 stats.points += item.reduce(function (x) { return x + 1; }, 0);
565 });
566 stats.vertices = 0;
567 vertices.forEach(function (item) {
568 stats.vertices += item.reduce(function (x) { return x + 1; }, 0);
569 });
570 return stats;
571 };
572 return GeodesicGeometry;
573}());
574
575function instanceOfLatLngLiteral(object) {
576 return ((typeof object === "object")
577 && (object !== null)
578 && ('lat' in object)
579 && ('lng' in object)
580 && (typeof object.lat === "number")
581 && (typeof object.lng === "number"));
582}
583function instanceOfLatLngTuple(object) {
584 return ((object instanceof Array)
585 && (typeof object[0] === "number")
586 && (typeof object[1] === "number"));
587}
588function instanceOfLatLngExpression(object) {
589 if (object instanceof LatLng) {
590 return true;
591 }
592 else if (instanceOfLatLngTuple(object)) {
593 return true;
594 }
595 else if (instanceOfLatLngLiteral(object)) {
596 return true;
597 }
598 else {
599 return false;
600 }
601}
602function latlngExpressiontoLatLng(input) {
603 if (input instanceof LatLng) {
604 return input;
605 }
606 else if (instanceOfLatLngTuple(input)) {
607 return new LatLng(input[0], input[1]);
608 }
609 else if (instanceOfLatLngLiteral(input)) {
610 return new LatLng(input.lat, input.lng);
611 }
612 else {
613 throw new Error("L.LatLngExpression expected. Unknown object found.");
614 }
615}
616function latlngExpressionArraytoLatLngArray(input) {
617 var latlng = [];
618 var _loop_1 = function (group) {
619 // it's a 1D-Array L.LatLngExpression[]
620 if (instanceOfLatLngExpression(group)) {
621 var sub_1 = [];
622 input.forEach(function (point) {
623 sub_1.push(latlngExpressiontoLatLng(point));
624 });
625 latlng.push(sub_1);
626 return "break";
627 }
628 // it's a 2D-Array L.LatLngExpression[][]
629 else if (group instanceof Array) {
630 if (instanceOfLatLngExpression(group[0])) {
631 var sub_2 = [];
632 group.forEach(function (point) {
633 sub_2.push(latlngExpressiontoLatLng(point));
634 });
635 latlng.push(sub_2);
636 }
637 else {
638 throw new Error("L.LatLngExpression[] | L.LatLngExpression[][] expected. Unknown object found.");
639 }
640 }
641 else {
642 throw new Error("L.LatLngExpression[] | L.LatLngExpression[][] expected. Unknown object found.");
643 }
644 };
645 for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
646 var group = input_1[_i];
647 var state_1 = _loop_1(group);
648 if (state_1 === "break")
649 break;
650 }
651 return latlng;
652}
653
654/**
655 * Draw geodesic lines based on L.Polyline
656 */
657var GeodesicLine = /** @class */ (function (_super) {
658 __extends(GeodesicLine, _super);
659 function GeodesicLine(latlngs, options) {
660 var _this = _super.call(this, [], options) || this;
661 /** these should be good for most use-cases */
662 _this.defaultOptions = { wrap: true, steps: 3 };
663 /** use this if you need some detailled info about the current geometry */
664 _this.statistics = {};
665 /** stores all positions that are used to create the geodesic line */
666 _this.points = [];
667 Util.setOptions(_this, __assign(__assign({}, _this.defaultOptions), options));
668 _this.geom = new GeodesicGeometry(_this.options);
669 if (latlngs !== undefined) {
670 _this.setLatLngs(latlngs);
671 }
672 return _this;
673 }
674 /** calculates the geodesics and update the polyline-object accordingly */
675 GeodesicLine.prototype.updateGeometry = function () {
676 var geodesic = [];
677 geodesic = this.geom.multiLineString(this.points);
678 this.statistics = this.geom.updateStatistics(this.points, geodesic);
679 if (this.options.wrap) {
680 var split = this.geom.splitMultiLineString(geodesic);
681 _super.prototype.setLatLngs.call(this, split);
682 }
683 else {
684 _super.prototype.setLatLngs.call(this, this.geom.wrapMultiLineString(geodesic));
685 }
686 };
687 /**
688 * overwrites the original function with additional functionality to create a geodesic line
689 * @param latlngs an array (or 2d-array) of positions
690 */
691 GeodesicLine.prototype.setLatLngs = function (latlngs) {
692 this.points = latlngExpressionArraytoLatLngArray(latlngs);
693 this.updateGeometry();
694 return this;
695 };
696 /**
697 * add a given point to the geodesic line object
698 * @param latlng point to add. The point will always be added to the last linestring of a multiline
699 * @param latlngs define a linestring to add the new point to. Read from points-property before (e.g. `line.addLatLng(Beijing, line.points[0]);`)
700 */
701 GeodesicLine.prototype.addLatLng = function (latlng, latlngs) {
702 var point = latlngExpressiontoLatLng(latlng);
703 if (this.points.length === 0) {
704 this.points.push([point]);
705 }
706 else {
707 if (latlngs === undefined) {
708 this.points[this.points.length - 1].push(point);
709 }
710 else {
711 latlngs.push(point);
712 }
713 }
714 this.updateGeometry();
715 return this;
716 };
717 /**
718 * Creates geodesic lines from a given GeoJSON-Object.
719 * @param input GeoJSON-Object
720 */
721 GeodesicLine.prototype.fromGeoJson = function (input) {
722 var latlngs = [];
723 var features = [];
724 if (input.type === "FeatureCollection") {
725 features = input.features;
726 }
727 else if (input.type === "Feature") {
728 features = [input];
729 }
730 else if (["MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"].includes(input.type)) {
731 features = [{
732 type: "Feature",
733 geometry: input,
734 properties: {}
735 }];
736 }
737 else {
738 console.log("[Leaflet.Geodesic] fromGeoJson() - Type \"" + input.type + "\" not supported.");
739 }
740 features.forEach(function (feature) {
741 switch (feature.geometry.type) {
742 case "MultiPoint":
743 case "LineString":
744 latlngs = __spreadArrays(latlngs, [GeoJSON.coordsToLatLngs(feature.geometry.coordinates, 0)]);
745 break;
746 case "MultiLineString":
747 case "Polygon":
748 latlngs = __spreadArrays(latlngs, GeoJSON.coordsToLatLngs(feature.geometry.coordinates, 1));
749 break;
750 case "MultiPolygon":
751 feature.geometry.coordinates.forEach(function (item) {
752 latlngs = __spreadArrays(latlngs, GeoJSON.coordsToLatLngs(item, 1));
753 });
754 break;
755 default:
756 console.log("[Leaflet.Geodesic] fromGeoJson() - Type \"" + feature.geometry.type + "\" not supported.");
757 }
758 });
759 if (latlngs.length) {
760 this.setLatLngs(latlngs);
761 }
762 return this;
763 };
764 /**
765 * Calculates the distance between two geo-positions
766 * @param start 1st position
767 * @param dest 2nd position
768 * @return the distance in meters
769 */
770 GeodesicLine.prototype.distance = function (start, dest) {
771 return this.geom.distance(latlngExpressiontoLatLng(start), latlngExpressiontoLatLng(dest));
772 };
773 return GeodesicLine;
774}(Polyline));
775
776/**
777 * Can be used to create a geodesic circle based on L.Polyline
778 */
779var GeodesicCircleClass = /** @class */ (function (_super) {
780 __extends(GeodesicCircleClass, _super);
781 function GeodesicCircleClass(center, options) {
782 var _this = _super.call(this, [], options) || this;
783 _this.defaultOptions = { wrap: true, steps: 24, fill: true, noClip: true };
784 _this.statistics = {};
785 Util.setOptions(_this, __assign(__assign({}, _this.defaultOptions), options));
786 // merge/set options
787 var extendedOptions = _this.options;
788 _this.radius = (extendedOptions.radius === undefined) ? 1000 * 1000 : extendedOptions.radius;
789 _this.center = (center === undefined) ? new LatLng(0, 0) : latlngExpressiontoLatLng(center);
790 _this.geom = new GeodesicGeometry(_this.options);
791 // update the geometry
792 _this.update();
793 return _this;
794 }
795 /**
796 * Updates the geometry and re-calculates some statistics
797 */
798 GeodesicCircleClass.prototype.update = function () {
799 var circle = this.geom.circle(this.center, this.radius);
800 this.statistics = this.geom.updateStatistics([[this.center]], [circle]);
801 // circumfence must be re-calculated from geodesic
802 this.statistics.totalDistance = this.geom.multilineDistance([circle]).reduce(function (x, y) { return x + y; }, 0);
803 if (this.options.wrap) {
804 var split = this.geom.splitCircle(circle);
805 _super.prototype.setLatLngs.call(this, split);
806 }
807 else {
808 _super.prototype.setLatLngs.call(this, circle);
809 }
810 };
811 /**
812 * Calculate the distance between the current center and an arbitrary position.
813 * @param latlng geo-position to calculate distance to
814 * @return distance in meters
815 */
816 GeodesicCircleClass.prototype.distanceTo = function (latlng) {
817 var dest = latlngExpressiontoLatLng(latlng);
818 return this.geom.distance(this.center, dest);
819 };
820 /**
821 * Set a new center for the geodesic circle and update the geometry. Radius may also be set.
822 * @param center the new center
823 * @param radius the new radius
824 */
825 GeodesicCircleClass.prototype.setLatLng = function (center, radius) {
826 this.center = latlngExpressiontoLatLng(center);
827 this.radius = radius ? radius : this.radius;
828 this.update();
829 };
830 /**
831 * Set a new radius for the geodesic circle and update the geometry. Center may also be set.
832 * @param radius the new radius
833 * @param center the new center
834 */
835 GeodesicCircleClass.prototype.setRadius = function (radius, center) {
836 this.radius = radius;
837 this.center = center ? latlngExpressiontoLatLng(center) : this.center;
838 this.update();
839 };
840 return GeodesicCircleClass;
841}(Polyline));
842
843if (typeof window.L !== "undefined") {
844 window.L.Geodesic = GeodesicLine;
845 window.L.geodesic = function () {
846 var args = [];
847 for (var _i = 0; _i < arguments.length; _i++) {
848 args[_i] = arguments[_i];
849 }
850 return new (GeodesicLine.bind.apply(GeodesicLine, __spreadArrays([void 0], args)))();
851 };
852 window.L.GeodesicCircle = GeodesicCircleClass;
853 window.L.geodesiccircle = function () {
854 var args = [];
855 for (var _i = 0; _i < arguments.length; _i++) {
856 args[_i] = arguments[_i];
857 }
858 return new (GeodesicCircleClass.bind.apply(GeodesicCircleClass, __spreadArrays([void 0], args)))();
859 };
860}
861
862export { GeodesicCircleClass, GeodesicLine };