@raruto/leaflet-elevation 2.2.3 → 2.2.6

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.
@@ -3,164 +3,164 @@
3
3
  factory();
4
4
  })((function () { 'use strict';
5
5
 
6
- /**
7
- * TODO: exget computed styles of theese values from actual "CSS vars"
8
- **/
9
- const Colors = {
10
- 'lightblue': { area: '#3366CC', alpha: 0.45, stroke: '#3366CC' },
11
- 'magenta' : { area: '#FF005E' },
12
- 'yellow' : { area: '#FF0' },
13
- 'purple' : { area: '#732C7B' },
14
- 'steelblue': { area: '#4682B4' },
15
- 'red' : { area: '#F00' },
16
- 'lime' : { area: '#9CC222', line: '#566B13' }
17
- };
18
-
19
- const SEC = 1000;
20
- const MIN = SEC * 60;
21
- const HOUR = MIN * 60;
22
- const DAY = HOUR * 24;
23
-
24
- /**
25
- * Convert a time (millis) to a human readable duration string (%Dd %H:%M'%S")
26
- */
27
- function formatTime(t) {
28
- let d = Math.floor(t / DAY);
29
- let h = Math.floor( (t - d * DAY) / HOUR);
30
- let m = Math.floor( (t - d * DAY - h * HOUR) / MIN);
31
- let s = Math.round( (t - d * DAY - h * HOUR - m * MIN) / SEC);
32
- if ( s === 60 ) { m++; s = 0; }
33
- if ( m === 60 ) { h++; m = 0; }
34
- if ( h === 24 ) { d++; h = 0; }
35
- return (d ? d + "d " : '') + h.toString().padStart(2, 0) + ':' + m.toString().padStart(2, 0) + "'" + s.toString().padStart(2, 0) + '"';
36
- }
37
-
38
- /**
39
- * Convert a time (millis) to human readable date string (dd-mm-yyyy hh:mm:ss)
40
- */
41
- function formatDate(format) {
42
- if (!format) {
43
- return (time) => (new Date(time)).toLocaleString().replaceAll('/', '-').replaceAll(',', ' ');
44
- } else if (format == 'time') {
45
- return (time) => (new Date(time)).toLocaleTimeString();
46
- } else if (format == 'date') {
47
- return (time) => (new Date(time)).toLocaleDateString();
48
- }
49
- return (time) => format(time);
50
- }
51
-
52
- /**
53
- * Generate download data event.
54
- */
55
- function saveFile(dataURI, fileName) {
56
- let a = create('a', '', { href: dataURI, target: '_new', download: fileName || "", style: "display:none;" });
57
- let b = document.body;
58
- b.appendChild(a);
59
- a.click();
60
- b.removeChild(a);
61
- }
62
-
63
-
64
- /**
65
- * Convert SVG Path into Path2D and then update canvas
66
- */
67
- function drawCanvas(ctx, path) {
68
- path.classed('canvas-path', true);
69
-
70
- ctx.beginPath();
71
- ctx.moveTo(0, 0);
72
- let p = new Path2D(path.attr('d'));
73
-
74
- ctx.strokeStyle = path.attr('stroke');
75
- ctx.fillStyle = path.attr('fill');
76
- ctx.lineWidth = 1.25;
77
- ctx.globalCompositeOperation = 'source-over';
78
-
79
- // stroke opacity
80
- ctx.globalAlpha = path.attr('stroke-opacity') || 0.3;
81
- ctx.stroke(p);
82
-
83
- // fill opacity
84
- ctx.globalAlpha = path.attr('fill-opacity') || 0.45;
85
- ctx.fill(p);
86
-
87
- ctx.globalAlpha = 1;
88
-
89
- ctx.closePath();
90
- }
91
-
92
- /**
93
- * Loop and extract GPX Extensions handled by "@tmcw/toGeoJSON" (eg. "coordinateProperties" > "times")
94
- */
95
- function coordPropsToMeta(coordProps, name, parser) {
96
- return coordProps && (({props, point, id, isMulti }) => {
97
- if (props) {
98
- for (const key of coordProps) {
99
- if (key in props) {
100
- point.meta[name] = (parser || parseNumeric).call(this, (isMulti ? props[key][isMulti] : props[key]), id);
101
- break;
102
- }
103
- }
104
- }
105
- });
106
- }
107
-
108
- /**
109
- * Extract numeric property (id) from GeoJSON object
110
- */
111
- const parseNumeric = (property, id) => parseInt((typeof property === 'object' ? property[id] : property));
112
-
113
- /**
114
- * Extract datetime property (id) from GeoJSON object
115
- */
116
- const parseDate = (property, id) => new Date(Date.parse((typeof property === 'object' ? property[id] : property)));
117
-
118
- /**
119
- * A little bit shorter than L.DomUtil
120
- */
121
- const addClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.addClass(n, s));
122
- const removeClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.removeClass(n, s));
123
- const toggleClass = (n, str, cond) => (cond ? addClass : removeClass)(n, str);
124
- const replaceClass = (n, rem, add) => (rem && removeClass(n, rem)) || (add && addClass(n, add));
125
- const style = (n, k, v) => (typeof v === "undefined" && L.DomUtil.getStyle(n, k)) || n.style.setProperty(k, v);
126
- const toggleStyle = (n, k, v, cond) => style(n, k, cond ? v : '');
127
- const setAttributes = (n, attrs) => { for (let k in attrs) { n.setAttribute(k, attrs[k]); } };
128
- const toggleEvent = (el, e, fn, cond) => el[cond ? 'on' : 'off'](e, fn);
129
- const create = (tag, str, attrs, n) => { let elem = L.DomUtil.create(tag, str || ""); if (attrs) setAttributes(elem, attrs); if (n) append(n, elem); return elem; };
130
- const append = (n, c) => n.appendChild(c);
131
- const insert = (n, c, pos) => n.insertAdjacentElement(pos, c);
132
- const select = (str, n) => (n || document).querySelector(str);
133
- const each = (obj, fn) => { for (let i in obj) fn(obj[i], i); };
134
- const randomId = () => Math.random().toString(36).substr(2, 9);
135
-
136
- /**
137
- * TODO: use generators instead? (ie. "yield")
138
- */
139
- const iMax = (iVal, max = -Infinity) => (iVal > max ? iVal : max);
140
- const iMin = (iVal, min = +Infinity) => (iVal < min ? iVal : min);
141
- const iAvg = (iVal, avg = 0, idx = 1) => (iVal + avg * (idx - 1)) / idx;
142
- const iSum = (iVal, sum = 0) => iVal + sum;
143
-
144
- /**
145
- * Alias for some leaflet core functions
146
- */
147
- const { on, off } = L.DomEvent;
148
- const { throttle, wrapNum } = L.Util;
149
- const { hasClass } = L.DomUtil;
150
-
151
- /**
152
- * Limit floating point precision
153
- */
154
- const round = L.Util.formatNum;
155
-
156
- /**
157
- * Limit a number between min / max values
158
- */
159
- const clamp = (val, range) => range ? (val < range[0] ? range[0] : val > range[1] ? range[1] : val) : val;
160
-
161
- /**
162
- * Limit a delta difference between two values
163
- */
6
+ /**
7
+ * TODO: exget computed styles of theese values from actual "CSS vars"
8
+ **/
9
+ const Colors = {
10
+ 'lightblue': { area: '#3366CC', alpha: 0.45, stroke: '#3366CC' },
11
+ 'magenta' : { area: '#FF005E' },
12
+ 'yellow' : { area: '#FF0' },
13
+ 'purple' : { area: '#732C7B' },
14
+ 'steelblue': { area: '#4682B4' },
15
+ 'red' : { area: '#F00' },
16
+ 'lime' : { area: '#9CC222', line: '#566B13' }
17
+ };
18
+
19
+ const SEC = 1000;
20
+ const MIN = SEC * 60;
21
+ const HOUR = MIN * 60;
22
+ const DAY = HOUR * 24;
23
+
24
+ /**
25
+ * Convert a time (millis) to a human readable duration string (%Dd %H:%M'%S")
26
+ */
27
+ function formatTime(t) {
28
+ let d = Math.floor(t / DAY);
29
+ let h = Math.floor( (t - d * DAY) / HOUR);
30
+ let m = Math.floor( (t - d * DAY - h * HOUR) / MIN);
31
+ let s = Math.round( (t - d * DAY - h * HOUR - m * MIN) / SEC);
32
+ if ( s === 60 ) { m++; s = 0; }
33
+ if ( m === 60 ) { h++; m = 0; }
34
+ if ( h === 24 ) { d++; h = 0; }
35
+ return (d ? d + "d " : '') + h.toString().padStart(2, 0) + ':' + m.toString().padStart(2, 0) + "'" + s.toString().padStart(2, 0) + '"';
36
+ }
37
+
38
+ /**
39
+ * Convert a time (millis) to human readable date string (dd-mm-yyyy hh:mm:ss)
40
+ */
41
+ function formatDate(format) {
42
+ if (!format) {
43
+ return (time) => (new Date(time)).toLocaleString().replaceAll('/', '-').replaceAll(',', ' ');
44
+ } else if (format == 'time') {
45
+ return (time) => (new Date(time)).toLocaleTimeString();
46
+ } else if (format == 'date') {
47
+ return (time) => (new Date(time)).toLocaleDateString();
48
+ }
49
+ return (time) => format(time);
50
+ }
51
+
52
+ /**
53
+ * Generate download data event.
54
+ */
55
+ function saveFile(dataURI, fileName) {
56
+ let a = create('a', '', { href: dataURI, target: '_new', download: fileName || "", style: "display:none;" });
57
+ let b = document.body;
58
+ b.appendChild(a);
59
+ a.click();
60
+ b.removeChild(a);
61
+ }
62
+
63
+
64
+ /**
65
+ * Convert SVG Path into Path2D and then update canvas
66
+ */
67
+ function drawCanvas(ctx, path) {
68
+ path.classed('canvas-path', true);
69
+
70
+ ctx.beginPath();
71
+ ctx.moveTo(0, 0);
72
+ let p = new Path2D(path.attr('d'));
73
+
74
+ ctx.strokeStyle = path.attr('stroke');
75
+ ctx.fillStyle = path.attr('fill');
76
+ ctx.lineWidth = 1.25;
77
+ ctx.globalCompositeOperation = 'source-over';
78
+
79
+ // stroke opacity
80
+ ctx.globalAlpha = path.attr('stroke-opacity') || 0.3;
81
+ ctx.stroke(p);
82
+
83
+ // fill opacity
84
+ ctx.globalAlpha = path.attr('fill-opacity') || 0.45;
85
+ ctx.fill(p);
86
+
87
+ ctx.globalAlpha = 1;
88
+
89
+ ctx.closePath();
90
+ }
91
+
92
+ /**
93
+ * Loop and extract GPX Extensions handled by "@tmcw/toGeoJSON" (eg. "coordinateProperties" > "times")
94
+ */
95
+ function coordPropsToMeta(coordProps, name, parser) {
96
+ return coordProps && (({props, point, id, isMulti }) => {
97
+ if (props) {
98
+ for (const key of coordProps) {
99
+ if (key in props) {
100
+ point.meta[name] = (parser || parseNumeric).call(this, (isMulti ? props[key][isMulti] : props[key]), id);
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Extract numeric property (id) from GeoJSON object
110
+ */
111
+ const parseNumeric = (property, id) => parseInt((typeof property === 'object' ? property[id] : property));
112
+
113
+ /**
114
+ * Extract datetime property (id) from GeoJSON object
115
+ */
116
+ const parseDate = (property, id) => new Date(Date.parse((typeof property === 'object' ? property[id] : property)));
117
+
118
+ /**
119
+ * A little bit shorter than L.DomUtil
120
+ */
121
+ const addClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.addClass(n, s));
122
+ const removeClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.removeClass(n, s));
123
+ const toggleClass = (n, str, cond) => (cond ? addClass : removeClass)(n, str);
124
+ const replaceClass = (n, rem, add) => (rem && removeClass(n, rem)) || (add && addClass(n, add));
125
+ const style = (n, k, v) => (typeof v === "undefined" && L.DomUtil.getStyle(n, k)) || n.style.setProperty(k, v);
126
+ const toggleStyle = (n, k, v, cond) => style(n, k, cond ? v : '');
127
+ const setAttributes = (n, attrs) => { for (let k in attrs) { n.setAttribute(k, attrs[k]); } };
128
+ const toggleEvent = (el, e, fn, cond) => el[cond ? 'on' : 'off'](e, fn);
129
+ const create = (tag, str, attrs, n) => { let elem = L.DomUtil.create(tag, str || ""); if (attrs) setAttributes(elem, attrs); if (n) append(n, elem); return elem; };
130
+ const append = (n, c) => n.appendChild(c);
131
+ const insert = (n, c, pos) => n.insertAdjacentElement(pos, c);
132
+ const select = (str, n) => (n || document).querySelector(str);
133
+ const each = (obj, fn) => { for (let i in obj) fn(obj[i], i); };
134
+ const randomId = () => Math.random().toString(36).substr(2, 9);
135
+
136
+ /**
137
+ * TODO: use generators instead? (ie. "yield")
138
+ */
139
+ const iMax = (iVal, max = -Infinity) => (iVal > max ? iVal : max);
140
+ const iMin = (iVal, min = +Infinity) => (iVal < min ? iVal : min);
141
+ const iAvg = (iVal, avg = 0, idx = 1) => (iVal + avg * (idx - 1)) / idx;
142
+ const iSum = (iVal, sum = 0) => iVal + sum;
143
+
144
+ /**
145
+ * Alias for some leaflet core functions
146
+ */
147
+ const { on, off } = L.DomEvent;
148
+ const { throttle, wrapNum } = L.Util;
149
+ const { hasClass } = L.DomUtil;
150
+
151
+ /**
152
+ * Limit floating point precision
153
+ */
154
+ const round = L.Util.formatNum;
155
+
156
+ /**
157
+ * Limit a number between min / max values
158
+ */
159
+ const clamp = (val, range) => range ? (val < range[0] ? range[0] : val > range[1] ? range[1] : val) : val;
160
+
161
+ /**
162
+ * Limit a delta difference between two values
163
+ */
164
164
  const wrapDelta = (curr, prev, deltaMax) => Math.abs(curr - prev) > deltaMax ? prev + deltaMax * Math.sign(curr - prev) : curr;
165
165
 
166
166
  var _ = /*#__PURE__*/Object.freeze({
@@ -201,1432 +201,1438 @@
201
201
  wrapDelta: wrapDelta
202
202
  });
203
203
 
204
- var Options = {
205
- autofitBounds: true,
206
- autohide: false,
207
- autohideMarker: true,
208
- almostover: true,
209
- altitude: true,
210
- closeBtn: true,
211
- collapsed: false,
212
- detached: true,
213
- distance: true,
214
- distanceMarkers: { lazy: true, distance: true, direction: true },
215
- dragging: !L.Browser.mobile,
216
- downloadLink: 'link',
217
- elevationDiv: "#elevation-div",
218
- followMarker: true,
219
- imperial: false,
220
- legend: true,
221
- handlers: ["Distance", "Time", "Altitude", "Slope", "Speed", "Acceleration"],
222
- hotline: 'elevation',
223
- marker: 'elevation-line',
224
- markerIcon: L.divIcon({
225
- className: 'elevation-position-marker',
226
- html: '<i class="elevation-position-icon"></i>',
227
- iconSize: [32, 32],
228
- iconAnchor: [16, 16],
229
- }),
230
- position: "topright",
231
- polyline: {
232
- className: 'elevation-polyline',
233
- color: '#000',
234
- opacity: 0.75,
235
- weight: 5,
236
- lineCap: 'round'
237
- },
238
- polylineSegments: {
239
- className: 'elevation-polyline-segments',
240
- color: '#F00',
241
- interactive: false,
242
- },
243
- preferCanvas: false,
244
- reverseCoords: false,
245
- ruler: true,
246
- theme: "lightblue-theme",
247
- summary: 'inline',
248
- slope: false,
249
- speed: false,
250
- time: true,
251
- timeFactor: 3600,
252
- timestamps: false,
253
- trkStart: L.circleMarker([0,0], { className: 'start-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#00d800', fillOpacity: 1, interactive: false }),
254
- trkEnd: L.circleMarker([0,0], { className: 'end-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#ff0606', fillOpacity: 1, interactive: false }),
255
- waypoints: true,
256
- wptIcons: {
257
- '': L.divIcon({
258
- className: 'elevation-waypoint-marker',
259
- html: '<i class="elevation-waypoint-icon default"></i>',
260
- iconSize: [30, 30],
261
- iconAnchor: [8, 30],
262
- }),
263
- },
264
- wptLabels: true,
265
- xAttr: "dist",
266
- xLabel: "km",
267
- yAttr: "z",
268
- yLabel: "m",
269
- zFollow: false,
270
- zooming: !L.Browser.Mobile,
271
-
272
- // Quite uncommon and undocumented options
273
- margins: { top: 30, right: 30, bottom: 30, left: 40 },
274
- height: (screen.height * 0.3) || 200,
275
- width: (screen.width * 0.6) || 600,
276
- xTicks: undefined,
277
- yTicks: undefined,
278
-
279
- decimalsX: 2,
280
- decimalsY: 0,
281
- forceAxisBounds: false,
282
- interpolation: "curveLinear",
283
- skipNullZCoords: false,
284
- yAxisMax: undefined,
285
- yAxisMin: undefined,
204
+ var Options = {
205
+ autofitBounds: true,
206
+ autohide: false,
207
+ autohideMarker: true,
208
+ almostover: true,
209
+ altitude: true,
210
+ closeBtn: true,
211
+ collapsed: false,
212
+ detached: true,
213
+ distance: true,
214
+ distanceMarkers: { lazy: true, distance: true, direction: true },
215
+ dragging: !L.Browser.mobile,
216
+ downloadLink: 'link',
217
+ elevationDiv: "#elevation-div",
218
+ followMarker: true,
219
+ imperial: false,
220
+ legend: true,
221
+ handlers: ["Distance", "Time", "Altitude", "Slope", "Speed", "Acceleration"],
222
+ hotline: 'elevation',
223
+ marker: 'elevation-line',
224
+ markerIcon: L.divIcon({
225
+ className: 'elevation-position-marker',
226
+ html: '<i class="elevation-position-icon"></i>',
227
+ iconSize: [32, 32],
228
+ iconAnchor: [16, 16],
229
+ }),
230
+ position: "topright",
231
+ polyline: {
232
+ className: 'elevation-polyline',
233
+ color: '#000',
234
+ opacity: 0.75,
235
+ weight: 5,
236
+ lineCap: 'round'
237
+ },
238
+ polylineSegments: {
239
+ className: 'elevation-polyline-segments',
240
+ color: '#F00',
241
+ interactive: false,
242
+ },
243
+ preferCanvas: false,
244
+ reverseCoords: false,
245
+ ruler: true,
246
+ theme: "lightblue-theme",
247
+ summary: 'inline',
248
+ slope: false,
249
+ speed: false,
250
+ time: true,
251
+ timeFactor: 3600,
252
+ timestamps: false,
253
+ trkStart: { className: 'start-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#00d800', fillOpacity: 1, interactive: false },
254
+ trkEnd: { className: 'end-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#ff0606', fillOpacity: 1, interactive: false },
255
+ waypoints: true,
256
+ wptIcons: {
257
+ '': L.divIcon({
258
+ className: 'elevation-waypoint-marker',
259
+ html: '<i class="elevation-waypoint-icon default"></i>',
260
+ iconSize: [30, 30],
261
+ iconAnchor: [8, 30],
262
+ }),
263
+ },
264
+ wptLabels: true,
265
+ xAttr: "dist",
266
+ xLabel: "km",
267
+ yAttr: "z",
268
+ yLabel: "m",
269
+ zFollow: false,
270
+ zooming: !L.Browser.Mobile,
271
+
272
+ // Quite uncommon and undocumented options
273
+ margins: { top: 30, right: 30, bottom: 30, left: 40 },
274
+ height: (screen.height * 0.3) || 200,
275
+ width: (screen.width * 0.6) || 600,
276
+ xTicks: undefined,
277
+ yTicks: undefined,
278
+
279
+ decimalsX: 2,
280
+ decimalsY: 0,
281
+ forceAxisBounds: false,
282
+ interpolation: "curveLinear",
283
+ skipNullZCoords: false,
284
+ yAxisMax: undefined,
285
+ yAxisMin: undefined,
286
286
  };
287
287
 
288
- // "leaflet-i18n" fallback
289
- if (!L._ || !L.i18n) {
290
- L._ = L.i18n = (string, data) => string;
291
- }
292
-
293
- const Elevation = L.Control.Elevation = L.Control.extend({
294
-
295
- includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
296
-
297
- options: Options,
298
- __mileFactor: 0.621371, // 1 km = (0.621371 mi)
299
- __footFactor: 3.28084, // 1 m = (3.28084 ft)
300
- __D3: 'https://unpkg.com/d3@6.5.0/dist/d3.min.js',
301
- __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.6.0/dist/togeojson.umd.js',
302
- __LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.9.3/src/leaflet.geometryutil.js',
303
- __LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
304
- __LHOTLINE: '../libs/leaflet-hotline.min.js',
305
- __LDISTANCEM: '../libs/leaflet-distance-marker.min.js',
306
- __LCHART: '../src/components/chart.js',
307
- __LMARKER: '../src/components/marker.js',
308
- __LSUMMARY: '../src/components/summary.js',
309
- __modulesFolder: '../src/handlers/',
310
- __btnIcon: '../images/elevation.svg',
311
-
312
- /*
313
- * Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
314
- */
315
- addData: function(d, layer) {
316
- this.import(this.__D3)
317
- .then(() => {
318
- if (this._modulesLoaded) {
319
- layer = layer ?? (d.on && d);
320
- this._addData(d);
321
- this._addLayer(layer);
322
- this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
323
- } else {
324
- this.once('modules_loaded', () => this.addData(d,layer));
325
- }
326
- });
327
- },
328
-
329
- /**
330
- * Adds the control to the given map.
331
- */
332
- addTo: function(map) {
333
- if (this.options.detached) {
334
- let parent = select(this.options.elevationDiv);
335
- let eleDiv = this.onAdd(map);
336
- parent ? append(parent, eleDiv) : insert(map.getContainer(), eleDiv, 'afterend');
337
- } else {
338
- L.Control.prototype.addTo.call(this, map);
339
- }
340
- return this;
341
- },
342
-
343
- /*
344
- * Reset data and display
345
- */
346
- clear: function() {
347
- if (this._marker) this._marker.remove();
348
- if (this._chart) this._clearChart();
349
- if (this._layers) this._clearLayers(this._layers);
350
- if (this._markers) this._clearLayers(this._markers);
351
- if (this._circleMarkers) this._circleMarkers.remove();
352
- if (this._hotline) this._clearLayers(this._hotline);
353
-
354
- this._data = [];
355
- this.track_info = {};
356
-
357
- this._fireEvt("eledata_clear");
358
-
359
- this._updateChart();
360
- },
361
-
362
- _clearChart: function() {
363
- if (this._events && this._events.elechart_updated) {
364
- this._events.elechart_updated.forEach(({fn, ctx}) => this.off('elechart_updated', fn, ctx));
365
- }
366
- if (this._chart && this._chart._container) {
367
- this._chart._container.selectAll('g.point .point').remove();
368
- this._chart.clear();
369
- }
370
- },
371
-
372
- _clearLayers: function(l) {
373
- l = l || this._layers;
374
- if (l && l.eachLayer) {
375
- l.eachLayer(f => f.remove());
376
- l.clearLayers();
377
- }
378
- },
379
-
380
- /**
381
- * TODO: Create a base class to handle custom data attributes (heart rate, cadence, temperature, ...)
382
- *
383
- * @link https://leafletjs.com/examples/extending/extending-3-controls.html#handlers
384
- */
385
- // addHandler: function (name, HandlerClass) {
386
- // if (HandlerClass) {
387
- // let handler = this[name] = new HandlerClass(this);
388
- // this.handlers.push(handler);
389
- // if (this.options[name]) {
390
- // handler.enable();
391
- // }
392
- // }
393
- // return this;
394
- // },
395
-
396
- /**
397
- * Disable chart brushing.
398
- */
399
- disableBrush: function() {
400
- this._chart._brushEnabled = false;
401
- this._resetDrag();
402
- },
403
-
404
- /**
405
- * Enable chart brushing.
406
- */
407
- enableBrush: function() {
408
- this._chart._brushEnabled = true;
409
- },
410
-
411
- /**
412
- * Disable chart zooming.
413
- */
414
- disableZoom: function() {
415
- this._chart._zoomEnabled = false;
416
- this._chart._resetZoom();
417
- },
418
-
419
- /**
420
- * Enable chart zooming.
421
- */
422
- enableZoom: function() {
423
- this._chart._zoomEnabled = true;
424
- },
425
-
426
- /**
427
- * Sets a map view that contains the given geographical bounds.
428
- */
429
- fitBounds: function(bounds) {
430
- bounds = bounds || this.getBounds();
431
- if (this._map && bounds.isValid()) this._map.fitBounds(bounds);
432
- },
433
-
434
- getBounds: function(data) {
435
- return L.latLngBounds((data || this._data).map((d) => d.latlng));
436
- },
437
-
438
- /**
439
- * Get default zoom level (followMarker: true).
440
- */
441
- getZFollow: function() {
442
- return this.options.zFollow;
443
- },
444
-
445
- /**
446
- * Hide current elevation chart profile.
447
- */
448
- hide: function() {
449
- style(this._container, "display", "none");
450
- },
451
-
452
- /**
453
- * Initialize chart control "options" and "container".
454
- */
455
- initialize: function(opts) {
456
-
457
- opts = L.setOptions(this, opts);
458
-
459
- this._data = [];
460
- this._layers = L.featureGroup();
461
- this._markers = L.featureGroup();
462
- this._hotline = L.featureGroup();
463
- this._circleMarkers = L.featureGroup();
464
- this._markedSegments = L.polyline([]);
465
- this._start = (opts.trkStart || Options.trkStart);
466
- this._end = (opts.trkEnd || Options.trkEnd);
467
- this._chartEnabled = true;
468
- this._yCoordMax = -Infinity;
469
- this.track_info = {};
470
- // this.handlers = [];
471
-
472
- if (opts.followMarker) this._setMapView = throttle(this._setMapView, 300, this);
473
- if (opts.legend) opts.margins.bottom += 30;
474
- if (opts.theme) opts.polylineSegments.className += ' ' + opts.theme;
475
- if (opts.wptIcons === true) opts.wptIcons = Options.wptIcons;
476
- if (opts.distanceMarkers === true) opts.distanceMarkers = Options.distanceMarkers;
477
- if (opts.trkStart) this._start.addTo(this._circleMarkers);
478
- if (opts.trkEnd) this._end.addTo(this._circleMarkers);
479
-
480
-
481
- this._markedSegments.setStyle(opts.polylineSegments);
482
-
483
- // Leaflet canvas renderer colors
484
- L.extend(Colors, opts.colors || {});
485
-
486
- // Various stuff
487
- this._fixCanvasPaths();
488
- this._fixTooltipSize();
489
-
490
- },
491
-
492
- /**
493
- * Javascript scripts downloader (lazy loader)
494
- */
495
- import: function(src, condition) {
496
- switch(src) {
497
- case this.__D3: condition = typeof d3 !== 'object'; break;
498
- case this.__TOGEOJSON: condition = typeof toGeoJSON !== 'object'; break;
499
- case this.__LGEOMUTIL: condition = typeof L.GeometryUtil !== 'object'; break;
500
- case this.__LALMOSTOVER: condition = typeof L.Handler.AlmostOver !== 'function'; break;
501
- case this.__LDISTANCEM: condition = typeof L.DistanceMarkers !== 'function'; break;
502
- }
503
- return condition !== false ? import(src) : Promise.resolve();
504
- },
505
-
506
- /**
507
- * Load elevation data (GPX, GeoJSON, KML or TCX).
508
- */
509
- load: function(data) {
510
- this._parseFromString(data).then( geojson => geojson ? this._loadLayer(geojson) : this._loadFile(data));
511
- },
512
-
513
- /**
514
- * Create container DOM element and related event listeners.
515
- * Called on control.addTo(map).
516
- */
517
- onAdd: function(map) {
518
- this._map = map;
519
-
520
- let container = this._container = create("div", "elevation-control " + this.options.theme + " " + (this.options.detached ? 'elevation-detached' : 'leaflet-control'), this.options.detached ? { id: 'elevation-' + randomId() } : {});
521
-
522
- if (!this.eleDiv) this.eleDiv = container;
523
-
524
- this._loadModules(this.options.handlers).then(() => { // Inject here required modules (data handlers)
525
- this._initChart(container);
526
- this._initButton(container);
527
- this._initSummary(container);
528
- this._initMarker(map);
529
- this._initLayer(map);
530
- this._modulesLoaded = true;
531
- this.fire('modules_loaded');
532
- });
533
-
534
- this.fire('add');
535
-
536
- return container;
537
- },
538
-
539
- /**
540
- * Clean up control code and related event listeners.
541
- * Called on control.remove().
542
- */
543
- onRemove: function(map) {
544
- this._container = null;
545
-
546
- map
547
- .off('zoom viewreset zoomanim', this._hideMarker, this)
548
- .off('resize', this._resetView, this)
549
- .off('resize', this._resizeChart, this)
550
- .off('mousedown', this._resetDrag, this);
551
-
552
- off(map.getContainer(), 'mousewheel', this._resetDrag, this);
553
- off(map.getContainer(), 'touchstart', this._resetDrag, this);
554
- off(document, 'keydown', this._onKeyDown, this);
555
-
556
- this
557
- .off('eledata_added eledata_loaded', this._updateChart, this)
558
- .off('eledata_added eledata_loaded', this._updateSummary, this);
559
-
560
- this.fire('remove');
561
- },
562
-
563
- /**
564
- * Redraws the chart control. Sometimes useful after screen resize.
565
- */
566
- redraw: function() {
567
- this._resizeChart();
568
- },
569
-
570
- /**
571
- * Set default zoom level (followMarker: true).
572
- */
573
- setZFollow: function(zoom) {
574
- this.options.zFollow = zoom;
575
- },
576
-
577
- /**
578
- * Hide current elevation chart profile.
579
- */
580
- show: function() {
581
- style(this._container, "display", "block");
582
- },
583
-
584
- /*
585
- * Parsing data either from GPX or GeoJSON and update the diagram data
586
- */
587
- _addData: function(d) {
588
- if (!d) {
589
- return;
590
- }
591
-
592
- // Standard GeoJSON
593
- if (d.type === "FeatureCollection" ) {
594
- return each(d.features, feature => this._addData(feature));
595
- } else if (d.type === "Feature") {
596
- let geom = d.geometry;
597
- if (geom) {
598
- switch (geom.type) {
599
- case 'LineString': return this._addGeoJSONData(geom.coordinates, d.properties);
600
- case 'MultiLineString': return each(geom.coordinates, (coords, i) => this._addGeoJSONData(coords, d.properties, i));
601
- case 'Point':
602
- default: return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
603
- }
604
- }
605
- }
606
-
607
- // Fallback for leaflet layers (eg. L.Gpx)
608
- if (d._latlngs) {
609
- return this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
610
- }
611
-
612
- },
613
-
614
- /*
615
- * Parsing of GeoJSON data lines and their elevation in z-coordinate
616
- */
617
- _addGeoJSONData: function(coords, properties, nestingLevel) {
618
-
619
- // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
620
- let props = (properties && properties.coordinateProperties) || properties;
621
-
622
- coords.forEach((point, i) => {
623
-
624
- // GARMIN_EXTENSIONS = ["hr", "cad", "atemp", "wtemp", "depth", "course", "bearing"];
625
- point.meta = point.meta ?? { time: null, ele: null };
626
-
627
- point.prev = (attr) => (attr ? this._data[i > 0 ? i - 1 : 0][attr] : this._data[i > 0 ? i - 1 : 0]);
628
-
629
- this.fire("elepoint_init", { point: point, props: props, id: i, isMulti: nestingLevel });
630
-
631
- this._addPoint(
632
- point.lat ?? point[1],
633
- point.lng ?? point[0],
634
- point.alt ?? point.meta.ele ?? point[2]
635
- );
636
-
637
- this.fire("elepoint_added", { point: point, index: this._data.length - 1 });
638
-
639
- if (this._yCoordMax < this._data[this._data.length - 1][this.options.yAttr]) this._yCoordMax = this._data[this._data.length - 1][this.options.yAttr];
640
- });
641
-
642
- this.fire("eletrack_added", { coords: coords, index: this._data.length - 1 });
643
- },
644
-
645
- /*
646
- * Parse and push a single (x, y, z) point to current elevation profile.
647
- */
648
- _addPoint: function(x, y, z) {
649
- if (this.options.reverseCoords) {
650
- [x, y] = [y, x];
651
- }
652
-
653
- this._data.push({
654
- x: x,
655
- y: y,
656
- z: z,
657
- latlng: L.latLng(x, y, z)
658
- });
659
-
660
- this.fire("eledata_updated", { index: this._data.length - 1 });
661
- },
662
-
663
- _addLayer: function(layer) {
664
- if (layer) this._layers.addLayer(layer);
665
- // Postpone adding the distance markers (lazy: true)
666
- if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
667
- layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
668
- }
669
- return layer;
670
- },
671
-
672
- _addMarker: function(marker) {
673
- if (marker) this._markers.addLayer(marker);
674
- return marker;
675
- },
676
-
677
- /**
678
- * Initialize "L.AlmostOver" integration
679
- */
680
- _initAlmostOverHandler: function(map, layer) {
681
- return (map && this.options.almostOver && !L.Browser.mobile) ? Promise.all([
682
- this.import(this.__LGEOMUTIL),
683
- this.import(this.__LALMOSTOVER)
684
- ]).then(() => {
685
- map.addHandler('almostOver', L.Handler.AlmostOver);
686
- if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
687
- map.almostOver.addLayer(layer);
688
- map
689
- .on('almost:move', (e) => this._onMouseMoveLayer(e))
690
- .on('almost:out', (e) => this._onMouseOut(e));
691
- }
692
- }) : Promise.resolve();
693
- },
694
-
695
- /**
696
- * Initialize "L.DistanceMarkers" integration
697
- */
698
- _initDistanceMarkers: function() {
699
- return this.options.distanceMarkers ? Promise.all([this.import(this.__LGEOMUTIL), this.import(this.__LDISTANCEM)]) : Promise.resolve();
700
- },
701
-
702
- _initHotLine: function(layer) {
703
- let prop = typeof this.options.hotline == 'string' ? this.options.hotline : 'elevation';
704
- return this.options.hotline ? this.import(this.__LHOTLINE)
705
- .then(() => {
706
- layer.eachLayer((trkseg) => {
707
- if(trkseg.feature.geometry.type != "Point") {
708
- let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], coords[2])});
709
- let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
710
- min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
711
- max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
712
- palette: {
713
- 0.0: '#008800',
714
- 0.5: '#ffff00',
715
- 1.0: '#ff0000'
716
- },
717
- weight: 5,
718
- outlineColor: '#000000',
719
- outlineWidth: 1
720
- }).addTo(this._hotline);
721
- let alpha = trkseg.options.style && trkseg.options.style.opacity || 1;
722
- trkseg.on('add remove', ({type}) => {
723
- trkseg.setStyle({opacity: (type == 'add' ? 0 : alpha)});
724
- line[(type == 'add' ? 'addTo' : 'removeFrom')](trkseg._map);
725
- if (line._renderer) line._renderer._container.parentElement.insertBefore(line._renderer._container, line._renderer._container.parentElement.firstChild);
726
- });
727
- }
728
- });
729
- }) : Promise.resolve();
730
- },
731
-
732
- /**
733
- * Initialize "L.AlmostOver" and "L.DistanceMarkers"
734
- */
735
- _initMapIntegrations: function(layer) {
736
- let map = this._map;
737
- if (map) {
738
- if (this._data.length) {
739
- this._start.setLatLng(this._data[0].latlng);
740
- this._end.setLatLng(this._data[this._data.length -1].latlng);
741
- }
742
- Promise.all([
743
- this._initHotLine(layer),
744
- this._initAlmostOverHandler(map, layer),
745
- this._initDistanceMarkers(),
746
- ]).then(() => {
747
- if (this.options.polyline) {
748
- layer.addTo(map);
749
- this._circleMarkers.addTo(map);
750
- }
751
- if (this.options.autofitBounds) {
752
- this.fitBounds(layer.getBounds());
753
- }
754
- map.invalidateSize();
755
- });
756
- } else {
757
- this.once('add', () => this._initMapIntegrations(layer));
758
- }
759
- },
760
-
761
- /*
762
- * Collapse current chart control.
763
- */
764
- _collapse: function() {
765
- replaceClass(this._container, 'elevation-expanded', 'elevation-collapsed');
766
- if (this._map) this._map.invalidateSize();
767
- },
768
-
769
- /*
770
- * Expand current chart control.
771
- */
772
- _expand: function() {
773
- replaceClass(this._container, 'elevation-collapsed', 'elevation-expanded');
774
- if (this._map) this._map.invalidateSize();
775
- },
776
-
777
- /**
778
- * Add some basic colors to leaflet canvas renderer (preferCanvas: true).
779
- */
780
- _fixCanvasPaths: function() {
781
- let oldProto = L.Canvas.prototype._fillStroke;
782
- let control = this;
783
-
784
- let theme = this.options.theme.split(' ')[0].replace('-theme', '');
785
- let color = Colors[theme] || {};
786
-
787
- L.Canvas.include({
788
- _fillStroke: function(ctx, layer) {
789
- if (control._layers.hasLayer(layer)) {
790
-
791
- let options = layer.options;
792
-
793
- options.color = color.line || color.area || theme;
794
- options.stroke = !!options.color;
795
-
796
- oldProto.call(this, ctx, layer);
797
-
798
- if (options.stroke && options.weight !== 0) {
799
- let oldVal = ctx.globalCompositeOperation || 'source-over';
800
- ctx.globalCompositeOperation = 'destination-over';
801
- ctx.strokeStyle = color.outline || '#FFF';
802
- ctx.lineWidth = options.weight * 1.75;
803
- ctx.stroke();
804
- ctx.globalCompositeOperation = oldVal;
805
- }
806
-
807
- } else {
808
- oldProto.call(this, ctx, layer);
809
- }
810
- }
811
- });
812
- },
813
-
814
- /**
815
- * Partial fix for initial tooltip size
816
- *
817
- * @link https://github.com/Raruto/leaflet-elevation/issues/81#issuecomment-713477050
818
- */
819
- _fixTooltipSize: function() {
820
- this.on('elechart_init', () =>
821
- this.once('elechart_change elechart_hover', ({data, xCoord}) => {
822
- if (this._chartEnabled) {
823
- this._chart._showDiagramIndicator(data, xCoord);
824
- this._chart._showDiagramIndicator(data, xCoord);
825
- }
826
- this._updateMarker(data);
827
- })
828
- );
829
- },
830
-
831
- /*
832
- * Finds a data entry for the given LatLng
833
- */
834
- _findItemForLatLng: function(latlng) {
835
- return this._data[this._chart._findIndexForLatLng(latlng)];
836
- },
837
-
838
- /*
839
- * Finds a data entry for the given xDiagCoord
840
- */
841
- _findItemForX: function(x) {
842
- return this._data[this._chart._findIndexForXCoord(x)];
843
- },
844
-
845
- /**
846
- * Fires an event of the specified type.
847
- */
848
- _fireEvt: function(type, data, propagate) {
849
- if (this.fire) this.fire(type, data, propagate);
850
- if (this._map) this._map.fire(type, data, propagate);
851
- },
852
-
853
- /*
854
- * Hides the position/height indicator marker drawn onto the map
855
- */
856
- _hideMarker: function() {
857
- if (this.options.autohideMarker) {
858
- this._marker.remove();
859
- }
860
- },
861
-
862
- /**
863
- * Generate "svg" chart (DOM element).
864
- */
865
- _initChart: function(container) {
866
- let opts = this.options;
867
- let map = this._map;
868
-
869
- if (opts.detached) {
870
- let { offsetWidth, offsetHeight} = this.eleDiv;
871
- if (offsetWidth > 0) opts.width = offsetWidth;
872
- if (offsetHeight > 20) opts.height = offsetHeight - 20; // 20 = horizontal scrollbar size.
873
- } else {
874
- let { clientWidth } = map.getContainer();
875
- opts._maxWidth = opts._maxWidth > opts.width ? opts._maxWidth : opts.width;
876
- this._container.style.maxWidth = opts._maxWidth + 'px';
877
- if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
878
- }
879
-
880
- Promise.all([
881
- this.import(this.__D3),
882
- this.import(this.__LCHART)
883
- ]).then((m) => {
884
-
885
- let chart = this._chart = new (m[1] || Elevation).Chart(opts, this);
886
-
887
- this._x = this._chart._x;
888
- this._y = this._chart._y;
889
-
890
- d3
891
- .select(container)
892
- .call(chart.render());
893
-
894
- chart
895
- .on('reset_drag', this._hideMarker, this)
896
- .on('mouse_enter', this._onMouseEnter, this)
897
- .on('dragged', this._onDragEnd, this)
898
- .on('mouse_move', this._onMouseMove, this)
899
- .on('mouse_out', this._onMouseOut, this)
900
- .on('ruler_filter', this._onRulerFilter, this)
901
- .on('zoom', this._updateChart, this)
902
- .on('elepath_toggle', this._onToggleChart, this)
903
- .on('margins_updated', this._resizeChart, this);
904
-
905
-
906
- this.fire("elechart_init");
907
-
908
- map
909
- .on('zoom viewreset zoomanim', this._hideMarker, this)
910
- .on('resize', this._resetView, this)
911
- .on('resize', this._resizeChart, this)
912
- .on('rotate', this._rotateMarker, this)
913
- .on('mousedown', this._resetDrag, this);
914
-
915
- on(map.getContainer(), 'mousewheel', this._resetDrag, this);
916
- on(map.getContainer(), 'touchstart', this._resetDrag, this);
917
- on(document, 'keydown', this._onKeyDown, this);
918
-
919
- this
920
- .on('eledata_added eledata_loaded', this._updateChart, this)
921
- .on('eledata_added eledata_loaded', this._updateSummary, this);
922
-
923
- this._updateChart();
924
- this._updateSummary();
925
- });
926
-
927
-
928
- },
929
-
930
- _initLayer: function() {
931
- this._layers
932
- .on('layeradd layerremove', ({layer, type}) => {
933
- let node = layer.getElement && layer.getElement();
934
- toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, type == 'layeradd');
935
- toggleEvent(layer, "mousemove", this._onMouseMoveLayer.bind(this), type == 'layeradd');
936
- toggleEvent(layer, "mouseout", this._onMouseOut.bind(this), type == 'layeradd');
937
- });
938
- },
939
-
940
- _initMarker: function(map) {
941
- let pane = map.getPane('elevationPane');
942
- if (!pane) {
943
- pane = this._pane = map.createPane('elevationPane', map.getPane('norotatePane') || map.getPane('mapPane'));
944
- pane.style.zIndex = 625; // This pane is above markers but below popups.
945
- pane.style.pointerEvents = 'none';
946
- }
947
-
948
- if (this._renderer) this._renderer.remove();
949
- this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
950
-
951
- Promise.all([
952
- this.import(this.__D3),
953
- this.import(this.__LMARKER)
954
- ]).then((m) => {
955
- this._marker = new (m[1] || Elevation).Marker(this.options, this);
956
- this.fire("elechart_marker");
957
- });
958
- },
959
-
960
- /**
961
- * Inspired by L.Control.Layers
962
- */
963
- _initButton: function(container) {
964
- L.DomEvent
965
- .disableClickPropagation(container)
966
- .disableScrollPropagation(container);
967
-
968
- this.options.collapsed ? this._collapse() : this._expand();
969
-
970
- if (this.options.autohide) {
971
- on(container, 'mouseover', this._expand, this);
972
- on(container, 'mouseout', this._collapse, this);
973
- this._map.on('click', this._collapse, this);
974
- }
975
-
976
- if (this.options.closeBtn) {
977
- let link = this._button = create('a', "elevation-toggle-icon", { href: '#', title: L._('Elevation'), }, container);
978
- on(link, 'click', L.DomEvent.stop);
979
- on(link, 'click', this._toggle, this);
980
- on(link, 'focus', this._toggle, this);
981
- fetch(this.__btnIcon).then(r => r.ok && r.text().then(img => link.innerHTML = img));
982
- }
983
- },
984
-
985
- _initSummary: function(container) {
986
- this.import(this.__LSUMMARY).then((m)=>{
987
- this._summary = new (m || Elevation).Summary({ summary: this.options.summary }, this);
988
-
989
- this.on('elechart_init', () => {
990
- d3.select(container).call(this._summary.render());
991
- });
992
- });
993
- },
994
-
995
- /**
996
- * Retrieve data from a remote url (HTTP).
997
- */
998
- _loadFile: function(url) {
999
- fetch(url)
1000
- .then((response) => response.text())
1001
- .then((data) => {
1002
- this._downloadURL = url; // TODO: handle multiple urls?
1003
- this._parseFromString(data)
1004
- .then( geojson => geojson && this._loadLayer(geojson));
1005
- }).catch((err) => console.warn(err));
1006
- },
1007
-
1008
- /**
1009
- * Dynamically import only required javascript modules (code splitting)
1010
- */
1011
- _loadModules: function(handlers) {
1012
- // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
1013
- handlers = handlers.map((h) => typeof h === 'string' && typeof Elevation[h] !== "undefined" ? Elevation[h] : h);
1014
- // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
1015
- let modules = handlers.map(file => (typeof file === 'string' && this.import(this.__modulesFolder + file.toLowerCase() + '.js')) || (file instanceof Promise && file) || Promise.resolve());
1016
- return Promise.all(modules).then((m) => {
1017
- each(m, (exported, i) => {
1018
- let fn = exported && Object.keys(exported)[0];
1019
- if (fn) {
1020
- handlers[i] = Elevation[fn] = (Elevation[fn] ?? exported[fn]);
1021
- }
1022
- });
1023
- each(handlers, h => ["function", "object"].includes(typeof h) && this._registerHandler(h));
1024
- });
1025
- },
1026
-
1027
- /**
1028
- * Simple GeoJSON data loader (L.GeoJSON).
1029
- */
1030
- _loadLayer: function(geojson) {
1031
- let { polyline, theme, waypoints, wptIcons, wptLabels, distanceMarkers } = this.options;
1032
- let style = L.extend({}, polyline);
1033
-
1034
- if (theme) {
1035
- style.className += ' ' + theme;
1036
- }
1037
-
1038
- if (geojson.name) {
1039
- this.track_info.name = geojson.name;
1040
- }
1041
-
1042
- let layer = L.geoJson(geojson, {
1043
- distanceMarkers: distanceMarkers,
1044
- style: style,
1045
- pointToLayer: (feature, latlng) => {
1046
- if (waypoints) {
1047
- let { desc, name, sym } = feature.properties;
1048
- desc = desc || '';
1049
- name = name || '';
1050
- // Handle chart waypoints (dots)
1051
- if ([true, 'dots'].includes(waypoints)) {
1052
- this._registerCheckPoint({
1053
- latlng: latlng,
1054
- label : ([true, 'dots'].includes(wptLabels) ? name : '')
1055
- });
1056
- }
1057
- // Handle map waypoints (markers)
1058
- if ([true, 'markers'].includes(waypoints) && wptIcons != false) {
1059
- return this._registerMarker({
1060
- latlng : latlng,
1061
- sym : (sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase(),
1062
- content: [true, 'markers'].includes(wptLabels) && (name || desc) && decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''))
1063
- });
1064
- }
1065
- }
1066
- },
1067
- onEachFeature: (feature, layer) => feature.geometry && feature.geometry.type != 'Point' && this.addData(feature, layer),
1068
- });
1069
-
1070
- this.import(this.__D3).then(() => {
1071
- this._initMapIntegrations(layer);
1072
- this._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: this.track_info.name, track_info: this.track_info });
1073
- });
1074
-
1075
- return layer;
1076
- },
1077
-
1078
- _onDragEnd: function({ dragstart, dragend}) {
1079
- this._hideMarker();
1080
- this.fitBounds(L.latLngBounds([dragstart.latlng, dragend.latlng]));
1081
-
1082
- this.fire("elechart_dragged");
1083
- },
1084
-
1085
- _onKeyDown: function({key}) {
1086
- if(!this.options.detached && key === "Escape"){
1087
- this._collapse();
1088
- } },
1089
-
1090
- /**
1091
- * Trigger mouseenter event.
1092
- */
1093
- _onMouseEnter: function() {
1094
- this.fire('elechart_enter');
1095
- },
1096
-
1097
- /*
1098
- * Handles the moueseover the chart and displays distance and altitude level.
1099
- */
1100
- _onMouseMove: function({xCoord}) {
1101
- if (this._chartEnabled && this._data.length) {
1102
- let item = this._findItemForX(xCoord);
1103
- if (item) {
1104
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
1105
-
1106
- this._updateMarker(item);
1107
- this._setMapView(item);
1108
-
1109
- if (this._map) {
1110
- addClass(this._map.getContainer(), 'elechart-hover');
1111
- }
1112
-
1113
- this.fire("elechart_change", { data: item, xCoord: xCoord });
1114
- this.fire("elechart_hover", { data: item, xCoord: xCoord });
1115
- }
1116
- }
1117
- },
1118
-
1119
- /*
1120
- * Handles mouseover events of the data layers on the map.
1121
- */
1122
- _onMouseMoveLayer: function({latlng}) {
1123
- if (this._data.length) {
1124
- let item = this._findItemForLatLng(latlng);
1125
- if (item) {
1126
- let xCoord = item.xDiagCoord;
1127
-
1128
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
1129
-
1130
- this._updateMarker(item);
1131
-
1132
- this.fire("elechart_change", { data: item, xCoord: xCoord });
1133
- }
1134
- }
1135
- },
1136
-
1137
- /*
1138
- * Handles the moueseout over the chart.
1139
- */
1140
- _onMouseOut: function() {
1141
- if (!this.options.detached) {
1142
- this._hideMarker();
1143
- this._chart._hideDiagramIndicator();
1144
- }
1145
-
1146
- if (this._map) {
1147
- removeClass(this._map.getContainer(), 'elechart-hover');
1148
- }
1149
-
1150
- this.fire("elechart_leave");
1151
- },
1152
-
1153
- /**
1154
- * Handles the drag event over the ruler filter.
1155
- */
1156
- _onRulerFilter: function({coords}) {
1157
- this._updateMapSegments(coords);
1158
- },
1159
-
1160
- /**
1161
- * Toggle chart data on legend click
1162
- */
1163
- _onToggleChart: function({ name, enabled }) {
1164
-
1165
- this._chartEnabled = this._chart._hasActiveLayers();
1166
-
1167
- // toggle layer visibility on empty chart
1168
- this._layers.eachLayer(layer => toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
1169
-
1170
- // toggle option value (eg. altitude = { 'disabled' || 'enabled' })
1171
- this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
1172
-
1173
- // remove marker on empty chart
1174
- if (!this._chartEnabled) {
1175
- this._chart._hideDiagramIndicator();
1176
- this._marker.remove();
1177
- }
1178
- },
1179
-
1180
- /**
1181
- * Simple GeoJSON Parser
1182
- */
1183
- _parseFromGeoJSONString: function(data) {
1184
- try {
1185
- return JSON.parse(data);
1186
- } catch (e) { }
1187
- },
1188
-
1189
- /**
1190
- * Attempt to parse raw response data (GeoJSON or XML > GeoJSON)
1191
- */
1192
- _parseFromString: function(data) {
1193
- return new Promise(resolve =>
1194
- this.import(this.__TOGEOJSON).then(() => {
1195
- let geojson;
1196
- try {
1197
- geojson = this._parseFromXMLString(data.trim());
1198
- } catch (e) {
1199
- geojson = this._parseFromGeoJSONString(data.toString());
1200
- }
1201
- if (geojson) {
1202
- geojson.name = geojson.name || (this._downloadURL || '').split('/').pop().split('#')[0].split('?')[0];
1203
- }
1204
- resolve(geojson);
1205
- })
1206
- );
1207
- },
1208
-
1209
- /**
1210
- * Simple XML Parser (GPX, KML, TCX)
1211
- */
1212
- _parseFromXMLString: function(data) {
1213
- if (data.indexOf("<") != 0) {
1214
- throw 'Invalid XML';
1215
- }
1216
- let xml = (new DOMParser()).parseFromString(data, "text/xml");
1217
- let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
1218
- let name = xml.getElementsByTagName('name');
1219
- if (xml.getElementsByTagName('parsererror').length) {
1220
- throw 'Invalid XML';
1221
- }
1222
- if (!(type in toGeoJSON)) {
1223
- type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
1224
- }
1225
- let geojson = toGeoJSON[type](xml);
1226
- geojson.name = name.length > 0 ? (Array.from(name).find(tag => tag.parentElement.tagName == "trk") ?? name[0]).textContent : '';
1227
- return geojson;
1228
- },
1229
-
1230
- /**
1231
- * Add chart profile to diagram
1232
- */
1233
- _registerAreaPath: function(props) {
1234
- this.on("elechart_init", () => this._chart._registerAreaPath(props));
1235
- },
1236
-
1237
- /**
1238
- * Add chart grid to diagram
1239
- */
1240
- _registerAxisGrid: function(props) {
1241
- this.on("elechart_axis", () => this._chart._registerAxisGrid(props));
1242
- },
1243
-
1244
- /**
1245
- * Add chart axis to diagram
1246
- */
1247
- _registerAxisScale: function(props) {
1248
- this.on("elechart_axis", () => this._chart._registerAxisScale(props));
1249
- },
1250
-
1251
- /**
1252
- * Add a point of interest over the diagram
1253
- */
1254
- _registerCheckPoint: function(props) {
1255
- this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
1256
- },
1257
-
1258
-
1259
- /**
1260
- * Base handler for iterative track statistics (dist, time, z, slope, speed, acceleration, ...)
1261
- */
1262
- _registerDataAttribute: function(props) {
1263
-
1264
- // parse of "coordinateProperties" for later usage
1265
- if (props.coordPropsToMeta) {
1266
- this.on("elepoint_init", (e) => props.coordPropsToMeta.call(this, e));
1267
- }
1268
-
1269
- // prevent excessive variabile instanstations
1270
- let i, curr, prev, attr;
1271
-
1272
- // save here a reference to last used point
1273
- let lastValid = null;
1274
-
1275
- // iteration
1276
- this.on("elepoint_added", ({index, point}) => {
1277
- i = index;
1278
-
1279
- prev = curr ?? this._data[i]; // same as: this._data[i > 0 ? i - 1 : i]
1280
- curr = this._data[i];
1281
- attr = props.attr || props.name;
1282
-
1283
- // check and fix missing data on last added point
1284
- if (props.skipNull === false) {
1285
- let curr = this._data[i][attr];
1286
- if(i > 0) {
1287
- let prev = this._data[i - 1][attr];
1288
- if (isNaN(prev)) {
1289
- if (!isNaN(lastValid) && !isNaN(curr)) {
1290
- prev = (lastValid + curr) / 2;
1291
- } else if (!isNaN(lastValid)) {
1292
- prev = lastValid;
1293
- } else if (!isNaN(curr)) {
1294
- prev = curr;
1295
- }
1296
- if (!isNaN(prev)) return this._data.splice(i - 1, 1);
1297
- this._data[i - 1][attr] = prev;
1298
- }
1299
- }
1300
- // update reference to last used point (ie. if it has data)
1301
- if (!isNaN(curr)) {
1302
- lastValid = curr;
1303
- }
1304
- }
1305
-
1306
- // retrieve point value
1307
- curr[attr] = props.pointToAttr.call(this, point, i);
1308
-
1309
- // update "track_info" stats (min, max, avg, ...)
1310
- if (props.stats) {
1311
- for (const key in props.stats) {
1312
- let sname = (props.statsName || attr) + (key != '' ? '_' : '');
1313
- this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key], this._data.length);
1314
- }
1315
- }
1316
-
1317
- // Limit "crazy" delta values.
1318
- if (props.deltaMax) {
1319
- curr[attr] =wrapDelta(curr[attr], prev[attr], props.deltaMax);
1320
- }
1321
-
1322
- // Range of acceptable values.
1323
- if (props.clampRange) {
1324
- curr[attr] = clamp(curr[attr], props.clampRange);
1325
- }
1326
-
1327
- // Limit floating point precision.
1328
- if (!isNaN(props.decimals)) {
1329
- curr[attr] = round(curr[attr], props.decimals);
1330
- }
1331
-
1332
- // update here some mixins (eg. complex "track_info" stuff)
1333
- if(props.onPointAdded) props.onPointAdded.call(this, curr[attr], i, point);
1334
- });
1335
- },
1336
-
1337
- /**
1338
- * Parse a module definition and attach related function listeners
1339
- */
1340
- _registerHandler: function(props) {
1341
-
1342
- // eg. L.Control.Altitude
1343
- if (typeof props === "function") {
1344
- return this._registerHandler(props.call(this));
1345
- }
1346
-
1347
- let {
1348
- name,
1349
- attr,
1350
- required,
1351
- skipNull,
1352
- deltaMax,
1353
- clampRange,
1354
- decimals,
1355
- meta,
1356
- unit,
1357
- coordinateProperties,
1358
- coordPropsToMeta: coordPropsToMeta$1,
1359
- pointToAttr,
1360
- onPointAdded,
1361
- stats,
1362
- statsName,
1363
- grid,
1364
- scale,
1365
- path,
1366
- tooltip,
1367
- summary
1368
- } = props;
1369
-
1370
- // eg. "altitude" == true
1371
- if (this.options[name] || required) {
1372
-
1373
- this._registerDataAttribute({
1374
- name,
1375
- attr,
1376
- skipNull,
1377
- deltaMax,
1378
- clampRange,
1379
- decimals,
1380
- coordPropsToMeta: coordPropsToMeta(coordinateProperties, meta || name, coordPropsToMeta$1),
1381
- pointToAttr,
1382
- onPointAdded,
1383
- stats,
1384
- statsName,
1385
- });
1386
-
1387
- if (grid) {
1388
- this._registerAxisGrid(L.extend({ name }, grid));
1389
- }
1390
-
1391
- if (this.options[name] !== "summary") {
1392
- if (scale) this._registerAxisScale(L.extend({ name, label: unit }, scale));
1393
- if (path) this._registerAreaPath(L.extend({ name }, path));
1394
- }
1395
-
1396
- if (tooltip || props.tooltips) {
1397
- each([tooltip, ...(props.tooltips || [])], t => t && this._registerTooltip(L.extend({ name }, t)));
1398
- }
1399
-
1400
- if (summary) {
1401
- each(summary, (s, k) => summary[k] = L.extend({ unit }, s));
1402
- this._registerSummary(summary);
1403
- }
1404
- }
1405
- },
1406
-
1407
- _registerMarker({latlng, sym, content}) {
1408
- let { wptIcons } = this.options;
1409
- // generate and cache appropriate icon symbol
1410
- if (!wptIcons.hasOwnProperty(sym)) {
1411
- wptIcons[sym] = L.divIcon(L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } ));
1412
- }
1413
- let marker = L.marker(latlng, { icon: wptIcons[sym] });
1414
- if (content) {
1415
- marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
1416
- marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
1417
- }
1418
- return this._addMarker(marker)
1419
- },
1420
-
1421
- /**
1422
- * Add chart or marker tooltip info
1423
- */
1424
- _registerTooltip: function(props) {
1425
- props.chart && this.on("elechart_init", () => this._chart._registerTooltip(L.extend({}, props, { value: props.chart })));
1426
- props.marker && this.on("elechart_marker", () => this._marker._registerTooltip(L.extend({}, props, { value: props.marker })));
1427
- },
1428
-
1429
- /**
1430
- * Add summary info to diagram
1431
- */
1432
- _registerSummary: function(props) {
1433
- this.on('elechart_summary', () => this._summary._registerSummary(props));
1434
- },
1435
-
1436
- /*
1437
- * Removes the drag rectangle and zoms back to the total extent of the data.
1438
- */
1439
- _resetDrag: function() {
1440
- this._chart._resetDrag();
1441
- this._hideMarker();
1442
- },
1443
-
1444
- /**
1445
- * Resets drag, marker and bounds.
1446
- */
1447
- _resetView: function() {
1448
- if (this._map && this._map._isFullscreen) return;
1449
- this._resetDrag();
1450
- this._hideMarker();
1451
- if (this.options.autofitBounds) {
1452
- this.fitBounds();
1453
- }
1454
- },
1455
-
1456
- /**
1457
- * Hacky way for handling chart resize. Deletes it and redraw chart.
1458
- */
1459
- _resizeChart: function() {
1460
- if (this._container && style(this._container, "display") != "none") {
1461
- let opts = this.options;
1462
- let newWidth = opts.detached ? (this.eleDiv || this._container).offsetWidth : clamp(opts._maxWidth, [0, this._map.getContainer().clientWidth - 30]);
1463
- if (newWidth) {
1464
- opts.width = newWidth;
1465
- if (this._chart && this._chart._chart) {
1466
- this._chart._chart._resize(opts);
1467
- this._updateChart();
1468
- }
1469
- }
1470
- this._updateMapSegments();
1471
- }
1472
- },
1473
-
1474
- /**
1475
- * Collapse or Expand chart control.
1476
- */
1477
- _toggle: function() {
1478
- hasClass(this._container, "elevation-expanded") ? this._collapse() : this._expand();
1479
- },
1480
-
1481
- /**
1482
- * Update map center and zoom (followMarker: true)
1483
- */
1484
- _setMapView: function(item) {
1485
- if (this._map && this.options.followMarker) {
1486
- let zoom = this._map.getZoom();
1487
- let z = this.options.zFollow;
1488
- if (typeof z === "number") {
1489
- this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1490
- } else if (!this._map.getBounds().contains(item.latlng)) {
1491
- this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1492
- }
1493
- }
1494
- },
1495
-
1496
- /**
1497
- * Calculates [x, y] domain and then update chart.
1498
- */
1499
- _updateChart: function() {
1500
- if (this._chart && this._container) {
1501
- this.fire("elechart_axis");
1502
-
1503
- this._chart.update({ data: this._data, options: this.options });
1504
-
1505
- this._x = this._chart._x;
1506
- this._y = this._chart._y;
1507
-
1508
- this.fire('elechart_updated');
1509
- }
1510
- },
1511
-
1512
- /*
1513
- * Update the position/height indicator marker drawn onto the map
1514
- */
1515
- _updateMarker: function(item) {
1516
- if (this._marker) {
1517
- this._marker.update({
1518
- map : this._map,
1519
- item : item,
1520
- yCoordMax : this._yCoordMax || 0,
1521
- options : this.options
1522
- });
1523
- }
1524
- },
1525
-
1526
- /**
1527
- * Fix marker rotation on rotated maps
1528
- */
1529
- _rotateMarker: function() {
1530
- if (this._marker) {
1531
- this._marker.update();
1532
- }
1533
- },
1534
-
1535
- /**
1536
- * Highlight track segments on the map.
1537
- */
1538
- _updateMapSegments: function(coords) {
1539
- this._markedSegments.setLatLngs(coords || []);
1540
- if (coords && this._map && !this._map.hasLayer(this._markedSegments)) {
1541
- this._markedSegments.addTo(this._map);
1542
- }
1543
- },
1544
-
1545
- /**
1546
- * Update chart summary.
1547
- */
1548
- _updateSummary: function() {
1549
- if (this._summary) {
1550
- this._summary.reset();
1551
- if (this.options.summary) {
1552
- this.fire("elechart_summary");
1553
- this._summary.update();
1554
- }
1555
- if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
1556
- this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>';
1557
- select('.download a', this._summary._container).onclick = (e) => {
1558
- e.preventDefault();
1559
- let event = { downloadLink: this.options.downloadLink, confirm: saveFile.bind(this, this._downloadURL) };
1560
- if (this.options.downloadLink == 'modal' && typeof CustomEvent === "function") {
1561
- document.dispatchEvent(new CustomEvent("eletrack_download", { detail: event }));
1562
- } else if (this.options.downloadLink == 'link' || this.options.downloadLink === true) {
1563
- event.confirm();
1564
- }
1565
- this.fire('eletrack_download', event);
1566
- };
1567
- }
1568
- }
1569
- },
1570
-
1571
-
1572
- /**
1573
- * Calculates chart width.
1574
- */
1575
- _width: function() {
1576
- if (this._chart) return this._chart._width();
1577
- const { width, margins } = this.options;
1578
- return width - margins.left - margins.right;
1579
- },
1580
-
1581
- /**
1582
- * Calculates chart height.
1583
- */
1584
- _height: function() {
1585
- if (this._chart) return this._chart._height();
1586
- const { height, margins } = this.options;
1587
- return height - margins.top - margins.bottom;
1588
- },
1589
-
288
+ // "leaflet-i18n" fallback
289
+ if (!L._ || !L.i18n) {
290
+ L._ = L.i18n = (string, data) => string;
291
+ }
292
+
293
+ // Prevent CORS issues for relative locations (dynamic import)
294
+ const baseURL = ((document.currentScript && document.currentScript.src) || (({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('leaflet-elevation.js', document.baseURI).href)) }) && (typeof document === 'undefined' && typeof location === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('leaflet-elevation.js', document.baseURI).href)))).split("/").slice(0,-1).join("/") + '/';
295
+
296
+ const Elevation = L.Control.Elevation = L.Control.extend({
297
+
298
+ includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
299
+
300
+ options: Options,
301
+ __mileFactor: 0.621371, // 1 km = (0.621371 mi)
302
+ __footFactor: 3.28084, // 1 m = (3.28084 ft)
303
+ __D3: 'https://unpkg.com/d3@6.5.0/dist/d3.min.js',
304
+ __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.6.0/dist/togeojson.umd.js',
305
+ __LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.9.3/src/leaflet.geometryutil.js',
306
+ __LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
307
+ __LHOTLINE: '../libs/leaflet-hotline.min.js',
308
+ __LDISTANCEM: '../libs/leaflet-distance-marker.min.js',
309
+ __LCHART: '../src/components/chart.js',
310
+ __LMARKER: '../src/components/marker.js',
311
+ __LSUMMARY: '../src/components/summary.js',
312
+ __modulesFolder: '../src/handlers/',
313
+ __btnIcon: '../images/elevation.svg',
314
+
315
+ /*
316
+ * Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
317
+ */
318
+ addData: function(d, layer) {
319
+ this.import(this.__D3)
320
+ .then(() => {
321
+ if (this._modulesLoaded) {
322
+ layer = layer ?? (d.on && d);
323
+ this._addData(d);
324
+ this._addLayer(layer);
325
+ this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
326
+ } else {
327
+ this.once('modules_loaded', () => this.addData(d,layer));
328
+ }
329
+ });
330
+ },
331
+
332
+ /**
333
+ * Adds the control to the given map.
334
+ */
335
+ addTo: function(map) {
336
+ if (this.options.detached) {
337
+ let parent = select(this.options.elevationDiv);
338
+ let eleDiv = this.onAdd(map);
339
+ parent ? append(parent, eleDiv) : insert(map.getContainer(), eleDiv, 'afterend');
340
+ } else {
341
+ L.Control.prototype.addTo.call(this, map);
342
+ }
343
+ return this;
344
+ },
345
+
346
+ /*
347
+ * Reset data and display
348
+ */
349
+ clear: function() {
350
+ if (this._marker) this._marker.remove();
351
+ if (this._chart) this._clearChart();
352
+ if (this._layers) this._clearLayers(this._layers);
353
+ if (this._markers) this._clearLayers(this._markers);
354
+ if (this._circleMarkers) this._circleMarkers.remove();
355
+ if (this._hotline) this._clearLayers(this._hotline);
356
+
357
+ this._data = [];
358
+ this.track_info = {};
359
+
360
+ this._fireEvt("eledata_clear");
361
+
362
+ this._updateChart();
363
+ },
364
+
365
+ _clearChart: function() {
366
+ if (this._events && this._events.elechart_updated) {
367
+ this._events.elechart_updated.forEach(({fn, ctx}) => this.off('elechart_updated', fn, ctx));
368
+ }
369
+ if (this._chart && this._chart._container) {
370
+ this._chart._container.selectAll('g.point .point').remove();
371
+ this._chart.clear();
372
+ }
373
+ },
374
+
375
+ _clearLayers: function(l) {
376
+ l = l || this._layers;
377
+ if (l && l.eachLayer) {
378
+ l.eachLayer(f => f.remove());
379
+ l.clearLayers();
380
+ }
381
+ },
382
+
383
+ /**
384
+ * TODO: Create a base class to handle custom data attributes (heart rate, cadence, temperature, ...)
385
+ *
386
+ * @link https://leafletjs.com/examples/extending/extending-3-controls.html#handlers
387
+ */
388
+ // addHandler: function (name, HandlerClass) {
389
+ // if (HandlerClass) {
390
+ // let handler = this[name] = new HandlerClass(this);
391
+ // this.handlers.push(handler);
392
+ // if (this.options[name]) {
393
+ // handler.enable();
394
+ // }
395
+ // }
396
+ // return this;
397
+ // },
398
+
399
+ /**
400
+ * Disable chart brushing.
401
+ */
402
+ disableBrush: function() {
403
+ this._chart._brushEnabled = false;
404
+ this._resetDrag();
405
+ },
406
+
407
+ /**
408
+ * Enable chart brushing.
409
+ */
410
+ enableBrush: function() {
411
+ this._chart._brushEnabled = true;
412
+ },
413
+
414
+ /**
415
+ * Disable chart zooming.
416
+ */
417
+ disableZoom: function() {
418
+ this._chart._zoomEnabled = false;
419
+ this._chart._resetZoom();
420
+ },
421
+
422
+ /**
423
+ * Enable chart zooming.
424
+ */
425
+ enableZoom: function() {
426
+ this._chart._zoomEnabled = true;
427
+ },
428
+
429
+ /**
430
+ * Sets a map view that contains the given geographical bounds.
431
+ */
432
+ fitBounds: function(bounds) {
433
+ bounds = bounds || this.getBounds();
434
+ if (this._map && bounds.isValid()) this._map.fitBounds(bounds);
435
+ },
436
+
437
+ getBounds: function(data) {
438
+ return L.latLngBounds((data || this._data).map((d) => d.latlng));
439
+ },
440
+
441
+ /**
442
+ * Get default zoom level (followMarker: true).
443
+ */
444
+ getZFollow: function() {
445
+ return this.options.zFollow;
446
+ },
447
+
448
+ /**
449
+ * Hide current elevation chart profile.
450
+ */
451
+ hide: function() {
452
+ style(this._container, "display", "none");
453
+ },
454
+
455
+ /**
456
+ * Initialize chart control "options" and "container".
457
+ */
458
+ initialize: function(opts) {
459
+
460
+ opts = L.setOptions(this, opts);
461
+
462
+ this._data = [];
463
+ this._layers = L.featureGroup();
464
+ this._markers = L.featureGroup();
465
+ this._hotline = L.featureGroup();
466
+ this._circleMarkers = L.featureGroup();
467
+ this._markedSegments = L.polyline([]);
468
+ this._start = L.circleMarker([0,0], (opts.trkStart || Options.trkStart));
469
+ this._end = L.circleMarker([0,0], (opts.trkEnd || Options.trkEnd));
470
+ this._chartEnabled = true;
471
+ this._yCoordMax = -Infinity;
472
+ this.track_info = {};
473
+ // this.handlers = [];
474
+
475
+ if (opts.followMarker) this._setMapView = throttle(this._setMapView, 300, this);
476
+ if (opts.legend) opts.margins.bottom += 30;
477
+ if (opts.theme) opts.polylineSegments.className += ' ' + opts.theme;
478
+ if (opts.wptIcons === true) opts.wptIcons = Options.wptIcons;
479
+ if (opts.distanceMarkers === true) opts.distanceMarkers = Options.distanceMarkers;
480
+ if (opts.trkStart) this._start.addTo(this._circleMarkers);
481
+ if (opts.trkEnd) this._end.addTo(this._circleMarkers);
482
+
483
+
484
+ this._markedSegments.setStyle(opts.polylineSegments);
485
+
486
+ // Leaflet canvas renderer colors
487
+ L.extend(Colors, opts.colors || {});
488
+
489
+ // Various stuff
490
+ this._fixCanvasPaths();
491
+ this._fixTooltipSize();
492
+
493
+ },
494
+
495
+ /**
496
+ * Javascript scripts downloader (lazy loader)
497
+ */
498
+ import: function(src, condition) {
499
+ switch(src) {
500
+ case this.__D3: condition = typeof d3 !== 'object'; break;
501
+ case this.__TOGEOJSON: condition = typeof toGeoJSON !== 'object'; break;
502
+ case this.__LGEOMUTIL: condition = typeof L.GeometryUtil !== 'object'; break;
503
+ case this.__LALMOSTOVER: condition = typeof L.Handler.AlmostOver !== 'function'; break;
504
+ case this.__LDISTANCEM: condition = typeof L.DistanceMarkers !== 'function'; break;
505
+ case this.__LHOTLINE: condition = typeof L.Hotline !== 'function'; break;
506
+ }
507
+ src = (src.startsWith('../') || src.startsWith('./') ? baseURL : '') + src;
508
+ return condition !== false ? import(src) : Promise.resolve();
509
+ },
510
+
511
+ /**
512
+ * Load elevation data (GPX, GeoJSON, KML or TCX).
513
+ */
514
+ load: function(data) {
515
+ this._parseFromString(data).then( geojson => geojson ? this._loadLayer(geojson) : this._loadFile(data));
516
+ },
517
+
518
+ /**
519
+ * Create container DOM element and related event listeners.
520
+ * Called on control.addTo(map).
521
+ */
522
+ onAdd: function(map) {
523
+ this._map = map;
524
+
525
+ let container = this._container = create("div", "elevation-control " + this.options.theme + " " + (this.options.detached ? 'elevation-detached' : 'leaflet-control'), this.options.detached ? { id: 'elevation-' + randomId() } : {});
526
+
527
+ if (!this.eleDiv) this.eleDiv = container;
528
+
529
+ this._loadModules(this.options.handlers).then(() => { // Inject here required modules (data handlers)
530
+ this._initChart(container);
531
+ this._initButton(container);
532
+ this._initSummary(container);
533
+ this._initMarker(map);
534
+ this._initLayer(map);
535
+ this._modulesLoaded = true;
536
+ this.fire('modules_loaded');
537
+ });
538
+
539
+ this.fire('add');
540
+
541
+ return container;
542
+ },
543
+
544
+ /**
545
+ * Clean up control code and related event listeners.
546
+ * Called on control.remove().
547
+ */
548
+ onRemove: function(map) {
549
+ this._container = null;
550
+
551
+ map
552
+ .off('zoom viewreset zoomanim', this._hideMarker, this)
553
+ .off('resize', this._resetView, this)
554
+ .off('resize', this._resizeChart, this)
555
+ .off('mousedown', this._resetDrag, this);
556
+
557
+ off(map.getContainer(), 'mousewheel', this._resetDrag, this);
558
+ off(map.getContainer(), 'touchstart', this._resetDrag, this);
559
+ off(document, 'keydown', this._onKeyDown, this);
560
+
561
+ this
562
+ .off('eledata_added eledata_loaded', this._updateChart, this)
563
+ .off('eledata_added eledata_loaded', this._updateSummary, this);
564
+
565
+ this.fire('remove');
566
+ },
567
+
568
+ /**
569
+ * Redraws the chart control. Sometimes useful after screen resize.
570
+ */
571
+ redraw: function() {
572
+ this._resizeChart();
573
+ },
574
+
575
+ /**
576
+ * Set default zoom level (followMarker: true).
577
+ */
578
+ setZFollow: function(zoom) {
579
+ this.options.zFollow = zoom;
580
+ },
581
+
582
+ /**
583
+ * Hide current elevation chart profile.
584
+ */
585
+ show: function() {
586
+ style(this._container, "display", "block");
587
+ },
588
+
589
+ /*
590
+ * Parsing data either from GPX or GeoJSON and update the diagram data
591
+ */
592
+ _addData: function(d) {
593
+ if (!d) {
594
+ return;
595
+ }
596
+
597
+ // Standard GeoJSON
598
+ if (d.type === "FeatureCollection" ) {
599
+ return each(d.features, feature => this._addData(feature));
600
+ } else if (d.type === "Feature") {
601
+ let geom = d.geometry;
602
+ if (geom) {
603
+ switch (geom.type) {
604
+ case 'LineString': return this._addGeoJSONData(geom.coordinates, d.properties);
605
+ case 'MultiLineString': return each(geom.coordinates, (coords, i) => this._addGeoJSONData(coords, d.properties, i));
606
+ case 'Point':
607
+ default: return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
608
+ }
609
+ }
610
+ }
611
+
612
+ // Fallback for leaflet layers (eg. L.Gpx)
613
+ if (d._latlngs) {
614
+ return this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
615
+ }
616
+
617
+ },
618
+
619
+ /*
620
+ * Parsing of GeoJSON data lines and their elevation in z-coordinate
621
+ */
622
+ _addGeoJSONData: function(coords, properties, nestingLevel) {
623
+
624
+ // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
625
+ let props = (properties && properties.coordinateProperties) || properties;
626
+
627
+ coords.forEach((point, i) => {
628
+
629
+ // GARMIN_EXTENSIONS = ["hr", "cad", "atemp", "wtemp", "depth", "course", "bearing"];
630
+ point.meta = point.meta ?? { time: null, ele: null };
631
+
632
+ point.prev = (attr) => (attr ? this._data[i > 0 ? i - 1 : 0][attr] : this._data[i > 0 ? i - 1 : 0]);
633
+
634
+ this.fire("elepoint_init", { point: point, props: props, id: i, isMulti: nestingLevel });
635
+
636
+ this._addPoint(
637
+ point.lat ?? point[1],
638
+ point.lng ?? point[0],
639
+ point.alt ?? point.meta.ele ?? point[2]
640
+ );
641
+
642
+ this.fire("elepoint_added", { point: point, index: this._data.length - 1 });
643
+
644
+ if (this._yCoordMax < this._data[this._data.length - 1][this.options.yAttr]) this._yCoordMax = this._data[this._data.length - 1][this.options.yAttr];
645
+ });
646
+
647
+ this.fire("eletrack_added", { coords: coords, index: this._data.length - 1 });
648
+ },
649
+
650
+ /*
651
+ * Parse and push a single (x, y, z) point to current elevation profile.
652
+ */
653
+ _addPoint: function(x, y, z) {
654
+ if (this.options.reverseCoords) {
655
+ [x, y] = [y, x];
656
+ }
657
+
658
+ this._data.push({
659
+ x: x,
660
+ y: y,
661
+ z: z,
662
+ latlng: L.latLng(x, y, z)
663
+ });
664
+
665
+ this.fire("eledata_updated", { index: this._data.length - 1 });
666
+ },
667
+
668
+ _addLayer: function(layer) {
669
+ if (layer) this._layers.addLayer(layer);
670
+ // Postpone adding the distance markers (lazy: true)
671
+ if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
672
+ layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
673
+ }
674
+ return layer;
675
+ },
676
+
677
+ _addMarker: function(marker) {
678
+ if (marker) this._markers.addLayer(marker);
679
+ return marker;
680
+ },
681
+
682
+ /**
683
+ * Initialize "L.AlmostOver" integration
684
+ */
685
+ _initAlmostOverHandler: function(map, layer) {
686
+ return (map && this.options.almostOver && !L.Browser.mobile) ? Promise.all([
687
+ this.import(this.__LGEOMUTIL),
688
+ this.import(this.__LALMOSTOVER)
689
+ ]).then(() => {
690
+ map.addHandler('almostOver', L.Handler.AlmostOver);
691
+ if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
692
+ map.almostOver.addLayer(layer);
693
+ map
694
+ .on('almost:move', (e) => this._onMouseMoveLayer(e))
695
+ .on('almost:out', (e) => this._onMouseOut(e));
696
+ }
697
+ }) : Promise.resolve();
698
+ },
699
+
700
+ /**
701
+ * Initialize "L.DistanceMarkers" integration
702
+ */
703
+ _initDistanceMarkers: function() {
704
+ return this.options.distanceMarkers ? Promise.all([this.import(this.__LGEOMUTIL), this.import(this.__LDISTANCEM)]) : Promise.resolve();
705
+ },
706
+
707
+ _initHotLine: function(layer) {
708
+ let prop = typeof this.options.hotline == 'string' ? this.options.hotline : 'elevation';
709
+ return this.options.hotline ? this.import(this.__LHOTLINE)
710
+ .then(() => {
711
+ layer.eachLayer((trkseg) => {
712
+ if(trkseg.feature.geometry.type != "Point") {
713
+ let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], coords[2] * (this.options.altitudeFactor || 1))});
714
+ let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
715
+ renderer: L.Hotline.renderer(),
716
+ min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
717
+ max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
718
+ palette: {
719
+ 0.0: '#008800',
720
+ 0.5: '#ffff00',
721
+ 1.0: '#ff0000'
722
+ },
723
+ weight: 5,
724
+ outlineColor: '#000000',
725
+ outlineWidth: 1
726
+ }).addTo(this._hotline);
727
+ let alpha = trkseg.options.style && trkseg.options.style.opacity || 1;
728
+ trkseg.on('add remove', ({type}) => {
729
+ trkseg.setStyle({opacity: (type == 'add' ? 0 : alpha)});
730
+ line[(type == 'add' ? 'addTo' : 'removeFrom')](trkseg._map);
731
+ if (line._renderer) line._renderer._container.parentElement.insertBefore(line._renderer._container, line._renderer._container.parentElement.firstChild);
732
+ });
733
+ }
734
+ });
735
+ }) : Promise.resolve();
736
+ },
737
+
738
+ /**
739
+ * Initialize "L.AlmostOver" and "L.DistanceMarkers"
740
+ */
741
+ _initMapIntegrations: function(layer) {
742
+ let map = this._map;
743
+ if (map) {
744
+ if (this._data.length) {
745
+ this._start.setLatLng(this._data[0].latlng);
746
+ this._end.setLatLng(this._data[this._data.length -1].latlng);
747
+ }
748
+ Promise.all([
749
+ this._initHotLine(layer),
750
+ this._initAlmostOverHandler(map, layer),
751
+ this._initDistanceMarkers(),
752
+ ]).then(() => {
753
+ if (this.options.polyline) {
754
+ layer.addTo(map);
755
+ this._circleMarkers.addTo(map);
756
+ }
757
+ if (this.options.autofitBounds) {
758
+ this.fitBounds(layer.getBounds());
759
+ }
760
+ map.invalidateSize();
761
+ });
762
+ } else {
763
+ this.once('add', () => this._initMapIntegrations(layer));
764
+ }
765
+ },
766
+
767
+ /*
768
+ * Collapse current chart control.
769
+ */
770
+ _collapse: function() {
771
+ replaceClass(this._container, 'elevation-expanded', 'elevation-collapsed');
772
+ if (this._map) this._map.invalidateSize();
773
+ },
774
+
775
+ /*
776
+ * Expand current chart control.
777
+ */
778
+ _expand: function() {
779
+ replaceClass(this._container, 'elevation-collapsed', 'elevation-expanded');
780
+ if (this._map) this._map.invalidateSize();
781
+ },
782
+
783
+ /**
784
+ * Add some basic colors to leaflet canvas renderer (preferCanvas: true).
785
+ */
786
+ _fixCanvasPaths: function() {
787
+ let oldProto = L.Canvas.prototype._fillStroke;
788
+ let control = this;
789
+
790
+ let theme = this.options.theme.split(' ')[0].replace('-theme', '');
791
+ let color = Colors[theme] || {};
792
+
793
+ L.Canvas.include({
794
+ _fillStroke: function(ctx, layer) {
795
+ if (control._layers.hasLayer(layer)) {
796
+
797
+ let options = layer.options;
798
+
799
+ options.color = color.line || color.area || theme;
800
+ options.stroke = !!options.color;
801
+
802
+ oldProto.call(this, ctx, layer);
803
+
804
+ if (options.stroke && options.weight !== 0) {
805
+ let oldVal = ctx.globalCompositeOperation || 'source-over';
806
+ ctx.globalCompositeOperation = 'destination-over';
807
+ ctx.strokeStyle = color.outline || '#FFF';
808
+ ctx.lineWidth = options.weight * 1.75;
809
+ ctx.stroke();
810
+ ctx.globalCompositeOperation = oldVal;
811
+ }
812
+
813
+ } else {
814
+ oldProto.call(this, ctx, layer);
815
+ }
816
+ }
817
+ });
818
+ },
819
+
820
+ /**
821
+ * Partial fix for initial tooltip size
822
+ *
823
+ * @link https://github.com/Raruto/leaflet-elevation/issues/81#issuecomment-713477050
824
+ */
825
+ _fixTooltipSize: function() {
826
+ this.on('elechart_init', () =>
827
+ this.once('elechart_change elechart_hover', ({data, xCoord}) => {
828
+ if (this._chartEnabled) {
829
+ this._chart._showDiagramIndicator(data, xCoord);
830
+ this._chart._showDiagramIndicator(data, xCoord);
831
+ }
832
+ this._updateMarker(data);
833
+ })
834
+ );
835
+ },
836
+
837
+ /*
838
+ * Finds a data entry for the given LatLng
839
+ */
840
+ _findItemForLatLng: function(latlng) {
841
+ return this._data[this._chart._findIndexForLatLng(latlng)];
842
+ },
843
+
844
+ /*
845
+ * Finds a data entry for the given xDiagCoord
846
+ */
847
+ _findItemForX: function(x) {
848
+ return this._data[this._chart._findIndexForXCoord(x)];
849
+ },
850
+
851
+ /**
852
+ * Fires an event of the specified type.
853
+ */
854
+ _fireEvt: function(type, data, propagate) {
855
+ if (this.fire) this.fire(type, data, propagate);
856
+ if (this._map) this._map.fire(type, data, propagate);
857
+ },
858
+
859
+ /*
860
+ * Hides the position/height indicator marker drawn onto the map
861
+ */
862
+ _hideMarker: function() {
863
+ if (this.options.autohideMarker) {
864
+ this._marker.remove();
865
+ }
866
+ },
867
+
868
+ /**
869
+ * Generate "svg" chart (DOM element).
870
+ */
871
+ _initChart: function(container) {
872
+ let opts = this.options;
873
+ let map = this._map;
874
+
875
+ if (opts.detached) {
876
+ let { offsetWidth, offsetHeight} = this.eleDiv;
877
+ if (offsetWidth > 0) opts.width = offsetWidth;
878
+ if (offsetHeight > 20) opts.height = offsetHeight - 20; // 20 = horizontal scrollbar size.
879
+ } else {
880
+ let { clientWidth } = map.getContainer();
881
+ opts._maxWidth = opts._maxWidth > opts.width ? opts._maxWidth : opts.width;
882
+ this._container.style.maxWidth = opts._maxWidth + 'px';
883
+ if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
884
+ }
885
+
886
+ Promise.all([
887
+ this.import(this.__D3),
888
+ this.import(this.__LCHART)
889
+ ]).then((m) => {
890
+
891
+ let chart = this._chart = new (m[1] || Elevation).Chart(opts, this);
892
+
893
+ this._x = this._chart._x;
894
+ this._y = this._chart._y;
895
+
896
+ d3
897
+ .select(container)
898
+ .call(chart.render());
899
+
900
+ chart
901
+ .on('reset_drag', this._hideMarker, this)
902
+ .on('mouse_enter', this._onMouseEnter, this)
903
+ .on('dragged', this._onDragEnd, this)
904
+ .on('mouse_move', this._onMouseMove, this)
905
+ .on('mouse_out', this._onMouseOut, this)
906
+ .on('ruler_filter', this._onRulerFilter, this)
907
+ .on('zoom', this._updateChart, this)
908
+ .on('elepath_toggle', this._onToggleChart, this)
909
+ .on('margins_updated', this._resizeChart, this);
910
+
911
+
912
+ this.fire("elechart_init");
913
+
914
+ map
915
+ .on('zoom viewreset zoomanim', this._hideMarker, this)
916
+ .on('resize', this._resetView, this)
917
+ .on('resize', this._resizeChart, this)
918
+ .on('rotate', this._rotateMarker, this)
919
+ .on('mousedown', this._resetDrag, this);
920
+
921
+ on(map.getContainer(), 'mousewheel', this._resetDrag, this);
922
+ on(map.getContainer(), 'touchstart', this._resetDrag, this);
923
+ on(document, 'keydown', this._onKeyDown, this);
924
+
925
+ this
926
+ .on('eledata_added eledata_loaded', this._updateChart, this)
927
+ .on('eledata_added eledata_loaded', this._updateSummary, this);
928
+
929
+ this._updateChart();
930
+ this._updateSummary();
931
+ });
932
+
933
+
934
+ },
935
+
936
+ _initLayer: function() {
937
+ this._layers
938
+ .on('layeradd layerremove', ({layer, type}) => {
939
+ let node = layer.getElement && layer.getElement();
940
+ toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, type == 'layeradd');
941
+ toggleEvent(layer, "mousemove", this._onMouseMoveLayer.bind(this), type == 'layeradd');
942
+ toggleEvent(layer, "mouseout", this._onMouseOut.bind(this), type == 'layeradd');
943
+ });
944
+ },
945
+
946
+ _initMarker: function(map) {
947
+ let pane = map.getPane('elevationPane');
948
+ if (!pane) {
949
+ pane = this._pane = map.createPane('elevationPane', map.getPane('norotatePane') || map.getPane('mapPane'));
950
+ pane.style.zIndex = 625; // This pane is above markers but below popups.
951
+ pane.style.pointerEvents = 'none';
952
+ }
953
+
954
+ if (this._renderer) this._renderer.remove();
955
+ this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
956
+
957
+ Promise.all([
958
+ this.import(this.__D3),
959
+ this.import(this.__LMARKER)
960
+ ]).then((m) => {
961
+ this._marker = new (m[1] || Elevation).Marker(this.options, this);
962
+ this.fire("elechart_marker");
963
+ });
964
+ },
965
+
966
+ /**
967
+ * Inspired by L.Control.Layers
968
+ */
969
+ _initButton: function(container) {
970
+ L.DomEvent
971
+ .disableClickPropagation(container)
972
+ .disableScrollPropagation(container);
973
+
974
+ this.options.collapsed ? this._collapse() : this._expand();
975
+
976
+ if (this.options.autohide) {
977
+ on(container, 'mouseover', this._expand, this);
978
+ on(container, 'mouseout', this._collapse, this);
979
+ this._map.on('click', this._collapse, this);
980
+ }
981
+
982
+ if (this.options.closeBtn) {
983
+ let link = this._button = create('a', "elevation-toggle-icon", { href: '#', title: L._('Elevation'), }, container);
984
+ on(link, 'click', L.DomEvent.stop);
985
+ on(link, 'click', this._toggle, this);
986
+ on(link, 'focus', this._toggle, this);
987
+ fetch(this.__btnIcon).then(r => r.ok && r.text().then(img => link.innerHTML = img));
988
+ }
989
+ },
990
+
991
+ _initSummary: function(container) {
992
+ this.import(this.__LSUMMARY).then((m)=>{
993
+ this._summary = new (m || Elevation).Summary({ summary: this.options.summary }, this);
994
+
995
+ this.on('elechart_init', () => {
996
+ d3.select(container).call(this._summary.render());
997
+ });
998
+ });
999
+ },
1000
+
1001
+ /**
1002
+ * Retrieve data from a remote url (HTTP).
1003
+ */
1004
+ _loadFile: function(url) {
1005
+ fetch(url)
1006
+ .then((response) => response.text())
1007
+ .then((data) => {
1008
+ this._downloadURL = url; // TODO: handle multiple urls?
1009
+ this._parseFromString(data)
1010
+ .then( geojson => geojson && this._loadLayer(geojson));
1011
+ }).catch((err) => console.warn(err));
1012
+ },
1013
+
1014
+ /**
1015
+ * Dynamically import only required javascript modules (code splitting)
1016
+ */
1017
+ _loadModules: function(handlers) {
1018
+ // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
1019
+ handlers = handlers.map((h) => typeof h === 'string' && typeof Elevation[h] !== "undefined" ? Elevation[h] : h);
1020
+ // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
1021
+ let modules = handlers.map(file => (typeof file === 'string' && this.import(this.__modulesFolder + file.toLowerCase() + '.js')) || (file instanceof Promise && file) || Promise.resolve());
1022
+ return Promise.all(modules).then((m) => {
1023
+ each(m, (exported, i) => {
1024
+ let fn = exported && Object.keys(exported)[0];
1025
+ if (fn) {
1026
+ handlers[i] = Elevation[fn] = (Elevation[fn] ?? exported[fn]);
1027
+ }
1028
+ });
1029
+ each(handlers, h => ["function", "object"].includes(typeof h) && this._registerHandler(h));
1030
+ });
1031
+ },
1032
+
1033
+ /**
1034
+ * Simple GeoJSON data loader (L.GeoJSON).
1035
+ */
1036
+ _loadLayer: function(geojson) {
1037
+ let { polyline, theme, waypoints, wptIcons, wptLabels, distanceMarkers } = this.options;
1038
+ let style = L.extend({}, polyline);
1039
+
1040
+ if (theme) {
1041
+ style.className += ' ' + theme;
1042
+ }
1043
+
1044
+ if (geojson.name) {
1045
+ this.track_info.name = geojson.name;
1046
+ }
1047
+
1048
+ let layer = L.geoJson(geojson, {
1049
+ distanceMarkers: distanceMarkers,
1050
+ style: style,
1051
+ pointToLayer: (feature, latlng) => {
1052
+ if (waypoints) {
1053
+ let { desc, name, sym } = feature.properties;
1054
+ desc = desc || '';
1055
+ name = name || '';
1056
+ // Handle chart waypoints (dots)
1057
+ if ([true, 'dots'].includes(waypoints)) {
1058
+ this._registerCheckPoint({
1059
+ latlng: latlng,
1060
+ label : ([true, 'dots'].includes(wptLabels) ? name : '')
1061
+ });
1062
+ }
1063
+ // Handle map waypoints (markers)
1064
+ if ([true, 'markers'].includes(waypoints) && wptIcons != false) {
1065
+ return this._registerMarker({
1066
+ latlng : latlng,
1067
+ sym : (sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase(),
1068
+ content: [true, 'markers'].includes(wptLabels) && (name || desc) && decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''))
1069
+ });
1070
+ }
1071
+ }
1072
+ },
1073
+ onEachFeature: (feature, layer) => feature.geometry && feature.geometry.type != 'Point' && this.addData(feature, layer),
1074
+ });
1075
+
1076
+ this.import(this.__D3).then(() => {
1077
+ this._initMapIntegrations(layer);
1078
+ this._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: this.track_info.name, track_info: this.track_info });
1079
+ });
1080
+
1081
+ return layer;
1082
+ },
1083
+
1084
+ _onDragEnd: function({ dragstart, dragend}) {
1085
+ this._hideMarker();
1086
+ this.fitBounds(L.latLngBounds([dragstart.latlng, dragend.latlng]));
1087
+
1088
+ this.fire("elechart_dragged");
1089
+ },
1090
+
1091
+ _onKeyDown: function({key}) {
1092
+ if(!this.options.detached && key === "Escape"){
1093
+ this._collapse();
1094
+ } },
1095
+
1096
+ /**
1097
+ * Trigger mouseenter event.
1098
+ */
1099
+ _onMouseEnter: function() {
1100
+ this.fire('elechart_enter');
1101
+ },
1102
+
1103
+ /*
1104
+ * Handles the moueseover the chart and displays distance and altitude level.
1105
+ */
1106
+ _onMouseMove: function({xCoord}) {
1107
+ if (this._chartEnabled && this._data.length) {
1108
+ let item = this._findItemForX(xCoord);
1109
+ if (item) {
1110
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
1111
+
1112
+ this._updateMarker(item);
1113
+ this._setMapView(item);
1114
+
1115
+ if (this._map) {
1116
+ addClass(this._map.getContainer(), 'elechart-hover');
1117
+ }
1118
+
1119
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
1120
+ this.fire("elechart_hover", { data: item, xCoord: xCoord });
1121
+ }
1122
+ }
1123
+ },
1124
+
1125
+ /*
1126
+ * Handles mouseover events of the data layers on the map.
1127
+ */
1128
+ _onMouseMoveLayer: function({latlng}) {
1129
+ if (this._data.length) {
1130
+ let item = this._findItemForLatLng(latlng);
1131
+ if (item) {
1132
+ let xCoord = item.xDiagCoord;
1133
+
1134
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
1135
+
1136
+ this._updateMarker(item);
1137
+
1138
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
1139
+ }
1140
+ }
1141
+ },
1142
+
1143
+ /*
1144
+ * Handles the moueseout over the chart.
1145
+ */
1146
+ _onMouseOut: function() {
1147
+ if (!this.options.detached) {
1148
+ this._hideMarker();
1149
+ this._chart._hideDiagramIndicator();
1150
+ }
1151
+
1152
+ if (this._map) {
1153
+ removeClass(this._map.getContainer(), 'elechart-hover');
1154
+ }
1155
+
1156
+ this.fire("elechart_leave");
1157
+ },
1158
+
1159
+ /**
1160
+ * Handles the drag event over the ruler filter.
1161
+ */
1162
+ _onRulerFilter: function({coords}) {
1163
+ this._updateMapSegments(coords);
1164
+ },
1165
+
1166
+ /**
1167
+ * Toggle chart data on legend click
1168
+ */
1169
+ _onToggleChart: function({ name, enabled }) {
1170
+
1171
+ this._chartEnabled = this._chart._hasActiveLayers();
1172
+
1173
+ // toggle layer visibility on empty chart
1174
+ this._layers.eachLayer(layer => toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
1175
+
1176
+ // toggle option value (eg. altitude = { 'disabled' || 'enabled' })
1177
+ this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
1178
+
1179
+ // remove marker on empty chart
1180
+ if (!this._chartEnabled) {
1181
+ this._chart._hideDiagramIndicator();
1182
+ this._marker.remove();
1183
+ }
1184
+ },
1185
+
1186
+ /**
1187
+ * Simple GeoJSON Parser
1188
+ */
1189
+ _parseFromGeoJSONString: function(data) {
1190
+ try {
1191
+ return JSON.parse(data);
1192
+ } catch (e) { }
1193
+ },
1194
+
1195
+ /**
1196
+ * Attempt to parse raw response data (GeoJSON or XML > GeoJSON)
1197
+ */
1198
+ _parseFromString: function(data) {
1199
+ return new Promise(resolve =>
1200
+ this.import(this.__TOGEOJSON).then(() => {
1201
+ let geojson;
1202
+ try {
1203
+ geojson = this._parseFromXMLString(data.trim());
1204
+ } catch (e) {
1205
+ geojson = this._parseFromGeoJSONString(data.toString());
1206
+ }
1207
+ if (geojson) {
1208
+ geojson.name = geojson.name || (this._downloadURL || '').split('/').pop().split('#')[0].split('?')[0];
1209
+ }
1210
+ resolve(geojson);
1211
+ })
1212
+ );
1213
+ },
1214
+
1215
+ /**
1216
+ * Simple XML Parser (GPX, KML, TCX)
1217
+ */
1218
+ _parseFromXMLString: function(data) {
1219
+ if (data.indexOf("<") != 0) {
1220
+ throw 'Invalid XML';
1221
+ }
1222
+ let xml = (new DOMParser()).parseFromString(data, "text/xml");
1223
+ let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
1224
+ let name = xml.getElementsByTagName('name');
1225
+ if (xml.getElementsByTagName('parsererror').length) {
1226
+ throw 'Invalid XML';
1227
+ }
1228
+ if (!(type in toGeoJSON)) {
1229
+ type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
1230
+ }
1231
+ let geojson = toGeoJSON[type](xml);
1232
+ geojson.name = name.length > 0 ? (Array.from(name).find(tag => tag.parentElement.tagName == "trk") ?? name[0]).textContent : '';
1233
+ return geojson;
1234
+ },
1235
+
1236
+ /**
1237
+ * Add chart profile to diagram
1238
+ */
1239
+ _registerAreaPath: function(props) {
1240
+ this.on("elechart_init", () => this._chart._registerAreaPath(props));
1241
+ },
1242
+
1243
+ /**
1244
+ * Add chart grid to diagram
1245
+ */
1246
+ _registerAxisGrid: function(props) {
1247
+ this.on("elechart_axis", () => this._chart._registerAxisGrid(props));
1248
+ },
1249
+
1250
+ /**
1251
+ * Add chart axis to diagram
1252
+ */
1253
+ _registerAxisScale: function(props) {
1254
+ this.on("elechart_axis", () => this._chart._registerAxisScale(props));
1255
+ },
1256
+
1257
+ /**
1258
+ * Add a point of interest over the diagram
1259
+ */
1260
+ _registerCheckPoint: function(props) {
1261
+ this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
1262
+ },
1263
+
1264
+
1265
+ /**
1266
+ * Base handler for iterative track statistics (dist, time, z, slope, speed, acceleration, ...)
1267
+ */
1268
+ _registerDataAttribute: function(props) {
1269
+
1270
+ // parse of "coordinateProperties" for later usage
1271
+ if (props.coordPropsToMeta) {
1272
+ this.on("elepoint_init", (e) => props.coordPropsToMeta.call(this, e));
1273
+ }
1274
+
1275
+ // prevent excessive variabile instanstations
1276
+ let i, curr, prev, attr;
1277
+
1278
+ // save here a reference to last used point
1279
+ let lastValid = null;
1280
+
1281
+ // iteration
1282
+ this.on("elepoint_added", ({index, point}) => {
1283
+ i = index;
1284
+
1285
+ prev = curr ?? this._data[i]; // same as: this._data[i > 0 ? i - 1 : i]
1286
+ curr = this._data[i];
1287
+ attr = props.attr || props.name;
1288
+
1289
+ // check and fix missing data on last added point
1290
+ if (props.skipNull === false) {
1291
+ let curr = this._data[i][attr];
1292
+ if(i > 0) {
1293
+ let prev = this._data[i - 1][attr];
1294
+ if (isNaN(prev)) {
1295
+ if (!isNaN(lastValid) && !isNaN(curr)) {
1296
+ prev = (lastValid + curr) / 2;
1297
+ } else if (!isNaN(lastValid)) {
1298
+ prev = lastValid;
1299
+ } else if (!isNaN(curr)) {
1300
+ prev = curr;
1301
+ }
1302
+ if (!isNaN(prev)) return this._data.splice(i - 1, 1);
1303
+ this._data[i - 1][attr] = prev;
1304
+ }
1305
+ }
1306
+ // update reference to last used point (ie. if it has data)
1307
+ if (!isNaN(curr)) {
1308
+ lastValid = curr;
1309
+ }
1310
+ }
1311
+
1312
+ // retrieve point value
1313
+ curr[attr] = props.pointToAttr.call(this, point, i);
1314
+
1315
+ // update "track_info" stats (min, max, avg, ...)
1316
+ if (props.stats) {
1317
+ for (const key in props.stats) {
1318
+ let sname = (props.statsName || attr) + (key != '' ? '_' : '');
1319
+ this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key], this._data.length);
1320
+ }
1321
+ }
1322
+
1323
+ // Limit "crazy" delta values.
1324
+ if (props.deltaMax) {
1325
+ curr[attr] =wrapDelta(curr[attr], prev[attr], props.deltaMax);
1326
+ }
1327
+
1328
+ // Range of acceptable values.
1329
+ if (props.clampRange) {
1330
+ curr[attr] = clamp(curr[attr], props.clampRange);
1331
+ }
1332
+
1333
+ // Limit floating point precision.
1334
+ if (!isNaN(props.decimals)) {
1335
+ curr[attr] = round(curr[attr], props.decimals);
1336
+ }
1337
+
1338
+ // update here some mixins (eg. complex "track_info" stuff)
1339
+ if(props.onPointAdded) props.onPointAdded.call(this, curr[attr], i, point);
1340
+ });
1341
+ },
1342
+
1343
+ /**
1344
+ * Parse a module definition and attach related function listeners
1345
+ */
1346
+ _registerHandler: function(props) {
1347
+
1348
+ // eg. L.Control.Altitude
1349
+ if (typeof props === "function") {
1350
+ return this._registerHandler(props.call(this));
1351
+ }
1352
+
1353
+ let {
1354
+ name,
1355
+ attr,
1356
+ required,
1357
+ skipNull,
1358
+ deltaMax,
1359
+ clampRange,
1360
+ decimals,
1361
+ meta,
1362
+ unit,
1363
+ coordinateProperties,
1364
+ coordPropsToMeta: coordPropsToMeta$1,
1365
+ pointToAttr,
1366
+ onPointAdded,
1367
+ stats,
1368
+ statsName,
1369
+ grid,
1370
+ scale,
1371
+ path,
1372
+ tooltip,
1373
+ summary
1374
+ } = props;
1375
+
1376
+ // eg. "altitude" == true
1377
+ if (this.options[name] || required) {
1378
+
1379
+ this._registerDataAttribute({
1380
+ name,
1381
+ attr,
1382
+ skipNull,
1383
+ deltaMax,
1384
+ clampRange,
1385
+ decimals,
1386
+ coordPropsToMeta: coordPropsToMeta(coordinateProperties, meta || name, coordPropsToMeta$1),
1387
+ pointToAttr,
1388
+ onPointAdded,
1389
+ stats,
1390
+ statsName,
1391
+ });
1392
+
1393
+ if (grid) {
1394
+ this._registerAxisGrid(L.extend({ name }, grid));
1395
+ }
1396
+
1397
+ if (this.options[name] !== "summary") {
1398
+ if (scale) this._registerAxisScale(L.extend({ name, label: unit }, scale));
1399
+ if (path) this._registerAreaPath(L.extend({ name }, path));
1400
+ }
1401
+
1402
+ if (tooltip || props.tooltips) {
1403
+ each([tooltip, ...(props.tooltips || [])], t => t && this._registerTooltip(L.extend({ name }, t)));
1404
+ }
1405
+
1406
+ if (summary) {
1407
+ each(summary, (s, k) => summary[k] = L.extend({ unit }, s));
1408
+ this._registerSummary(summary);
1409
+ }
1410
+ }
1411
+ },
1412
+
1413
+ _registerMarker({latlng, sym, content}) {
1414
+ let { wptIcons } = this.options;
1415
+ // generate and cache appropriate icon symbol
1416
+ if (!wptIcons.hasOwnProperty(sym)) {
1417
+ wptIcons[sym] = L.divIcon(L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } ));
1418
+ }
1419
+ let marker = L.marker(latlng, { icon: wptIcons[sym] });
1420
+ if (content) {
1421
+ marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
1422
+ marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
1423
+ }
1424
+ return this._addMarker(marker)
1425
+ },
1426
+
1427
+ /**
1428
+ * Add chart or marker tooltip info
1429
+ */
1430
+ _registerTooltip: function(props) {
1431
+ props.chart && this.on("elechart_init", () => this._chart._registerTooltip(L.extend({}, props, { value: props.chart })));
1432
+ props.marker && this.on("elechart_marker", () => this._marker._registerTooltip(L.extend({}, props, { value: props.marker })));
1433
+ },
1434
+
1435
+ /**
1436
+ * Add summary info to diagram
1437
+ */
1438
+ _registerSummary: function(props) {
1439
+ this.on('elechart_summary', () => this._summary._registerSummary(props));
1440
+ },
1441
+
1442
+ /*
1443
+ * Removes the drag rectangle and zoms back to the total extent of the data.
1444
+ */
1445
+ _resetDrag: function() {
1446
+ this._chart._resetDrag();
1447
+ this._hideMarker();
1448
+ },
1449
+
1450
+ /**
1451
+ * Resets drag, marker and bounds.
1452
+ */
1453
+ _resetView: function() {
1454
+ if (this._map && this._map._isFullscreen) return;
1455
+ this._resetDrag();
1456
+ this._hideMarker();
1457
+ if (this.options.autofitBounds) {
1458
+ this.fitBounds();
1459
+ }
1460
+ },
1461
+
1462
+ /**
1463
+ * Hacky way for handling chart resize. Deletes it and redraw chart.
1464
+ */
1465
+ _resizeChart: function() {
1466
+ if (this._container && style(this._container, "display") != "none") {
1467
+ let opts = this.options;
1468
+ let newWidth = opts.detached ? (this.eleDiv || this._container).offsetWidth : clamp(opts._maxWidth, [0, this._map.getContainer().clientWidth - 30]);
1469
+ if (newWidth) {
1470
+ opts.width = newWidth;
1471
+ if (this._chart && this._chart._chart) {
1472
+ this._chart._chart._resize(opts);
1473
+ this._updateChart();
1474
+ }
1475
+ }
1476
+ this._updateMapSegments();
1477
+ }
1478
+ },
1479
+
1480
+ /**
1481
+ * Collapse or Expand chart control.
1482
+ */
1483
+ _toggle: function() {
1484
+ hasClass(this._container, "elevation-expanded") ? this._collapse() : this._expand();
1485
+ },
1486
+
1487
+ /**
1488
+ * Update map center and zoom (followMarker: true)
1489
+ */
1490
+ _setMapView: function(item) {
1491
+ if (this._map && this.options.followMarker) {
1492
+ let zoom = this._map.getZoom();
1493
+ let z = this.options.zFollow;
1494
+ if (typeof z === "number") {
1495
+ this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1496
+ } else if (!this._map.getBounds().contains(item.latlng)) {
1497
+ this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1498
+ }
1499
+ }
1500
+ },
1501
+
1502
+ /**
1503
+ * Calculates [x, y] domain and then update chart.
1504
+ */
1505
+ _updateChart: function() {
1506
+ if (this._chart && this._container) {
1507
+ this.fire("elechart_axis");
1508
+
1509
+ this._chart.update({ data: this._data, options: this.options });
1510
+
1511
+ this._x = this._chart._x;
1512
+ this._y = this._chart._y;
1513
+
1514
+ this.fire('elechart_updated');
1515
+ }
1516
+ },
1517
+
1518
+ /*
1519
+ * Update the position/height indicator marker drawn onto the map
1520
+ */
1521
+ _updateMarker: function(item) {
1522
+ if (this._marker) {
1523
+ this._marker.update({
1524
+ map : this._map,
1525
+ item : item,
1526
+ yCoordMax : this._yCoordMax || 0,
1527
+ options : this.options
1528
+ });
1529
+ }
1530
+ },
1531
+
1532
+ /**
1533
+ * Fix marker rotation on rotated maps
1534
+ */
1535
+ _rotateMarker: function() {
1536
+ if (this._marker) {
1537
+ this._marker.update();
1538
+ }
1539
+ },
1540
+
1541
+ /**
1542
+ * Highlight track segments on the map.
1543
+ */
1544
+ _updateMapSegments: function(coords) {
1545
+ this._markedSegments.setLatLngs(coords || []);
1546
+ if (coords && this._map && !this._map.hasLayer(this._markedSegments)) {
1547
+ this._markedSegments.addTo(this._map);
1548
+ }
1549
+ },
1550
+
1551
+ /**
1552
+ * Update chart summary.
1553
+ */
1554
+ _updateSummary: function() {
1555
+ if (this._summary) {
1556
+ this._summary.reset();
1557
+ if (this.options.summary) {
1558
+ this.fire("elechart_summary");
1559
+ this._summary.update();
1560
+ }
1561
+ if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
1562
+ this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>';
1563
+ select('.download a', this._summary._container).onclick = (e) => {
1564
+ e.preventDefault();
1565
+ let event = { downloadLink: this.options.downloadLink, confirm: saveFile.bind(this, this._downloadURL) };
1566
+ if (this.options.downloadLink == 'modal' && typeof CustomEvent === "function") {
1567
+ document.dispatchEvent(new CustomEvent("eletrack_download", { detail: event }));
1568
+ } else if (this.options.downloadLink == 'link' || this.options.downloadLink === true) {
1569
+ event.confirm();
1570
+ }
1571
+ this.fire('eletrack_download', event);
1572
+ };
1573
+ }
1574
+ }
1575
+ },
1576
+
1577
+
1578
+ /**
1579
+ * Calculates chart width.
1580
+ */
1581
+ _width: function() {
1582
+ if (this._chart) return this._chart._width();
1583
+ const { width, margins } = this.options;
1584
+ return width - margins.left - margins.right;
1585
+ },
1586
+
1587
+ /**
1588
+ * Calculates chart height.
1589
+ */
1590
+ _height: function() {
1591
+ if (this._chart) return this._chart._height();
1592
+ const { height, margins } = this.options;
1593
+ return height - margins.top - margins.bottom;
1594
+ },
1595
+
1590
1596
  });
