├── .gitignore ├── others └── images │ ├── leaflet.png │ ├── example-map.png │ ├── example-track.png │ ├── example-marker.png │ ├── example-multi-marker.png │ └── example-track-graphDetached.png ├── static ├── img │ ├── markers_default.png │ ├── markers_shadow.png │ ├── markers_default@2x.png │ └── markers_shadow@2x.png ├── css │ └── leaflet.extra-markers.min.css └── js │ ├── leaflet.extra-markers.min.js │ ├── leaflet.hugo.js │ ├── leaflet.extra-markers.js.map │ └── leaflet.elevation.js ├── layouts ├── partials │ └── leaflet-loader.html └── shortcodes │ ├── leaflet-marker.html │ ├── leaflet-map.html │ └── leaflet-track.html ├── config.yaml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /others/images/leaflet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/leaflet.png -------------------------------------------------------------------------------- /others/images/example-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/example-map.png -------------------------------------------------------------------------------- /others/images/example-track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/example-track.png -------------------------------------------------------------------------------- /static/img/markers_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/static/img/markers_default.png -------------------------------------------------------------------------------- /static/img/markers_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/static/img/markers_shadow.png -------------------------------------------------------------------------------- /others/images/example-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/example-marker.png -------------------------------------------------------------------------------- /static/img/markers_default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/static/img/markers_default@2x.png -------------------------------------------------------------------------------- /static/img/markers_shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/static/img/markers_shadow@2x.png -------------------------------------------------------------------------------- /others/images/example-multi-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/example-multi-marker.png -------------------------------------------------------------------------------- /others/images/example-track-graphDetached.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altrdev/hugo-leaflet/HEAD/others/images/example-track-graphDetached.png -------------------------------------------------------------------------------- /layouts/partials/leaflet-loader.html: -------------------------------------------------------------------------------- 1 | {{ range $.Site.Params.hugoLeaflet.css }} 2 | 3 | {{ end }} 4 | 5 | {{ range $.Site.Params.hugoLeaflet.js }} 6 | 7 | {{ end }} 8 | 9 | 22 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | 2 | params: 3 | hugoLeaflet: 4 | css: 5 | - href: "https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" 6 | params: 7 | integrity: "sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" 8 | crossorigin: "" 9 | - href: "https://unpkg.com/@raruto/leaflet-elevation@1.1.1/dist/leaflet-elevation.css" 10 | - href: "css/leaflet.extra-markers.min.css" 11 | js: 12 | - src: "https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" 13 | params: 14 | integrity: "sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" 15 | crossorigin: "" 16 | - src: "js/leaflet.elevation.js" 17 | - src: "js/leaflet.hugo.js" 18 | - src: "js/leaflet.extra-markers.min.js" 19 | -------------------------------------------------------------------------------- /layouts/shortcodes/leaflet-marker.html: -------------------------------------------------------------------------------- 1 | {{ if .IsNamedParams }} 2 | 3 | {{ $markerLat := default "" (.Get "markerLat") }} 4 | {{ $markerLon := default "" (.Get "markerLon") }} 5 | {{ $markerContent := default "" (.Get "markerContent") }} 6 | 7 | {{ with .Parent }} 8 | 9 | {{ $mapLat := default "" (.Get "mapLat") }} 10 | {{ $mapLon := default "" (.Get "mapLon") }} 11 | {{ $mapId := default (md5 (printf "%s%s" $mapLat $mapLon)) (.Get "mapId") }} 12 | {{ $markerId := md5 (printf "%s%s%s" $mapId $markerLat $markerLon)}} 13 | 14 |
15 | 16 | {{ else }} 17 | {{ errorf "Leaflet Hugo Shortcode: impossible using marker outside leaflet-map" }} 18 | {{ end }} 19 | 20 | {{ else }} 21 | {{ errorf "Leaflet Hugo Shortcode: please provide named Parameters for marker" }} 22 | {{ end }} 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /layouts/shortcodes/leaflet-map.html: -------------------------------------------------------------------------------- 1 | {{ if .IsNamedParams }} 2 | 3 | {{ $mapLat := default "" (.Get "mapLat") }} 4 | {{ $mapLon := default "" (.Get "mapLon") }} 5 | {{ $zoom := default "13" (.Get "zoom") }} 6 | {{ $mapWidth := default "100%" (.Get "mapWidth") }} 7 | {{ $mapHeight := default "400px" (.Get "mapHeight") }} 8 | {{ $mapMargin := default "0px" (.Get "mapMargin") }} 9 | {{ $mapId := default (md5 (printf "%s%s" $mapLat $mapLon)) (.Get "mapId") }} 10 | {{ $scrollWheelZoom := default "true" (.Get "scrollWheelZoom") }} 11 | {{ $mapDescription := default "" (.Get "mapDescription") }} 12 | 13 | 14 |
15 | 16 | {{.Inner}} 17 | 18 | {{ else }} 19 | {{ errorf "Leaflet Hugo Shortcode: please provide named Parameters" }} 20 | {{ end }} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alessandro Travi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /layouts/shortcodes/leaflet-track.html: -------------------------------------------------------------------------------- 1 | {{ if .IsNamedParams }} 2 | 3 | {{ $trackPath := default "" (.Get "trackPath") }} 4 | {{ $lineColor := default "#006EFF" (.Get "lineColor") }} 5 | {{ $lineWeight := default "3" (.Get "lineWeight") }} 6 | {{ $lineOpacity := default "1" (.Get "lineOpacity") }} 7 | 8 | {{ $graphPosition := default "topright" (.Get "graphPosition") }} 9 | {{ $graphTheme := default "steelblue-theme" (.Get "graphTheme") }} 10 | {{ $graphWidth := default "500" (.Get "graphWidth") }} 11 | {{ $graphHeight := default "150" (.Get "graphHeight") }} 12 | {{ $graphFollowMarker := default false (.Get "graphFollowMarker") }} 13 | {{ $graphCollapsed := default false (.Get "graphCollapsed") }} 14 | {{ $graphDetached := default true (.Get "graphDetached") }} 15 | 16 | {{ $markerIcon := default "fa-thumb-tack" (.Get "markerIcon") }} 17 | {{ $markerIconColor := default "cyan" (.Get "markerIconColor") }} 18 | {{ $markerIconShape := default "penta" (.Get "markerIconShape") }} 19 | {{ $markerIconClasses := default "fa-icon-marker" (.Get "markerIconClasses") }} 20 | {{ $markerStartIcon := default "fa-play" (.Get "markerStartIcon") }} 21 | {{ $markerStartIconColor := default "green-light" (.Get "markerStartIconColor") }} 22 | {{ $markerStartIconShape := default "circle" (.Get "markerStartIconShape") }} 23 | {{ $markerStartIconClasses := default "fa-icon-marker fa-icon-start-stop" (.Get "markerStartIconClasses") }} 24 | {{ $markerEndIcon := default "fa-flag-checkered" (.Get "markerEndIcon") }} 25 | {{ $markerEndIconColor := default "red" (.Get "markerEndIconColor") }} 26 | {{ $markerEndIconShape := default "circle" (.Get "markerEndIconShape") }} 27 | {{ $markerEndIconClasses := default "fa-icon-marker fa-icon-start-stop" (.Get "markerEndIconClasses") }} 28 | 29 | {{ with .Parent }} 30 | 31 | {{ $mapLat := default "" (.Get "mapLat") }} 32 | {{ $mapLon := default "" (.Get "mapLon") }} 33 | {{ $mapId := default (md5 (printf "%s%s" $mapLat $mapLon)) (.Get "mapId") }} 34 | {{ $trackId := md5 (printf "%s%s" $mapId $trackPath)}} 35 | 36 | 70 | 71 |
72 | 73 | {{ else }} 74 | {{ errorf "Leaflet Hugo Shortcode: impossible using track outside leaflet-map" }} 75 | {{ end }} 76 | 77 | {{ else }} 78 | {{ errorf "Leaflet Hugo Shortcode: please provide named Parameters for marker" }} 79 | {{ end }} 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /static/css/leaflet.extra-markers.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * leaflet-extra-markers 3 | * Custom Markers for Leaflet JS based on Awesome Markers 4 | * Leaflet ExtraMarkers 5 | * https://github.com/coryasilva/Leaflet.ExtraMarkers/ 6 | * @author coryasilva 7 | * @version 1.2.1 8 | */.extra-marker{background:url("../img/markers_default.png") no-repeat 0 0;width:35px;height:46px;position:absolute;left:0;top:0;display:block;text-align:center} .extra-marker-shadow{background:url("../img/markers_shadow.png") no-repeat 0 0;width:36px;height:16px}@media (min--moz-device-pixel-ratio:1.5),(-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){.extra-marker{background-image:url("../img/markers_default@2x.png");background-size:540px 184px} .extra-marker-shadow{background-image:url("../img/markers_shadow@2x.png");background-size:35px 16px}} .extra-marker.extra-marker-svg{background:none} .extra-marker.extra-marker-svg .svg-inline--fa,.extra-marker.extra-marker-svg i{position:absolute;left:0;width:35px} .extra-marker .svg-inline--fa,.extra-marker i{color:#fff;margin-top:7px;display:inline-block;font-size:14px} .extra-marker .svg-inline--fa{margin-top:10px;background:none} .extra-marker .svg-inline--fa,.extra-marker i.fa,.extra-marker i.fab,.extra-marker i.fas,.extra-marker i.far,.extra-marker i.fal{margin-top:10px} .extra-marker .svg-inline--fa.fa-2x,.extra-marker i.fa.fa-2x,.extra-marker i.fab.fa-2x,.extra-marker i.fas.fa-2x,.extra-marker i.far.fa-2x,.extra-marker i.fal.fa-2x{font-size:16px;margin-top:9px} .extra-marker .svg-inline--fa.fa-3x,.extra-marker i.fa.fa-3x,.extra-marker i.fab.fa-3x,.extra-marker i.fas.fa-3x,.extra-marker i.far.fa-3x,.extra-marker i.fal.fa-3x{font-size:18px;margin-top:9px} .extra-marker .svg-inline--fa.fa-4x,.extra-marker i.fa.fa-4x,.extra-marker i.fab.fa-4x,.extra-marker i.fas.fa-4x,.extra-marker i.far.fa-4x,.extra-marker i.fal.fa-4x{font-size:20px;margin-top:8px} .extra-marker .svg-inline--fa.fa-5x,.extra-marker i.fa.fa-5x,.extra-marker i.fab.fa-5x,.extra-marker i.fas.fa-5x,.extra-marker i.far.fa-5x,.extra-marker i.fal.fa-5x{font-size:24px;margin-top:6px} .extra-marker .fa-number:before{content:attr(number)} .extra-marker i.glyphicon{margin-top:10px} .extra-marker i.icon{margin-right:0;opacity:1} .extra-marker-circle-red{background-position:0 0} .extra-marker-circle-orange-dark{background-position:-36px 0} .extra-marker-circle-orange{background-position:-72px 0} .extra-marker-circle-yellow{background-position:-108px 0} .extra-marker-circle-blue-dark{background-position:-144px 0} .extra-marker-circle-blue{background-position:-180px 0} .extra-marker-circle-cyan{background-position:-216px 0} .extra-marker-circle-purple{background-position:-252px 0} .extra-marker-circle-violet{background-position:-288px 0} .extra-marker-circle-pink{background-position:-324px 0} .extra-marker-circle-green-dark{background-position:-360px 0} .extra-marker-circle-green{background-position:-396px 0} .extra-marker-circle-green-light{background-position:-432px 0} .extra-marker-circle-black{background-position:-468px 0} .extra-marker-circle-white{background-position:-504px 0} .extra-marker-square-red{background-position:0 -46px} .extra-marker-square-orange-dark{background-position:-36px -46px} .extra-marker-square-orange{background-position:-72px -46px} .extra-marker-square-yellow{background-position:-108px -46px} .extra-marker-square-blue-dark{background-position:-144px -46px} .extra-marker-square-blue{background-position:-180px -46px} .extra-marker-square-cyan{background-position:-216px -46px} .extra-marker-square-purple{background-position:-252px -46px} .extra-marker-square-violet{background-position:-288px -46px} .extra-marker-square-pink{background-position:-324px -46px} .extra-marker-square-green-dark{background-position:-360px -46px} .extra-marker-square-green{background-position:-396px -46px} .extra-marker-square-green-light{background-position:-432px -46px} .extra-marker-square-black{background-position:-468px -46px} .extra-marker-square-white{background-position:-504px -46px} .extra-marker-star-red{background-position:0 -92px} .extra-marker-star-orange-dark{background-position:-36px -92px} .extra-marker-star-orange{background-position:-72px -92px} .extra-marker-star-yellow{background-position:-108px -92px} .extra-marker-star-blue-dark{background-position:-144px -92px} .extra-marker-star-blue{background-position:-180px -92px} .extra-marker-star-cyan{background-position:-216px -92px} .extra-marker-star-purple{background-position:-252px -92px} .extra-marker-star-violet{background-position:-288px -92px} .extra-marker-star-pink{background-position:-324px -92px} .extra-marker-star-green-dark{background-position:-360px -92px} .extra-marker-star-green{background-position:-396px -92px} .extra-marker-star-green-light{background-position:-432px -92px} .extra-marker-star-black{background-position:-468px -92px} .extra-marker-star-white{background-position:-504px -92px} .extra-marker-penta-red{background-position:0 -138px} .extra-marker-penta-orange-dark{background-position:-36px -138px} .extra-marker-penta-orange{background-position:-72px -138px} .extra-marker-penta-yellow{background-position:-108px -138px} .extra-marker-penta-blue-dark{background-position:-144px -138px} .extra-marker-penta-blue{background-position:-180px -138px} .extra-marker-penta-cyan{background-position:-216px -138px} .extra-marker-penta-purple{background-position:-252px -138px} .extra-marker-penta-violet{background-position:-288px -138px} .extra-marker-penta-pink{background-position:-324px -138px} .extra-marker-penta-green-dark{background-position:-360px -138px} .extra-marker-penta-green{background-position:-396px -138px} .extra-marker-penta-green-light{background-position:-432px -138px} .extra-marker-penta-black{background-position:-468px -138px} .extra-marker-penta-white{background-position:-504px -138px} 9 | -------------------------------------------------------------------------------- /static/js/leaflet.extra-markers.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * leaflet-extra-markers 3 | * Custom Markers for Leaflet JS based on Awesome Markers 4 | * Leaflet ExtraMarkers 5 | * https://github.com/coryasilva/Leaflet.ExtraMarkers/ 6 | * @author coryasilva 7 | * @version 1.2.1 8 | */ 9 | 10 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.leaflet=e.leaflet||{},e.leaflet["extra-markers"]={}))}(this,function(e){"use strict";var t=L.ExtraMarkers={};t.version=L.ExtraMarkers.version="1.2.1",t.Icon=L.ExtraMarkers.Icon=L.Icon.extend({options:{iconSize:[35,45],iconAnchor:[17,42],popupAnchor:[1,-32],shadowAnchor:[10,12],shadowSize:[36,16],className:"",prefix:"",extraClasses:"",shape:"circle",icon:"",innerHTML:"",markerColor:"red",svgBorderColor:"#fff",svgOpacity:1,iconColor:"#fff",iconRotate:0,number:"",svg:!1},initialize:function(e){e=L.Util.setOptions(this,e)},createIcon:function(){var e=document.createElement("div"),t=this.options;return t.icon&&(e.innerHTML=this._createInner()),t.innerHTML&&(e.innerHTML=t.innerHTML),t.bgPos&&(e.style.backgroundPosition=-t.bgPos.x+"px "+-t.bgPos.y+"px"),t.svg?this._setIconStyles(e,"svg"):this._setIconStyles(e,t.shape+"-"+t.markerColor),e},_getColorHex:function(e){return{red:"#a23337","orange-dark":"#d73e29",orange:"#ef9227",yellow:"#f5bb39","blue-dark":"#276273",cyan:"#32a9dd",purple:"#440444",violet:"#90278d",pink:"#c057a0",green:"#006838",white:"#e8e8e8",black:"#211c1d"}[e]||e},_createSvg:function(e,t){return{circle:'',square:'',star:'',penta:''}[e]},_createInner:function(){var e="",t="",o="",r="",a=this.options;return a.iconColor&&(e="color: "+a.iconColor+";"),0!==a.iconRotate&&(e+="-webkit-transform: rotate("+a.iconRotate+"deg);",e+="-moz-transform: rotate("+a.iconRotate+"deg);",e+="-o-transform: rotate("+a.iconRotate+"deg);",e+="-ms-transform: rotate("+a.iconRotate+"deg);",e+="transform: rotate("+a.iconRotate+"deg);"),a.number&&(t='number="'+a.number+'" '),a.extraClasses.length&&(o+=a.extraClasses+" "),a.prefix.length&&(o+=a.prefix+" "),a.icon.length&&(o+=a.icon+" "),a.svg&&(r+=this._createSvg(a.shape,this._getColorHex(a.markerColor))),r+="'},_setIconStyles:function(e,t){var o,r,a=this.options,n=L.point(a["shadow"===t?"shadowSize":"iconSize"]);r="shadow"===t?(o=L.point(a.shadowAnchor||a.iconAnchor),"shadow"):(o=L.point(a.iconAnchor),"icon"),!o&&n&&(o=n.divideBy(2,!0)),e.className="leaflet-marker-"+r+" extra-marker extra-marker-"+t+" "+a.className,o&&(e.style.marginLeft=-o.x+"px",e.style.marginTop=-o.y+"px"),n&&(e.style.width=n.x+"px",e.style.height=n.y+"px")},createShadow:function(){var e=document.createElement("div");return this._setIconStyles(e,"shadow"),e}}),t.icon=L.ExtraMarkers.icon=function(e){return new L.ExtraMarkers.Icon(e)},e.ExtraMarkers=t,Object.defineProperty(e,"__esModule",{value:!0})}); -------------------------------------------------------------------------------- /static/js/leaflet.hugo.js: -------------------------------------------------------------------------------- 1 | let leafletMapsObj = {}; 2 | let leafletMarkersObj = {}; 3 | 4 | function drawTrack(trackOpts, elevationOpts, markerOpts) { 5 | var opts = { 6 | elevationControl: { 7 | options: { 8 | position: elevationOpts.graphPosition, 9 | theme: elevationOpts.graphTheme, 10 | width: elevationOpts.graphWidth, 11 | height: elevationOpts.graphHeight, 12 | margins: { 13 | top: 20, 14 | right: 20, 15 | bottom: 35, 16 | left: 50 17 | }, 18 | followMarker: elevationOpts.graphFollowMarker, 19 | collapsed: elevationOpts.graphCollapsed, 20 | detached: elevationOpts.graphDetached, 21 | legend: false, 22 | summary: false, 23 | downloadLink: '', 24 | gpxOptions: { 25 | polyline_options: { 26 | className: 'track-' + trackOpts.trackId + '-', 27 | color: trackOpts.lineColor, 28 | opacity: trackOpts.lineOpacity, 29 | weight: trackOpts.lineWeight, 30 | }, 31 | marker_options: { 32 | startIcon: new L.ExtraMarkers.icon({ 33 | icon: markerOpts.iconStart, 34 | markerColor: markerOpts.iconStartColor, 35 | shape: markerOpts.iconStartShape, 36 | prefix: 'fa', 37 | extraClasses: markerOpts.iconStartClasses 38 | }), 39 | endIcon: new L.ExtraMarkers.icon({ 40 | icon: markerOpts.iconEnd, 41 | markerColor: markerOpts.iconEndColor, 42 | shape: markerOpts.iconEndShape, 43 | prefix: 'fa', 44 | extraClasses: markerOpts.iconEndClasses 45 | }), 46 | wptIcons: { 47 | '': new L.ExtraMarkers.icon({ 48 | icon: markerOpts.icon, 49 | markerColor: markerOpts.iconColor, 50 | shape: markerOpts.iconShape, 51 | prefix: 'fa', 52 | extraClasses: markerOpts.iconClasses, 53 | }) 54 | } 55 | } 56 | }, 57 | 58 | }, 59 | }, 60 | }; 61 | 62 | L.control.elevation(opts.elevationControl.options).addTo(leafletMapsObj[trackOpts.mapId]).load(trackOpts.trackPath); 63 | 64 | /*map.on('eledata_loaded', function(e) { 65 | track = e.track_info; 66 | });*/ 67 | } 68 | 69 | window.downloadFile = function (sUrl) { 70 | 71 | //iOS devices do not support downloading. We have to inform user about this. 72 | if (/(iP)/g.test(navigator.userAgent)) { 73 | alert('Your device does not support files downloading. Please try again in desktop browser.'); 74 | return false; 75 | } 76 | 77 | //If in Chrome or Safari - download via virtual link click 78 | if (window.downloadFile.isChrome || window.downloadFile.isSafari) { 79 | //Creating new link node. 80 | var link = document.createElement('a'); 81 | link.href = sUrl; 82 | 83 | if (link.download !== undefined) { 84 | //Set HTML5 download attribute. This will prevent file from opening if supported. 85 | var fileName = sUrl.substring(sUrl.lastIndexOf('/') + 1, sUrl.length); 86 | link.download = fileName; 87 | } 88 | 89 | //Dispatching click event. 90 | if (document.createEvent) { 91 | var e = document.createEvent('MouseEvents'); 92 | e.initEvent('click', true, true); 93 | link.dispatchEvent(e); 94 | return true; 95 | } 96 | } 97 | 98 | // Force file download (whether supported by server). 99 | if (sUrl.indexOf('?') === -1) { 100 | sUrl += '?download'; 101 | } 102 | 103 | window.open(sUrl, '_self'); 104 | return true; 105 | }; 106 | 107 | window.downloadFile.isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; 108 | window.downloadFile.isSafari = navigator.userAgent.toLowerCase().indexOf('safari') > -1; 109 | 110 | function createMap(mapnode) { 111 | mapId=mapnode.getAttribute("mapId") 112 | mapLat=mapnode.getAttribute("mapLat") 113 | mapLon=mapnode.getAttribute("mapLon") 114 | zoom=mapnode.getAttribute("Zoom") 115 | 116 | //Create Map 117 | leafletMapsObj[mapId] = L.map("mapid_" + mapId).setView([mapLat, mapLon], zoom); 118 | //{{ if eq $scrollWheelZoom "false" }} 119 | // leafletMapsObj[{{ $mapId }}].scrollWheelZoom.disable(); 120 | //{{ end }} 121 | //Add tiles 122 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 123 | attribution: '© OpenStreetMap contributors' 124 | }).addTo(leafletMapsObj[mapId]); 125 | }; 126 | 127 | function createMarker(markernode) { 128 | markerId=markernode.getAttribute("markerId") 129 | markerLat=markernode.getAttribute("markerLat") 130 | markerLon=markernode.getAttribute("markerLon") 131 | mapId=markernode.getAttribute("mapId") 132 | //Marker 133 | leafletMarkersObj[markerId] = L.marker([markerLat, markerLon]).addTo(leafletMapsObj[mapId]); 134 | /*{{ if $markerContent }} 135 | leafletMarkersObj[{{ $markerId }}].bindPopup("{{ $markerContent }}").openPopup(); 136 | {{ end }}*/ 137 | }; 138 | 139 | window.onload = function(){ 140 | maps=document.getElementsByClassName("leaflet-map") 141 | Array.from(maps).forEach(createMap) 142 | markers=document.getElementsByClassName("leaflet-marker") 143 | Array.from(markers).forEach(createMarker) 144 | } 145 | -------------------------------------------------------------------------------- /static/js/leaflet.extra-markers.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":null,"sources":["/Users/cory/Projects/Leaflet.ExtraMarkers/src/assets/js/leaflet.extra-markers.js"],"sourcesContent":["export var ExtraMarkers = L.ExtraMarkers = {};\nExtraMarkers.version = L.ExtraMarkers.version = \"1.2.1\";\nExtraMarkers.Icon = L.ExtraMarkers.Icon = L.Icon.extend({\n options: {\n iconSize: [ 35, 45 ],\n iconAnchor: [ 17, 42 ],\n popupAnchor: [ 1, -32 ],\n shadowAnchor: [ 10, 12 ],\n shadowSize: [ 36, 16 ],\n className: \"\",\n prefix: \"\",\n extraClasses: \"\",\n shape: \"circle\",\n icon: \"\",\n innerHTML: \"\",\n markerColor: \"red\",\n svgBorderColor: \"#fff\",\n svgOpacity: 1,\n iconColor: \"#fff\",\n iconRotate: 0,\n number: \"\",\n svg: false\n },\n initialize: function(options) {\n options = L.Util.setOptions(this, options);\n },\n createIcon: function() {\n var div = document.createElement(\"div\"), options = this.options;\n if (options.icon) {\n div.innerHTML = this._createInner();\n }\n if (options.innerHTML) {\n div.innerHTML = options.innerHTML;\n }\n if (options.bgPos) {\n div.style.backgroundPosition = -options.bgPos.x + \"px \" + -options.bgPos.y + \"px\";\n }\n if (!options.svg) {\n this._setIconStyles(div, options.shape + \"-\" + options.markerColor);\n } else {\n this._setIconStyles(div, \"svg\");\n }\n return div;\n },\n _getColorHex: function (color) {\n var colorMap = {\n red: \"#a23337\",\n \"orange-dark\": \"#d73e29\",\n orange: \"#ef9227\",\n yellow: \"#f5bb39\",\n \"blue-dark\": \"#276273\",\n cyan: \"#32a9dd\",\n purple: \"#440444\",\n violet: \"#90278d\",\n pink: \"#c057a0\",\n green: \"#006838\",\n white: \"#e8e8e8\",\n black: \"#211c1d\"\n };\n return colorMap[color] || color;\n },\n _createSvg: function (shape, markerColor) {\n var svgMap = {\n circle: '',\n square: '',\n star: '',\n penta: ''\n };\n return svgMap[shape];\n },\n _createInner: function() {\n var iconStyle = \"\", iconNumber = \"\", iconClass = \"\", result = \"\", options = this.options;\n if (options.iconColor) {\n iconStyle = \"color: \" + options.iconColor + \";\";\n }\n if (options.iconRotate !== 0) {\n iconStyle += \"-webkit-transform: rotate(\" + options.iconRotate + \"deg);\";\n iconStyle += \"-moz-transform: rotate(\" + options.iconRotate + \"deg);\";\n iconStyle += \"-o-transform: rotate(\" + options.iconRotate + \"deg);\";\n iconStyle += \"-ms-transform: rotate(\" + options.iconRotate + \"deg);\";\n iconStyle += \"transform: rotate(\" + options.iconRotate + \"deg);\";\n }\n if (options.number) {\n iconNumber = 'number=\"' + options.number + '\" ';\n }\n if (options.extraClasses.length) {\n iconClass += options.extraClasses + \" \";\n }\n if (options.prefix.length) {\n iconClass += options.prefix + \" \";\n }\n if (options.icon.length) {\n iconClass += options.icon + \" \";\n }\n if (options.svg) {\n result += this._createSvg(options.shape, this._getColorHex(options.markerColor));\n }\n result += '';\n return result;\n },\n _setIconStyles: function(img, name) {\n var options = this.options, size = L.point(options[name === \"shadow\" ? \"shadowSize\" : \"iconSize\"]), anchor, leafletName;\n if (name === \"shadow\") {\n anchor = L.point(options.shadowAnchor || options.iconAnchor);\n leafletName = \"shadow\";\n } else {\n anchor = L.point(options.iconAnchor);\n leafletName = \"icon\";\n }\n if (!anchor && size) {\n anchor = size.divideBy(2, true);\n }\n img.className = \"leaflet-marker-\" + leafletName + \" extra-marker extra-marker-\" + name + \" \" + options.className;\n if (anchor) {\n img.style.marginLeft = -anchor.x + \"px\";\n img.style.marginTop = -anchor.y + \"px\";\n }\n if (size) {\n img.style.width = size.x + \"px\";\n img.style.height = size.y + \"px\";\n }\n },\n createShadow: function() {\n var div = document.createElement(\"div\");\n this._setIconStyles(div, \"shadow\");\n return div;\n }\n});\nExtraMarkers.icon = L.ExtraMarkers.icon = function(options) {\n return new L.ExtraMarkers.Icon(options);\n};"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | Shortcodes for inserting a OSM (Open Street Maps) Map, Marker or Track into your posts by using leaflet. 6 | 7 | I use [Leaflet](https://github.com/Leaflet/Leaflet), a custom version of [Leaflet Elevation](https://github.com/Raruto/leaflet-elevation) and [Leaflet ExtraMarkers](https://github.com/coryasilva/Leaflet.ExtraMarkers) 8 | 9 | Initially based on the work of [Simon Frey](https://github.com/simonfrey/hugo-leaflet) 10 | 11 | ## Table of Contents 12 | 13 | - [Table of Contents](#table-of-contents) 14 | - [Prerequisite](#prerequisite) 15 | - [Installation](#installation) 16 | - [Usage](#usage) 17 | - [Map only](#map-only) 18 | - [Shortcut](#shortcut) 19 | - [Parameters `leaflet-map`](#parameters-leaflet-map) 20 | - [Map with one marker](#map-with-one-marker) 21 | - [Shortcut](#shortcut-1) 22 | - [Parameters `leaflet-marker`](#parameters-leaflet-marker) 23 | - [Map with multiple marker](#map-with-multiple-marker) 24 | - [Shortcut](#shortcut-2) 25 | - [Map with gpx track](#map-with-gpx-track) 26 | - [Shortcut](#shortcut-3) 27 | - [Parameters `leaflet-track`](#parameters-leaflet-track) 28 | - [License](#license) 29 | 30 | ## Prerequisite 31 | Make sure you already have Font Awesome icons in your project 32 | 33 | ## Installation 34 | 35 | [Download the project as ZIP](https://github.com/altrdev/hugo-leaflet/archive/master.zip) 36 | 37 | 1. Copy the layouts folder over (containing the shortcuts) 38 | 2. Copy the assets folder over (containing js) 39 | 3. Copy the static folder over (containing css and images) 40 | 4. Call the loader partial layout in every post or globally in the theme `{{ partial "leaflet-loader" . }}` 41 | 42 | I recommend add it globally in your `` and use a parameter to include or exclude like this: 43 | 44 | ``` 45 | {{ if .HasShortcode "leaflet-map" }} 46 | {{ partial "leaflet-loader" . }} 47 | {{ end }} 48 | ``` 49 | 50 | ## Usage 51 | 52 | ### Map only 53 | 54 | #### Shortcut 55 | ``` 56 | {{< leaflet-map mapHeight="500px" mapWidth="100%" mapLat="27.66995" mapLon="85.43249" >}} 57 | ``` 58 | 59 | #### Parameters `leaflet-map` 60 | 61 | | **Parameter** | **Description** | **Mandatory** | **Default** | **Possible values** | 62 | |:-------------------:|:-------------------------------------------------------------------------------------------:|:-------------:|:----------------------:|:--------------------------:| 63 | | **mapHeight** | Map height size | **no** | "400px" | any number in px | 64 | | **mapWidth** | Map width size | **no** | "100%" | any number in px or % | 65 | | **mapLat** | Latitude where to center the map | **yes** | "" | any valid coords number | 66 | | **mapLon** | Longitude where to center the map | **yes** | "" | any valid coords number | 67 | | **mapId** | Unique id. Useful for add multiple map in the post with same longitude and latitude | **no** | md5(mapLat,mapLon) | any string | 68 | | **zoom** | The zoom level. If set, it must be parsable as int. | **no** | "13" | any number | 69 | | **scrollWheelZoom** | Enable or disable zoom with mouse scroll wheel | **no** | "true" | `"true"` or `"false"` | 70 | 71 | #### Result 72 | ![Map Example](others/images/example-map.png) 73 | 74 | ### Map with one marker 75 | 76 | #### Shortcut 77 | ``` 78 | {{< leaflet-map ... >}} 79 | {{< leaflet-marker markerLat="27.66995" markerLon="85.43249" >}} 80 | {{< /leaflet-map >}} 81 | ``` 82 | 83 | #### Parameters `leaflet-marker` 84 | 85 | | **Parameter** | **Description** | **Mandatory** | **Default** | **Possible values** | 86 | |:-------------------:|:---------------------------------------:|:-------------:|:-----------------:|:------------------------:| 87 | | **markerLat** | Latitude where to place the marker | **yes** | "" | any number in px | 88 | | **markerLon** | Longitude where to place the marker | **yes** | "" | any number in px or % | 89 | | **markerContent** | Popup content text | **no** | "" | any text, html accepted | 90 | 91 | #### Result 92 | ![Map Example](others/images/example-marker.png) 93 | 94 | ### Map with multiple marker 95 | 96 | #### Shortcut 97 | 98 | ``` 99 | {{< leaflet-map ... >}} 100 | {{< leaflet-marker markerLat="27.66995" markerLon="85.43249" >}} 101 | {{< leaflet-marker markerLat="27.66995" markerLon="85.43255" >}} 102 | {{< leaflet-marker markerLat="27.66995" markerLon="85.43345" >}} 103 | {{< /leaflet-map >}} 104 | ``` 105 | 106 | #### Result 107 | ![Map Example](others/images/example-multi-marker.png) 108 | 109 | ### Map with GPX track 110 | 111 | #### Shortcut 112 | ``` 113 | {{< leaflet-map ... >}} 114 | {{< leaflet-track trackPath="track.gpx" lineColor="#3796bf" lineWeight="5" >}} 115 | {{< /leaflet-map >}} 116 | ``` 117 | 118 | #### Parameters `leaflet-track` 119 | 120 | | **Parameter** | **Description** | **Mandatory** | **Default** | **Possible values** | 121 | |:---------------------------:|:-----------------------------------------------------------------------------:|:-------------:|:-----------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| 122 | | **trackPath** | Your GPX file. You must place in `static/gpx` folder. | **yes** | "" | any filename like `"my_track.gpx"` | 123 | | **lineColor** | Line track color | **no** | "#006EFF" | any valid hex color | 124 | | **lineWeight** | Line track weight on the map | **no** | "3" | any number | 125 | | **lineOpacity** | Line track opacity on the map | **no** | "1" | decimal range from 0 to 1 | 126 | | **graphPosition** | Elevation graph position on the map. Not valid if you use `graphDetached` | **no** | "topright" | `"myLocation"` | 127 | | **graphTheme** | Elevation graph theme already present | **no** | "steelblue-theme" | `"steelblue-theme"`, `"lime-theme"` or `"purple-theme"` | 128 | | **graphWidth** | Elevation graph width. Not valid if you use `graphDetached` | **no** | "500" | any number | 129 | | **graphHeight** | Elevation graph height | **no** | "150" | any number | 130 | | **graphFollowMarker** | Auto zoom on the mouse movement over the map | **no** | false | `true` or `false` | 131 | | **graphCollapsed** | Hide elevation graph below the button | **no** | false | `true` or `false` | 132 | | **graphDetached** | Detach elevation graph outside map | **no** | true | `true` or `false` | 133 | | **markerIcon** | Waypoint Fontawesome icon name | **no** | "fa-thumb-tack" | `"fa-thumb-tack"` (see fontawesome documentation) | 134 | | **markerIconColor** | Waypoint icon color | **no** | "cyan" | `"red"`, `"orange-dark"`, `"orange"`, `"yellow"`, `"blue-dark"`, `"cyan"`, `"purple"`, `"violet"`, `"pink"`, `"green-dark"`, `"green"`, `"green-light"`, `"black"`, `"white"` | 135 | | **markerIconShape** | Waypoint icon shape | **no** | "penta" | `"circle"`, `square`, `penta` or `star` | 136 | | **markerIconClasses** | Extra classes for extends icon css | **no** | "fa-icon-marker" | `"my-class my-second-class"` | 137 | | **markerStartIcon** | Start Waypoint Fontawesome icon name | **no** | "fa-play" | `"fa-play"` (see fontawesome documentation) | 138 | | **markerStartIconColor** | Start Waypoint icon color | **no** | "green-light" | `"red"`, `"orange-dark"`, `"orange"`, `"yellow"`, `"blue-dark"`, `"cyan"`, `"purple"`, `"violet"`, `"pink"`, `"green-dark"`, `"green"`, `"green-light"`, `"black"`, `"white"` | 139 | | **markerStartIconShape** | Start Waypoint icon shape | **no** | "circle" | `"circle"`, `square`, `penta` or `star` | 140 | | **markerStartIconClasses** | Extra classes for extends start icon css | **no** | "fa-icon-marker fa-icon-start-stop" | `"my-class my-second-class"` | 141 | | **markerEndIcon** | End Waypoint Fontawesome icon name | **no** | "fa-flag-checkered" | `"fa-flag-checkered"` (see fontawesome documentation) | 142 | | **markerEndIconColor** | End Waypoint icon color | **no** | "red" | `"red"`, `"orange-dark"`, `"orange"`, `"yellow"`, `"blue-dark"`, `"cyan"`, `"purple"`, `"violet"`, `"pink"`, `"green-dark"`, `"green"`, `"green-light"`, `"black"`, `"white"` | 143 | | **markerEndIconShape** | End Waypoint icon shape | **no** | "circle" | `"circle"`, `square`, `penta` or `star` | 144 | | **markerEndIconClasses** | Extra classes for extends end icon css | **no** | "fa-icon-marker fa-icon-start-stop" | `"my-class my-second-class"` | 145 | 146 | #### Result 147 | ![Map Example](others/images/example-track.png) 148 | 149 | With `graphDetached=false` 150 | 151 | ![Map Example Detached](others/images/example-track-graphDetached.png) 152 | 153 | 154 | ## License 155 |

