@raruto/leaflet-elevation 2.0.1 → 2.0.2

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.
@@ -143,102 +143,107 @@ L.DistanceMarkers = L.LayerGroup.extend({
143
143
  // Get line coords as an array
144
144
  let coords = typeof line.getLatLngs == 'function' ? line.getLatLngs() : line;
145
145
 
146
- // Get accumulated line lengths as well as overall length
147
- let accumulated = L.GeometryUtil.accumulatedLengths(line);
148
- let length = accumulated.length > 0 ? accumulated[accumulated.length - 1] : 0;
149
-
150
- // count = Number of distance markers to be added
151
- // j = Position in accumulated line length array
152
- for (let i = 1, count = Math.floor(length / options.offset), j = 0; i <= count; ++i) {
153
-
154
- let distance = options.offset * i;
155
-
156
- // Find the first accumulated distance that is greater than the distance of this marker
157
- while (j < accumulated.length - 1 && accumulated[j] < distance) ++j;
158
-
159
- // Grab two nearest points either side marker position
160
- let p1 = coords[j - 1];
161
- let p2 = coords[j];
162
- let m_line = L.polyline([p1, p2]);
163
-
164
- // and create a simple line to interpolate on
165
- let ratio = (distance - accumulated[j - 1]) / (accumulated[j] - accumulated[j - 1]);
166
- let position = L.GeometryUtil.interpolateOnLine(map, m_line, ratio);
167
- let delta = map.project(p2).subtract(map.project(p1));
168
- let angle = Math.atan2(delta.y, delta.x);
169
-
170
- // Generate distance marker label
171
- let text = options.textFunction.call(this, distance, i, options.offset);
172
-
173
- // Grouping layer of visible layers at zoom level (arrow + distance)
174
- let zoom = this._minimumZoomLevelForItem(i, showAll);
175
- let markers = this._zoomLayers[zoom] = this._zoomLayers[zoom] || L.layerGroup()
176
-
177
- // create arrow markers
178
- if (options.direction && ((options.distance && i % 2 == 1) || !options.distance)) {
179
- if (preferCanvas) {
180
- markers.addLayer(
181
- new L.DistanceMarker(p1, {
182
- radius: 0,
183
- icon: {
184
- url: options.arrowUrl, //image link
185
- size: options.arrowSize, //image size ( default [40, 40] )
186
- rotate: 0, //image base rotate ( default 0 )
187
- offset: { x: 0, y: 0 }, //image offset ( default { x: 0, y: 0 } )
188
- },
189
- rotation: angle,
190
- interactive: false,
191
- // label: '⮞', //'➜',
192
- // font: 'normal 20pt "Helvetica Neue", Arial, Helvetica, sans-serif',
193
- // fillStyle: 'white',//'#3366CC',
194
- // strokeStyle: 'black',
195
- })
196
- );
197
- } else {
198
- markers.addLayer(
199
- L.marker(position.latLng, {
200
- icon: L.icon({
201
- iconUrl: options.arrowUrl,
202
- iconSize: options.arrowSize,
203
- }),
204
- // NB the following option is added by "leaflet-rotate"
205
- rotation: angle * L.DomUtil.RAD_TO_DEG,
206
- interactive: false,
207
- })
208
- );
146
+ // Handle "MultiLineString" features
147
+ coords = L.LineUtil.isFlat(coords) ? [coords] : coords;
148
+
149
+ coords.forEach(latlngs => {
150
+ // Get accumulated line lengths as well as overall length
151
+ let accumulated = L.GeometryUtil.accumulatedLengths(latlngs);
152
+ let length = accumulated.length > 0 ? accumulated[accumulated.length - 1] : 0;
153
+
154
+ // count = Number of distance markers to be added
155
+ // j = Position in accumulated line length array
156
+ for (let i = 1, count = Math.floor(length / options.offset), j = 0; i <= count; ++i) {
157
+
158
+ let distance = options.offset * i;
159
+
160
+ // Find the first accumulated distance that is greater than the distance of this marker
161
+ while (j < accumulated.length - 1 && accumulated[j] < distance) ++j;
162
+
163
+ // Grab two nearest points either side marker position
164
+ let p1 = latlngs[j - 1];
165
+ let p2 = latlngs[j];
166
+ let m_line = L.polyline([p1, p2]);
167
+
168
+ // and create a simple line to interpolate on
169
+ let ratio = (distance - accumulated[j - 1]) / (accumulated[j] - accumulated[j - 1]);
170
+ let position = L.GeometryUtil.interpolateOnLine(map, m_line, ratio);
171
+ let delta = map.project(p2).subtract(map.project(p1));
172
+ let angle = Math.atan2(delta.y, delta.x);
173
+
174
+ // Generate distance marker label
175
+ let text = options.textFunction.call(this, distance, i, options.offset);
176
+
177
+ // Grouping layer of visible layers at zoom level (arrow + distance)
178
+ let zoom = this._minimumZoomLevelForItem(i, showAll);
179
+ let markers = this._zoomLayers[zoom] = this._zoomLayers[zoom] || L.layerGroup()
180
+
181
+ // create arrow markers
182
+ if (options.direction && ((options.distance && i % 2 == 1) || !options.distance)) {
183
+ if (preferCanvas) {
184
+ markers.addLayer(
185
+ new L.DistanceMarker(p1, {
186
+ radius: 0,
187
+ icon: {
188
+ url: options.arrowUrl, //image link
189
+ size: options.arrowSize, //image size ( default [40, 40] )
190
+ rotate: 0, //image base rotate ( default 0 )
191
+ offset: { x: 0, y: 0 }, //image offset ( default { x: 0, y: 0 } )
192
+ },
193
+ rotation: angle,
194
+ interactive: false,
195
+ // label: '⮞', //'➜',
196
+ // font: 'normal 20pt "Helvetica Neue", Arial, Helvetica, sans-serif',
197
+ // fillStyle: 'white',//'#3366CC',
198
+ // strokeStyle: 'black',
199
+ })
200
+ );
201
+ } else {
202
+ markers.addLayer(
203
+ L.marker(position.latLng, {
204
+ icon: L.icon({
205
+ iconUrl: options.arrowUrl,
206
+ iconSize: options.arrowSize,
207
+ }),
208
+ // NB the following option is added by "leaflet-rotate"
209
+ rotation: angle * L.DomUtil.RAD_TO_DEG,
210
+ interactive: false,
211
+ })
212
+ );
213
+ }
209
214
  }
210
- }
211
215
 
212
- // create distance markers
213
- if (options.distance && i % 2 == 0) {
214
- if (preferCanvas) {
215
- markers.addLayer(
216
- new L.DistanceMarker(position.latLng, {
217
- label: text, // TODO: handle text rotation (leaflet-rotate)
218
- radius: 7,
219
- fillColor: '#fff',
220
- fillOpacity: 1,
221
- fillStyle: 'black',
222
- color: '#777',
223
- weight: 1,
224
- interactive: false,
225
- })
226
- );
227
- } else {
228
- markers.addLayer(
229
- L.marker(position.latLng, {
230
- title: text,
231
- icon: L.divIcon({
232
- className: options.cssClass,
233
- html: text,
234
- iconSize: options.iconSize
235
- }),
236
- interactive: false,
237
- })
238
- );
216
+ // create distance markers
217
+ if (options.distance && i % 2 == 0) {
218
+ if (preferCanvas) {
219
+ markers.addLayer(
220
+ new L.DistanceMarker(position.latLng, {
221
+ label: text, // TODO: handle text rotation (leaflet-rotate)
222
+ radius: 7,
223
+ fillColor: '#fff',
224
+ fillOpacity: 1,
225
+ fillStyle: 'black',
226
+ color: '#777',
227
+ weight: 1,
228
+ interactive: false,
229
+ })
230
+ );
231
+ } else {
232
+ markers.addLayer(
233
+ L.marker(position.latLng, {
234
+ title: text,
235
+ icon: L.divIcon({
236
+ className: options.cssClass,
237
+ html: text,
238
+ iconSize: options.iconSize
239
+ }),
240
+ interactive: false,
241
+ })
242
+ );
243
+ }
239
244
  }
240
245
  }
241
- }
246
+ });
242
247
 
243
248
  const updateMarkerVisibility = () => {
244
249
  let oldZoom = this._lastZoomLevel || 0;
@@ -1 +1 @@
1
- L.DistanceMarker=L.CircleMarker.extend({_updatePath:function(){let ctx=this._renderer._ctx,p=this._point;if(this.options.rotation=this.options.rotation||0,this.options.radius&&this._renderer._updateCircle&&this._renderer._updateCircle(this),this.options.icon&&this.options.icon.url)if(this.options.icon.element){const icon=this.options.icon;let cx=p.x+icon.offset.x,cy=p.y+icon.offset.y;ctx.save(),icon.rotate&&(ctx.translate(p.x,p.y),ctx.rotate(icon.rotate),cx=0,cy=0),ctx.drawImage(icon.element,cx-icon.size[0]/2,cy-icon.size[1]/2,icon.size[0],icon.size[1]),ctx.restore()}else{const icon=document.createElement("img");this.options.icon=L.extend({rotate:0,size:[40,40],offset:{x:0,y:0}},this.options.icon),this.options.icon.rotate+=this.options.rotation,this.options.icon.element=icon,icon.src=this.options.icon.url,icon.onload=()=>this.redraw(),icon.onerror=()=>this.options.icon=null}if(this.options.label){let cx=p.x,cy=p.y;ctx.save(),ctx.font=this.options.font||'normal 7pt "Helvetica Neue", Arial, Helvetica, sans-serif',ctx.textAlign="center",ctx.textBaseline="middle",ctx.fillStyle=this.options.fillStyle||"black",this.options.rotation&&(ctx.translate(p.x,p.y),ctx.rotate(this.options.rotation),cx=0,cy=0),this._map.getZoom()>17&&(ctx.fillStyle=this.options.strokeStyle||"black"),ctx.fillText(this.options.label,cx,cy),this.options.strokeStyle&&this._map.getZoom()<=17&&(ctx.strokeStyle=this.options.strokeStyle,ctx.strokeText(this.options.label,cx,cy)),ctx.restore()}}}),L.DistanceMarkers=L.LayerGroup.extend({options:{cssClass:"dist-marker",iconSize:[12,12],arrowSize:[10,10],arrowUrl:"data:image/svg+xml,%3Csvg transform='rotate(90)' xmlns='http://www.w3.org/2000/svg' width='560px' height='560px' viewBox='0 0 560 560'%3E%3Cpath stroke-width='35' fill='%23000' stroke='%23FFF' d='M280,40L522,525L280,420L38,525z'/%3E%3C/svg%3E",offset:1e3,showAll:12,textFunction:(distance,i,offset)=>i,distance:!0,direction:!0},initialize:function(line,map,options){this._layers={},this._zoomLayers={},options=L.setOptions(this,options);let preferCanvas=map.options.preferCanvas,showAll=Math.min(map.getMaxZoom(),options.showAll);preferCanvas||map.options.rotate||console.warn('Missing dependency: "leaflet-rotate"');let coords="function"==typeof line.getLatLngs?line.getLatLngs():line,accumulated=L.GeometryUtil.accumulatedLengths(line),length=accumulated.length>0?accumulated[accumulated.length-1]:0;for(let i=1,count=Math.floor(length/options.offset),j=0;i<=count;++i){let distance=options.offset*i;for(;j<accumulated.length-1&&accumulated[j]<distance;)++j;let p1=coords[j-1],p2=coords[j],m_line=L.polyline([p1,p2]),ratio=(distance-accumulated[j-1])/(accumulated[j]-accumulated[j-1]),position=L.GeometryUtil.interpolateOnLine(map,m_line,ratio),delta=map.project(p2).subtract(map.project(p1)),angle=Math.atan2(delta.y,delta.x),text=options.textFunction.call(this,distance,i,options.offset),zoom=this._minimumZoomLevelForItem(i,showAll),markers=this._zoomLayers[zoom]=this._zoomLayers[zoom]||L.layerGroup();options.direction&&(options.distance&&i%2==1||!options.distance)&&(preferCanvas?markers.addLayer(new L.DistanceMarker(p1,{radius:0,icon:{url:options.arrowUrl,size:options.arrowSize,rotate:0,offset:{x:0,y:0}},rotation:angle,interactive:!1})):markers.addLayer(L.marker(position.latLng,{icon:L.icon({iconUrl:options.arrowUrl,iconSize:options.arrowSize}),rotation:angle*L.DomUtil.RAD_TO_DEG,interactive:!1}))),options.distance&&i%2==0&&(preferCanvas?markers.addLayer(new L.DistanceMarker(position.latLng,{label:text,radius:7,fillColor:"#fff",fillOpacity:1,fillStyle:"black",color:"#777",weight:1,interactive:!1})):markers.addLayer(L.marker(position.latLng,{title:text,icon:L.divIcon({className:options.cssClass,html:text,iconSize:options.iconSize}),interactive:!1})))}const updateMarkerVisibility=()=>{let oldZoom=this._lastZoomLevel||0,newZoom=map.getZoom();if(newZoom>oldZoom)for(let i=oldZoom+1;i<=newZoom;++i)void 0!==this._zoomLayers[i]&&this.addLayer(this._zoomLayers[i]);else if(newZoom<oldZoom)for(let i=oldZoom;i>newZoom;--i)void 0!==this._zoomLayers[i]&&this.removeLayer(this._zoomLayers[i]);this._lastZoomLevel=newZoom};map.on("zoomend",updateMarkerVisibility),updateMarkerVisibility()},_minimumZoomLevelForItem:function(i,zoom){for(;i>0&&i%2==0;)--zoom,i=Math.floor(i/2);return zoom}}),L.Polyline.include({_originalOnAdd:L.Polyline.prototype.onAdd,_originalOnRemove:L.Polyline.prototype.onRemove,addDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.addLayer(this._distanceMarkers)},removeDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.removeLayer(this._distanceMarkers)},onAdd:function(map){this._originalOnAdd(map);let opts=this.options.distanceMarkers||{};this.options.distanceMarkers&&(this._distanceMarkers=this._distanceMarkers||new L.DistanceMarkers(this,map,opts)),void 0!==opts.lazy&&!1!==opts.lazy||this.addDistanceMarkers()},onRemove:function(map){this.removeDistanceMarkers(),this._originalOnRemove(map)}});
1
+ L.DistanceMarker=L.CircleMarker.extend({_updatePath:function(){let ctx=this._renderer._ctx,p=this._point;if(this.options.rotation=this.options.rotation||0,this.options.radius&&this._renderer._updateCircle&&this._renderer._updateCircle(this),this.options.icon&&this.options.icon.url)if(this.options.icon.element){const icon=this.options.icon;let cx=p.x+icon.offset.x,cy=p.y+icon.offset.y;ctx.save(),icon.rotate&&(ctx.translate(p.x,p.y),ctx.rotate(icon.rotate),cx=0,cy=0),ctx.drawImage(icon.element,cx-icon.size[0]/2,cy-icon.size[1]/2,icon.size[0],icon.size[1]),ctx.restore()}else{const icon=document.createElement("img");this.options.icon=L.extend({rotate:0,size:[40,40],offset:{x:0,y:0}},this.options.icon),this.options.icon.rotate+=this.options.rotation,this.options.icon.element=icon,icon.src=this.options.icon.url,icon.onload=()=>this.redraw(),icon.onerror=()=>this.options.icon=null}if(this.options.label){let cx=p.x,cy=p.y;ctx.save(),ctx.font=this.options.font||'normal 7pt "Helvetica Neue", Arial, Helvetica, sans-serif',ctx.textAlign="center",ctx.textBaseline="middle",ctx.fillStyle=this.options.fillStyle||"black",this.options.rotation&&(ctx.translate(p.x,p.y),ctx.rotate(this.options.rotation),cx=0,cy=0),this._map.getZoom()>17&&(ctx.fillStyle=this.options.strokeStyle||"black"),ctx.fillText(this.options.label,cx,cy),this.options.strokeStyle&&this._map.getZoom()<=17&&(ctx.strokeStyle=this.options.strokeStyle,ctx.strokeText(this.options.label,cx,cy)),ctx.restore()}}}),L.DistanceMarkers=L.LayerGroup.extend({options:{cssClass:"dist-marker",iconSize:[12,12],arrowSize:[10,10],arrowUrl:"data:image/svg+xml,%3Csvg transform='rotate(90)' xmlns='http://www.w3.org/2000/svg' width='560px' height='560px' viewBox='0 0 560 560'%3E%3Cpath stroke-width='35' fill='%23000' stroke='%23FFF' d='M280,40L522,525L280,420L38,525z'/%3E%3C/svg%3E",offset:1e3,showAll:12,textFunction:(distance,i,offset)=>i,distance:!0,direction:!0},initialize:function(line,map,options){this._layers={},this._zoomLayers={},options=L.setOptions(this,options);let preferCanvas=map.options.preferCanvas,showAll=Math.min(map.getMaxZoom(),options.showAll);preferCanvas||map.options.rotate||console.warn('Missing dependency: "leaflet-rotate"');let coords="function"==typeof line.getLatLngs?line.getLatLngs():line;coords=L.LineUtil.isFlat(coords)?[coords]:coords,coords.forEach(latlngs=>{let accumulated=L.GeometryUtil.accumulatedLengths(latlngs),length=accumulated.length>0?accumulated[accumulated.length-1]:0;for(let i=1,count=Math.floor(length/options.offset),j=0;i<=count;++i){let distance=options.offset*i;for(;j<accumulated.length-1&&accumulated[j]<distance;)++j;let p1=latlngs[j-1],p2=latlngs[j],m_line=L.polyline([p1,p2]),ratio=(distance-accumulated[j-1])/(accumulated[j]-accumulated[j-1]),position=L.GeometryUtil.interpolateOnLine(map,m_line,ratio),delta=map.project(p2).subtract(map.project(p1)),angle=Math.atan2(delta.y,delta.x),text=options.textFunction.call(this,distance,i,options.offset),zoom=this._minimumZoomLevelForItem(i,showAll),markers=this._zoomLayers[zoom]=this._zoomLayers[zoom]||L.layerGroup();options.direction&&(options.distance&&i%2==1||!options.distance)&&(preferCanvas?markers.addLayer(new L.DistanceMarker(p1,{radius:0,icon:{url:options.arrowUrl,size:options.arrowSize,rotate:0,offset:{x:0,y:0}},rotation:angle,interactive:!1})):markers.addLayer(L.marker(position.latLng,{icon:L.icon({iconUrl:options.arrowUrl,iconSize:options.arrowSize}),rotation:angle*L.DomUtil.RAD_TO_DEG,interactive:!1}))),options.distance&&i%2==0&&(preferCanvas?markers.addLayer(new L.DistanceMarker(position.latLng,{label:text,radius:7,fillColor:"#fff",fillOpacity:1,fillStyle:"black",color:"#777",weight:1,interactive:!1})):markers.addLayer(L.marker(position.latLng,{title:text,icon:L.divIcon({className:options.cssClass,html:text,iconSize:options.iconSize}),interactive:!1})))}});const updateMarkerVisibility=()=>{let oldZoom=this._lastZoomLevel||0,newZoom=map.getZoom();if(newZoom>oldZoom)for(let i=oldZoom+1;i<=newZoom;++i)void 0!==this._zoomLayers[i]&&this.addLayer(this._zoomLayers[i]);else if(newZoom<oldZoom)for(let i=oldZoom;i>newZoom;--i)void 0!==this._zoomLayers[i]&&this.removeLayer(this._zoomLayers[i]);this._lastZoomLevel=newZoom};map.on("zoomend",updateMarkerVisibility),updateMarkerVisibility()},_minimumZoomLevelForItem:function(i,zoom){for(;i>0&&i%2==0;)--zoom,i=Math.floor(i/2);return zoom}}),L.Polyline.include({_originalOnAdd:L.Polyline.prototype.onAdd,_originalOnRemove:L.Polyline.prototype.onRemove,addDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.addLayer(this._distanceMarkers)},removeDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.removeLayer(this._distanceMarkers)},onAdd:function(map){this._originalOnAdd(map);let opts=this.options.distanceMarkers||{};this.options.distanceMarkers&&(this._distanceMarkers=this._distanceMarkers||new L.DistanceMarkers(this,map,opts)),void 0!==opts.lazy&&!1!==opts.lazy||this.addDistanceMarkers()},onRemove:function(map){this.removeDistanceMarkers(),this._originalOnRemove(map)}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raruto/leaflet-elevation",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "A Leaflet plugin that allows to add elevation profiles using d3js",
5
5
  "main": "dist/leaflet-elevation.min.js",
6
6
  "module": "src/index.js",
package/src/control.js CHANGED
@@ -25,9 +25,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
25
25
  addData: function(d, layer) {
26
26
  this.import(this.__D3)
27
27
  .then(() => {
28
- if (typeof layer === "undefined" && d.on) {
29
- layer = d;
30
- }
28
+ layer = layer ?? (d.on && d);
31
29
  this._addData(d);
32
30
  this._addLayer(layer);
33
31
  this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
@@ -296,51 +294,44 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
296
294
  return;
297
295
  }
298
296
 
299
- /**
300
- * Standard GeoJSON --> doesn't handle "time", "heart", …
301
- */
302
- let geom = d.geometry;
303
- if (geom) {
304
- switch (geom.type) {
305
- case 'LineString':
306
- this._addGeoJSONData(geom.coordinates);
307
- break;
308
-
309
- case 'MultiLineString':
310
- _.each(geom.coordinates, coords => this._addGeoJSONData(coords));
311
- break;
312
-
313
- default:
314
- return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
297
+ // Standard GeoJSON
298
+ if (d.type === "FeatureCollection" ) {
299
+ return _.each(d.features, feature => this._addData(feature));
300
+ } else if (d.type === "Feature") {
301
+ let geom = d.geometry;
302
+ if (geom) {
303
+ switch (geom.type) {
304
+ case 'LineString': return this._addGeoJSONData(geom.coordinates, d.properties);
305
+ case 'MultiLineString': return _.each(geom.coordinates, (coords, i) => this._addGeoJSONData(coords, d.properties, i));
306
+ case 'Point':
307
+ default: return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
308
+ }
315
309
  }
316
310
  }
317
- if (d.type === "FeatureCollection") {
318
- _.each(d.features, feature => this._addData(feature));
319
- }
320
311
 
321
- /**
322
- * Extended GeoJSON --> rely on leaflet implementation
323
- */
312
+ // Fallback for leaflet layers (eg. L.Gpx)
324
313
  if (d._latlngs) {
325
- this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
314
+ return this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
326
315
  }
316
+
327
317
  },
328
318
 
329
319
  /*
330
320
  * Parsing of GeoJSON data lines and their elevation in z-coordinate
331
321
  */
332
- _addGeoJSONData: function(coords, properties) {
322
+ _addGeoJSONData: function(coords, properties, nestingLevel) {
323
+
324
+ // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
325
+ let props = (properties && properties.coordinateProperties) || properties;
326
+
333
327
  coords.forEach((point, i) => {
334
328
 
335
329
  // GARMIN_EXTENSIONS = ["hr", "cad", "atemp", "wtemp", "depth", "course", "bearing"];
336
330
  point.meta = point.meta ?? { time: null, ele: null };
337
331
 
338
- // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
339
- let props = (properties && properties.coordinateProperties) || properties;
340
-
341
332
  point.prev = (attr) => (attr ? this._data[i > 0 ? i - 1 : 0][attr] : this._data[i > 0 ? i - 1 : 0]);
342
333
 
343
- this.fire("elepoint_init", { point: point, props: props, id: i });
334
+ this.fire("elepoint_init", { point: point, props: props, id: i, isMulti: nestingLevel });
344
335
 
345
336
  this._addPoint(
346
337
  point.lat ?? point[1],
@@ -380,10 +371,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
380
371
  if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
381
372
  layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
382
373
  }
374
+ return layer;
383
375
  },
384
376
 
385
377
  _addMarker: function(marker) {
386
378
  if (marker) this._markers.addLayer(marker)
379
+ return marker;
387
380
  },
388
381
 
389
382
  /**
@@ -716,7 +709,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
716
709
  */
717
710
  _loadModules: function(handlers) {
718
711
  // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
719
- handlers = handlers.map((h) => typeof h === 'string' && typeof L.Control.Elevation[h] !== "undefined" ? L.Control.Elevation[h] : h);
712
+ handlers = handlers.map((h) => typeof h === 'string' && typeof Elevation[h] !== "undefined" ? Elevation[h] : h);
720
713
  // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
721
714
  Promise.
722
715
  all(
@@ -727,8 +720,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
727
720
  .then((m) => {
728
721
  _.each(m, (exported) => {
729
722
  let fn = Object.keys(exported)[0];
730
- if (fn && typeof L.Control.Elevation[fn] === "undefined" ) {
731
- handlers[fn] = L.Control.Elevation[fn] = exported[fn];
723
+ if (fn && typeof Elevation[fn] === "undefined" ) {
724
+ handlers[fn] = Elevation[fn] = exported[fn];
732
725
 
733
726
  }
734
727
  });
@@ -740,65 +733,48 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
740
733
  * Simple GeoJSON data loader (L.GeoJSON).
741
734
  */
742
735
  _loadLayer: function(geojson) {
743
- let control = this;
736
+ let { polyline, theme, waypoints, wptIcons, wptLabels, distanceMarkers } = this.options;
737
+ let style = L.extend({}, polyline);
744
738
 
745
- let { wptIcons, wptLabels } = control.options;
739
+ if (theme) {
740
+ style.className += ' ' + theme;
741
+ }
742
+
743
+ if (geojson.name) {
744
+ this.track_info.name = geojson.name;
745
+ }
746
746
 
747
747
  let layer = L.geoJson(geojson, {
748
- distanceMarkers: control.options.distanceMarkers,
749
- style: (feature) => {
750
- let style = L.extend({}, control.options.polyline);
751
- if (control.options.theme) {
752
- style.className += ' ' + control.options.theme;
753
- }
754
- return style;
755
- },
748
+ distanceMarkers: distanceMarkers,
749
+ style: style,
756
750
  pointToLayer: (feature, latlng) => {
757
- if (!control.options.waypoints) return;
758
-
759
- let prop = feature.properties;
760
- let desc = prop.desc ?? '';
761
- let name = prop.name ?? '';
762
- let sym = (prop.sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase();
763
-
764
- // Handle chart waypoints (dots)
765
- if ([true, 'dots'].includes(control.options.waypoints)) {
766
- control._registerCheckPoint({
767
- latlng: latlng,
768
- label: ([true, 'dots'].includes(wptLabels) ? name : '')
769
- });
770
- }
771
-
772
- // Handle map waypoints (markers)
773
- if ([true, 'markers'].includes(control.options.waypoints) && wptIcons != false) {
774
- // generate and cache appropriate icon symbol
775
- if (!wptIcons.hasOwnProperty(sym)) {
776
- wptIcons[sym] = L.divIcon(
777
- L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } )
778
- );
751
+ if (waypoints) {
752
+ let { desc, name, sym } = feature.properties;
753
+ desc = desc || '';
754
+ name = name || '';
755
+ // Handle chart waypoints (dots)
756
+ if ([true, 'dots'].includes(waypoints)) {
757
+ this._registerCheckPoint({
758
+ latlng: latlng,
759
+ label : ([true, 'dots'].includes(wptLabels) ? name : '')
760
+ });
779
761
  }
780
- let marker = L.marker(latlng, { icon: wptIcons[sym] });
781
- if ([true, 'markers'].includes(wptLabels) && (name || desc)) {
782
- let content = decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''));
783
- marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
784
- marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
762
+ // Handle map waypoints (markers)
763
+ if ([true, 'markers'].includes(waypoints) && wptIcons != false) {
764
+ return this._registerMarker({
765
+ latlng : latlng,
766
+ sym : (sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase(),
767
+ content: [true, 'markers'].includes(wptLabels) && (name || desc) && decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''))
768
+ });
785
769
  }
786
- control._addMarker(marker);
787
- control.fire('waypoint_added', { point: marker, element: latlng, properties: prop });
788
- return marker;
789
770
  }
790
771
  },
791
- onEachFeature: (feature, layer) => {
792
- if (feature.geometry && feature.geometry.type != 'Point') {
793
- control.addData(layer);
794
- control.track_info = L.extend({}, control.track_info, { name: geojson.name });
795
- }
796
- },
772
+ onEachFeature: (feature, layer) => feature.geometry && feature.geometry.type != 'Point' && this.addData(feature, layer),
797
773
  });
798
774
 
799
- control.import(control.__D3).then(() => {
800
- control._initMapIntegrations(control, layer);
801
- control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info });
775
+ this.import(this.__D3).then(() => {
776
+ this._initMapIntegrations(this, layer);
777
+ this._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: this.track_info.name, track_info: this.track_info });
802
778
  });
803
779
 
804
780
  return layer;
@@ -929,8 +905,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
929
905
  geojson = this._parseFromGeoJSONString(data.toString());
930
906
  }
931
907
  if (geojson) {
932
- geojson = this._prepareMultiLineStrings(geojson);
933
- geojson = this._prepareAdditionalProperties(geojson);
908
+ geojson.name = geojson.name || (this._downloadURL || '').split('/').pop().split('#')[0].split('?')[0];
934
909
  }
935
910
  resolve(geojson);
936
911
  })
@@ -958,37 +933,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
958
933
  return geojson;
959
934
  },
960
935
 
961
- /**
962
- * Extend GeoJSON properties (name, time, ...)
963
- */
964
- _prepareAdditionalProperties: function(geojson) {
965
- if (!geojson.name && this._downloadURL) {
966
- geojson.name = this._downloadURL.split('/').pop().split('#')[0].split('?')[0];
967
- }
968
- return geojson;
969
- },
970
-
971
- /**
972
- * Just a temporary fix for MultiLineString data (trk > trkseg + trkseg),
973
- * just split them into seperate LineStrings (trk > trkseg, trk > trkseg)
974
- *
975
- * @link https://github.com/Raruto/leaflet-elevation/issues/56
976
- */
977
- _prepareMultiLineStrings: function(geojson) {
978
- geojson.features.forEach(feauture => {
979
- if (feauture.geometry.type == "MultiLineString") {
980
- feauture.geometry.coordinates.forEach(coords => {
981
- geojson.features.push({
982
- geometry: { type: 'LineString', coordinates: coords },
983
- properties: feauture.properties,
984
- type: 'Feature'
985
- });
986
- });
987
- }
988
- });
989
- return geojson.features.filter(feauture => feauture.geometry.type != "MultiLineString");
990
- },
991
-
992
936
  /**
993
937
  * Add chart profile to diagram
994
938
  */
@@ -1106,60 +1050,79 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1106
1050
  return this._registerHandler(props.call(this));
1107
1051
  }
1108
1052
 
1053
+ let {
1054
+ name,
1055
+ attr,
1056
+ required,
1057
+ skipNull,
1058
+ deltaMax,
1059
+ clampRange,
1060
+ decimals,
1061
+ meta,
1062
+ unit,
1063
+ coordinateProperties,
1064
+ coordPropsToMeta,
1065
+ pointToAttr,
1066
+ onPointAdded,
1067
+ stats,
1068
+ statsName,
1069
+ grid,
1070
+ scale,
1071
+ path,
1072
+ tooltip,
1073
+ summary
1074
+ } = props;
1075
+
1109
1076
  // eg. "altitude" == true
1110
- if (this.options[props.name] || props.required) {
1077
+ if (this.options[name] || required) {
1111
1078
 
1112
1079
  this._registerDataAttribute({
1113
- name: props.name,
1114
- attr: props.attr,
1115
- skipNull: props.skipNull,
1116
- deltaMax: props.deltaMax,
1117
- clampRange: props.clampRange,
1118
- decimals: props.decimals,
1119
- coordPropsToMeta: _.coordPropsToMeta(props.coordinateProperties, props.meta || props.name, props.coordPropsToMeta),
1120
- pointToAttr: props.pointToAttr,
1121
- onPointAdded: props.onPointAdded,
1122
- stats: props.stats,
1123
- statsName: props.statsName,
1080
+ name,
1081
+ attr,
1082
+ skipNull,
1083
+ deltaMax,
1084
+ clampRange,
1085
+ decimals,
1086
+ coordPropsToMeta: _.coordPropsToMeta(coordinateProperties, meta || name, coordPropsToMeta),
1087
+ pointToAttr,
1088
+ onPointAdded,
1089
+ stats,
1090
+ statsName,
1124
1091
  });
1125
1092
 
1126
- if (props.grid) {
1127
- props.grid.name = props.grid.name || props.name;
1128
- this._registerAxisGrid(props.grid);
1093
+ if (grid) {
1094
+ this._registerAxisGrid(L.extend({ name }, grid));
1129
1095
  }
1130
1096
 
1131
- if (this.options[props.name] !== "summary") {
1132
- if (props.scale) {
1133
- props.scale.name = props.scale.name || props.name;
1134
- props.scale.label = props.scale.label || props.unit;
1135
- this._registerAxisScale(props.scale);
1136
- }
1137
- if (props.path) {
1138
- props.path.name = props.path.name || props.name;
1139
- this._registerAreaPath(props.path);
1140
- }
1097
+ if (this.options[name] !== "summary") {
1098
+ if (scale) this._registerAxisScale(L.extend({ name, label: unit }, scale));
1099
+ if (path) this._registerAreaPath(L.extend({ name }, path));
1141
1100
  }
1142
1101
 
1143
- if (props.tooltips) {
1144
- _.each(props.tooltips, t => {
1145
- if (t) {
1146
- t.name = t.name || props.name;
1147
- this._registerTooltip(t)
1148
- }
1149
- });
1150
- } else if (props.tooltip) {
1151
- props.tooltip.name = props.tooltip.name || props.name;
1152
- this._registerTooltip(props.tooltip)
1102
+ if (tooltip || props.tooltips) {
1103
+ _.each([tooltip, ...(props.tooltips || [])], t => t && this._registerTooltip(L.extend({ name }, t)));
1153
1104
  }
1154
1105
 
1155
- if (props.summary) {
1156
- for (const key in props.summary) {
1157
- props.summary[key].unit = props.summary[key].unit || props.unit;
1158
- }
1159
- this._registerSummary(props.summary);
1106
+ if (summary) {
1107
+ _.each(summary, (s, k) => summary[k] = L.extend({ unit }, s));
1108
+ this._registerSummary(summary);
1160
1109
  }
1161
1110
  }
1162
1111
  },
1112
+
1113
+ _registerMarker({latlng, sym, content}) {
1114
+ let { wptIcons } = this.options;
1115
+ // generate and cache appropriate icon symbol
1116
+ if (!wptIcons.hasOwnProperty(sym)) {
1117
+ wptIcons[sym] = L.divIcon(L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } ));
1118
+ }
1119
+ let marker = L.marker(latlng, { icon: wptIcons[sym] });
1120
+ if (content) {
1121
+ marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
1122
+ marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
1123
+ }
1124
+ return this._addMarker(marker)
1125
+ },
1163
1126
 
1164
1127
  /**
1165
1128
  * Add chart or marker tooltip info
@@ -24,7 +24,10 @@ export function Altitude() {
24
24
  // // "alt" property is generated inside "leaflet"
25
25
  // if ("alt" in point) point.meta.ele = point.alt;
26
26
  // },
27
- pointToAttr: (_, i) => this._data[i].z * opts.altitudeFactor,
27
+ pointToAttr: (point, i) => {
28
+ if ("alt" in point) point.meta.ele = point.alt; // "alt" property is generated inside "leaflet"
29
+ return this._data[i].z * opts.altitudeFactor;
30
+ },
28
31
  stats: { max: _.iMax, min: _.iMin, avg: _.iAvg },
29
32
  grid: {
30
33
  axis : "y",
package/src/utils.js CHANGED
@@ -88,11 +88,11 @@ export function formatTime(t) {
88
88
  * Loop and extract GPX Extensions handled by "@tmcw/toGeoJSON" (eg. "coordinateProperties" > "times")
89
89
  */
90
90
  export function coordPropsToMeta(coordProps, name, parser) {
91
- return coordProps && (({props, point, id}) => {
91
+ return coordProps && (({props, point, id, isMulti }) => {
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, props[key], id);
95
+ point.meta[name] = (parser || parseInt).call(this, (isMulti ? props[key][isMulti] : props[key]), id);
96
96
  break;
97
97
  }
98
98
  }