├── .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 | 
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 | 
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 | 
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 | 
148 |
149 | With `graphDetached=false`
150 |
151 | 
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 |
--------------------------------------------------------------------------------