156 | 157 |

158 | -------------------------------------------------------------------------------- /static/js/leaflet.elevation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, GPL-3.0+ Project, altrdev 3 | * 4 | * This file is free software: you may copy, redistribute and/or modify it 5 | * under the terms of the GNU General Public License as published by the 6 | * Free Software Foundation, either version 2 of the License, or (at your 7 | * option) any later version. 8 | * 9 | * This file is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * This file incorporates work covered by the following copyright and 18 | * permission notice: 19 | * 20 | * Copyright (c) 2019, GPL-3.0+ Project, Raruto 21 | * 22 | * This file is free software: you may copy, redistribute and/or modify it 23 | * under the terms of the GNU General Public License as published by the 24 | * Free Software Foundation, either version 2 of the License, or (at your 25 | * option) any later version. 26 | * 27 | * This file is distributed in the hope that it will be useful, but 28 | * WITHOUT ANY WARRANTY; without even the implied warranty of 29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 30 | * General Public License for more details. 31 | * 32 | * You should have received a copy of the GNU General Public License 33 | * along with this program. If not, see . 34 | * 35 | * This file incorporates work covered by the following copyright and 36 | * permission notice: 37 | * 38 | * Copyright (c) 2013-2016, MIT License, Felix “MrMufflon” Bache 39 | * 40 | * Permission to use, copy, modify, and/or distribute this software 41 | * for any purpose with or without fee is hereby granted, provided 42 | * that the above copyright notice and this permission notice appear 43 | * in all copies. 44 | * 45 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 46 | * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 47 | * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 48 | * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 49 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 50 | * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 51 | * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 52 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 53 | */ 54 | L.Control.Elevation = L.Control.extend({ 55 | 56 | includes: L.Evented ? L.Evented.prototype : L.Mixin.Events, 57 | 58 | options: { 59 | autohide: true, 60 | autohideMarker: true, 61 | collapsed: false, 62 | controlButton: { 63 | iconCssClass: "elevation-toggle-icon", 64 | title: "Elevation" 65 | }, 66 | detached: true, 67 | distanceFactor: 1, 68 | downloadLink: 'link', 69 | elevationDiv: "#elevation-div", 70 | followMarker: true, 71 | forceAxisBounds: false, 72 | gpxOptions: { 73 | async: true, 74 | marker_options: { 75 | startIconUrl: null, 76 | endIconUrl: null, 77 | shadowUrl: null, 78 | wptIcons: { 79 | '': L.divIcon({ 80 | className: 'elevation-waypoint-marker', 81 | html: '', 82 | iconSize: [30, 30], 83 | iconAnchor: [8, 30], 84 | }) 85 | }, 86 | }, 87 | polyline_options: { 88 | className: '', 89 | color: '#566B13', 90 | opacity: 0.75, 91 | weight: 5, 92 | lineCap: 'round' 93 | }, 94 | }, 95 | height: 200, 96 | heightFactor: 1, 97 | hoverNumber: { 98 | decimalsX: 2, 99 | decimalsY: 0, 100 | formatter: undefined 101 | }, 102 | imperial: false, 103 | interpolation: "curveLinear", 104 | lazyLoadJS: true, 105 | legend: true, 106 | loadData: { 107 | defer: false, 108 | lazy: false, 109 | }, 110 | marker: 'elevation-line', 111 | markerIcon: L.divIcon({ 112 | className: 'elevation-position-marker', 113 | html: '', 114 | iconSize: [32, 32], 115 | iconAnchor: [16, 16], 116 | }), 117 | placeholder: false, 118 | position: "topright", 119 | reverseCoords: false, 120 | skipNullZCoords: false, 121 | theme: "lightblue-theme", 122 | margins: { 123 | top: 10, 124 | right: 20, 125 | bottom: 30, 126 | left: 50 127 | }, 128 | responsive: true, 129 | summary: 'inline', 130 | width: 600, 131 | xLabel: "km", 132 | xTicks: undefined, 133 | yAxisMax: undefined, 134 | yAxisMin: undefined, 135 | yLabel: "m", 136 | yTicks: undefined, 137 | zFollow: 13, 138 | }, 139 | __mileFactor: 0.621371, 140 | __footFactor: 3.28084, 141 | 142 | /* 143 | * Add data to the diagram either from GPX or GeoJSON and update the axis domain and data 144 | */ 145 | addData: function(d, layer) { 146 | this._addData(d); 147 | 148 | if (this._container) { 149 | this._applyData(); 150 | } 151 | if ((typeof layer === "undefined" || layer === null) && d.on) { 152 | layer = d; 153 | } 154 | if (layer) { 155 | if (layer._path) { 156 | L.DomUtil.addClass(layer._path, 'elevation-polyline ' + this.options.theme); 157 | } 158 | layer 159 | .on("mousemove", this._mousemoveLayerHandler, this) 160 | .on("mouseout", this._mouseoutHandler, this); 161 | } 162 | 163 | this.track_info = this.track_info || {}; 164 | this.track_info.distance = this._distance; 165 | this.track_info.elevation_max = this._maxElevation; 166 | this.track_info.elevation_min = this._minElevation; 167 | 168 | this._layers = this._layers || {}; 169 | this._layers[L.Util.stamp(layer)] = layer; 170 | 171 | var evt = { 172 | data: d, 173 | layer: layer, 174 | track_info: this.track_info, 175 | }; 176 | if (this.fire) this.fire("eledata_added", evt, true); 177 | if (this._map) this._map.fire("eledata_added", evt, true); 178 | }, 179 | 180 | addTo: function(map) { 181 | if (this.options.detached) { 182 | this._addToChartDiv(map); 183 | } else { 184 | L.Control.prototype.addTo.call(this, map); 185 | } 186 | return this; 187 | }, 188 | 189 | /* 190 | * Reset data and display 191 | */ 192 | clear: function() { 193 | 194 | this._clearPath(); 195 | this._clearChart(); 196 | this._clearData(); 197 | 198 | if (this.fire) this.fire("eledata_clear"); 199 | if (this._map) this._map.fire("eledata_clear"); 200 | }, 201 | 202 | disableDragging: function() { 203 | this._draggingEnabled = false; 204 | this._resetDrag(); 205 | }, 206 | 207 | enableDragging: function() { 208 | this._draggingEnabled = true; 209 | }, 210 | 211 | fitBounds: function(bounds) { 212 | bounds = bounds || this._fullExtent; 213 | if (this._map && bounds) this._map.fitBounds(bounds); 214 | }, 215 | 216 | getZFollow: function() { 217 | return this._zFollow; 218 | }, 219 | 220 | hide: function() { 221 | this._container.style.display = "none"; 222 | }, 223 | 224 | initialize: function(options) { 225 | this.options.autohide = typeof options.autohide !== "undefined" ? options.autohide : !L.Browser.mobile; 226 | 227 | // Aliases. 228 | if (typeof options.detachedView !== "undefined") this.options.detached = options.detachedView; 229 | if (typeof options.responsiveView !== "undefined") this.options.responsive = options.responsiveView; 230 | if (typeof options.showTrackInfo !== "undefined") this.options.summary = options.showTrackInfo; 231 | if (typeof options.summaryType !== "undefined") this.options.summary = options.summaryType; 232 | if (typeof options.autohidePositionMarker !== "undefined") this.options.autohideMarker = options.autohidePositionMarker; 233 | if (typeof options.followPositionMarker !== "undefined") this.options.followMarker = options.followPositionMarker; 234 | if (typeof options.useLeafletMarker !== "undefined") this.options.marker = options.useLeafletMarker ? 'position-marker' : 'elevation-line'; 235 | if (typeof options.leafletMarkerIcon !== "undefined") this.options.markerIcon = options.leafletMarkerIcon; 236 | if (typeof options.download !== "undefined") this.options.downloadLink = options.download; 237 | 238 | // L.Util.setOptions(this, options); 239 | this.options = this._deepMerge({}, this.options, options); 240 | 241 | this._draggingEnabled = !L.Browser.mobile; 242 | this._chartEnabled = true; 243 | 244 | if (this.options.imperial) { 245 | this._distanceFactor = this.__mileFactor; 246 | this._heightFactor = this.__footFactor; 247 | this._xLabel = "mi"; 248 | this._yLabel = "ft"; 249 | } else { 250 | this._distanceFactor = this.options.distanceFactor; 251 | this._heightFactor = this.options.heightFactor; 252 | this._xLabel = this.options.xLabel; 253 | this._yLabel = this.options.yLabel; 254 | } 255 | 256 | this._zFollow = this.options.zFollow; 257 | 258 | if (this.options.followMarker) this._setMapView = L.Util.throttle(this._setMapView, 300, this); 259 | if (this.options.placeholder) this.options.loadData.lazy = this.options.loadData.defer = true; 260 | }, 261 | 262 | /** 263 | * Alias for loadData 264 | */ 265 | load: function(data, opts) { 266 | this.loadData(data, opts); 267 | }, 268 | 269 | /** 270 | * Alias for addTo 271 | */ 272 | loadChart: function(map) { 273 | this.addTo(map); 274 | }, 275 | 276 | loadData: function(data, opts) { 277 | opts = L.extend({}, this.options.loadData, opts); 278 | if (opts.defer) { 279 | this.loadDefer(data, opts); 280 | } else if (opts.lazy) { 281 | this.loadLazy(data, opts); 282 | } else if (this._isXMLDoc(data)) { 283 | this.loadGPX(data); 284 | } else if (this._isJSONDoc(data)) { 285 | this.loadGeoJSON(data); 286 | } else { 287 | this.loadFile(data); 288 | } 289 | }, 290 | 291 | loadDefer: function(data, opts) { 292 | opts = L.extend({}, this.options.loadData, opts); 293 | opts.defer = false; 294 | if (document.readyState !== 'complete') window.addEventListener("load", L.bind(this.loadData, this, data, opts), { once: true }); 295 | else this.loadData(data, opts) 296 | }, 297 | 298 | loadFile: function(url) { 299 | this._downloadURL = url; // TODO: handle multiple urls? 300 | try { 301 | var xhr = new XMLHttpRequest(); 302 | xhr.responseType = "text"; 303 | xhr.open('GET', url); 304 | xhr.onload = function() { 305 | if (xhr.status !== 200) { 306 | throw "Error " + xhr.status + " while fetching remote file: " + url; 307 | } else { 308 | this.loadData(xhr.response, { lazy: false, defer: false }); 309 | } 310 | }.bind(this); 311 | xhr.send(); 312 | } catch (e) { 313 | console.warn(e); 314 | } 315 | }, 316 | 317 | loadGeoJSON: function(data) { 318 | if (typeof data === "string") { 319 | data = JSON.parse(data); 320 | } 321 | 322 | this.layer = this.geojson = L.geoJson(data, { 323 | style: function(feature) { 324 | return { 325 | color: '#566B13', 326 | className: 'elevation-polyline ' + this.options.theme, 327 | }; 328 | }.bind(this), 329 | onEachFeature: function(feature, layer) { 330 | this.addData(feature, layer); 331 | 332 | this.track_info = this.track_info || {}; 333 | this.track_info.type = "geojson"; 334 | this.track_info.name = data.name; 335 | this.track_info.distance = this._distance; 336 | this.track_info.elevation_max = this._maxElevation; 337 | this.track_info.elevation_min = this._minElevation; 338 | 339 | }.bind(this), 340 | }); 341 | if (this._map) { 342 | this._map.once('layeradd', function(e) { 343 | this.fitBounds(this.layer.getBounds()); 344 | var evt = { 345 | data: data, 346 | layer: this.layer, 347 | name: this.track_info.name, 348 | track_info: this.track_info, 349 | }; 350 | if (this.fire) this.fire("eledata_loaded", evt, true); 351 | if (this._map) this._map.fire("eledata_loaded", evt, true); 352 | }, this); 353 | 354 | this.layer.addTo(this._map); 355 | } else { 356 | console.warn("Undefined elevation map object"); 357 | } 358 | }, 359 | 360 | loadGPX: function(data) { 361 | var callback = function(data) { 362 | this.options.gpxOptions.polyline_options.className += 'elevation-polyline ' + this.options.theme; 363 | 364 | this.layer = this.gpx = new L.GPX(data, this.options.gpxOptions); 365 | 366 | this.layer.on('loaded', function(e) { 367 | this.fitBounds(e.target.getBounds()); 368 | }, this); 369 | this.layer.on('addpoint', function(e) { 370 | 371 | if(e.point_type === "start" || e.point_type === "end") { 372 | e.point.setZIndexOffset(10000); 373 | } 374 | if (e.point._popup) { 375 | e.point._popup.options.className = 'elevation-popup'; 376 | e.point._popup._content = decodeURI(e.point._popup._content); 377 | } 378 | if (e.point._popup && e.point._popup._content) { 379 | e.point.bindTooltip(e.point._popup._content, { direction: 'top', sticky: true, opacity: 1, className: 'elevation-tooltip' }).openTooltip(); 380 | } 381 | }); 382 | this.layer.once("addline", function(e) { 383 | this.addData(e.line /*, this.layer*/ ); 384 | 385 | this.track_info = this.track_info || {}; 386 | this.track_info.type = "gpx"; 387 | this.track_info.name = this.layer.get_name(); 388 | this.track_info.distance = this._distance; 389 | this.track_info.elevation_max = this._maxElevation; 390 | this.track_info.elevation_min = this._minElevation; 391 | 392 | var evt = { 393 | data: data, 394 | layer: this.layer, 395 | name: this.track_info.name, 396 | track_info: this.track_info, 397 | }; 398 | 399 | if (this.fire) this.fire("eledata_loaded", evt, true); 400 | if (this._map) this._map.fire("eledata_loaded", evt, true); 401 | }, this); 402 | 403 | if (this._map) { 404 | this.layer.addTo(this._map); 405 | } else { 406 | console.warn("Undefined elevation map object"); 407 | } 408 | }.bind(this, data); 409 | if (typeof L.GPX !== 'function' && this.options.lazyLoadJS) { 410 | L.Control.Elevation._gpxLazyLoader = this._lazyLoadJS('https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.0/gpx.js', L.Control.Elevation._gpxLazyLoader); 411 | L.Control.Elevation._gpxLazyLoader.then(callback); 412 | } else { 413 | callback.call(); 414 | } 415 | }, 416 | 417 | loadLazy: function(data, opts) { 418 | opts = L.extend({}, this.options.loadData, opts); 419 | opts.lazy = false; 420 | let ticking = false; 421 | let scrollFn = L.bind(function(data) { 422 | if (!ticking) { 423 | L.Util.requestAnimFrame(function() { 424 | if (this._isVisible(this.placeholder)) { 425 | window.removeEventListener('scroll', scrollFn); 426 | this.loadData(data, opts); 427 | this.once('eledata_loaded', function() { 428 | if (this.placeholder && this.placeholder.parentNode) { 429 | this.placeholder.parentNode.removeChild(this.placeholder); 430 | } 431 | }, this) 432 | } 433 | ticking = false; 434 | }, this); 435 | ticking = true; 436 | } 437 | }, this, data); 438 | window.addEventListener('scroll', scrollFn); 439 | if (this.placeholder) this.placeholder.addEventListener('mouseenter', scrollFn, { once: true }); 440 | scrollFn(); 441 | }, 442 | 443 | onAdd: function(map) { 444 | this._map = map; 445 | 446 | var container = this._container = L.DomUtil.create("div", "elevation-control elevation"); 447 | 448 | if (!this.options.detached) { 449 | L.DomUtil.addClass(container, 'leaflet-control'); 450 | } 451 | 452 | if (this.options.theme) { 453 | L.DomUtil.addClass(container, this.options.theme); // append theme to control 454 | } 455 | 456 | if (this.options.placeholder && !this._data) { 457 | this.placeholder = L.DomUtil.create('img', 'elevation-placeholder'); 458 | if (typeof this.options.placeholder === 'string') { 459 | this.placeholder.src = this.options.placeholder; 460 | this.placeholder.alt = ''; 461 | } else { 462 | for (let i in this.options.placeholder) { this.placeholder.setAttribute(i, this.options.placeholder[i]); } 463 | } 464 | container.insertBefore(this.placeholder, container.firstChild); 465 | } 466 | 467 | var callback = function(map, container) { 468 | this._initToggle(container); 469 | this._initChart(container); 470 | 471 | this._applyData(); 472 | 473 | this._map.on('zoom viewreset zoomanim', this._hidePositionMarker, this); 474 | this._map.on('resize', this._resetView, this); 475 | this._map.on('resize', this._resizeChart, this); 476 | this._map.on('mousedown', this._resetDrag, this); 477 | 478 | this._map.on('eledata_loaded', this._updateSummary, this); 479 | 480 | L.DomEvent.on(this._map._container, 'mousewheel', this._resetDrag, this); 481 | L.DomEvent.on(this._map._container, 'touchstart', this._resetDrag, this); 482 | 483 | }.bind(this, map, container); 484 | if (typeof d3 !== 'object' && this.options.lazyLoadJS) { 485 | L.Control.Elevation._d3LazyLoader = this._lazyLoadJS('https://unpkg.com/d3@4.13.0/build/d3.min.js', L.Control.Elevation._d3LazyLoader); 486 | L.Control.Elevation._d3LazyLoader.then(callback); 487 | } else { 488 | callback.call(); 489 | } 490 | return container; 491 | }, 492 | 493 | onRemove: function(map) { 494 | this._container = null; 495 | }, 496 | 497 | redraw: function() { 498 | this._resizeChart(); 499 | }, 500 | 501 | setZFollow: function(zoom) { 502 | this._zFollow = zoom; 503 | }, 504 | 505 | show: function() { 506 | this._container.style.display = "block"; 507 | }, 508 | 509 | /* 510 | * Parsing data either from GPX or GeoJSON and update the diagram data 511 | */ 512 | _addData: function(d) { 513 | var geom = d && d.geometry && d.geometry; 514 | var i; 515 | 516 | if (geom) { 517 | switch (geom.type) { 518 | case 'LineString': 519 | this._addGeoJSONData(geom.coordinates); 520 | break; 521 | 522 | case 'MultiLineString': 523 | for (i = 0; i < geom.coordinates.length; i++) { 524 | this._addGeoJSONData(geom.coordinates[i]); 525 | } 526 | break; 527 | 528 | default: 529 | console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type); 530 | } 531 | } 532 | 533 | var feat = d && d.type === "FeatureCollection"; 534 | if (feat) { 535 | for (i = 0; i < d.features.length; i++) { 536 | this._addData(d.features[i]); 537 | } 538 | } 539 | 540 | if (d && d._latlngs) { 541 | this._addGPXdata(d._latlngs); 542 | } 543 | }, 544 | 545 | /* 546 | * Parsing of GeoJSON data lines and their elevation in z-coordinate 547 | */ 548 | _addGeoJSONData: function(coords) { 549 | if (coords) { 550 | for (var i = 0; i < coords.length; i++) { 551 | this._addPoint(coords[i][1], coords[i][0], coords[i][2]); 552 | } 553 | } 554 | }, 555 | 556 | /* 557 | * Parsing function for GPX data and their elevation in z-coordinate 558 | */ 559 | _addGPXdata: function(coords) { 560 | if (coords) { 561 | for (var i = 0; i < coords.length; i++) { 562 | this._addPoint(coords[i].lat, coords[i].lng, coords[i].meta.ele); 563 | } 564 | } 565 | }, 566 | 567 | _addPoint: function(x, y, z) { 568 | if (this.options.reverseCoords) { 569 | var tmp = x; 570 | x = y; 571 | y = tmp; 572 | } 573 | 574 | var data = this._data || []; 575 | var eleMax = this._maxElevation || -Infinity; 576 | var eleMin = this._minElevation || +Infinity; 577 | var dist = this._distance || 0; 578 | 579 | var curr = new L.LatLng(x, y); 580 | var prev = data.length ? data[data.length - 1].latlng : curr; 581 | 582 | var delta = curr.distanceTo(prev) * this._distanceFactor; 583 | 584 | dist = dist + Math.round(delta / 1000 * 100000) / 100000; 585 | 586 | // check and fix missing elevation data on last added point 587 | if (!this.options.skipNullZCoords && data.length > 0) { 588 | var prevZ = data[data.length - 1].z; 589 | if (isNaN(prevZ)) { 590 | var lastZ = this._lastValidZ; 591 | var currZ = z * this._heightFactor; 592 | if (!isNaN(lastZ) && !isNaN(currZ)) { 593 | prevZ = (lastZ + currZ) / 2; 594 | } else if (!isNaN(lastZ)) { 595 | prevZ = lastZ; 596 | } else if (!isNaN(currZ)) { 597 | prevZ = currZ; 598 | } 599 | if (!isNaN(prevZ)) data[data.length - 1].z = prevZ; 600 | else data.splice(data.length - 1, 1); 601 | } 602 | } 603 | 604 | z = z * this._heightFactor; 605 | 606 | // skip point if it has not elevation 607 | if (!isNaN(z)) { 608 | eleMax = eleMax < z ? z : eleMax; 609 | eleMin = eleMin > z ? z : eleMin; 610 | this._lastValidZ = z; 611 | } 612 | 613 | data.push({ 614 | dist: dist, 615 | x: x, 616 | y: y, 617 | z: z, 618 | latlng: curr 619 | }); 620 | 621 | this._data = data; 622 | this._distance = dist; 623 | this._maxElevation = eleMax; 624 | this._minElevation = eleMin; 625 | }, 626 | 627 | _addToChartDiv: function(map) { 628 | this._appendElevationDiv(map._container).appendChild(this.onAdd(map)); 629 | }, 630 | 631 | _appendChart: function(svg) { 632 | var g = svg 633 | .append("g") 634 | .attr("transform", "translate(" + this.options.margins.left + "," + this.options.margins.top + ")"); 635 | 636 | this._appendGrid(g); 637 | this._appendAreaPath(g); 638 | this._appendAxis(g); 639 | this._appendFocusRect(g); 640 | this._appendMouseFocusG(g); 641 | this._appendLegend(g); 642 | }, 643 | 644 | _appendElevationDiv: function(container) { 645 | var eleDiv = document.querySelector(this.options.elevationDiv); 646 | if (!eleDiv) { 647 | eleDiv = L.DomUtil.create('div', 'leaflet-control elevation elevation-div'); 648 | this.options.elevationDiv = '#elevation-div_' + Math.random().toString(36).substr(2, 9); 649 | eleDiv.id = this.options.elevationDiv.substr(1); 650 | container.parentNode.insertBefore(eleDiv, container.nextSibling); // insert after end of container. 651 | } 652 | if (this.options.detached) { 653 | L.DomUtil.addClass(eleDiv, 'elevation-detached'); 654 | L.DomUtil.removeClass(eleDiv, 'leaflet-control'); 655 | } 656 | this.eleDiv = eleDiv; 657 | return this.eleDiv; 658 | }, 659 | 660 | _appendXaxis: function(axis) { 661 | axis 662 | .append("g") 663 | .attr("class", "x axis") 664 | .attr("transform", "translate(0," + this._height() + ")") 665 | .call( 666 | d3 667 | .axisBottom() 668 | .scale(this._x) 669 | .ticks(this.options.xTicks) 670 | ) 671 | .append("text") 672 | .attr("x", this._width() + 6) 673 | .attr("y", 30) 674 | .text(this._xLabel); 675 | }, 676 | 677 | _appendXGrid: function(grid) { 678 | grid.append("g") 679 | .attr("class", "x grid") 680 | .attr("transform", "translate(0," + this._height() + ")") 681 | .call( 682 | d3 683 | .axisBottom() 684 | .scale(this._x) 685 | .ticks(this.options.xTicks) 686 | .tickSize(-this._height()) 687 | .tickFormat("") 688 | ); 689 | 690 | }, 691 | 692 | _appendYaxis: function(axis) { 693 | axis 694 | .append("g") 695 | .attr("class", "y axis") 696 | .call( 697 | d3 698 | .axisLeft() 699 | .scale(this._y) 700 | .ticks(this.options.yTicks) 701 | ) 702 | .append("text") 703 | .attr("x", -30) 704 | .attr("y", -5) 705 | .text(this._yLabel); 706 | }, 707 | 708 | _appendYGrid: function(grid) { 709 | grid.append("g") 710 | .attr("class", "y grid") 711 | .call( 712 | d3 713 | .axisLeft() 714 | .scale(this._y) 715 | .ticks(this.options.yTicks) 716 | .tickSize(-this._width()) 717 | .tickFormat("") 718 | ); 719 | }, 720 | 721 | _appendAreaPath: function(g) { 722 | this._areapath = g.append("path") 723 | .attr("class", "area"); 724 | }, 725 | 726 | _appendAxis: function(g) { 727 | this._axis = g.append("g") 728 | .attr("class", "axis"); 729 | this._appendXaxis(this._axis); 730 | this._appendYaxis(this._axis); 731 | }, 732 | 733 | _appendFocusRect: function(g) { 734 | var focusRect = this._focusRect = g.append("rect") 735 | .attr("width", this._width()) 736 | .attr("height", this._height()) 737 | .style("fill", "none") 738 | .style("stroke", "none") 739 | .style("pointer-events", "all"); 740 | 741 | if (L.Browser.mobile) { 742 | focusRect 743 | .on("touchmove.drag", this._dragHandler.bind(this)) 744 | .on("touchstart.drag", this._dragStartHandler.bind(this)) 745 | .on("touchstart.focus", this._mousemoveHandler.bind(this)) 746 | .on("touchmove.focus", this._mousemoveHandler.bind(this)); 747 | L.DomEvent.on(this._container, 'touchend', this._dragEndHandler, this); 748 | } 749 | 750 | focusRect 751 | .on("mousemove.drag", this._dragHandler.bind(this)) 752 | .on("mousedown.drag", this._dragStartHandler.bind(this)) 753 | .on("mouseenter.focus", this._mouseenterHandler.bind(this)) 754 | .on("mousemove.focus", this._mousemoveHandler.bind(this)) 755 | .on("mouseout.focus", this._mouseoutHandler.bind(this)); 756 | L.DomEvent.on(this._container, 'mouseup', this._dragEndHandler, this); 757 | }, 758 | 759 | _appendGrid: function(g) { 760 | this._grid = g.append("g") 761 | .attr("class", "grid"); 762 | this._appendXGrid(this._grid); 763 | this._appendYGrid(this._grid); 764 | }, 765 | 766 | _appendMouseFocusG: function(g) { 767 | var focusG = this._focusG = g.append("g") 768 | .attr("class", "mouse-focus-group"); 769 | 770 | this._mousefocus = focusG.append('svg:line') 771 | .attr('class', 'mouse-focus-line') 772 | .attr('x2', '0') 773 | .attr('y2', '0') 774 | .attr('x1', '0') 775 | .attr('y1', '0'); 776 | 777 | this._focuslabelrect = focusG.append("rect") 778 | .attr('class', 'mouse-focus-label') 779 | .attr("x", 0) 780 | .attr("y", 0) 781 | .attr("width", 0) 782 | .attr("height", 0) 783 | .attr("rx", 3) 784 | .attr("ry", 3); 785 | 786 | this._focuslabeltext = focusG.append("svg:text") 787 | .attr("class", "mouse-focus-label-text"); 788 | this._focuslabelY = this._focuslabeltext.append("svg:tspan") 789 | .attr("class", "mouse-focus-label-y") 790 | .attr("dy", "-1em"); 791 | this._focuslabelX = this._focuslabeltext.append("svg:tspan") 792 | .attr("class", "mouse-focus-label-x") 793 | .attr("dy", "2em"); 794 | }, 795 | 796 | _appendLegend: function(g) { 797 | if (!this.options.legend) return; 798 | 799 | var legend = this._legend = g.append('g') 800 | .attr("class", "legend"); 801 | 802 | var altitude = this._altitudeLegend = this._legend.append('g') 803 | .attr("class", "legend-altitude"); 804 | 805 | altitude.append("rect") 806 | .attr("class", "area") 807 | .attr("x", (this._width() / 2) - 50) 808 | .attr("y", this._height() + this.options.margins.bottom - 17) 809 | .attr("width", 50) 810 | .attr("height", 5) 811 | .attr("opacity", 0.75); 812 | 813 | altitude.append('text') 814 | .text('Altitude') 815 | .attr("x", (this._width() / 2) + 5) 816 | .attr("font-size", 10) 817 | .style("text-decoration-thickness", "2px") 818 | .style("font-weight", "700") 819 | .attr('y', this._height() + this.options.margins.bottom - 11); 820 | 821 | }, 822 | 823 | _appendPositionMarker: function(pane) { 824 | var theme = this.options.theme; 825 | var heightG = pane.select("g"); 826 | 827 | this._mouseHeightFocus = heightG.append('svg:line') 828 | .attr("class", theme + " height-focus line") 829 | .attr("x2", 0) 830 | .attr("y2", 0) 831 | .attr("x1", 0) 832 | .attr("y1", 0); 833 | 834 | this._pointG = heightG.append("g"); 835 | this._pointG.append("svg:circle") 836 | .attr("class", theme + " height-focus circle-lower") 837 | .attr("r", 6) 838 | .attr("cx", 0) 839 | .attr("cy", 0); 840 | 841 | this._mouseHeightFocusLabel = heightG.append("svg:text") 842 | .attr("class", theme + " height-focus-label") 843 | .style("pointer-events", "none"); 844 | }, 845 | 846 | _applyData: function() { 847 | if (!this._data) return; 848 | 849 | var xdomain = d3.extent(this._data, function(d) { 850 | return d.dist; 851 | }); 852 | var ydomain = d3.extent(this._data, function(d) { 853 | return d.z; 854 | }); 855 | var opts = this.options; 856 | 857 | if (opts.yAxisMin !== undefined && (opts.yAxisMin < ydomain[0] || opts.forceAxisBounds)) { 858 | ydomain[0] = opts.yAxisMin; 859 | } 860 | if (opts.yAxisMax !== undefined && (opts.yAxisMax > ydomain[1] || opts.forceAxisBounds)) { 861 | ydomain[1] = opts.yAxisMax; 862 | } 863 | 864 | this._x.domain(xdomain); 865 | this._y.domain(ydomain); 866 | this._areapath.datum(this._data) 867 | .attr("d", this._area); 868 | this._updateAxis(); 869 | 870 | this._fullExtent = this._calculateFullExtent(this._data); 871 | }, 872 | 873 | /* 874 | * Calculates the full extent of the data array 875 | */ 876 | _calculateFullExtent: function(data) { 877 | if (!data || data.length < 1) { 878 | throw new Error("no data in parameters"); 879 | } 880 | 881 | var ext = new L.latLngBounds(data[0].latlng, data[0].latlng); 882 | 883 | data.forEach(function(item) { 884 | ext.extend(item.latlng); 885 | }); 886 | 887 | return ext; 888 | }, 889 | 890 | _clearChart: function() { 891 | this._resetDrag(); 892 | if (this._areapath) { 893 | // workaround for 'Error: Problem parsing d=""' in Webkit when empty data 894 | // https://groups.google.com/d/msg/d3-js/7rFxpXKXFhI/HzIO_NPeDuMJ 895 | //this._areapath.datum(this._data).attr("d", this._area); 896 | this._areapath.attr("d", "M0 0"); 897 | 898 | this._x.domain([0, 1]); 899 | this._y.domain([0, 1]); 900 | this._updateAxis(); 901 | } 902 | if (this._altitudeLegend) { 903 | this._altitudeLegend.select('text').style("text-decoration-line", "line-through"); 904 | } 905 | }, 906 | 907 | /* 908 | * Reset data 909 | */ 910 | _clearData: function() { 911 | this._data = null; 912 | this._distance = null; 913 | this._maxElevation = null; 914 | this._minElevation = null; 915 | this.track_info = null; 916 | this._layers = null; 917 | // if (this.layer) { 918 | // this.layer.removeFrom(this._map); 919 | // } 920 | }, 921 | 922 | _clearPath: function() { 923 | this._hidePositionMarker(); 924 | for (var id in this._layers) { 925 | L.DomUtil.removeClass(this._layers[id]._path, "elevation-polyline"); 926 | L.DomUtil.removeClass(this._layers[id]._path, this.options.theme); 927 | } 928 | }, 929 | 930 | _collapse: function() { 931 | if (this._container) { 932 | L.DomUtil.removeClass(this._container, 'elevation-expanded'); 933 | L.DomUtil.addClass(this._container, 'elevation-collapsed'); 934 | } 935 | }, 936 | 937 | _deepMerge: function(target, ...sources) { 938 | if (!sources.length) return target; 939 | const source = sources.shift(); 940 | if (this._isObject(target) && this._isObject(source)) { 941 | for (const key in source) { 942 | if (this._isObject(source[key])) { 943 | if (!target[key]) Object.assign(target, { 944 | [key]: {} 945 | }); 946 | this._deepMerge(target[key], source[key]); 947 | } else { 948 | Object.assign(target, { 949 | [key]: source[key] 950 | }); 951 | } 952 | } 953 | } 954 | return this._deepMerge(target, ...sources); 955 | }, 956 | 957 | _saveFile: function(fileUrl) { 958 | var d = document, 959 | a = d.createElement('a'), 960 | b = d.body; 961 | a.href = fileUrl; 962 | a.target = '_new'; 963 | a.download = ""; // fileName 964 | a.style.display = 'none'; 965 | b.appendChild(a); 966 | a.click(); 967 | b.removeChild(a); 968 | }, 969 | 970 | _dragHandler: function() { 971 | //we don't want map events to occur here 972 | d3.event.preventDefault(); 973 | d3.event.stopPropagation(); 974 | 975 | this._gotDragged = true; 976 | this._drawDragRectangle(); 977 | }, 978 | 979 | /* 980 | * Handles end of drag operations. Zooms the map to the selected items extent. 981 | */ 982 | _dragEndHandler: function() { 983 | if (!this._dragStartCoords || !this._dragCurrentCoords || !this._gotDragged) { 984 | this._dragStartCoords = null; 985 | this._gotDragged = false; 986 | if (this._draggingEnabled) this._resetDrag(); 987 | // autotoggle chart data on single click 988 | /*if (this._chartEnabled) { 989 | this._clearChart(); 990 | this._clearPath(); 991 | this._chartEnabled = false; 992 | } else { 993 | this._resizeChart(); 994 | this._chartEnabled = true; 995 | }*/ 996 | return; 997 | } 998 | 999 | var item1 = this._findItemForX(this._dragStartCoords[0]), 1000 | item2 = this._findItemForX(this._dragCurrentCoords[0]); 1001 | 1002 | if (item1 == item2) return; 1003 | 1004 | this._hidePositionMarker(); 1005 | 1006 | this._fitSection(item1, item2); 1007 | 1008 | this._dragStartCoords = null; 1009 | this._gotDragged = false; 1010 | 1011 | var evt = { 1012 | data: { 1013 | dragstart: this._data[item1], 1014 | dragend: this._data[item2] 1015 | } 1016 | }; 1017 | if (this.fire) this.fire("elechart_dragged", evt, true); 1018 | if (this._map) this._map.fire("elechart_dragged", evt, true); 1019 | }, 1020 | 1021 | _dragStartHandler: function() { 1022 | d3.event.preventDefault(); 1023 | d3.event.stopPropagation(); 1024 | 1025 | this._gotDragged = false; 1026 | this._dragStartCoords = d3.mouse(this._focusRect.node()); 1027 | }, 1028 | 1029 | /* 1030 | * Draws the currently dragged rectangle over the chart. 1031 | */ 1032 | _drawDragRectangle: function() { 1033 | if (!this._dragStartCoords || !this._draggingEnabled) { 1034 | return; 1035 | } 1036 | 1037 | var dragEndCoords = this._dragCurrentCoords = d3.mouse(this._focusRect.node()); 1038 | 1039 | var x1 = Math.min(this._dragStartCoords[0], dragEndCoords[0]), 1040 | x2 = Math.max(this._dragStartCoords[0], dragEndCoords[0]); 1041 | 1042 | if (!this._dragRectangle && !this._dragRectangleG) { 1043 | var g = d3.select(this._container).select("svg").select("g"); 1044 | 1045 | this._dragRectangleG = g.insert("g", ".mouse-focus-group"); 1046 | 1047 | this._dragRectangle = this._dragRectangleG.append("rect") 1048 | .attr("width", x2 - x1) 1049 | .attr("height", this._height()) 1050 | .attr("x", x1) 1051 | .attr('class', 'mouse-drag') 1052 | .style("pointer-events", "none"); 1053 | } else { 1054 | this._dragRectangle.attr("width", x2 - x1) 1055 | .attr("x", x1); 1056 | } 1057 | }, 1058 | 1059 | _expand: function() { 1060 | if (this._container) { 1061 | L.DomUtil.removeClass(this._container, 'elevation-collapsed'); 1062 | L.DomUtil.addClass(this._container, 'elevation-expanded'); 1063 | } 1064 | }, 1065 | 1066 | /* 1067 | * Finds an item with the smallest delta in distance to the given latlng coords 1068 | */ 1069 | _findItemForLatLng: function(latlng) { 1070 | var result = null, 1071 | d = Infinity; 1072 | this._data.forEach(function(item) { 1073 | var dist = latlng.distanceTo(item.latlng); 1074 | if (dist < d) { 1075 | d = dist; 1076 | result = item; 1077 | } 1078 | }); 1079 | return result; 1080 | }, 1081 | 1082 | /* 1083 | * Finds a data entry for a given x-coordinate of the diagram 1084 | */ 1085 | _findItemForX: function(x) { 1086 | var bisect = d3.bisector(function(d) { 1087 | return d.dist; 1088 | }).left; 1089 | var xinvert = this._x.invert(x); 1090 | return bisect(this._data, xinvert); 1091 | }, 1092 | 1093 | /** 1094 | * Make the map fit the route section between given indexes. 1095 | */ 1096 | _fitSection: function(index1, index2) { 1097 | var start = Math.min(index1, index2); 1098 | var end = Math.max(index1, index2); 1099 | var ext = this._calculateFullExtent(this._data.slice(start, end)); 1100 | this.fitBounds(ext); 1101 | }, 1102 | 1103 | /* 1104 | * Fromatting funciton using the given decimals and seperator 1105 | */ 1106 | _formatter: function(num, dec, sep) { 1107 | var res; 1108 | if (dec === 0) { 1109 | res = Math.round(num) + ""; 1110 | } else { 1111 | res = L.Util.formatNum(num, dec) + ""; 1112 | } 1113 | var numbers = res.split("."); 1114 | if (numbers[1]) { 1115 | var d = dec - numbers[1].length; 1116 | for (; d > 0; d--) { 1117 | numbers[1] += "0"; 1118 | } 1119 | res = numbers.join(sep || "."); 1120 | } 1121 | return res; 1122 | }, 1123 | 1124 | _height: function() { 1125 | var opts = this.options; 1126 | return opts.height - opts.margins.top - opts.margins.bottom; 1127 | }, 1128 | 1129 | /* 1130 | * Hides the position/height indicator marker drawn onto the map 1131 | */ 1132 | _hidePositionMarker: function() { 1133 | if (!this.options.autohideMarker) { 1134 | return; 1135 | } 1136 | 1137 | this._selectedItem = null; 1138 | 1139 | if (this._marker) { 1140 | if (this._map) this._map.removeLayer(this._marker); 1141 | this._marker = null; 1142 | } 1143 | if (this._mouseHeightFocus) { 1144 | this._mouseHeightFocus.style("visibility", "hidden"); 1145 | this._mouseHeightFocusLabel.style("visibility", "hidden"); 1146 | } 1147 | if (this._pointG) { 1148 | this._pointG.style("visibility", "hidden"); 1149 | } 1150 | if (this._focusG) { 1151 | this._focusG.style("visibility", "hidden"); 1152 | } 1153 | }, 1154 | 1155 | _initChart: function() { 1156 | var opts = this.options; 1157 | opts.xTicks = opts.xTicks || Math.round(this._width() / 75); 1158 | opts.yTicks = opts.yTicks || Math.round(this._height() / 30); 1159 | opts.hoverNumber.formatter = opts.hoverNumber.formatter || this._formatter; 1160 | 1161 | if (opts.responsive) { 1162 | if (opts.detached) { 1163 | var offWi = this.eleDiv.offsetWidth; 1164 | var offHe = this.eleDiv.offsetHeight; 1165 | opts.width = offWi > 0 ? offWi : opts.width; 1166 | opts.height = (offHe - 20) > 0 ? offHe - 20 : opts.height; // 20 = horizontal scrollbar size. 1167 | } else { 1168 | opts._maxWidth = opts._maxWidth > opts.width ? opts._maxWidth : opts.width; 1169 | var containerWidth = this._map._container.clientWidth; 1170 | opts.width = opts._maxWidth > containerWidth ? containerWidth - 30 : opts.width; 1171 | } 1172 | } 1173 | 1174 | var x = this._x = d3.scaleLinear().range([0, this._width()]); 1175 | var y = this._y = d3.scaleLinear().range([this._height(), 0]); 1176 | 1177 | var interpolation = typeof opts.interpolation === 'function' ? opts.interpolation : d3[opts.interpolation]; 1178 | 1179 | var area = this._area = d3.area().curve(interpolation) 1180 | .x(function(d) { 1181 | return (d.xDiagCoord = x(d.dist)); 1182 | }) 1183 | .y0(this._height()) 1184 | .y1(function(d) { 1185 | return y(d.z); 1186 | }); 1187 | var line = this._line = d3.line() 1188 | .x(function(d) { 1189 | return d3.mouse(svg.select("g"))[0]; 1190 | }) 1191 | .y(function(d) { 1192 | return this._height(); 1193 | }); 1194 | 1195 | var container = d3.select(this._container); 1196 | 1197 | var svg = container.append("svg") 1198 | .attr("class", "background") 1199 | .attr("width", opts.width) 1200 | .attr("height", opts.height); 1201 | 1202 | var summary = this.summaryDiv = container.append("div") 1203 | .attr("class", "elevation-summary " + this.options.summary + "-summary").node(); 1204 | 1205 | this._appendChart(svg); 1206 | this._updateSummary(); 1207 | 1208 | }, 1209 | 1210 | /** 1211 | * Inspired by L.Control.Layers 1212 | */ 1213 | _initToggle: function(container) { 1214 | //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released 1215 | container.setAttribute('aria-haspopup', true); 1216 | 1217 | if (!this.options.detached) { 1218 | L.DomEvent 1219 | .disableClickPropagation(container); 1220 | //.disableScrollPropagation(container); 1221 | } 1222 | 1223 | if (L.Browser.mobile) { 1224 | L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); 1225 | } 1226 | 1227 | //L.DomEvent.on(container, 'mousewheel', this._mousewheelHandler, this); 1228 | 1229 | if (!this.options.detached) { 1230 | var iconCssClass = "elevation-toggle " + this.options.controlButton.iconCssClass + (this.options.autohide ? "" : " close-button"); 1231 | var link = this._button = L.DomUtil.create('a', iconCssClass, container); 1232 | link.href = '#'; 1233 | link.title = this.options.controlButton.title; 1234 | 1235 | if (this.options.collapsed) { 1236 | this._collapse(); 1237 | if (this.options.autohide) { 1238 | L.DomEvent 1239 | .on(container, 'mouseover', this._expand, this) 1240 | .on(container, 'mouseout', this._collapse, this); 1241 | } else { 1242 | L.DomEvent 1243 | .on(link, 'click', L.DomEvent.stop) 1244 | .on(link, 'click', this._toggle, this); 1245 | } 1246 | 1247 | L.DomEvent.on(link, 'focus', this._toggle, this); 1248 | 1249 | this._map.on('click', this._collapse, this); 1250 | // TODO: keyboard accessibility 1251 | } 1252 | } else { 1253 | // TODO: handle autohide when detached=true 1254 | } 1255 | }, 1256 | 1257 | _isObject: function(item) { 1258 | return (item && typeof item === 'object' && !Array.isArray(item)); 1259 | }, 1260 | 1261 | _isJSONDoc: function(doc, lazy) { 1262 | lazy = typeof lazy === "undefined" ? true : lazy; 1263 | if (typeof doc === "string" && lazy) { 1264 | doc = doc.trim(); 1265 | return doc.indexOf("{") == 0 || doc.indexOf("[") == 0; 1266 | } else { 1267 | try { 1268 | JSON.parse(doc.toString()); 1269 | } catch (e) { 1270 | if (typeof doc === "object" && lazy) return true; 1271 | console.warn(e); 1272 | return false; 1273 | } 1274 | return true; 1275 | } 1276 | }, 1277 | 1278 | _isXMLDoc: function(doc, lazy) { 1279 | lazy = typeof lazy === "undefined" ? true : lazy; 1280 | if (typeof doc === "string" && lazy) { 1281 | doc = doc.trim(); 1282 | return doc.indexOf("<") == 0; 1283 | } else { 1284 | var documentElement = (doc ? doc.ownerDocument || doc : 0).documentElement; 1285 | return documentElement ? documentElement.nodeName !== "HTML" : false; 1286 | } 1287 | }, 1288 | 1289 | _isDomVisible: function(elem) { 1290 | return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); 1291 | }, 1292 | 1293 | _isVisible: function(elem) { 1294 | if (!elem) return false; 1295 | 1296 | let styles = window.getComputedStyle(elem); 1297 | 1298 | function isVisibleByStyles(elem, styles) { 1299 | return styles.visibility !== 'hidden' && styles.display !== 'none'; 1300 | } 1301 | 1302 | function isAboveOtherElements(elem, styles) { 1303 | let boundingRect = elem.getBoundingClientRect(); 1304 | let left = boundingRect.left + 1; 1305 | let right = boundingRect.right - 1; 1306 | let top = boundingRect.top + 1; 1307 | let bottom = boundingRect.bottom - 1; 1308 | let above = true; 1309 | 1310 | let pointerEvents = elem.style.pointerEvents; 1311 | 1312 | if (styles['pointer-events'] == 'none') elem.style.pointerEvents = 'auto'; 1313 | 1314 | if (document.elementFromPoint(left, top) !== elem) above = false; 1315 | if (document.elementFromPoint(right, top) !== elem) above = false; 1316 | 1317 | // Only for completely visible elements 1318 | // if (document.elementFromPoint(left, bottom) !== elem) above = false; 1319 | // if (document.elementFromPoint(right, bottom) !== elem) above = false; 1320 | 1321 | elem.style.pointerEvents = pointerEvents; 1322 | 1323 | return above; 1324 | } 1325 | 1326 | if (!isVisibleByStyles(elem, styles)) return false; 1327 | if (!isAboveOtherElements(elem, styles)) return false; 1328 | return true; 1329 | }, 1330 | 1331 | _lazyLoadJS: function(url, skip) { 1332 | if (typeof skip == "undefined") { 1333 | skip = false; 1334 | } 1335 | if (skip instanceof Promise) { 1336 | return skip; 1337 | } 1338 | return new Promise(function(resolve, reject) { 1339 | if (skip) return resolve(); 1340 | var tag = document.createElement("script"); 1341 | tag.addEventListener('load', resolve, { once: true }); 1342 | tag.src = url; 1343 | document.head.appendChild(tag); 1344 | }); 1345 | }, 1346 | 1347 | _mouseenterHandler: function() { 1348 | if (this.fire) { 1349 | this.fire("elechart_enter", null, true); 1350 | } 1351 | if (this._map) { 1352 | this._map.fire("elechart_enter", null, true); 1353 | } 1354 | }, 1355 | 1356 | /* 1357 | * Handles the moueseover the chart and displays distance and altitude level 1358 | */ 1359 | _mousemoveHandler: function(d, i, ctx) { 1360 | if (!this._data || this._data.length === 0 || !this._chartEnabled) { 1361 | return; 1362 | } 1363 | var coords = d3.mouse(this._focusRect.node()); 1364 | var xCoord = coords[0]; 1365 | var item = this._data[this._findItemForX(xCoord)]; 1366 | 1367 | this._hidePositionMarker(); 1368 | this._showDiagramIndicator(item, xCoord); 1369 | this._showPositionMarker(item); 1370 | this._setMapView(item); 1371 | 1372 | if (this._map && this._map._container) { 1373 | L.DomUtil.addClass(this._map._container, 'elechart-hover'); 1374 | } 1375 | 1376 | var evt = { 1377 | data: item 1378 | }; 1379 | if (this.fire) { 1380 | this.fire("elechart_change", evt, true); 1381 | this.fire("elechart_hover", evt, true); 1382 | } 1383 | if (this._map) { 1384 | this._map.fire("elechart_change", evt, true); 1385 | this._map.fire("elechart_hover", evt, true); 1386 | } 1387 | }, 1388 | 1389 | /* 1390 | * Handles mouseover events of the data layers on the map. 1391 | */ 1392 | _mousemoveLayerHandler: function(e) { 1393 | if (!this._data || this._data.length === 0) { 1394 | return; 1395 | } 1396 | var latlng = e.latlng; 1397 | var item = this._findItemForLatLng(latlng); 1398 | if (item) { 1399 | var xCoord = item.xDiagCoord; 1400 | 1401 | this._hidePositionMarker(); 1402 | this._showDiagramIndicator(item, xCoord); 1403 | this._showPositionMarker(item); 1404 | } 1405 | }, 1406 | 1407 | _mouseoutHandler: function() { 1408 | if (!this.options.detached) { 1409 | this._hidePositionMarker(); 1410 | } 1411 | 1412 | if (this._map && this._map._container) { 1413 | L.DomUtil.removeClass(this._map._container, 'elechart-hover'); 1414 | } 1415 | 1416 | if (this.fire) this.fire("elechart_leave", null, true); 1417 | if (this._map) this._map.fire("elechart_leave", null, true); 1418 | }, 1419 | 1420 | _mousewheelHandler: function(e) { 1421 | if (this._map.gestureHandling && this._map.gestureHandling._enabled) return; 1422 | var ll = this._selectedItem ? this._selectedItem.latlng : this._map.getCenter(); 1423 | var z = e.deltaY > 0 ? this._map.getZoom() - 1 : this._map.getZoom() + 1; 1424 | this._resetDrag(); 1425 | this._map.flyTo(ll, z); 1426 | 1427 | }, 1428 | 1429 | /* 1430 | * Removes the drag rectangle and zoms back to the total extent of the data. 1431 | */ 1432 | _resetDrag: function() { 1433 | if (this._dragRectangleG) { 1434 | this._dragRectangleG.remove(); 1435 | this._dragRectangleG = null; 1436 | this._dragRectangle = null; 1437 | this._hidePositionMarker(); 1438 | } 1439 | }, 1440 | 1441 | _resetView: function() { 1442 | if (this._map && this._map._isFullscreen) return; 1443 | this._resetDrag(); 1444 | this._hidePositionMarker(); 1445 | this.fitBounds(this._fullExtent); 1446 | }, 1447 | 1448 | _resizeChart: function() { 1449 | if (this.options.responsive) { 1450 | if (this.options.detached) { 1451 | var newWidth = this.eleDiv.offsetWidth; // - 20; 1452 | 1453 | if (newWidth <= 0) return; 1454 | 1455 | this.options.width = newWidth; 1456 | this.eleDiv.innerHTML = ""; 1457 | this.eleDiv.appendChild(this.onAdd(this._map)); 1458 | } else { 1459 | this._map.removeControl(this._container); 1460 | this.addTo(this._map); 1461 | } 1462 | } 1463 | }, 1464 | 1465 | _showDiagramIndicator: function(item, xCoordinate) { 1466 | if (!this._chartEnabled) return; 1467 | 1468 | var opts = this.options; 1469 | this._focusG.style("visibility", "visible"); 1470 | 1471 | this._mousefocus.attr('x1', xCoordinate) 1472 | .attr('y1', 0) 1473 | .attr('x2', xCoordinate) 1474 | .attr('y2', this._height()) 1475 | .classed('hidden', false); 1476 | 1477 | var alt = item.z, 1478 | dist = item.dist, 1479 | ll = item.latlng, 1480 | numY = opts.hoverNumber.formatter(alt, opts.hoverNumber.decimalsY), 1481 | numX = opts.hoverNumber.formatter(dist, opts.hoverNumber.decimalsX); 1482 | 1483 | this._focuslabeltext 1484 | // .attr("x", xCoordinate) 1485 | .attr("y", this._y(item.z)) 1486 | .style("font-weight", "700"); 1487 | 1488 | this._focuslabelX 1489 | .text(numX + " " + this._xLabel) 1490 | .attr("x", xCoordinate + 10); 1491 | 1492 | this._focuslabelY 1493 | .text(numY + " " + this._yLabel) 1494 | .attr("x", xCoordinate + 10); 1495 | 1496 | var focuslabeltext = this._focuslabeltext.node(); 1497 | if (this._isDomVisible(focuslabeltext)) { 1498 | var bbox = focuslabeltext.getBBox(); 1499 | var padding = 2; 1500 | 1501 | this._focuslabelrect 1502 | .attr("x", bbox.x - padding) 1503 | .attr("y", bbox.y - padding) 1504 | .attr("width", bbox.width + (padding * 2)) 1505 | .attr("height", bbox.height + (padding * 2)); 1506 | 1507 | // move focus label to left 1508 | if (xCoordinate >= this._width() / 2) { 1509 | this._focuslabelrect.attr("x", this._focuslabelrect.attr("x") - this._focuslabelrect.attr("width") - (padding * 2) - 10); 1510 | this._focuslabelX.attr("x", this._focuslabelX.attr("x") - this._focuslabelrect.attr("width") - (padding * 2) - 10); 1511 | this._focuslabelY.attr("x", this._focuslabelY.attr("x") - this._focuslabelrect.attr("width") - (padding * 2) - 10); 1512 | } 1513 | } 1514 | 1515 | }, 1516 | 1517 | _toggle: function() { 1518 | if (L.DomUtil.hasClass(this._container, "elevation-expanded")) 1519 | this._collapse(); 1520 | else 1521 | this._expand(); 1522 | }, 1523 | 1524 | _setMapView: function(item) { 1525 | if (!this.options.followMarker || !this._map) return; 1526 | var zoom = this._map.getZoom(); 1527 | zoom = zoom < this._zFollow ? this._zFollow : zoom; 1528 | this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 }); 1529 | }, 1530 | 1531 | _showPositionMarker: function(item) { 1532 | this._selectedItem = item; 1533 | 1534 | if (this._map && !this._map.getPane('elevationPane')) { 1535 | this._map.createPane('elevationPane'); 1536 | this._map.getPane('elevationPane').style.zIndex = 625; // This pane is above markers but below popups. 1537 | this._map.getPane('elevationPane').style.pointerEvents = 'none'; 1538 | } 1539 | 1540 | if (this.options.marker == 'elevation-line') { 1541 | this._updatePositionMarker(item); 1542 | } else if (this.options.marker == 'position-marker') { 1543 | this._updateLeafletMarker(item); 1544 | } 1545 | }, 1546 | 1547 | _updateAxis: function() { 1548 | this._grid.selectAll("g").remove(); 1549 | this._axis.selectAll("g").remove(); 1550 | this._appendXGrid(this._grid); 1551 | this._appendYGrid(this._grid); 1552 | this._appendXaxis(this._axis); 1553 | this._appendYaxis(this._axis); 1554 | }, 1555 | 1556 | _updateHeightIndicator: function(item) { 1557 | var opts = this.options; 1558 | 1559 | var numY = opts.hoverNumber.formatter(item.z, opts.hoverNumber.decimalsY), 1560 | numX = opts.hoverNumber.formatter(item.dist, opts.hoverNumber.decimalsX); 1561 | 1562 | var normalizedAlt = this._height() / this._maxElevation * item.z, 1563 | normalizedY = item.y - normalizedAlt; 1564 | 1565 | this._mouseHeightFocus 1566 | .attr("x1", item.x) 1567 | .attr("x2", item.x) 1568 | .attr("y1", item.y) 1569 | .attr("y2", normalizedY) 1570 | .style("visibility", "visible"); 1571 | 1572 | this._mouseHeightFocusLabel 1573 | .attr("x", item.x) 1574 | .attr("y", normalizedY) 1575 | .text(numY + " " + this._yLabel) 1576 | .style("visibility", "visible"); 1577 | }, 1578 | 1579 | _updateLeafletMarker: function(item) { 1580 | var ll = item.latlng; 1581 | 1582 | if (!this._marker) { 1583 | this._marker = new L.Marker(ll, { 1584 | icon: this.options.markerIcon, 1585 | zIndexOffset: 1000000, 1586 | }); 1587 | this._marker.addTo(this._map, { 1588 | pane: 'elevationPane', 1589 | }); 1590 | } else { 1591 | this._marker.setLatLng(ll); 1592 | } 1593 | }, 1594 | 1595 | _updatePointG: function(item) { 1596 | this._pointG 1597 | .attr("transform", "translate(" + item.x + "," + item.y + ")") 1598 | .style("visibility", "visible"); 1599 | }, 1600 | 1601 | _updatePositionMarker: function(item) { 1602 | var point = this._map.latLngToLayerPoint(item.latlng); 1603 | var layerpoint = { 1604 | dist: item.dist, 1605 | x: point.x, 1606 | y: point.y, 1607 | z: item.z, 1608 | }; 1609 | 1610 | if (!this._mouseHeightFocus) { 1611 | L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer 1612 | var layerpane = d3.select(this._map.getContainer()).select(".leaflet-elevation-pane svg"); 1613 | this._appendPositionMarker(layerpane); 1614 | } 1615 | 1616 | this._updatePointG(layerpoint); 1617 | this._updateHeightIndicator(layerpoint); 1618 | }, 1619 | 1620 | _updateSummary: function() { 1621 | if (this.options.summary && this.summaryDiv) { 1622 | this.track_info = this.track_info || {}; 1623 | this.track_info.distance = this._distance || 0; 1624 | this.track_info.elevation_max = this._maxElevation || 0; 1625 | this.track_info.elevation_min = this._minElevation || 0; 1626 | d3.select(this.summaryDiv).html('Total Length: ' + this.track_info.distance.toFixed(2) + ' ' + this._xLabel + 'Max Elevation: ' + this.track_info.elevation_max.toFixed(2) + ' ' + this._yLabel + 'Min Elevation: ' + this.track_info.elevation_min.toFixed(2) + ' ' + this._yLabel + ''); 1627 | } 1628 | if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls. 1629 | var span = document.createElement('span'); 1630 | span.className = 'download'; 1631 | var save = document.createElement('a'); 1632 | save.innerHTML = "Download"; 1633 | save.href = "#"; 1634 | save.onclick = function(e) { 1635 | e.preventDefault(); 1636 | var evt = { confirm: this._saveFile.bind(this, this._downloadURL) }; 1637 | var type = this.options.downloadLink; 1638 | if (type == 'modal') { 1639 | if (typeof CustomEvent === "function") document.dispatchEvent(new CustomEvent("eletrack_download", { detail: evt })); 1640 | if (this.fire) this.fire('eletrack_download', evt); 1641 | if (this._map) this._map.fire('eletrack_download', evt); 1642 | } else if (type == 'link' || type === true) { 1643 | evt.confirm(); 1644 | } 1645 | }.bind(this); 1646 | 1647 | this.summaryDiv.appendChild(span).appendChild(save); 1648 | } 1649 | }, 1650 | 1651 | _width: function() { 1652 | var opts = this.options; 1653 | return opts.width - opts.margins.left - opts.margins.right; 1654 | }, 1655 | 1656 | }); 1657 | 1658 | L.control.elevation = function(options) { 1659 | return new L.Control.Elevation(options); 1660 | }; 1661 | --------------------------------------------------------------------------------