1591
1597
 
1592
- /*
1593
- * Copyright (c) 2019, GPL-3.0+ Project, Raruto
1594
- *
1595
- * This file is free software: you may copy, redistribute and/or modify it
1596
- * under the terms of the GNU General Public License as published by the
1597
- * Free Software Foundation, either version 2 of the License, or (at your
1598
- * option) any later version.
1599
- *
1600
- * This file is distributed in the hope that it will be useful, but
1601
- * WITHOUT ANY WARRANTY; without even the implied warranty of
1602
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1603
- * General Public License for more details.
1604
- *
1605
- * You should have received a copy of the GNU General Public License
1606
- * along with this program. If not, see .
1607
- *
1608
- * This file incorporates work covered by the following copyright and
1609
- * permission notice:
1610
- *
1611
- * Copyright (c) 2013-2016, MIT License, Felix “MrMufflon” Bache
1612
- *
1613
- * Permission to use, copy, modify, and/or distribute this software
1614
- * for any purpose with or without fee is hereby granted, provided
1615
- * that the above copyright notice and this permission notice appear
1616
- * in all copies.
1617
- *
1618
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
1619
- * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
1620
- * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
1621
- * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
1622
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
1623
- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
1624
- * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
1625
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1626
- */
1627
-
1628
- Elevation.Utils = _;
1629
-
1598
+ /*
1599
+ * Copyright (c) 2019, GPL-3.0+ Project, Raruto
1600
+ *
1601
+ * This file is free software: you may copy, redistribute and/or modify it
1602
+ * under the terms of the GNU General Public License as published by the
1603
+ * Free Software Foundation, either version 2 of the License, or (at your
1604
+ * option) any later version.
1605
+ *
1606
+ * This file is distributed in the hope that it will be useful, but
1607
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1608
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1609
+ * General Public License for more details.
1610
+ *
1611
+ * You should have received a copy of the GNU General Public License
1612
+ * along with this program. If not, see .
1613
+ *
1614
+ * This file incorporates work covered by the following copyright and
1615
+ * permission notice:
1616
+ *
1617
+ * Copyright (c) 2013-2016, MIT License, Felix “MrMufflon” Bache
1618
+ *
1619
+ * Permission to use, copy, modify, and/or distribute this software
1620
+ * for any purpose with or without fee is hereby granted, provided
1621
+ * that the above copyright notice and this permission notice appear
1622
+ * in all copies.
1623
+ *
1624
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
1625
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
1626
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
1627
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
1628
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
1629
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
1630
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
1631
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1632
+ */
1633
+
1634
+ Elevation.Utils = _;
1635
+
1630
1636
  L.control.elevation = (options) => new Elevation(options);
1631
1637
 
1632
1638
  }));