UNPKG

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