@raruto/leaflet-elevation 2.0.7 → 2.1.1

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/src/control.js CHANGED
@@ -1,7 +1,11 @@
1
- import 'leaflet-i18n';
2
1
  import * as _ from './utils';
3
2
  import { Options } from './options';
4
3
 
4
+ // "leaflet-i18n" fallback
5
+ if (!L._ || !L.i18n) {
6
+ L._ = L.i18n = (string, data) => string;
7
+ }
8
+
5
9
  export const Elevation = L.Control.Elevation = L.Control.extend({
6
10
 
7
11
  includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
@@ -55,7 +59,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
55
59
  if (this._chart) this._clearChart();
56
60
  if (this._layers) this._clearLayers(this._layers);
57
61
  if (this._markers) this._clearLayers(this._markers);
58
- if (this._circleMarkers) this._clearLayers(this._circleMarkers);
62
+ if (this._circleMarkers) this._circleMarkers.remove();
63
+ if (this._hotline) this._clearLayers(this._hotline);
59
64
 
60
65
  this._data = [];
61
66
  this.track_info = {};
@@ -165,6 +170,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
165
170
  this._data = [];
166
171
  this._layers = L.featureGroup();
167
172
  this._markers = L.featureGroup();
173
+ this._hotline = L.featureGroup();
168
174
  this._circleMarkers = L.featureGroup();
169
175
  this._markedSegments = L.polyline([]);
170
176
  this._start = (opts.trkStart || Options.trkStart);
@@ -236,12 +242,15 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
236
242
  ...this.options.handlers
237
243
  ].filter(m => false !== this.options[typeof m == "string" && m.toLowerCase()]);
238
244
 
239
- this._loadModules(modules); // Inject here required modules (data handlers)
240
- this._initButton(container);
241
- this._initChart(container);
242
- this._initSummary(container);
243
- this._initMarker(map);
244
- this._initLayer(map);
245
+ this._loadModules(modules).then(() => { // Inject here required modules (data handlers)
246
+ this._initChart(container);
247
+ this._initButton(container);
248
+ this._initSummary(container);
249
+ this._initMarker(map);
250
+ this._initLayer(map);
251
+ });
252
+
253
+ this.fire('add');
245
254
 
246
255
  return container;
247
256
  },
@@ -266,6 +275,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
266
275
  this
267
276
  .off('eledata_added eledata_loaded', this._updateChart, this)
268
277
  .off('eledata_added eledata_loaded', this._updateSummary, this);
278
+
279
+ this.fire('remove');
269
280
  },
270
281
 
271
282
  /**
@@ -386,8 +397,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
386
397
  * Initialize "L.AlmostOver" integration
387
398
  */
388
399
  _initAlmostOverHandler: function(map, layer) {
389
- if(!map || !this.options.almostOver) return;
390
- Promise.all([
400
+ return (map && this.options.almostOver && !L.Browser.mobile) ? Promise.all([
391
401
  this.import(this.__LGEOMUTIL),
392
402
  this.import(this.__LALMOSTOVER)
393
403
  ]).then(() => {
@@ -398,81 +408,72 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
398
408
  .on('almost:move', (e) => this._onMouseMoveLayer(e))
399
409
  .on('almost:out', (e) => this._onMouseOut(e));
400
410
  }
401
- });
411
+ }) : Promise.resolve();
402
412
  },
403
413
 
404
414
  /**
405
415
  * Initialize "L.DistanceMarkers" integration
406
416
  */
