@raruto/leaflet-elevation 2.0.1 → 2.0.4

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)}});
@@ -164,7 +164,7 @@ L.GpxGroup = L.Class.extend({
164
164
  .then(text => this._elevation._parseFromString(text))
165
165
  .then(geojson => {
166
166
  if(geojson) {
167
- geojson.name = geojson.name ?? (geojson[0] && geojson[0].properties.name) ?? track.split('/').pop().split('#')[0].split('?')[0];
167
+ geojson.name = geojson.name || (geojson[0] && geojson[0].properties.name) || track.split('/').pop().split('#')[0].split('?')[0];
168
168
  this._loadRoute(geojson);
169
169
  }
170
170
  });
@@ -1 +1 @@
1
- L.Mixin.Selectable={includes:L.Mixin.Events,setSelected:function(s){var selected=!!s;this._selected!==selected&&(this._selected=selected,this.fire("selected"))},isSelected:function(){return!!this._selected}},L.Mixin.Selection={includes:L.Mixin.Events,getSelection:function(){return this._selected},setSelection:function(item){this._selected===item?null!==item&&(item.setSelected(!item.isSelected()),item.isSelected()||(this._selected=null)):(this._selected&&this._selected.setSelected(!1),this._selected=item,this._selected&&this._selected.setSelected(!0)),this.fire("selection_changed")}},L.Control.LayersLegend=L.Control.Layers.extend({_onInputClick:function(){var inputs=this._layerControlInputs,input,layer,addedLayers=[],removedLayers=[];this._handlingClick=!0;for(var i=inputs.length-1;i>=0;i--)input=inputs[i],layer=this._getLayer(input.layerId).layer,input.checked&&this._map.fireEvent("legend_selected",{layer:layer,input:input},!0);this._handlingClick=!1,this._refocusOnMap()}}),L.control.layersLegend=function(baseLayers,overlays,options){return new L.Control.LayersLegend(baseLayers,overlays,options)},L.GeoJSON.include(L.Mixin.Selectable),L.GpxGroup=L.Class.extend({options:{highlight:{color:"#ff0",opacity:1},points:[],points_options:{icon:{iconUrl:"../images/elevation-poi.png",iconSize:[12,12]}},flyToBounds:!0,legend:!1,legend_options:{position:"topright",collapsed:!1},elevation:!0,elevation_options:{theme:"yellow-theme",detachedView:!0,elevationDiv:"#elevation-div"},distanceMarkers:!0,distanceMarkers_options:{lazy:!0}},initialize:function(tracks,options){L.Util.setOptions(this,options),this._count=0,this._loadedCount=0,this._tracks=tracks,this._routes={},this._layers=L.featureGroup(),this._markers=L.featureGroup(),this._elevation=L.control.elevation(this.options.elevation_options),this._legend=L.control.layersLegend(null,null,this.options.legend_options);var icon=L.icon(this.options.points_options.icon);this.options.points.forEach((function(poi){var marker=L.marker(poi.latlng,{icon:icon});marker.bindTooltip(poi.name,{direction:"auto"}),marker.addTo(this._markers)}),this),void 0===L.DistanceMarkers&&(this.options.distanceMarkers=!1)},getBounds:function(){return this._layers.getBounds()},addTo:function(map){this._layers.addTo(map),this._markers.addTo(map),this._map=map,this.on("selection_changed",this._onSelectionChanged,this),this._map.on("legend_selected",this._onLegendSelected,this),this._tracks.forEach(this.addTrack,this)},addTrack:function(track){fetch(track).then(response=>{response.ok&&response.text().then(doc=>{let geojson;doc=doc.trim();try{let xml=(new DOMParser).parseFromString(doc,"text/xml"),type=xml.documentElement.tagName.toLowerCase(),name=xml.getElementsByTagName("name");type in toGeoJSON||(type="TrainingCenterDatabase"==xml.documentElement.tagName?"tcx":"gpx"),geojson=toGeoJSON[type](xml),geojson.name=name.length>0?Array.from(name).find(tag=>"trk"==tag.parentElement.tagName).textContent:""}catch(e){try{geojson=JSON.parse(doc.toString())}catch(e){console.warn("Error parsing track: "+track)}}geojson.name||(geojson.name=track.split("/").pop().split("#")[0].split("?")[0]),this._loadRoute(geojson)})})},_loadRoute:function(data){if(data){var colors,color,line_style={color:this._uniqueColors(this._tracks.length)[this._count++],opacity:.75,weight:5,distanceMarkers:this.options.distanceMarkers_options},route=L.geoJson(data,{name:data.name||"",style:feature=>line_style,distanceMarkers:line_style.distanceMarkers,originalStyle:line_style,filter:feature=>"Point"!=feature.geometry.type});route.addTo(this._layers),this._routes[route._leaflet_id]=route,route.eachLayer(layer=>this._onRouteLayer(route,layer)),this._onRouteLoaded(route)}},_onRouteLayer:function(route,layer){var polyline=layer;route.on("selected",L.bind(this._onRouteSelected,this,route,polyline)),polyline.on("mouseover",L.bind(this._onRouteMouseOver,this,route,polyline)),polyline.on("mouseout",L.bind(this._onRouteMouseOut,this,route,polyline)),polyline.on("click",L.bind(this._onRouteClick,this,route,polyline)),polyline.bindTooltip(route.options.name,{direction:"auto",sticky:!0})},highlight:function(route,polyline){polyline.setStyle(this.options.highlight),this.options.distanceMarkers&&polyline.addDistanceMarkers()},unhighlight:function(route,polyline){polyline.setStyle(route.options.originalStyle),this.options.distanceMarkers&&polyline.removeDistanceMarkers()},_onRouteMouseOver:function(route,polyline){route.isSelected()||(this.highlight(route,polyline),this.options.legend&&(this.setSelection(route),L.DomUtil.get("legend_"+route._leaflet_id).parentNode.previousSibling.click())),this.fire("route_mouseover",{route:route,polyline:polyline})},_onRouteMouseOut:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline),this.fire("route_mouseout",{route:route,polyline:polyline})},_onRouteClick:function(route,polyline){this.setSelection(route)},_onRouteSelected:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline)},_onRouteLoaded:function(route){this.options.legend&&this._legend.addBaseLayer(route,'<svg id="legend_'+route._leaflet_id+'" width="25" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg"><line x1="0" x2="50" y1="5" y2="5" stroke="'+route.options.originalStyle.color+'" fill="transparent" stroke-width="5" /></svg> '+route.options.name),this.fire("route_loaded",{route:route}),++this._loadedCount===this._tracks.length&&(this.fire("loaded"),this.options.flyToBounds&&this._map.flyToBounds(this.getBounds(),{duration:.25,easeLinearity:.25,noMoveStart:!0}),this.options.legend&&this._legend.addTo(this._map))},_onSelectionChanged:function(e){var elevation=this._elevation,eleDiv=elevation.getContainer(),route=this.getSelection();elevation.clear(),route&&route.isSelected()?(eleDiv||elevation.loadChart(this._map),route.getLayers().forEach((function(layer){layer instanceof L.Polyline&&(elevation.addData(layer,!1),layer.bringToFront())}))):eleDiv&&elevation.remove()},_onLegendSelected:function(e){var layers=this._layers._layers,route=e.layer;if(!route.isSelected()){for(var i in this.setSelection(route),route._layers)this.highlight(route,route._layers[i]);this._map.flyToBounds(e.layer.getBounds())}var parent=e.input.closest(".leaflet-control-layers-list"),offset=e.input.offsetTop-parent.offsetTop;for(var j in parent.scroll({top:offset||0,behavior:"smooth"}),layers){var selected=layers[j].isSelected(),legend=L.DomUtil.get("legend_"+layers[j]._leaflet_id);legend.querySelector("line").style.stroke=selected?this.options.highlight.color:"",legend.parentNode.style.fontWeight=selected?"700":""}},_uniqueColors:function(count){if(0===count)return[];if(1===count)return["#0000ff"];for(var increment=1/count,colors=[],i=0;i<count;++i){var hue=i*increment,rgb=this._hsvToRgb(hue,1,.7),hex="#"+this._rgbToHex(rgb[0],rgb[1],rgb[2]);colors.push(hex)}return colors},_hsvToRgb:function(h,s,v){var r,g,b,i=Math.floor(6*h),f=6*h-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s);switch(i%6){case 0:r=v,g=t,b=p;break;case 1:r=q,g=v,b=p;break;case 2:r=p,g=v,b=t;break;case 3:r=p,g=q,b=v;break;case 4:r=t,g=p,b=v;break;case 5:r=v,g=p,b=q}return[255*r,255*g,255*b]},_rgbToHex:function(r,g,b){return this._byteToHex(r)+this._byteToHex(g)+this._byteToHex(b)},_byteToHex:function(n){return(n>>4&15).toString(16)+(15&n).toString(16)},removeFrom:function(map){this._layers.removeFrom(map)}}),L.GpxGroup.include(L.Mixin.Events),L.GpxGroup.include(L.Mixin.Selection),L.gpxGroup=function(tracks,options){return new L.GpxGroup(tracks,options)};
1
+ L.Mixin.Selectable={includes:L.Mixin.Events,setSelected:function(s){var selected=!!s;this._selected!==selected&&(this._selected=selected,this.fire("selected"))},isSelected:function(){return!!this._selected}},L.Mixin.Selection={includes:L.Mixin.Events,getSelection:function(){return this._selected},setSelection:function(item){this._selected===item?null!==item&&(item.setSelected(!item.isSelected()),item.isSelected()||(this._selected=null)):(this._selected&&this._selected.setSelected(!1),this._selected=item,this._selected&&this._selected.setSelected(!0)),this.fire("selection_changed")}},L.Control.LayersLegend=L.Control.Layers.extend({_onInputClick:function(){this._handlingClick=!0,this._layerControlInputs.reduceRight((_,input)=>{if(input.checked)return this._map.fireEvent("legend_selected",{layer:this._getLayer(input.layerId).layer,input:input},!0),input},0),this._handlingClick=!1,this._refocusOnMap()}}),L.control.layersLegend=(baseLayers,overlays,options)=>new L.Control.LayersLegend(baseLayers,overlays,options),L.GeoJSON.include(L.Mixin.Selectable),L.GpxGroup=L.Class.extend({options:{highlight:{color:"#ff0",opacity:1},points:[],points_options:{icon:{iconUrl:"../images/elevation-poi.png",iconSize:[12,12]}},flyToBounds:!0,legend:!1,legend_options:{position:"topright",collapsed:!1},elevation:!0,elevation_options:{theme:"yellow-theme",detached:!0,elevationDiv:"#elevation-div"},distanceMarkers:!0,distanceMarkers_options:{lazy:!0}},initialize:function(tracks,options){L.Util.setOptions(this,options),this._count=0,this._loadedCount=0,this._tracks=tracks,this._layers=L.featureGroup(),this._markers=L.featureGroup(),this._elevation=L.control.elevation(this.options.elevation_options),this._legend=L.control.layersLegend(null,null,this.options.legend_options),this.options.points.forEach(poi=>L.marker(poi.latlng,{icon:L.icon(this.options.points_options.icon)}).bindTooltip(poi.name,{direction:"auto"}).addTo(this._markers))},getBounds:function(){return this._layers.getBounds()},addTo:function(map){this._layers.addTo(map),this._markers.addTo(map),this._map=map,this.on("selection_changed",this._onSelectionChanged,this),this._map.on("legend_selected",this._onLegendSelected,this),this._tracks.forEach(this.addTrack,this)},addTrack:function(track){fetch(track).then(response=>response.ok&&response.text()).then(text=>this._elevation._parseFromString(text)).then(geojson=>{geojson&&(geojson.name=geojson.name||geojson[0]&&geojson[0].properties.name||track.split("/").pop().split("#")[0].split("?")[0],this._loadRoute(geojson))})},_loadRoute:function(data){if(data){var line_style={color:this._uniqueColors(this._tracks.length)[this._count++],opacity:.75,weight:5,distanceMarkers:this.options.distanceMarkers_options},route=L.geoJson(data,{name:data.name||"",style:feature=>line_style,distanceMarkers:line_style.distanceMarkers,originalStyle:line_style,filter:feature=>"Point"!=feature.geometry.type});this._elevation.import(this._elevation.__LGEOMUTIL).then(()=>{route.addTo(this._layers),route.eachLayer(layer=>this._onEachRouteLayer(route,layer)),this._onEachRouteLoaded(route)})}},_onEachRouteLayer:function(route,layer){var polyline=layer;route.on("selected",L.bind(this._onRouteSelected,this,route,polyline)),polyline.on("mouseover",L.bind(this._onRouteMouseOver,this,route,polyline)),polyline.on("mouseout",L.bind(this._onRouteMouseOut,this,route,polyline)),polyline.on("click",L.bind(this._onRouteClick,this,route,polyline)),polyline.bindTooltip(route.options.name,{direction:"auto",sticky:!0})},_onEachRouteLoaded:function(route){this.options.legend&&this._legend.addBaseLayer(route,'<svg id="legend_'+route._leaflet_id+'" width="25" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg"><line x1="0" x2="50" y1="5" y2="5" stroke="'+route.options.originalStyle.color+'" fill="transparent" stroke-width="5" /></svg> '+route.options.name),this.fire("route_loaded",{route:route}),++this._loadedCount===this._tracks.length&&(this.fire("loaded"),this.options.flyToBounds&&this._map.flyToBounds(this.getBounds(),{duration:.25,easeLinearity:.25,noMoveStart:!0}),this.options.legend&&this._legend.addTo(this._map))},highlight:function(route,polyline){polyline.setStyle(this.options.highlight),this.options.distanceMarkers&&polyline.addDistanceMarkers()},unhighlight:function(route,polyline){polyline.setStyle(route.options.originalStyle),this.options.distanceMarkers&&polyline.removeDistanceMarkers()},_onRouteMouseOver:function(route,polyline){route.isSelected()||(this.highlight(route,polyline),this.options.legend&&(this.setSelection(route),L.DomUtil.get("legend_"+route._leaflet_id).parentNode.previousSibling.click())),this.fire("route_mouseover",{route:route,polyline:polyline})},_onRouteMouseOut:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline),this.fire("route_mouseout",{route:route,polyline:polyline})},_onRouteClick:function(route,polyline){this.setSelection(route)},_onRouteSelected:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline)},_onSelectionChanged:function(e){var elevation=this._elevation,eleDiv=elevation.getContainer(),route=this.getSelection();elevation.clear(),route&&route.isSelected()?(eleDiv||elevation.addTo(this._map),route.getLayers().forEach((function(layer){layer instanceof L.Polyline&&(elevation.addData(layer,!1),layer.bringToFront())}))):eleDiv&&elevation.remove()},_onLegendSelected:function(e){var parent=e.input.closest(".leaflet-control-layers-list"),route=e.layer;if(!route.isSelected()){for(var i in this.setSelection(route),route._layers)this.highlight(route,route._layers[i]);this._map.flyToBounds(e.layer.getBounds())}parent.scroll({top:e.input.offsetTop-parent.offsetTop||0,behavior:"smooth"}),this._layers.eachLayer(layer=>{var legend=L.DomUtil.get("legend_"+layer._leaflet_id);legend.querySelector("line").style.stroke=layer.isSelected()?this.options.highlight.color:"",legend.parentNode.style.fontWeight=layer.isSelected()?"700":""})},_uniqueColors:function(count){return 1===count?["#0000ff"]:new Array(count).fill(null).map((_,i)=>this._hsvToHex(i*(1/count),1,.7))},_hsvToHex:function(h,s,v){var i=Math.floor(6*h),f=6*h-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),rgb;return{0:[v,t,p],1:[q,v,p],2:[p,v,t],3:[p,q,v],4:[t,p,v],5:[v,p,q]}[i%6].map(d=>255*d).reduce((hex,byte)=>hex+(byte>>4&15).toString(16)+(15&byte).toString(16),"#")},removeFrom:function(map){this._layers.removeFrom(map)}}),L.GpxGroup.include(L.Mixin.Events),L.GpxGroup.include(L.Mixin.Selection),L.gpxGroup=(tracks,options)=>new L.GpxGroup(tracks,options);
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.4",
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",
@@ -85,6 +85,7 @@ export var Chart = L.Control.Elevation.Chart = L.Class.extend({
85
85
  this._updateScale();
86
86
  this._updateArea();
87
87
  this._updateAxis();
88
+ this._updateMargins();
88
89
  this._updateLegend();
89
90
  this._updateClipper();
90
91
 
@@ -350,7 +351,7 @@ export var Chart = L.Control.Elevation.Chart = L.Class.extend({
350
351
  if (typeof props.preferCanvas === "undefined") props.preferCanvas = opts.preferCanvas;
351
352
 
352
353
  // Save paths in memory for latter usage
353
- this._paths[props.name] = D3.Path(props);;
354
+ this._paths[props.name] = D3.Path(props);
354
355
  this._props.areas[props.name] = props;
355
356
 
356
357
  if (opts.legend) {
@@ -536,80 +537,92 @@ export var Chart = L.Control.Elevation.Chart = L.Class.extend({
536
537
  },
537
538
 
538
539
  _updateLegend: function () {
539
-
540
- if (this.options.legend === false) return;
541
-
542
- const legendOpts = {
543
- width : this._width(),
544
- height : this._height(),
545
- margins: this.options.margins,
546
- };
547
-
548
- // Reset legend items
549
- this._legend.selectAll('g').remove();
550
- _.each(this._props.legendItems, (legend) => {
551
- this._legend.append("g").call(D3.LegendItem(L.extend(legendOpts, legend)));
552
- });
553
-
540
+ let xAxesB = this._axis.selectAll('.x.axis.bottom').nodes().length;
541
+
554
542
  // Get legend items
555
- let items = this._legend.selectAll('.legend-item');
543
+ let items = Object.keys(this._paths);
556
544
 
557
545
  // Calculate legend item positions
558
- let n = items.nodes().length;
546
+ let n = items.length;
559
547
  let v = Array(Math.floor(n / 2)).fill(null).map((d, i) => (i + 1) * 2 - (1 - Math.sign(n % 2)));
560
548
  let rev = v.slice().reverse().map((d) => -(d));
561
549
 
550
+ // push a fake element to handle center alignment (odd numbers)
562
551
  if (n % 2 !== 0) {
563
552
  rev.push(0);
564
553
  }
565
554
  v = rev.concat(v);
566
555
 
567
- // Get chart margins
568
- let xAxesB = this._axis.selectAll('.x.axis.bottom').nodes().length;
569
- let marginB = 30 + (xAxesB * 2);
570
- let marginR = n * 30;
556
+ // Reset legend items
557
+ this._legend.selectAll('g').remove();
571
558
 
572
- // Adjust chart right margins
573
- if (n && this.options.margins.right < marginR) {
574
- this.options.margins.right = marginR;
575
- this.fire('margins_updated');
576
- }
559
+ // Render legend items
560
+ _.each(this._props.legendItems, (legend) => {
561
+ this._legend.append("g").call(
562
+ D3.LegendItem(L.extend({
563
+ width : this._width(),
564
+ height : this._height(),
565
+ margins: this.options.margins,
566
+ }, legend))
567
+ );
568
+ });
577
569
 
578
- // Adjust chart bottom margins
579
- if(xAxesB && this.options.margins.bottom < marginB) {
580
- this.options.margins.bottom = marginB;
581
- this.fire('margins_updated');
570
+ // Render legend item switcher
571
+ if (n > 1) {
572
+ this._legend.append("g").call(
573
+ D3.LegendSmall({
574
+ width : this._width(),
575
+ height : this._height(),
576
+ items : items,
577
+ onClick: (selected) => {
578
+ _.each(items, name => this._togglePath(name, selected == name, true));
579
+ this._updateArea();
580
+ }
581
+ })
582
+ );
582
583
  }
584
+
585
+ _.each(items, (name, i) => {
586
+ // Adjust legend item positions
587
+ this._legend.select('[data-name=' + name + ']').attr("transform", "translate(" + (v[i] * 55) + ", " + (xAxesB * 2) + ")");
588
+ // Set initial state (disabled controls)
589
+ this._togglePath(name, !(name in this.options && this.options[name] == 'disabled'), true);
590
+ });
583
591
 
584
- items
585
- .each((d, i, n) => {
586
-
587
- let legend = d3.select(n[i]);
588
- let rect = legend.select('rect');
589
- let name = legend.attr('data-name');
590
- let path = this._paths[name];
592
+ },
591
593
 
592
- let tx = v[i] * 55;
593
- let ty = xAxesB * 2;
594
+ _updateMargins: function() {
595
+ // Get chart margins
596
+ let xAxesB = this._axis.selectAll('.x.axis.bottom').nodes().length;
597
+ let xAxesL = this._axis.selectAll('.y.axis.left').nodes().length;
598
+ let xAxesR = this._axis.selectAll('.y.axis.right').nodes().length;
599
+ let marginB = 60 + (xAxesB * 2);
600
+ let marginL = 10 + (xAxesL * 30);
601
+ let marginR = 40 + (xAxesR * 30);
594
602
 
595
- legend
603
+ let marginsUpdated = false
596
604
 
597
- // Adjust legend item positions
598
- .attr("transform", "translate(" + tx + ", " + ty + ")");
605
+ // Adjust right margin
606
+ if (xAxesR && this.options.margins.right < marginR) {
607
+ this.options.margins.right = marginR;
608
+ marginsUpdated = true;
609
+ }
599
610
 
600
- // Set initial state (disabled controls)
601
- if (name in this.options && this.options[name] == 'disabled') {
602
- path.classed('leaflet-hidden', true);
603
- legend.select('text').style('text-decoration-line', 'line-through');
604
- legend.select('rect').style('fill-opacity', '0');
605
- }
611
+ // Adjust left margin
612
+ if(xAxesL && this.options.margins.left < marginL) {
613
+ this.options.margins.left = marginL;
614
+ marginsUpdated = true;
615
+ }
606
616
 
607
- // Apply d3-zoom (bind <clipPath> mask)
608
- if (this._mask) {
609
- path.attr('mask', 'url(#' + this._mask.attr('id') + ')');
610
- }
617
+ // Adjust bottom margin
618
+ if(xAxesB && this.options.margins.bottom < marginB) {
619
+ this.options.margins.bottom = marginB;
620
+ marginsUpdated = true;
621
+ }
611
622
 
612
- });
623
+ if (marginsUpdated) {
624
+ this.fire('margins_updated');
625
+ }
613
626
  },
614
627
 
615
628
  _updateScale: function() {
@@ -733,6 +746,21 @@ export var Chart = L.Control.Elevation.Chart = L.Class.extend({
733
746
  }));
734
747
  },
735
748
 
749
+ _togglePath(name, enabled = true, lazy = false) {
750
+ let path = this._paths[name];
751
+ let legend = this._container.select('.legend [data-name=' + name + ']');
752
+ path.classed('leaflet-hidden', !enabled);
753
+ legend.select('text').style('text-decoration-line', enabled ? '' : 'line-through');
754
+ legend.select('rect').style('fill-opacity', enabled ? '': '0');
755
+ // Apply d3-zoom (bind <clipPath> mask)
756
+ if (this._mask) {
757
+ path.attr('mask', 'url(#' + this._mask.attr('id') + ')');
758
+ }
759
+ if (!lazy) {
760
+ this._updateArea();
761
+ }
762
+ },
763
+
736
764
  _hideDiagramIndicator: function() {
737
765
  this._tooltip.attr("display", 'none');
738
766
  },
@@ -205,7 +205,6 @@ export const LegendItem = ({
205
205
  .text(L._(label || name))
206
206
  .attr("x", (width / 2) + 5)
207
207
  .attr("font-size", 10)
208
- .style("text-decoration-thickness", "2px")
209
208
  .style("font-weight", "700")
210
209
  .attr('y', height + margins.bottom / 2)
211
210
  .attr('dy', "0.75em");
@@ -214,6 +213,70 @@ export const LegendItem = ({
214
213
  }
215
214
  };
216
215
 
216
+ export const LegendSmall = ({
217
+ width,
218
+ height,
219
+ items,
220
+ onClick
221
+ }) => {
222
+ return g => {
223
+ let idx = 0;
224
+
225
+ g.data([{
226
+ x: width,
227
+ y: height + 40
228
+ }]).attr("transform", d => "translate(" + d.x + "," + d.y + ")").classed('legend-switcher');
229
+
230
+ // let label = g.selectAll(".legend-switcher-label") .data([{ idx: 0 }]);
231
+ let label = g.append('svg:text')
232
+ .attr("class", "legend-switcher-label")
233
+ .attr("text-anchor", "end")
234
+ .attr("x", -25)
235
+ .attr("y", 5)
236
+ .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
237
+
238
+ let symbol = g.selectAll(".legend-switcher-symbol").data([
239
+ { type: d3.symbolTriangle, x: 0, y: 3, angle: 0, size: 50, id: "down" },
240
+ { type: d3.symbolTriangle, x: -13, y: 0, angle: 180, size: 50, id: "up" }
241
+ ]);
242
+
243
+ symbol.exit().remove();
244
+ label.exit().remove();
245
+
246
+ symbol.enter()
247
+ .append("svg:path")
248
+ .attr("class", "legend-switcher-symbol")
249
+ .attr("cursor", 'pointer')
250
+ .attr("fill", "#000")
251
+ .merge(symbol)
252
+ .attr("d",
253
+ d3.symbol()
254
+ .type(d => d.type)
255
+ .size(d => d.size)
256
+ )
257
+ .attr("transform", d => "translate(" + d.x + "," + d.y + ") rotate(" + d.angle + ")")
258
+ .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((d.id === "up" ? idx + 1 : idx - 1), [0, items.length])));
259
+
260
+ const setIdx = (id) => {
261
+ idx = id;
262
+ // label.enter()
263
+ // .append('text')
264
+ // .attr("class", "legend-switcher-label")
265
+ // .attr("text-anchor", "end")
266
+ // .attr("x", -25)
267
+ // .attr("y", 5)
268
+ // .merge(label.data([{ idx: id }]))
269
+ // .text(d => items.length ? (items[idx][0].toUpperCase() + items[idx].slice(1)) : '')
270
+ // .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
271
+ label.text(items.length ? (items[idx][0].toUpperCase() + items[idx].slice(1)) : '');
272
+ onClick(items[idx]);
273
+ };
274
+ setIdx(0);
275
+
276
+ return g;
277
+ };
278
+ };
279
+
217
280
  export const Tooltip = ({
218
281
  xCoord,
219
282
  yCoord,
@@ -300,27 +363,21 @@ export const Ruler = ({
300
363
  return g => {
301
364
 
302
365
  g.data([{
303
- "x": 0,
304
- "y": height,
366
+ x: 0,
367
+ y: height,
305
368
  }])
306
369
  .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
307
370
 
308
- let rect = g.selectAll('.horizontal-drag-rect')
309
- .data([{ w: width }]);
310
-
311
- let line = g.selectAll('.horizontal-drag-line')
312
- .data([{ w: width }]);
313
-
314
- let label = g.selectAll('.horizontal-drag-label')
315
- .data([{ w: width - 8 }]);
316
-
371
+ let rect = g.selectAll('.horizontal-drag-rect').data([{ w: width }]);
372
+ let line = g.selectAll('.horizontal-drag-line').data([{ w: width }]);
373
+ let label = g.selectAll('.horizontal-drag-label').data([{ w: width - 8 }]);
317
374
  let symbol = g.selectAll('.horizontal-drag-symbol')
318
375
  .data([{
319
- "type": d3.symbolTriangle,
320
- "x": width + 7,
321
- "y": 0,
322
- "angle": -90,
323
- "size": 50
376
+ type: d3.symbolTriangle,
377
+ x: width + 7,
378
+ y: 0,
379
+ angle: -90,
380
+ size: 50
324
381
  }]);
325
382
 
326
383
  rect.exit().remove();