@raruto/leaflet-elevation 1.8.2 → 1.8.5
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/dist/leaflet-elevation.css +16 -5
- package/dist/leaflet-elevation.js +494 -529
- 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-distance-marker.min.js +1 -6
- package/libs/leaflet-gpxgroup.js +69 -186
- package/libs/leaflet-gpxgroup.min.js +1 -17
- package/package.json +1 -1
- package/src/chart.js +4 -1
- package/src/control.js +352 -219
- package/src/index.css +16 -5
- package/src/index.js +12 -0
- package/src/utils.js +0 -201
- package/images/remove.svg +0 -1
package/src/control.js
CHANGED
|
@@ -17,7 +17,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
17
17
|
__TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.3.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.5/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)
|
|
@@ -387,12 +371,53 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
387
371
|
|
|
388
372
|
_addLayer: function(layer) {
|
|
389
373
|
if (layer) this._layers.addLayer(layer)
|
|
374
|
+
// Postpone adding the distance markers (lazy: true)
|
|
375
|
+
if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
|
|
376
|
+
layer.on('add remove', (e) => L.DistanceMarkers && e.target instanceof L.Polyline && e.target[e.type + 'DistanceMarkers']());
|
|
377
|
+
}
|
|
390
378
|
},
|
|
391
379
|
|
|
392
380
|
_addMarker: function(marker) {
|
|
393
381
|
if (marker) this._markers.addLayer(marker)
|
|
394
382
|
},
|
|
395
383
|
|
|
384
|
+
_bindPopup: function(marker) {
|
|
385
|
+
let popup = marker._popup;
|
|
386
|
+
if (popup) {
|
|
387
|
+
popup.options.className = 'elevation-popup';
|
|
388
|
+
if (popup._content) {
|
|
389
|
+
popup._content = decodeURI(popup._content);
|
|
390
|
+
popup.bindTooltip(popup._content, { direction: 'auto', sticky: true, opacity: 1, className: 'elevation-tooltip' }).openTooltip();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
_initAlmostOverHandler: function(map, layer) {
|
|
396
|
+
if(!map || !this.options.almostOver) return;
|
|
397
|
+
this.lazyLoad(this.__LGEOMUTIL)
|
|
398
|
+
.then(() => this.lazyLoad(this.__LALMOSTOVER))
|
|
399
|
+
.then(() => {
|
|
400
|
+
map.addHandler('almostOver', L.Handler.AlmostOver)
|
|
401
|
+
if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
|
|
402
|
+
map.almostOver.addLayer(layer);
|
|
403
|
+
map
|
|
404
|
+
.on('almost:move', (e) => this._mousemoveLayerHandler(e))
|
|
405
|
+
.on('almost:out', (e) => this._mouseoutHandler(e));
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
_initDistanceMarkers: function(map, layer) {
|
|
411
|
+
if (!map) return;
|
|
412
|
+
if (this.options.distanceMarkers) {
|
|
413
|
+
this.lazyLoad(this.__LGEOMUTIL)
|
|
414
|
+
.then(() => this.lazyLoad(this.__LDISTANCEM))
|
|
415
|
+
.then(() => this.options.polyline && layer.addTo(map));
|
|
416
|
+
} else if (this.options.polyline) {
|
|
417
|
+
layer.addTo(map);
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
|
|
396
421
|
/**
|
|
397
422
|
* Adds the control to the given "detached" div.
|
|
398
423
|
*/
|
|
@@ -405,8 +430,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
405
430
|
if (this.options.detached) {
|
|
406
431
|
_.replaceClass(eleDiv, 'leaflet-control', 'elevation-detached');
|
|
407
432
|
}
|
|
408
|
-
this.eleDiv = eleDiv;
|
|
409
|
-
return this.eleDiv;
|
|
433
|
+
return this.eleDiv = eleDiv;
|
|
410
434
|
},
|
|
411
435
|
|
|
412
436
|
/*
|
|
@@ -423,6 +447,58 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
423
447
|
_.replaceClass(this._container, 'elevation-collapsed', 'elevation-expanded');
|
|
424
448
|
},
|
|
425
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Add some basic colors to leaflet canvas renderer (preferCanvas: true).
|
|
452
|
+
*/
|
|
453
|
+
_fixCanvasPaths: function() {
|
|
454
|
+
let oldProto = L.Canvas.prototype._fillStroke;
|
|
455
|
+
let control = this;
|
|
456
|
+
L.Canvas.include({
|
|
457
|
+
_fillStroke: function(ctx, layer) {
|
|
458
|
+
if (control._layers.hasLayer(layer)) {
|
|
459
|
+
|
|
460
|
+
let theme = control.options.theme.replace('-theme', '');
|
|
461
|
+
let color = D3.Colors[theme] || {};
|
|
462
|
+
let options = layer.options;
|
|
463
|
+
|
|
464
|
+
options.color = color.line || color.area || theme;
|
|
465
|
+
options.stroke = !!options.color;
|
|
466
|
+
|
|
467
|
+
oldProto.call(this, ctx, layer);
|
|
468
|
+
|
|
469
|
+
if (options.stroke && options.weight !== 0) {
|
|
470
|
+
let oldVal = ctx.globalCompositeOperation || 'source-over';
|
|
471
|
+
ctx.globalCompositeOperation = 'destination-over'
|
|
472
|
+
ctx.strokeStyle = color.outline || '#FFF';
|
|
473
|
+
ctx.lineWidth = options.weight * 1.75;
|
|
474
|
+
ctx.stroke();
|
|
475
|
+
ctx.globalCompositeOperation = oldVal;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
} else {
|
|
479
|
+
oldProto.call(this, ctx, layer);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Partial fix for initial tooltip size
|
|
487
|
+
*
|
|
488
|
+
* @link https://github.com/Raruto/leaflet-elevation/issues/81#issuecomment-713477050
|
|
489
|
+
*/
|
|
490
|
+
_fixTooltipSize: function() {
|
|
491
|
+
this.on('elechart_init', () =>
|
|
492
|
+
this.once('elechart_change elechart_hover', e => {
|
|
493
|
+
if (this._chartEnabled) {
|
|
494
|
+
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
495
|
+
this._chart._showDiagramIndicator(e.data, e.xCoord);
|
|
496
|
+
}
|
|
497
|
+
this._updateMarker(e.data);
|
|
498
|
+
})
|
|
499
|
+
);
|
|
500
|
+
},
|
|
501
|
+
|
|
426
502
|
/*
|
|
427
503
|
* Finds a data entry for the given LatLng
|
|
428
504
|
*/
|
|
@@ -522,7 +598,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
522
598
|
},
|
|
523
599
|
|
|
524
600
|
_initMarker: function(map) {
|
|
525
|
-
let pane
|
|
601
|
+
let pane = map.getPane('elevationPane');
|
|
526
602
|
if (!pane) {
|
|
527
603
|
pane = this._pane = map.createPane('elevationPane', map.getPane('norotatePane') || map.getPane('mapPane'));
|
|
528
604
|
pane.style.zIndex = 625; // This pane is above markers but below popups.
|
|
@@ -530,8 +606,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
530
606
|
}
|
|
531
607
|
|
|
532
608
|
if (this._renderer) this._renderer.remove()
|
|
533
|
-
this._renderer
|
|
534
|
-
this._marker
|
|
609
|
+
this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
|
|
610
|
+
this._marker = new Marker(this.options);
|
|
535
611
|
|
|
536
612
|
this._fireEvt("elechart_marker");
|
|
537
613
|
},
|
|
@@ -540,21 +616,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
540
616
|
* Inspired by L.Control.Layers
|
|
541
617
|
*/
|
|
542
618
|
_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
619
|
|
|
546
|
-
if (!this.options.detached) {
|
|
620
|
+
if (L.Browser.mobile || !this.options.detached) {
|
|
547
621
|
L.DomEvent
|
|
548
622
|
.disableClickPropagation(container)
|
|
549
623
|
.disableScrollPropagation(container);
|
|
550
624
|
}
|
|
551
625
|
|
|
552
|
-
if (L.Browser.mobile) {
|
|
553
|
-
_.on(container, 'click', L.DomEvent.stopPropagation);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
_.on(container, 'mousewheel', this._mousewheelHandler, this);
|
|
557
|
-
|
|
558
626
|
if (!this.options.detached) {
|
|
559
627
|
let link = this._button = _.create('a', "elevation-toggle elevation-toggle-icon" + (this.options.autohide ? "" : " close-button"), { href: '#', title: L._('Elevation') }, container);
|
|
560
628
|
|
|
@@ -571,21 +639,131 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
571
639
|
_.on(link, 'focus', this._toggle, this);
|
|
572
640
|
|
|
573
641
|
this._map.on('click', this._collapse, this);
|
|
574
|
-
// TODO: keyboard accessibility
|
|
575
642
|
}
|
|
576
|
-
} else {
|
|
577
|
-
// TODO: handle autohide when detached=true
|
|
578
643
|
}
|
|
579
644
|
},
|
|
580
645
|
|
|
581
646
|
_initSummary: function(container) {
|
|
582
|
-
|
|
647
|
+
this._summary = new Summary({ summary: this.options.summary }, this);
|
|
583
648
|
|
|
584
|
-
d3
|
|
585
|
-
|
|
586
|
-
.call(summary.render());
|
|
649
|
+
d3.select(container).call(this._summary.render());
|
|
650
|
+
},
|
|
587
651
|
|
|
588
|
-
|
|
652
|
+
_keydownHandler: function(e) {
|
|
653
|
+
if(!this.options.detached && e.key === "Escape"){
|
|
654
|
+
this._collapse()
|
|
655
|
+
};
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Retrieve data from a remote url (HTTP).
|
|
660
|
+
*/
|
|
661
|
+
_loadFile: function(url) {
|
|
662
|
+
fetch(url)
|
|
663
|
+
.then((response) => response.text())
|
|
664
|
+
.then((data) => {
|
|
665
|
+
this._downloadURL = url; // TODO: handle multiple urls?
|
|
666
|
+
this.load(data);
|
|
667
|
+
})
|
|
668
|
+
.catch((err) => console.warn(err));
|
|
669
|
+
},
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Simple GeoJSON data loader (L.GeoJSON).
|
|
673
|
+
*/
|
|
674
|
+
_loadLayer: function(geojson) {
|
|
675
|
+
let control = this;
|
|
676
|
+
|
|
677
|
+
let { wptIcons, wptLabels } = control.options;
|
|
678
|
+
|
|
679
|
+
let layer = L.geoJson(geojson, {
|
|
680
|
+
distanceMarkers: control.options.distanceMarkers,
|
|
681
|
+
style: (feature) => {
|
|
682
|
+
let style = L.extend({}, control.options.polyline);
|
|
683
|
+
if (control.options.theme) {
|
|
684
|
+
style.className += ' ' + control.options.theme;
|
|
685
|
+
}
|
|
686
|
+
return style;
|
|
687
|
+
},
|
|
688
|
+
pointToLayer: (feature, latlng) => {
|
|
689
|
+
if (!control.options.waypoints) return;
|
|
690
|
+
|
|
691
|
+
let prop = feature.properties;
|
|
692
|
+
let desc = prop.desc ?? '';
|
|
693
|
+
let name = prop.name ?? '';
|
|
694
|
+
let sym = (prop.sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase();
|
|
695
|
+
|
|
696
|
+
// Handle chart waypoints (dots)
|
|
697
|
+
if ([true, 'dots'].includes(control.options.waypoints)) {
|
|
698
|
+
control._registerCheckPoint({
|
|
699
|
+
latlng: latlng,
|
|
700
|
+
label: ([true, 'dots'].includes(wptLabels) ? name : '')
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Handle map waypoints (markers)
|
|
705
|
+
if ([true, 'markers'].includes(control.options.waypoints) && wptIcons != false) {
|
|
706
|
+
// generate and cache appropriate icon symbol
|
|
707
|
+
if (!wptIcons.hasOwnProperty(sym)) {
|
|
708
|
+
wptIcons[sym] = L.divIcon(
|
|
709
|
+
L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } )
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
let marker = L.marker(latlng, { icon: wptIcons[sym] });
|
|
713
|
+
if ([true, 'markers'].includes(wptLabels) && (name || desc)) {
|
|
714
|
+
marker.bindPopup("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : '')).openPopup();
|
|
715
|
+
}
|
|
716
|
+
control._addMarker(marker);
|
|
717
|
+
control._bindPopup(marker);
|
|
718
|
+
control.fire('waypoint_added', { point: marker, element: latlng, properties: prop });
|
|
719
|
+
return marker;
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
onEachFeature: (feature, layer) => {
|
|
723
|
+
if (feature.geometry && feature.geometry.type == 'Point') return;
|
|
724
|
+
|
|
725
|
+
// Standard GeoJSON --> doesn't handle "time"
|
|
726
|
+
// control.addData(feature, layer); // NB make use of "_addGeoJSONData"
|
|
727
|
+
|
|
728
|
+
// Extended GeoJSON --> rely on leaflet implementation
|
|
729
|
+
layer._latlngs.forEach((point, i) => {
|
|
730
|
+
// same properties as L.GPX layer
|
|
731
|
+
point.meta = { time: null, ele: null, hr: null, cad: null, atemp: null };
|
|
732
|
+
if("alt" in point) point.meta.ele = point.alt;
|
|
733
|
+
if(feature.properties) {
|
|
734
|
+
let prop = feature.properties;
|
|
735
|
+
if("coordTimes" in prop) point.meta.time = new Date(Date.parse(prop.coordTimes[i]));
|
|
736
|
+
else if("times" in prop) point.meta.time = new Date(Date.parse(prop.times[i]));
|
|
737
|
+
else if("time" in prop) point.meta.time = new Date(Date.parse((typeof prop.time === 'object' ? prop.time[i] : prop.time)));
|
|
738
|
+
if("heartRates" in prop) point.meta.hr = parseInt(prop.heartRates[i]);
|
|
739
|
+
else if("heartRate" in prop) point.meta.hr = parseInt((typeof prop.heartRate === 'object' ? prop.heartRate[i] : prop.heartRate));
|
|
740
|
+
// TODO: cadence, temperature
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
control.addData(layer); // NB make use of "_addGPXData"
|
|
744
|
+
|
|
745
|
+
control.track_info = L.extend({}, control.track_info, { name: geojson.name });
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
control.lazyLoad(control.__D3).then(() => {
|
|
750
|
+
let map = control._map;
|
|
751
|
+
if (map) {
|
|
752
|
+
map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
|
|
753
|
+
if (!L.Browser.mobile) {
|
|
754
|
+
control._initAlmostOverHandler(map, layer);
|
|
755
|
+
control._initDistanceMarkers(map, layer);
|
|
756
|
+
} else if (control.options.polyline) {
|
|
757
|
+
layer.addTo(map);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
console.warn("Undefined elevation map object");;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info })
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
return layer;
|
|
589
767
|
},
|
|
590
768
|
|
|
591
769
|
_dragendHandler: function(e) {
|
|
@@ -657,19 +835,90 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
657
835
|
this._fireEvt("elechart_leave");
|
|
658
836
|
},
|
|
659
837
|
|
|
660
|
-
|
|
661
|
-
*
|
|
838
|
+
/**
|
|
839
|
+
* Simple GeoJSON Parser
|
|
662
840
|
*/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
841
|
+
_parseFromGeoJSONString: function(data) {
|
|
842
|
+
try {
|
|
843
|
+
return JSON.parse(data);
|
|
844
|
+
} catch (e) { }
|
|
845
|
+
},
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Attempt to parse raw response data (GeoJSON or XML > GeoJSON)
|
|
849
|
+
*/
|
|
850
|
+
_parseFromString: function(data) {
|
|
851
|
+
return new Promise(resolve =>
|
|
852
|
+
this.lazyLoad(this.__TOGEOJSON).then(() => {
|
|
853
|
+
let geojson;
|
|
854
|
+
try {
|
|
855
|
+
geojson = this._parseFromXMLString(data.trim());
|
|
856
|
+
} catch (e) {
|
|
857
|
+
geojson = this._parseFromGeoJSONString(data.toString());
|
|
858
|
+
}
|
|
859
|
+
if (geojson) {
|
|
860
|
+
geojson = this._prepareMultiLineStrings(geojson);
|
|
861
|
+
geojson = this._prepareAdditionalProperties(geojson);
|
|
862
|
+
}
|
|
863
|
+
resolve(geojson);
|
|
864
|
+
})
|
|
865
|
+
);
|
|
866
|
+
},
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Simple XML Parser (GPX, KML, TCX)
|
|
870
|
+
*/
|
|
871
|
+
_parseFromXMLString: function(data) {
|
|
872
|
+
if (data.indexOf("<") != 0) {
|
|
873
|
+
throw 'Invalid XML';
|
|
874
|
+
}
|
|
875
|
+
let xml = (new DOMParser()).parseFromString(data, "text/xml");
|
|
876
|
+
let type = xml.documentElement.tagName.toLowerCase(); // "kml" or "gpx"
|
|
877
|
+
let name = xml.getElementsByTagName('name');
|
|
878
|
+
if (xml.getElementsByTagName('parsererror').length) {
|
|
879
|
+
throw 'Invalid XML';
|
|
880
|
+
}
|
|
881
|
+
if (!(type in toGeoJSON)) {
|
|
882
|
+
type = xml.documentElement.tagName == "TrainingCenterDatabase" ? 'tcx' : 'gpx';
|
|
883
|
+
}
|
|
884
|
+
let geojson = toGeoJSON[type](xml);
|
|
885
|
+
geojson.name = name.length > 0 ? Array.from(name).find(tag => tag.parentElement.tagName == "trk").textContent : '';
|
|
886
|
+
return geojson;
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Extend GeoJSON properties (name, time, ...)
|
|
891
|
+
*/
|
|
892
|
+
_prepareAdditionalProperties: function(geojson){
|
|
893
|
+
if (!geojson.name && this._downloadURL) {
|
|
894
|
+
geojson.name = this._downloadURL.split('/').pop().split('#')[0].split('?')[0];
|
|
895
|
+
}
|
|
896
|
+
return geojson;
|
|
669
897
|
},
|
|
670
898
|
|
|
671
899
|
/**
|
|
672
|
-
*
|
|
900
|
+
* Just a temporary fix for MultiLineString data (trk > trkseg + trkseg),
|
|
901
|
+
* just split them into seperate LineStrings (trk > trkseg, trk > trkseg)
|
|
902
|
+
*
|
|
903
|
+
* @link https://github.com/Raruto/leaflet-elevation/issues/56
|
|
904
|
+
*/
|
|
905
|
+
_prepareMultiLineStrings: function(geojson) {
|
|
906
|
+
geojson.features.forEach(feauture => {
|
|
907
|
+
if (feauture.geometry.type == "MultiLineString") {
|
|
908
|
+
feauture.geometry.coordinates.forEach(coords => {
|
|
909
|
+
geojson.features.push({
|
|
910
|
+
geometry: { type: 'LineString', coordinates: coords },
|
|
911
|
+
properties: feauture.properties,
|
|
912
|
+
type: 'Feature'
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
return geojson.features.filter(feauture => feauture.geometry.type != "MultiLineString");
|
|
918
|
+
},
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Add a point of interest over the diagram
|
|
673
922
|
*/
|
|
674
923
|
_registerCheckPoint: function(props) {
|
|
675
924
|
this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
|
|
@@ -887,9 +1136,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
887
1136
|
this._fireEvt("elechart_summary");
|
|
888
1137
|
}
|
|
889
1138
|
if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
|
|
890
|
-
this.
|
|
891
|
-
_.select('.download a', this.
|
|
1139
|
+
this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>'
|
|
1140
|
+
_.select('.download a', this._summary._container).onclick = (e) => {
|
|
892
1141
|
e.preventDefault();
|
|
1142
|
+
if (e.downloadLink == 'modal' && typeof CustomEvent === "function") {
|
|
1143
|
+
document.dispatchEvent(new CustomEvent("eletrack_download", { detail: e }));
|
|
1144
|
+
} else if (e.downloadLink == 'link' || e.downloadLink === true) {
|
|
1145
|
+
e.confirm();
|
|
1146
|
+
}
|
|
893
1147
|
this._fireEvt('eletrack_download', { downloadLink: this.options.downloadLink, confirm: _.saveFile.bind(this, this._downloadURL) });
|
|
894
1148
|
};
|
|
895
1149
|
};
|
|
@@ -921,125 +1175,4 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|
|
921
1175
|
return this.__yTicks || Math.round(this._height() / 30);
|
|
922
1176
|
}
|
|
923
1177
|
|
|
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
|
-
});
|
|
1178
|
+
});
|