407
- _initDistanceMarkers: function(map, layer) {
408
- if (!map) return;
409
- if (this.options.distanceMarkers) {
410
- Promise.all([
411
- this.import(this.__LGEOMUTIL),
412
- this.import(this.__LDISTANCEM)
413
- ]).then(() => this.options.polyline && layer.addTo(map) && this._circleMarkers.bringToFront());
414
- } else if (this.options.polyline) {
415
- layer.addTo(map);
416
- }
417
+ _initDistanceMarkers: function() {
418
+ return this.options.distanceMarkers ? Promise.all([this.import(this.__LGEOMUTIL), this.import(this.__LDISTANCEM)]) : Promise.resolve();
417
419
  },
418
420
 
419
- _initHotLine: function(map, layer) {
420
- this._hotline = L.featureGroup();
421
- if (this.options.hotline) {
422
- layer.on('add', () => {
423
- this._hotline.eachLayer(line => {
424
- this._hotline.addTo(layer._map);
425
- if (line._renderer)
426
- line._renderer._container.parentElement.insertBefore(line._renderer._container, line._renderer._container.parentElement.firstChild);
421
+ _initHotLine: function(layer) {
422
+ let prop = typeof this.options.hotline == 'string' ? this.options.hotline : 'elevation';
423
+ return this.options.hotline ? this.import(this.__LHOTLINE)
424
+ .then(() => {
425
+ layer.eachLayer((trkseg) => {
426
+ if(trkseg.feature.geometry.type != "Point") {
427
+ let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], coords[2])});
428
+ let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
429
+ min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
430
+ max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
431
+ palette: {
432
+ 0.0: '#008800',
433
+ 0.5: '#ffff00',
434
+ 1.0: '#ff0000'
435
+ },
436
+ weight: 5,
437
+ outlineColor: '#000000',
438
+ outlineWidth: 1
439
+ }).addTo(this._hotline);
440
+ let alpha = trkseg.options.style && trkseg.options.style.opacity || 1;
441
+ trkseg.on('add remove', ({type}) => {
442
+ trkseg.setStyle({opacity: (type == 'add' ? 0 : alpha)});
443
+ line[(type == 'add' ? 'addTo' : 'removeFrom')](trkseg._map);
444
+ if (line._renderer) line._renderer._container.parentElement.insertBefore(line._renderer._container, line._renderer._container.parentElement.firstChild);
445
+ });
446
+ }
427
447
  });
428
- });
429
- let prop = typeof this.options.hotline == 'string' ? this.options.hotline : 'elevation';
430
- this.import(this.__LHOTLINE)
431
- .then(() => {
432
- layer.setStyle({opacity: 0});
433
- layer.eachLayer((trkseg) => {
434
- if(trkseg.feature.geometry.type != "Point") {
435
- let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], coords[2])});
436
- let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
437
- min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
438
- max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
439
- palette: {
440
- 0.0: '#008800',
441
- 0.5: '#ffff00',
442
- 1.0: '#ff0000'
443
- },
444
- weight: 5,
445
- outlineColor: '#000000',
446
- outlineWidth: 1
447
- }).addTo(this._hotline);
448
- }
449
- });
450
- });
451
- }
448
+ }) : Promise.resolve();
452
449
  },
453
450
 
454
451
  /**
455
452
  * Initialize "L.AlmostOver" and "L.DistanceMarkers"
456
453
  */
