@raruto/leaflet-elevation 1.8.4 → 1.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/leaflet-elevation.css +16 -1
- package/dist/leaflet-elevation.js +515 -568
- package/dist/leaflet-elevation.js.map +1 -1
- package/dist/leaflet-elevation.min.css +1 -1
- package/dist/leaflet-elevation.min.js +2 -2
- package/dist/leaflet-elevation.min.js.map +1 -1
- package/libs/leaflet-gpxgroup.js +69 -186
- package/libs/leaflet-gpxgroup.min.js +1 -17
- package/package.json +1 -1
- package/src/control.js +377 -236
- package/src/index.css +16 -1
- package/src/index.js +14 -0
- package/src/utils.js +0 -218
- package/images/remove.svg +0 -1
package/src/control.js
CHANGED
|
@@ -14,10 +14,10 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
14
14
|
__mileFactor: 0.621371,
|
|
15
15
|
__footFactor: 3.28084,
|
|
16
16
|
__D3: 'https://unpkg.com/d3@6.5.0/dist/d3.min.js',
|
|
17
|
-
__TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.
|
|
17
|
+
__TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.5.0/dist/togeojson.umd.js',
|
|
18
18
|
__LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.9.3/src/leaflet.geometryutil.js',
|
|
19
19
|
__LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
|
|
20
|
-
__LDISTANCEM: 'https://unpkg.com/@raruto/leaflet-elevation@1.8.
|
|
20
|
+
__LDISTANCEM: 'https://unpkg.com/@raruto/leaflet-elevation@1.8.7/libs/leaflet-distance-marker.min.js',
|
|
21
21
|
|
|
22
22
|
/*
|
|
23
23
|
* Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
|
|
@@ -156,77 +156,62 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
156
156
|
this._markedSegments = L.polyline([]);
|
|
157
157
|
this._chartEnabled = true,
|
|
158
158
|
this.track_info = {};
|
|
159
|
-
this.options = _.deepMerge({}, this.options, options);
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (this.options.
|
|
164
|
-
if (this.options.
|
|
160
|
+
L.setOptions(this, options);
|
|
161
|
+
|
|
162
|
+
if (this.options.followMarker) this._setMapView = L.Util.throttle(this._setMapView, 300, this);
|
|
163
|
+
if (this.options.legend) this.options.margins.bottom += 30;
|
|
164
|
+
if (this.options.theme) this.options.polylineSegments.className += ' ' + this.options.theme;
|
|
165
|
+
if (this.options.wptIcons === true) this.options.wptIcons = Options.wptIcons;
|
|
166
|
+
if (this.options.distanceMarkers === true) this.options.distanceMarkers = { lazy: true };
|
|
165
167
|
|
|
166
168
|
this._markedSegments.setStyle(this.options.polylineSegments);
|
|
167
169
|
|
|
168
170
|
// Leaflet canvas renderer colors
|
|
169
171
|
L.extend(D3.Colors, this.options.colors || {});
|
|
172
|
+
|
|
173
|
+
// Various stuff
|
|
174
|
+
this._fixCanvasPaths();
|
|
175
|
+
this._fixTooltipSize();
|
|
170
176
|
},
|
|
171
177
|
|
|
172
178
|
/**
|
|
173
179
|
* Javascript scripts downloader (lazy loader)
|
|
174
180
|
*/
|
|
175
|
-
lazyLoad: function(src) {
|
|
176
|
-
|
|
181
|
+
lazyLoad: function(src, condition = false, loader = '') {
|
|
182
|
+
if (!this.options.lazyLoadJS) {
|
|
183
|
+
return Promise.resolve();
|
|
184
|
+
}
|
|
185
|
+
switch(src) {
|
|
186
|
+
case this.__D3:
|
|
187
|
+
loader = '_d3LazyLoader';
|
|
188
|
+
condition = typeof d3 !== 'object';
|
|
189
|
+
break;
|
|
190
|
+
case this.__TOGEOJSON:
|
|
191
|
+
loader = '_togeojsonLazyLoader';
|
|
192
|
+
condition = typeof toGeoJSON !== 'object';
|
|
193
|
+
break;
|
|
194
|
+
case this.__LGEOMUTIL:
|
|
195
|
+
loader = '_geomutilLazyLoader';
|
|
196
|
+
condition = typeof L.GeometryUtil !== 'object';
|
|
197
|
+
break;
|
|
198
|
+
case this.__LALMOSTOVER:
|
|
199
|
+
loader = '_almostoverLazyLoader';
|
|
200
|
+
condition = typeof L.Handler.AlmostOver !== 'function';
|
|
201
|
+
break;
|
|
202
|
+
case this.__LDISTANCEM:
|
|
203
|
+
loader = '_distanceMarkersLazyLoader';
|
|
204
|
+
condition = typeof L.DistanceMarkers !== 'function';
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
return L.Control.Elevation[loader] = _.lazyLoader(src, condition, L.Control.Elevation[loader]);
|
|
177
208
|
},
|
|
178
209
|
|
|
179
210
|
/**
|
|
180
211
|
* Load elevation data (GPX, GeoJSON or KML).
|
|
181
212
|
*/
|
|
182
213
|
load: function(data) {
|
|
183
|
-
|
|
184
|
-
this.loadXML(data);
|
|
185
|
-
} else if (_.isJSONDoc(data)) {
|
|
186
|
-
this.loadGeoJSON(data);
|
|
187
|
-
} else {
|
|
188
|
-
this.loadFile(data);
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Load data from a remote url.
|
|
194
|
-
*/
|
|
195
|
-
loadFile: function(url) {
|
|
196
|
-
fetch(url)
|
|
197
|
-
.then((response) => response.text())
|
|
198
|
-
.then((data) => {
|
|
199
|
-
this._downloadURL = url; // TODO: handle multiple urls?
|
|
200
|
-
this.load(data);
|
|
201
|
-
})
|
|
202
|
-
.catch((err) => console.warn(err));
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Load raw GeoJSON data.
|
|
207
|
-
*/
|
|
208
|
-
loadGeoJSON: function(data) {
|
|
209
|
-
_.GeoJSONLoader(data, this);
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Load raw XML data.
|
|
214
|
-
*/
|
|
215
|
-
loadXML: function(data) {
|
|
216
|
-
this.lazyLoad(this.__TOGEOJSON)
|
|
217
|
-
.then(() => {
|
|
218
|
-
let xml = (new DOMParser()).parseFromString(data, "text/xml");
|
|
219
|
-
let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
|
|
220
|
-
if (!(type in toGeoJSON)) {
|
|
221
|
-
type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
|
|
222
|
-
}
|
|
223
|
-
let geojson = toGeoJSON[type](xml);
|
|
224
|
-
let name = xml.getElementsByTagName('name');
|
|
225
|
-
if(name[0]) { geojson.name = name[0].innerHTML; }
|
|
226
|
-
else if(this._downloadURL) { geojson.name = this._downloadURL.split('/').pop().split('#')[0].split('?')[0]; }
|
|
227
|
-
|
|
228
|
-
return this.loadGeoJSON(geojson, this);
|
|
229
|
-
});
|
|
214
|
+
this._parseFromString(data).then( geojson => geojson ? this._loadLayer(geojson) : this._loadFile(data));
|
|
230
215
|
},
|
|
231
216
|
|
|
232
217
|
/**
|
|
@@ -236,11 +221,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
236
221
|
onAdd: function(map) {
|
|
237
222
|
this._map = map;
|
|
238
223
|
|
|
239
|
-
let container = this._container = _.create("div", "elevation-control elevation " + this.options.theme);
|
|
240
|
-
|
|
241
|
-
if (!this.options.detached) {
|
|
242
|
-
_.addClass(container, 'leaflet-control');
|
|
243
|
-
}
|
|
224
|
+
let container = this._container = _.create("div", "elevation-control elevation " + this.options.theme + " " + (this.options.detached ? '' : 'leaflet-control'));
|
|
225
|
+
|
|
244
226
|
this.lazyLoad(this.__D3)
|
|
245
227
|
.then(() => {
|
|
246
228
|
this._initButton(container);
|
|
@@ -256,8 +238,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
256
238
|
.on('rotate', this._rotateMarker, this)
|
|
257
239
|
.on('mousedown', this._resetDrag, this);
|
|
258
240
|
|
|
259
|
-
_.on(map.getContainer(), 'mousewheel', this._resetDrag,
|
|
260
|
-
_.on(map.getContainer(), 'touchstart', this._resetDrag,
|
|
241
|
+
_.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
|
|
242
|
+
_.on(map.getContainer(), 'touchstart', this._resetDrag, this);
|
|
243
|
+
_.on(document, 'keydown', this._keydownHandler, this);
|
|
261
244
|
|
|
262
245
|
this
|
|
263
246
|
.on('eledata_added eledata_loaded', this._updateChart, this)
|
|
@@ -283,8 +266,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
283
266
|
.off('resize', this._resizeChart, this)
|
|
284
267
|
.off('mousedown', this._resetDrag, this);
|
|
285
268
|
|
|
286
|
-
_.off(map.getContainer(), 'mousewheel', this._resetDrag,
|
|
287
|
-
_.off(map.getContainer(), 'touchstart', this._resetDrag,
|
|
269
|
+
_.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
|
|
270
|
+
_.off(map.getContainer(), 'touchstart', this._resetDrag, this);
|
|
271
|
+
_.off(document, 'keydown', this._keydownHandler, this);
|
|
288
272
|
|
|
289
273
|
this
|
|
290
274
|
.off('eledata_added eledata_loaded', this._updateChart, this)
|
|
@@ -320,6 +304,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
320
304
|
return;
|
|
321
305
|
}
|
|
322
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Standard GeoJSON --> doesn't handle "time", "heart", …
|
|
309
|
+
*/
|
|
323
310
|
let geom = d.geometry;
|
|
324
311
|
if (geom) {
|
|
325
312
|
switch (geom.type) {
|
|
@@ -335,35 +322,47 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
335
322
|
console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
|
|
336
323
|
}
|
|
337
324
|
}
|
|
338
|
-
|
|
339
325
|
if (d.type === "FeatureCollection") {
|
|
340
326
|
_.each(d.features, feature => this._addData(feature));
|
|
341
327
|
}
|
|
342
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Extended GeoJSON --> rely on leaflet implementation
|
|
331
|
+
*/
|
|
343
332
|
if (d._latlngs) {
|
|
344
|
-
this.
|
|
333
|
+
this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
|
|
345
334
|
}
|
|
346
335
|
},
|
|
347
336
|
|
|
348
337
|
/*
|
|
349
338
|
* Parsing of GeoJSON data lines and their elevation in z-coordinate
|
|
350
339
|
*/
|
|
351
|
-
_addGeoJSONData: function(coords) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
340
|
+
_addGeoJSONData: function(coords, properties) {
|
|
341
|
+
coords.forEach((point, i) => {
|
|
342
|
+
|
|
343
|
+
// Inspired by L.GPX layer properties
|
|
344
|
+
point.meta = point.meta ?? { time: null, ele: null, hr: null, cad: null, atemp: null };
|
|
345
|
+
|
|
346
|
+
// "alt" property is generated inside "leaflet"
|
|
347
|
+
if ("alt" in point) point.meta.ele = point.alt;
|
|
348
|
+
|
|
349
|
+
// "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
|
|
350
|
+
if (properties.coordinateProperties) {
|
|
351
|
+
let props = properties.coordinateProperties;
|
|
352
|
+
if("times" in props) point.meta.time = new Date(Date.parse(props.times[i]));
|
|
353
|
+
if("heart" in props) point.meta.hr = parseInt(props.heart[i]);
|
|
354
|
+
// TODO: ask to "@tmcw/toGeoJSON" for "cadence" and "temperature" implementation
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this._addPoint(
|
|
358
|
+
point.lat ?? point[1],
|
|
359
|
+
point.lng ?? point[0],
|
|
360
|
+
point.alt ?? point.meta.ele ?? point[2]
|
|
361
|
+
);
|
|
358
362
|
|
|
359
|
-
/*
|
|
360
|
-
* Parsing function for GPX data and their elevation in z-coordinate
|
|
361
|
-
*/
|
|
362
|
-
_addGPXData: function(coords) {
|
|
363
|
-
_.each(coords, point => {
|
|
364
|
-
this._addPoint(point.lat, point.lng, point.alt ?? point.meta.ele);
|
|
365
363
|
this._fireEvt("elepoint_added", { point: point, index: this._data.length - 1 });
|
|
366
364
|
});
|
|
365
|
+
|
|
367
366
|
this._fireEvt("eletrack_added", { coords: coords, index: this._data.length - 1 });
|
|
368
367
|
},
|
|
369
368
|
|
|
@@ -387,12 +386,59 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
387
386
|
|
|
388
387
|
_addLayer: function(layer) {
|
|
389
388
|
if (layer) this._layers.addLayer(layer)
|
|
389
|
+
// Postpone adding the distance markers (lazy: true)
|
|
390
|
+
if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
|
|
391
|
+
layer.on('add remove', (e) => L.DistanceMarkers && e.target instanceof L.Polyline && e.target[e.type + 'DistanceMarkers']());
|
|
392
|
+
}
|
|
390
393
|
},
|
|
391
394
|
|
|
392
395
|
_addMarker: function(marker) {
|
|
393
396
|
if (marker) this._markers.addLayer(marker)
|
|
394
397
|
},
|
|
395
398
|
|
|
399
|
+
_bindPopup: function(marker) {
|
|
400
|
+
let popup = marker._popup;
|
|
401
|
+
if (popup) {
|
|
402
|
+
popup.options.className = 'elevation-popup';
|
|
403
|
+
if (popup._content) {
|
|
404
|
+
popup._content = decodeURI(popup._content);
|
|
405
|
+
popup.bindTooltip(popup._content, { direction: 'auto', sticky: true, opacity: 1, className: 'elevation-tooltip' }).openTooltip();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Initialize "L.AlmostOver" integration
|
|
412
|
+
*/
|
|
413
|
+
_initAlmostOverHandler: function(map, layer) {
|
|
414
|
+
if(!map || !this.options.almostOver) return;
|
|
415
|
+
this.lazyLoad(this.__LGEOMUTIL)
|
|
416
|
+
.then(() => this.lazyLoad(this.__LALMOSTOVER))
|
|
417
|
+
.then(() => {
|
|
418
|
+
map.addHandler('almostOver', L.Handler.AlmostOver)
|
|
419
|
+
if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
|
|
420
|
+
map.almostOver.addLayer(layer);
|
|
421
|
+
map
|
|
422
|
+
.on('almost:move', (e) => this._mousemoveLayerHandler(e))
|
|
423
|
+
.on('almost:out', (e) => this._mouseoutHandler(e));
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Initialize "L.DistanceMarkers" integration
|
|
430
|
+
*/
|
|
431
|
+
_initDistanceMarkers: function(map, layer) {
|
|
432
|
+
if (!map) return;
|
|
433
|
+
if (this.options.distanceMarkers) {
|
|
434
|
+
this.lazyLoad(this.__LGEOMUTIL)
|
|
435
|
+
.then(() => this.lazyLoad(this.__LDISTANCEM))
|
|
436
|
+
.then(() => this.options.polyline && layer.addTo(map));
|
|
437
|
+
} else if (this.options.polyline) {
|
|
438
|
+
layer.addTo(map);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
|
|
396
442
|
/**
|
|
397
443
|
* Adds the control to the given "detached" div.
|
|
398
444
|
*/
|
|
@@ -405,8 +451,25 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
405
451
|
if (this.options.detached) {
|
|
406
452
|
_.replaceClass(eleDiv, 'leaflet-control', 'elevation-detached');
|
|
407
453
|
}
|
|
408
|
-
this.eleDiv = eleDiv;
|
|
409
|
-
|
|
454
|
+
return this.eleDiv = eleDiv;
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Initialize "L.AlmostOver" and "L.DistanceMarkers"
|
|
459
|
+
*/
|
|
460
|
+
_initMapIntegrations: function(control, layer) {
|
|
461
|
+
let map = control._map;
|
|
462
|
+
if (control._map) {
|
|
463
|
+
map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
|
|
464
|
+
if (!L.Browser.mobile) {
|
|
465
|
+
control._initAlmostOverHandler(map, layer);
|
|
466
|
+
control._initDistanceMarkers(map, layer);
|
|
467
|
+
} else if (control.options.polyline) {
|
|
468
|
+
layer.addTo(map);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
console.warn("Undefined elevation map object");;
|
|
472
|
+
}
|
|
410
473
|
},
|
|
411
474
|
|
|
412
475
|
/*
|
|
@@ -423,6 +486,58 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
423
486
|
_.replaceClass(this._container, 'elevation-collapsed', 'elevation-expanded');
|
|
424
487
|
},
|
|
425
488
|
|
|
489
|
+
/**
|
|
490
|
+
* Add some basic colors to leaflet canvas renderer (preferCanvas: true).
|
|
491
|
+
*/
|
|
492
|
+
_fixCanvasPaths: function() {
|
|
493
|
+
let oldProto = L.Canvas.prototype._fillStroke;
|
|
494
|
+
let control = this;
|
|
495
|
+
L.Canvas.include({
|
|
496
|
+
_fillStroke: function(ctx, layer) {
|
|
497
|
+
if (control._layers.hasLayer(layer)) {
|
|
498
|
+
|
|
499
|
+
let theme = control.options.theme.replace('-theme', '');
|
|
500
|
+
let color = D3.Colors[theme] || {};
|
|
501
|
+
let options = layer.options;
|
|
502
|
+
|
|
503
|
+
options.color = color.line || color.area || theme;
|
|
504
|
+
options.stroke = !!options.color;
|
|
505
|
+
|
|
506
|
+
oldProto.call(this, ctx, layer);
|
|
507
|
+
|
|
508
|
+
if (options.stroke && options.weight !== 0) {
|
|
509
|
+
let oldVal = ctx.globalCompositeOperation || 'source-over';
|
|
510
|
+
ctx.globalCompositeOperation = 'destination-over'
|
|
511
|
+
ctx.strokeStyle = color.outline || '#FFF';
|
|
512
|
+
ctx.lineWidth = options.weight * 1.75;
|
|
513
|
+
ctx.stroke();
|
|
514
|
+
ctx.globalCompositeOperation = oldVal;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
} else {
|
|
518
|
+
oldProto.call(this, ctx, layer);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Partial fix for initial tooltip size
|
|
526
|
+
*
|
|
527
|
+
* @link https://github.com/Raruto/leaflet-elevation/issues/81#issuecomment-713477050
|
|
528
|
+
*/
|
|
529
|
+
_fixTooltipSize: function() {
|
|
530
|
+
this.on('elechart_init', () =>
|
|
531
|
+
this.once('elechart_change elechart_hover', e => {
|
|
532
|
+
if (this._chartEnabled) {
|
|
533
|
+
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
534
|
+
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
535
|
+
}
|
|
536
|
+
this._updateMarker(e.data);
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
},
|
|
540
|
+
|
|
426
541
|
/*
|
|
427
542
|
* Finds a data entry for the given LatLng
|
|
428
543
|
*/
|
|
@@ -522,7 +637,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
522
637
|
},
|
|
523
638
|
|
|
524
639
|
_initMarker: function(map) {
|
|
525
|
-
let pane
|
|
640
|
+
let pane = map.getPane('elevationPane');
|
|
526
641
|
if (!pane) {
|
|
527
642
|
pane = this._pane = map.createPane('elevationPane', map.getPane('norotatePane') || map.getPane('mapPane'));
|
|
528
643
|
pane.style.zIndex = 625; // This pane is above markers but below popups.
|
|
@@ -530,8 +645,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
530
645
|
}
|
|
531
646
|
|
|
532
647
|
if (this._renderer) this._renderer.remove()
|
|
533
|
-
this._renderer
|
|
534
|
-
this._marker
|
|
648
|
+
this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
|
|
649
|
+
this._marker = new Marker(this.options);
|
|
535
650
|
|
|
536
651
|
this._fireEvt("elechart_marker");
|
|
537
652
|
},
|
|
@@ -540,21 +655,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
540
655
|
* Inspired by L.Control.Layers
|
|
541
656
|
*/
|
|
542
657
|
_initButton: function(container) {
|
|
543
|
-
//Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
|
|
544
|
-
container.setAttribute('aria-haspopup', true);
|
|
545
658
|
|
|
546
|
-
if (!this.options.detached) {
|
|
659
|
+
if (L.Browser.mobile || !this.options.detached) {
|
|
547
660
|
L.DomEvent
|
|
548
661
|
.disableClickPropagation(container)
|
|
549
662
|
.disableScrollPropagation(container);
|
|
550
663
|
}
|
|
551
664
|
|
|
552
|
-
if (L.Browser.mobile) {
|
|
553
|
-
_.on(container, 'click', L.DomEvent.stopPropagation);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
_.on(container, 'mousewheel', this._mousewheelHandler, this);
|
|
557
|
-
|
|
558
665
|
if (!this.options.detached) {
|
|
559
666
|
let link = this._button = _.create('a', "elevation-toggle elevation-toggle-icon" + (this.options.autohide ? "" : " close-button"), { href: '#', title: L._('Elevation') }, container);
|
|
560
667
|
|
|
@@ -571,21 +678,100 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
571
678
|
_.on(link, 'focus', this._toggle, this);
|
|
572
679
|
|
|
573
680
|
this._map.on('click', this._collapse, this);
|
|
574
|
-
// TODO: keyboard accessibility
|
|
575
681
|
}
|
|
576
|
-
} else {
|
|
577
|
-
// TODO: handle autohide when detached=true
|
|
578
682
|
}
|
|
579
683
|
},
|
|
580
684
|
|
|
581
685
|
_initSummary: function(container) {
|
|
582
|
-
|
|
686
|
+
this._summary = new Summary({ summary: this.options.summary }, this);
|
|
583
687
|
|
|
584
|
-
d3
|
|
585
|
-
|
|
586
|
-
|
|
688
|
+
d3.select(container).call(this._summary.render());
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
_keydownHandler: function(e) {
|
|
692
|
+
if(!this.options.detached && e.key === "Escape"){
|
|
693
|
+
this._collapse()
|
|
694
|
+
};
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Retrieve data from a remote url (HTTP).
|
|
699
|
+
*/
|
|
700
|
+
_loadFile: function(url) {
|
|
701
|
+
fetch(url)
|
|
702
|
+
.then((response) => response.text())
|
|
703
|
+
.then((data) => {
|
|
704
|
+
this._downloadURL = url; // TODO: handle multiple urls?
|
|
705
|
+
this._parseFromString(data)
|
|
706
|
+
.then( geojson => geojson && this._loadLayer(geojson));
|
|
707
|
+
}).catch((err) => console.warn(err));
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Simple GeoJSON data loader (L.GeoJSON).
|
|
712
|
+
*/
|
|
713
|
+
_loadLayer: function(geojson) {
|
|
714
|
+
let control = this;
|
|
715
|
+
|
|
716
|
+
let { wptIcons, wptLabels } = control.options;
|
|
717
|
+
|
|
718
|
+
let layer = L.geoJson(geojson, {
|
|
719
|
+
distanceMarkers: control.options.distanceMarkers,
|
|
720
|
+
style: (feature) => {
|
|
721
|
+
let style = L.extend({}, control.options.polyline);
|
|
722
|
+
if (control.options.theme) {
|
|
723
|
+
style.className += ' ' + control.options.theme;
|
|
724
|
+
}
|
|
725
|
+
return style;
|
|
726
|
+
},
|
|
727
|
+
pointToLayer: (feature, latlng) => {
|
|
728
|
+
if (!control.options.waypoints) return;
|
|
729
|
+
|
|
730
|
+
let prop = feature.properties;
|
|
731
|
+
let desc = prop.desc ?? '';
|
|
732
|
+
let name = prop.name ?? '';
|
|
733
|
+
let sym = (prop.sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase();
|
|
734
|
+
|
|
735
|
+
// Handle chart waypoints (dots)
|
|
736
|
+
if ([true, 'dots'].includes(control.options.waypoints)) {
|
|
737
|
+
control._registerCheckPoint({
|
|
738
|
+
latlng: latlng,
|
|
739
|
+
label: ([true, 'dots'].includes(wptLabels) ? name : '')
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Handle map waypoints (markers)
|
|
744
|
+
if ([true, 'markers'].includes(control.options.waypoints) && wptIcons != false) {
|
|
745
|
+
// generate and cache appropriate icon symbol
|
|
746
|
+
if (!wptIcons.hasOwnProperty(sym)) {
|
|
747
|
+
wptIcons[sym] = L.divIcon(
|
|
748
|
+
L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } )
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
let marker = L.marker(latlng, { icon: wptIcons[sym] });
|
|
752
|
+
if ([true, 'markers'].includes(wptLabels) && (name || desc)) {
|
|
753
|
+
marker.bindPopup("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : '')).openPopup();
|
|
754
|
+
}
|
|
755
|
+
control._addMarker(marker);
|
|
756
|
+
control._bindPopup(marker);
|
|
757
|
+
control.fire('waypoint_added', { point: marker, element: latlng, properties: prop });
|
|
758
|
+
return marker;
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
onEachFeature: (feature, layer) => {
|
|
762
|
+
if (feature.geometry && feature.geometry.type != 'Point') {
|
|
763
|
+
control.addData(layer);
|
|
764
|
+
control.track_info = L.extend({}, control.track_info, { name: geojson.name });
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
});
|
|
587
768
|
|
|
588
|
-
|
|
769
|
+
control.lazyLoad(control.__D3).then(() => {
|
|
770
|
+
control._initMapIntegrations(control, layer);
|
|
771
|
+
control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info })
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
return layer;
|
|
589
775
|
},
|
|
590
776
|
|
|
591
777
|
_dragendHandler: function(e) {
|
|
@@ -657,19 +843,90 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
657
843
|
this._fireEvt("elechart_leave");
|
|
658
844
|
},
|
|
659
845
|
|
|
660
|
-
|
|
661
|
-
*
|
|
846
|
+
/**
|
|
847
|
+
* Simple GeoJSON Parser
|
|
662
848
|
*/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
849
|
+
_parseFromGeoJSONString: function(data) {
|
|
850
|
+
try {
|
|
851
|
+
return JSON.parse(data);
|
|
852
|
+
} catch (e) { }
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Attempt to parse raw response data (GeoJSON or XML > GeoJSON)
|
|
857
|
+
*/
|
|
858
|
+
_parseFromString: function(data) {
|
|
859
|
+
return new Promise(resolve =>
|
|
860
|
+
this.lazyLoad(this.__TOGEOJSON).then(() => {
|
|
861
|
+
let geojson;
|
|
862
|
+
try {
|
|
863
|
+
geojson = this._parseFromXMLString(data.trim());
|
|
864
|
+
} catch (e) {
|
|
865
|
+
geojson = this._parseFromGeoJSONString(data.toString());
|
|
866
|
+
}
|
|
867
|
+
if (geojson) {
|
|
868
|
+
geojson = this._prepareMultiLineStrings(geojson);
|
|
869
|
+
geojson = this._prepareAdditionalProperties(geojson);
|
|
870
|
+
}
|
|
871
|
+
resolve(geojson);
|
|
872
|
+
})
|
|
873
|
+
);
|
|
874
|
+
},
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Simple XML Parser (GPX, KML, TCX)
|
|
878
|
+
*/
|
|
879
|
+
_parseFromXMLString: function(data) {
|
|
880
|
+
if (data.indexOf("<") != 0) {
|
|
881
|
+
throw 'Invalid XML';
|
|
882
|
+
}
|
|
883
|
+
let xml = (new DOMParser()).parseFromString(data, "text/xml");
|
|
884
|
+
let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
|
|
885
|
+
let name = xml.getElementsByTagName('name');
|
|
886
|
+
if (xml.getElementsByTagName('parsererror').length) {
|
|
887
|
+
throw 'Invalid XML';
|
|
888
|
+
}
|
|
889
|
+
if (!(type in toGeoJSON)) {
|
|
890
|
+
type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
|
|
891
|
+
}
|
|
892
|
+
let geojson = toGeoJSON[type](xml);
|
|
893
|
+
geojson.name = name.length > 0 ? (Array.from(name).find(tag => tag.parentElement.tagName == "trk") ?? name[0]).textContent : '';
|
|
894
|
+
return geojson;
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Extend GeoJSON properties (name, time, ...)
|
|
899
|
+
*/
|
|
900
|
+
_prepareAdditionalProperties: function(geojson) {
|
|
901
|
+
if (!geojson.name && this._downloadURL) {
|
|
902
|
+
geojson.name = this._downloadURL.split('/').pop().split('#')[0].split('?')[0];
|
|
903
|
+
}
|
|
904
|
+
return geojson;
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Just a temporary fix for MultiLineString data (trk > trkseg + trkseg),
|
|
909
|
+
* just split them into seperate LineStrings (trk > trkseg, trk > trkseg)
|
|
910
|
+
*
|
|
911
|
+
* @link https://github.com/Raruto/leaflet-elevation/issues/56
|
|
912
|
+
*/
|
|
913
|
+
_prepareMultiLineStrings: function(geojson) {
|
|
914
|
+
geojson.features.forEach(feauture => {
|
|
915
|
+
if (feauture.geometry.type == "MultiLineString") {
|
|
916
|
+
feauture.geometry.coordinates.forEach(coords => {
|
|
917
|
+
geojson.features.push({
|
|
918
|
+
geometry: { type: 'LineString', coordinates: coords },
|
|
919
|
+
properties: feauture.properties,
|
|
920
|
+
type: 'Feature'
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
return geojson.features.filter(feauture => feauture.geometry.type != "MultiLineString");
|
|
669
926
|
},
|
|
670
927
|
|
|
671
928
|
/**
|
|
672
|
-
* Add a
|
|
929
|
+
* Add a point of interest over the diagram
|
|
673
930
|
*/
|
|
674
931
|
_registerCheckPoint: function(props) {
|
|
675
932
|
this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
|
|
@@ -699,7 +956,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
699
956
|
/**
|
|
700
957
|
* Add chart or marker tooltip info
|
|
701
958
|
*/
|
|
702
|
-
|
|
959
|
+
_registerTooltip: function(props) {
|
|
703
960
|
if (props.chart) {
|
|
704
961
|
let label = L.extend({}, props, { value: props.chart });
|
|
705
962
|
this.on("elechart_init", () => this._chart._registerTooltip(label));
|
|
@@ -887,9 +1144,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
887
1144
|
this._fireEvt("elechart_summary");
|
|
888
1145
|
}
|
|
889
1146
|
if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
|
|
890
|
-
this.
|
|
891
|
-
_.select('.download a', this.
|
|
1147
|
+
this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>'
|
|
1148
|
+
_.select('.download a', this._summary._container).onclick = (e) => {
|
|
892
1149
|
e.preventDefault();
|
|
1150
|
+
if (e.downloadLink == 'modal' && typeof CustomEvent === "function") {
|
|
1151
|
+
document.dispatchEvent(new CustomEvent("eletrack_download", { detail: e }));
|
|
1152
|
+
} else if (e.downloadLink == 'link' || e.downloadLink === true) {
|
|
1153
|
+
e.confirm();
|
|
1154
|
+
}
|
|
893
1155
|
this._fireEvt('eletrack_download', { downloadLink: this.options.downloadLink, confirm: _.saveFile.bind(this, this._downloadURL) });
|
|
894
1156
|
};
|
|
895
1157
|
};
|
|
@@ -921,125 +1183,4 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
921
1183
|
return this.__yTicks || Math.round(this._height() / 30);
|
|
922
1184
|
}
|
|
923
1185
|
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Attach here some useful elevation hooks.
|
|
928
|
-
*/
|
|
929
|
-
Elevation.addInitHook(function() {
|
|
930
|
-
|
|
931
|
-
// Alias deprecated functions
|
|
932
|
-
this.enableDragging = this.enableBrush;
|
|
933
|
-
this.disableDragging = this.disableBrush;
|
|
934
|
-
this.loadChart = this.addTo;
|
|
935
|
-
this.loadData = this.load;
|
|
936
|
-
this.loadGPX = this.loadXML;
|
|
937
|
-
|
|
938
|
-
this.on('waypoint_added', function(e) {
|
|
939
|
-
let p = e.point,
|
|
940
|
-
pop = p._popup;
|
|
941
|
-
if (pop) {
|
|
942
|
-
pop.options.className = 'elevation-popup';
|
|
943
|
-
}
|
|
944
|
-
if (pop && pop._content) {
|
|
945
|
-
pop._content = decodeURI(pop._content);
|
|
946
|
-
p.bindTooltip(pop._content, { direction: 'auto', sticky: true, opacity: 1, className: 'elevation-tooltip' }).openTooltip();
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
this.on("eletrack_download", function(e) {
|
|
951
|
-
if (e.downloadLink == 'modal' && typeof CustomEvent === "function") {
|
|
952
|
-
document.dispatchEvent(new CustomEvent("eletrack_download", { detail: e }));
|
|
953
|
-
} else if (e.downloadLink == 'link' || e.downloadLink === true) {
|
|
954
|
-
e.confirm();
|
|
955
|
-
}
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
this.on('eledata_loaded', function(e) {
|
|
959
|
-
let map = this._map;
|
|
960
|
-
let layer = e.layer;
|
|
961
|
-
if (!map) {
|
|
962
|
-
console.warn("Undefined elevation map object");
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
map.once('layeradd', (e) => this.options.autofitBounds && this.fitBounds(layer.getBounds()));
|
|
966
|
-
|
|
967
|
-
if (L.Browser.mobile) {
|
|
968
|
-
|
|
969
|
-
if (this.options.polyline) layer.addTo(map);
|
|
970
|
-
|
|
971
|
-
} else {
|
|
972
|
-
|
|
973
|
-
// leaflet-geometryutil
|
|
974
|
-
this.lazyLoad(this.__LGEOMUTIL)
|
|
975
|
-
.then(() => {
|
|
976
|
-
|
|
977
|
-
// leaflet-almostover
|
|
978
|
-
if (this.options.almostOver) {
|
|
979
|
-
this.lazyLoad(this.__LALMOSTOVER)
|
|
980
|
-
.then(() => {
|
|
981
|
-
map.addHandler('almostOver', L.Handler.AlmostOver)
|
|
982
|
-
if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
|
|
983
|
-
map.almostOver.addLayer(layer);
|
|
984
|
-
map
|
|
985
|
-
.on('almost:move', (e) => this._mousemoveLayerHandler(e))
|
|
986
|
-
.on('almost:out', (e) => this._mouseoutHandler(e));
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// leaflet-distance-markers
|
|
992
|
-
if (this.options.distanceMarkers) {
|
|
993
|
-
this.lazyLoad(this.__LDISTANCEM)
|
|
994
|
-
.then(() => this.options.polyline && layer.addTo(map));
|
|
995
|
-
} else {
|
|
996
|
-
if (this.options.polyline) layer.addTo(map);
|
|
997
|
-
}
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
// Basic canvas renderer support.
|
|
1004
|
-
let oldProto = L.Canvas.prototype._fillStroke;
|
|
1005
|
-
let control = this;
|
|
1006
|
-
L.Canvas.include({
|
|
1007
|
-
_fillStroke: function(ctx, layer) {
|
|
1008
|
-
if (control._layers.hasLayer(layer)) {
|
|
1009
|
-
|
|
1010
|
-
let theme = control.options.theme.replace('-theme', '');
|
|
1011
|
-
let color = D3.Colors[theme] || {};
|
|
1012
|
-
let options = layer.options;
|
|
1013
|
-
|
|
1014
|
-
options.color = color.line || color.area || theme;
|
|
1015
|
-
options.stroke = !!options.color;
|
|
1016
|
-
|
|
1017
|
-
oldProto.call(this, ctx, layer);
|
|
1018
|
-
|
|
1019
|
-
if (options.stroke && options.weight !== 0) {
|
|
1020
|
-
let oldVal = ctx.globalCompositeOperation || 'source-over';
|
|
1021
|
-
ctx.globalCompositeOperation = 'destination-over'
|
|
1022
|
-
ctx.strokeStyle = color.outline || '#FFF';
|
|
1023
|
-
ctx.lineWidth = options.weight * 1.75;
|
|
1024
|
-
ctx.stroke();
|
|
1025
|
-
ctx.globalCompositeOperation = oldVal;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
} else {
|
|
1029
|
-
oldProto.call(this, ctx, layer);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
// Partially fix: https://github.com/Raruto/leaflet-elevation/issues/81#issuecomment-713477050
|
|
1035
|
-
this.on('elechart_init', function() {
|
|
1036
|
-
this.once('elechart_change elechart_hover', function(e) {
|
|
1037
|
-
if (this._chartEnabled) {
|
|
1038
|
-
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
1039
|
-
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
1040
|
-
}
|
|
1041
|
-
this._updateMarker(e.data);
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
});
|
|
1186
|
+
});
|