457
- _initMapIntegrations: function(control, layer) {
458
- let map = control._map;
459
- if (control._data.length) {
460
- control._start.setLatLng(control._data[0].latlng);
461
- control._end.setLatLng(control._data[control._data.length -1].latlng);
462
- }
454
+ _initMapIntegrations: function(layer) {
455
+ let map = this._map;
463
456
  if (map) {
464
- control._circleMarkers.addTo(map);
465
- map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
466
- if (!L.Browser.mobile) {
467
- control._initHotLine(map, layer);
468
- control._initAlmostOverHandler(map, layer);
469
- control._initDistanceMarkers(map, layer);
470
- } else if (control.options.polyline) {
471
- layer.addTo(map);
457
+ if (this._data.length) {
458
+ this._start.setLatLng(this._data[0].latlng);
459
+ this._end.setLatLng(this._data[this._data.length -1].latlng);
472
460
  }
473
- map.invalidateSize();
461
+ Promise.all([
462
+ this._initHotLine(layer),
463
+ this._initAlmostOverHandler(map, layer),
464
+ this._initDistanceMarkers(),
465
+ ]).then(() => {
466
+ if (this.options.polyline) {
467
+ layer.addTo(map);
468
+ this._circleMarkers.addTo(map);
469
+ }
470
+ if (this.options.autofitBounds) {
471
+ this.fitBounds(layer.getBounds());
472
+ }
473
+ map.invalidateSize();
474
+ });
474
475
  } else {
475
- console.warn("Undefined elevation map object");;
476
+ this.once('add', () => this._initMapIntegrations(layer));
476
477
  }
477
478
  },
478
479
 
@@ -581,14 +582,15 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
581
582
  * Generate "svg" chart (DOM element).
582
583
  */
583
584
  _initChart: function(container) {
584
- let opts = this.options;
585
+ let opts = this.options;
586
+ let map = this._map;
585
587
 
586
588
  if (opts.detached) {
587
589
  let { offsetWidth, offsetHeight} = this.eleDiv;
588
590
  if (offsetWidth > 0) opts.width = offsetWidth;
589
591
  if (offsetHeight > 20) opts.height = offsetHeight - 20; // 20 = horizontal scrollbar size.
590
592
  } else {
591
- let { clientWidth } = this._map.getContainer();
593
+ let { clientWidth } = map.getContainer();
592
594
  opts._maxWidth = opts._maxWidth > opts.width ? opts._maxWidth : opts.width;
593
595
  this._container.style.maxWidth = opts._maxWidth + 'px';
594
596
  if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
@@ -641,6 +643,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
641
643
  this._updateSummary();
642
644
  });
643
645
 
646
+
644
647
  },
645
648
 
646
649
  _initLayer: function() {
@@ -677,12 +680,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
677
680
  * Inspired by L.Control.Layers
678
681
  */
679
682
  _initButton: function(container) {
680
-
681
- if (L.Browser.mobile || !this.options.detached) {
682
- L.DomEvent
683
- .disableClickPropagation(container)
684
- .disableScrollPropagation(container);
685
- }
683
+ L.DomEvent
684
+ .disableClickPropagation(container)
685
+ .disableScrollPropagation(container);
686
686
 
687
687
  this.options.collapsed ? this._collapse() : this._expand();
688
688
 
@@ -730,22 +730,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
730
730
  // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
731
731
  handlers = handlers.map((h) => typeof h === 'string' && typeof Elevation[h] !== "undefined" ? Elevation[h] : h);
732
732
  // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
733
- Promise.
734
- all(
735
- handlers
736
- .filter(h => typeof h === 'string' || h instanceof Promise)
737
- .map(file => file instanceof Promise ? file : this.import(this.__modulesFolder + file.toLowerCase() + '.js'))
738
- )
739
- .then((m) => {
740
- _.each(m, (exported) => {
741
- let fn = Object.keys(exported)[0];
742
- if (fn && typeof Elevation[fn] === "undefined" ) {
743
- handlers[fn] = Elevation[fn] = exported[fn];
744
-
745
- }
746
- });
747
- _.each(handlers, h => this._registerHandler(h));
733
+ let modules = handlers
734
+ .filter(h => typeof h === 'string' || h instanceof Promise)
735
+ .map(file => file instanceof Promise ? file : this.import(this.__modulesFolder + file.toLowerCase() + '.js'));
736
+ return Promise.all(modules).then((m) => {
737
+ _.each(m, (exported) => {
738
+ let fn = Object.keys(exported)[0];
739
+ if (fn) {
740
+ handlers[fn] = Elevation[fn] = (Elevation[fn] ?? exported[fn]);
741
+ }
748
742
  });
743
+ _.each(handlers, h => ["function", "object"].includes(typeof h) && this._registerHandler(h));
744
+ });
749
745
  },
750
746
 
751
747
  /**
@@ -792,7 +788,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
792
788
  });
793
789
 
794
790
  this.import(this.__D3).then(() => {
795
- this._initMapIntegrations(this, layer);
791
+ this._initMapIntegrations(layer);
796
792
  this._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: this.track_info.name, track_info: this.track_info });
797
793
  });
798
794
 
@@ -1035,7 +1031,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1035
1031
  if (props.stats) {
1036
1032
  for (const key in props.stats) {
1037
1033
  let sname = (props.statsName || attr) + (key != '' ? '_' : '');
1038
- this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key]);
1034
+ this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key], index);
1039
1035
  }
1040
1036
  }
1041
1037
 
@@ -1,53 +1,53 @@
1
- export function Heart() {
2
-
3
- const _ = L.Control.Elevation.Utils;
4
-
5
- return {
6
- name: 'heart', // <-- Your custom option name (eg. "heart: true")
7
- unit: 'bpm',
8
- meta: 'hr', // <-- point.meta.hr
9
- coordinateProperties: ["heart", "heartRates", "heartRate"], // List of GPX Extensions ("coordinateProperties") to be handled by "@tmcw/toGeoJSON"
10
- pointToAttr: (point, i) => (point.hr ?? point.meta.hr ?? point.prev('heart')) || 0,
11
- stats: { max: _.iMax, min: _.iMin, avg: _.iAvg },
12
- scale: {
13
- axis : "y",
14
- position : "left",
15
- scale : { min: -1, max: +1 },
16
- tickPadding: 25,
17
- labelX : -30,
18
- labelY : -8,
19
- },
20
- path: {
21
- label : 'ECG',
22
- yAttr : 'heart',
23
- scaleX : 'distance',
24
- scaleY : 'heart',
25
- color : 'white',
26
- strokeColor : 'red',
27
- strokeOpacity: "0.85",
28
- fillOpacity : "0.1",
29
- },
30
- tooltip: {
31
- chart: (item) => L._("hr: ") + item.heart + " " + 'bpm',
32
- marker: (item) => Math.round(item.heart) + " " + 'bpm',
33
- order: 1
34
- },
35
- summary: {
36
- "minbpm": {
37
- label: "Min BPM: ",
38
- value: (track, unit) => Math.round(track.heart_min || 0) + '&nbsp;' + unit,
39
- // order: 30
40
- },
41
- "maxbpm": {
42
- label: "Max BPM: ",
43
- value: (track, unit) => Math.round(track.heart_min || 0) + '&nbsp;' + unit,
44
- // order: 30
45
- },
46
- "avgbpm": {
47
- label: "Avg BPM: ",
48
- value: (track, unit) => Math.round(track.heart_avg || 0) + '&nbsp;' + unit,
49
- // order: 20
50
- },
51
- }
52
- };
1
+ export function Heart() {
2
+
3
+ const _ = L.Control.Elevation.Utils;
4
+
5
+ return {
6
+ name: 'heart', // <-- Your custom option name (eg. "heart: true")
7
+ unit: 'bpm',
8
+ meta: 'hr', // <-- point.meta.hr
9
+ coordinateProperties: ["heart", "heartRates", "heartRate"], // List of GPX Extensions ("coordinateProperties") to be handled by "@tmcw/toGeoJSON"
10
+ pointToAttr: (point, i) => (point.hr ?? point.meta.hr ?? point.prev('heart')) || 0,
11
+ stats: { max: _.iMax, min: _.iMin, avg: _.iAvg },
12
+ scale: {
13
+ axis : "y",
14
+ position : "left",
15
+ scale : { min: -1, max: +1 },
16
+ tickPadding: 25,
17
+ labelX : -30,
18
+ labelY : -8,
19
+ },
20
+ path: {
21
+ label : 'ECG',
22
+ yAttr : 'heart',
23
+ scaleX : 'distance',
24
+ scaleY : 'heart',
25
+ color : 'white',
26
+ strokeColor : 'red',
27
+ strokeOpacity: "0.85",
28
+ fillOpacity : "0.1",
29
+ },
30
+ tooltip: {
31
+ chart: (item) => L._("hr: ") + item.heart + " " + 'bpm',
32
+ marker: (item) => Math.round(item.heart) + " " + 'bpm',
33
+ order: 1
34
+ },
35
+ summary: {
36
+ "minbpm": {
37
+ label: "Min BPM: ",
38
+ value: (track, unit) => Math.round(track.heart_min || 0) + '&nbsp;' + unit,
39
+ // order: 30
40
+ },
41
+ "maxbpm": {
42
+ label: "Max BPM: ",
43
+ value: (track, unit) => Math.round(track.heart_max || 0) + '&nbsp;' + unit,
44
+ // order: 30
45
+ },
46
+ "avgbpm": {
47
+ label: "Avg BPM: ",
48
+ value: (track, unit) => Math.round(track.heart_avg || 0) + '&nbsp;' + unit,
49
+ // order: 20
50
+ },
51
+ }
52
+ };
53
53
  };
package/src/index.js CHANGED
@@ -34,21 +34,9 @@
34
34
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
35
  */
36
36
 
37
- import 'leaflet-i18n';
38
37
  import * as _ from './utils';
39
38
  import { Elevation } from './control';
40
39
 
41
40
  Elevation.Utils = _;
42
41
 
43
- /* Temporary fix for empty values evaluated as false (leaflet-i18n v0.3.1) */
44
- (function(){
45
- let proto = L.i18n.bind({});
46
- L.i18n = L._ = (string, data) => {
47
- if (L.locale && L.locales[L.locale] && L.locales[L.locale][string] == "") {
48
- L.locales[L.locale][string] = "\u200B";
49
- }
50
- return proto.call(null, string, data);
51
- };
52
- })();
53
-
54
42
  L.control.elevation = (options) => new Elevation(options);
package/src/utils.js CHANGED
@@ -92,7 +92,7 @@ export function coordPropsToMeta(coordProps, name, parser) {
92
92
  if (props) {
93
93
  for (const key of coordProps) {
94
94
  if (key in props) {
95
- point.meta[name] = (parser || parseInt).call(this, (isMulti ? props[key][isMulti] : props[key]), id);
95
+ point.meta[name] = (parser || parseNumeric).call(this, (isMulti ? props[key][isMulti] : props[key]), id);
96
96
  break;
97
97
  }
98
98
  }
@@ -103,7 +103,7 @@ export function coordPropsToMeta(coordProps, name, parser) {
103
103
  /**
104
104
  * Extract numeric property (id) from GeoJSON object
105
105
  */
106
- export const parseInt = (property, id) => window.parseInt((typeof property === 'object' ? property[id] : property));
106
+ export const parseNumeric = (property, id) => parseInt((typeof property === 'object' ? property[id] : property));
107
107
 
108
108
  /**
109
109
  * Extract datetime property (id) from GeoJSON object
@@ -131,10 +131,10 @@ export const randomId = () => Math.random().toString(36).
131
131
  /**
132
132
  * TODO: use generators instead? (ie. "yield")
133
133
  */
134
- export const iMax = (iVal, max) => (iVal > max ? iVal : max) || -Infinity;
135
- export const iMin = (iVal, min) => (iVal < min ? iVal : min) || +Infinity;
136
- export const iAvg = (iVal, avg) => (iVal + (avg || 0)) / 2.0;
137
- export const iSum = (iVal, sum) => iVal + (sum || 0);
134
+ export const iMax = (iVal, max = -Infinity) => (iVal > max ? iVal : max);
135
+ export const iMin = (iVal, min = +Infinity) => (iVal < min ? iVal : min);
136
+ export const iAvg = (iVal, avg = 0, idx = 1) => (iVal + avg * (idx - 1)) / idx;
137
+ export const iSum = (iVal, sum = 0) => iVal + sum;
138
138
 
139
139
  /**
140
140
  * Alias for some leaflet core functions