diff --git a/admin_rust_wasm/www/index.js b/admin_rust_wasm/www/index.js index 87d00d3..55bbfd7 100644 --- a/admin_rust_wasm/www/index.js +++ b/admin_rust_wasm/www/index.js @@ -4,9 +4,15 @@ import createGpxFileAsString from "./createGpxFileAsString"; import { calcdistance, calc3DDistance } from "./distance"; import { fromHTML} from "./fromHTML"; + (function (window, document, undefined) { "use strict"; + let activeTab = document.getElementsByClassName("nav-tab-active")[0].innerHTML.includes("GPX") ? true : false; + if (!activeTab) { + return; + } + let esm = 4.5; let esmStore = 4.5; let dsm = 0.025; @@ -37,6 +43,7 @@ import { fromHTML} from "./fromHTML"; let selectedRow = null; let allMaps = []; let bounds = null; + let chart = 'chartjs'; //pageVarsForJs[0].charttype; statistics is not correct with elevation const input = document.querySelector(".file-input"); const text1 = document.querySelector("#gpx_text1"); @@ -62,8 +69,15 @@ import { fromHTML} from "./fromHTML"; const ignoreZeroElevsEnable = document.getElementById("gpx_ignore_zero_elev"); const stats = {}; - //const hashCode = (str) => [...str].reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0) + + /** + * A description of the entire function. + * + * @param {string} str - the string to be hashed + * @return {string} the hash value of the string + */ function hashCode(str) { + // replaces: const hashCode = (str) => [...str].reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0) let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); @@ -122,7 +136,9 @@ import { fromHTML} from "./fromHTML"; loadFileToString(clickedFile, 'filelist').then( () => { // do not filter the file. Show as saved on server if ( stats.file_content !== null && checksum != 0) { - showGpxFileOnLeaflet(); + let gjson = stats.parsedFile.toGeoJSON(); + //gjson = addInfoToGeoJSON(gjson, stats.parsedFile); + showGpxFileOnLeaflet(gjson); // show statistics as saved in file or calculated, Show hint not filtered. Show hint if no statsitics in file parseGpxString(text1); @@ -182,7 +198,9 @@ import { fromHTML} from "./fromHTML"; loadFileToString(clickedFile, 'filelist').then( () => { // do not filter the file. Show as saved on server if ( stats.file_content !== null && checksum != 0) { - showGpxFileOnLeaflet(); + let gjson = stats.parsedFile.toGeoJSON(); + //gjson = addInfoToGeoJSON(gjson, stats.parsedFile); + showGpxFileOnLeaflet(gjson); // show statistics as saved in file or calculated, Show hint not filtered. Show hint if no statsitics in file parseGpxString(text1); @@ -194,7 +212,7 @@ import { fromHTML} from "./fromHTML"; } // show the last saved gpx-file. Use the complete path on server. - window.addEventListener('load', (event) => { + window.addEventListener('load', () => { let lastFileResult = document.getElementById('fm-gpx-file')?.innerText || ""; @@ -218,10 +236,18 @@ import { fromHTML} from "./fromHTML"; }) } updateCSS(); + + if ( chart == 'ele' ) { + try { + document.getElementById('chartjs-profile-container0').remove(); + } catch (error) { + console.log(error); + } + }; }) // show the selected file. Use the preloaded content from the fakepath as xml-string - input.addEventListener("change", (event) => { + input.addEventListener("change", () => { // set the global variables stats.file_content = null; newFile = ""; @@ -253,7 +279,7 @@ import { fromHTML} from "./fromHTML"; }) - // ---------- listeners for file filter inputs + // ---------- listeners for filter Value inputs esmsel.addEventListener("input", () => { // update the selected file. Use the preloaded content from the fakepath as xml-string esm = esmsel.value; esmStore = esm; @@ -311,14 +337,13 @@ import { fromHTML} from "./fromHTML"; filterEventListener(); }) - // ---------- listeners for esm, dsm, filter, simplTol inputs + // ---------- listeners for esm, dsm, filter, simplTol enable inputs function handleEnableClick(storedValue, enableCheckbox, globalValue) { enableCheckbox.addEventListener("input", () => { - globalValue = enableCheckbox.checked ? storedValue : 0.0; + globalValue = enableCheckbox.checked ? storedValue : globalValue; // functional change filterEventListener(); }); } - handleEnableClick(esmStore, esmenable, esm); handleEnableClick(dsmStore, dsmenable, dsm); handleEnableClick(filterStore, filterenable, filter); @@ -357,7 +382,25 @@ import { fromHTML} from "./fromHTML"; showGpxFileOnLeaflet(); } // End: define all Event listeners -------------------- + /* + function addInfoToGeoJSON(geojson, parsedFile) { + parsedFile.tracks.forEach(element => { + // find feature in geojson + // add type to feature + // add name to feature, if available + }); + + parsedFile.routes.forEach(element => { + + }); + parsedFile.waypoints.forEach(element => { + + }); + + return geojson; + } + */ function updateCSS() { // add inline CSS @@ -471,9 +514,8 @@ import { fromHTML} from "./fromHTML"; NRtePts += element.points.length; }); - parsedFile.waypoints.forEach(element => { - NWayPts += element.points.length; - }); + NWayPts += parsedFile.waypoints.length; + if (NTrkPts + NRtePts == 0) { error = new Error("No points found in GPX file"); } } @@ -539,7 +581,7 @@ import { fromHTML} from "./fromHTML"; pageVarsForJs[0]['tracks']['track_0']['url'] = file; } - + // reset the leaflet and chart divs in dom if ( allMaps[0] != 'undefined' && ( allMaps[0] != null ) ) { try { allMaps[0].map.remove(); // leaflet map @@ -552,24 +594,54 @@ import { fromHTML} from "./fromHTML"; } catch (error) { console.log(error); } + + if ( chart == 'ele' ) { + try { + document.getElementById('chartjs-profile-container0').remove(); + } catch (error) { + console.log(error); + } + } try { - allMaps[0].chart.chart.destroy(); // chartjs - // rework for not understood behaviour of destroy funtion on canvas - allMaps[0].chart.elementOnPage.width = allMaps[0].chart.elementOnPage.clientWidth; + if (chart == 'chartjs') { + allMaps[0].chart.chart.destroy(); // chartjs + // rework for not understood behaviour of destroy funtion on canvas + allMaps[0].chart.elementOnPage.width = allMaps[0].chart.elementOnPage.clientWidth; + } else { + allMaps[0].controlElevation.clear() + let all = document.querySelectorAll("[id^='elevation-']"); + all.forEach(element => { + element.remove(); + }); + } + } catch (error) { console.log(error); } } - import(/* webpackChunkName: "leaflet_chartjs" */'../../js/leafletChartJs/leafletChartJsClass.js').then( (LeafletChartJs) => { - allMaps[0] = []; - LeafletChartJs.LeafletChartJs.count = 0; - LeafletChartJs.LeafletChartJs.numberOfMaps = null; - allMaps[0] = new LeafletChartJs.LeafletChartJs(0, 'boxmap' + 0 ); - if (bounds != null && bounds.isValid()) { allMaps[0].map.fitBounds(bounds); } - bounds = allMaps[0].map.getBounds(); - }) + + // load the leaflet map and height chart + if (chart == 'chartjs') { + import(/* webpackChunkName: "leaflet_chartjs" */'../../js/leafletChartJs/leafletChartJsClass.js').then( (LeafletChartJs) => { + allMaps[0] = []; + LeafletChartJs.LeafletChartJs.count = 0; + LeafletChartJs.LeafletChartJs.numberOfMaps = null; + allMaps[0] = new LeafletChartJs.LeafletChartJs(0, 'boxmap' + 0 ); + if (bounds != null && bounds.isValid()) { allMaps[0].map.fitBounds(bounds); } + bounds = allMaps[0].map.getBounds(); + }) + } else { + import(/* webpackChunkName: "elevation-admin" */'../../js/elevationClass.js').then( (LeafletElevation) => { + allMaps[0] = []; + LeafletElevation.LeafletElevation.count = 0; + LeafletElevation.LeafletElevation.numberOfMaps = null; + allMaps[0] = new LeafletElevation.LeafletElevation(0, 'boxmap' + 0 ); + if (bounds != null && bounds.isValid()) { allMaps[0].map.fitBounds(bounds); } + bounds = allMaps[0].map.getBounds(); + }); + } } function filterGPXTrack(fileContent, fileName=null) { diff --git a/admin_rust_wasm/www/package-lock.json b/admin_rust_wasm/www/package-lock.json index 7b68497..2697fa2 100644 --- a/admin_rust_wasm/www/package-lock.json +++ b/admin_rust_wasm/www/package-lock.json @@ -10,6 +10,7 @@ "license": "(MIT OR Apache-2.0)", "dependencies": { "admin_rust_wasm": "file:../pkg", + "fast-xml-parser": "^4.4.0", "plotly.js-dist": "^2.33.0" }, "bin": { @@ -1561,6 +1562,27 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", + "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -3602,6 +3624,11 @@ "node": ">=6" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", diff --git a/admin_rust_wasm/www/package.json b/admin_rust_wasm/www/package.json index 1041172..3de5e65 100644 --- a/admin_rust_wasm/www/package.json +++ b/admin_rust_wasm/www/package.json @@ -28,6 +28,7 @@ "homepage": "https://github.com/rustwasm/create-wasm-app#readme", "dependencies": { "admin_rust_wasm": "file:../pkg", + "fast-xml-parser": "^4.4.0", "plotly.js-dist": "^2.33.0" }, "devDependencies": { diff --git a/build_release.ps1 b/build_release.ps1 index d103c01..a3ef827 100644 --- a/build_release.ps1 +++ b/build_release.ps1 @@ -17,6 +17,12 @@ npm run build npx webpack --config .\webpack.config.js npx webpack --config .\webpack.chartjs-config.js +# change path to .\admin_rust_wasm\www +Set-Location .\admin_rust_wasm\www +npx webpack --config .\webpack.admin.js +# change the path back +Set-Location ..\.. + # create swiper bundle #npx webpack --config .\webpack.swiper.js diff --git a/inc/admin_settings.php b/inc/admin_settings.php index 0c57acf..e2bc210 100644 --- a/inc/admin_settings.php +++ b/inc/admin_settings.php @@ -1262,6 +1262,7 @@ public function fm_admin_scripts_enqueue() { 'ngpxfiles' => 1, 'tracks' => $tracks, 'eletheme' => $this->leaflet_settings["colour_theme_for_leaflet_elevation_1"], //'custom-theme', + 'charttype' => $this->leaflet_settings["charttype"], 'chartheight' => intval( $this->leaflet_settings["height_of_chart_11"] ), // 300, 'mapheight' => intval( $this->leaflet_settings["height_of_map_10"] ), // 400, 'mapaspect' => floatval( $this->leaflet_settings["aspect_ratio_of_map"] ), // 2, diff --git a/js/leafletChartJs/gpx.js b/js/leafletChartJs/gpx.js index c6b9ee6..33298be 100644 --- a/js/leafletChartJs/gpx.js +++ b/js/leafletChartJs/gpx.js @@ -45,29 +45,36 @@ var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS; var _DAY_IN_MILLIS = 24 * _HOUR_IN_MILLIS; var _GPX_STYLE_NS = 'http://www.topografix.com/GPX/gpx_style/0/2'; +var _DEFAULT_ICON = new L.Icon.Default; -var _DEFAULT_MARKER_OPTS = { - startIconUrl: 'pin-icon-start.png', - endIconUrl: 'pin-icon-end.png', - shadowUrl: 'pin-shadow.png', - wptIcons: [], - wptIconsType: [], - wptIconUrls : { - '': 'pin-icon-wpt.png', +var _DEFAULT_MARKERS = { + startIcon: _DEFAULT_ICON, + endIcon: _DEFAULT_ICON, + + // Based on 'sym' waypoint key + wptIcons: { + '': _DEFAULT_ICON, }, - wptIconTypeUrls : { - '': 'pin-icon-wpt.png', + + // Based on 'type' waypoint key + wptTypeIcons: { + '': _DEFAULT_ICON }, + + // Based on a regex over the waypoint's name pointMatchers: [], +}; + +var _DEFAULT_MARKER_OPTS = { iconSize: [33, 45], - shadowSize: [50, 50], iconAnchor: [16, 45], - shadowAnchor: [16, 47], clickable: false }; + var _DEFAULT_POLYLINE_OPTS = { color: 'blue' }; + var _DEFAULT_GPX_OPTS = { parseElements: ['track', 'route', 'waypoint'], joinTrackSegments: true @@ -76,10 +83,13 @@ var _DEFAULT_GPX_OPTS = { L.GPX = L.FeatureGroup.extend({ initialize: function(gpx, options) { options.max_point_interval = options.max_point_interval || _MAX_POINT_INTERVAL_MS; + options.markers = this._merge_objs( + _DEFAULT_MARKERS, + options.markers || {}); options.marker_options = this._merge_objs( _DEFAULT_MARKER_OPTS, options.marker_options || {}); - options.polyline_options = options.polyline_options || {}; + options.polyline_options = options.polyline_options || []; options.gpx_options = this._merge_objs( _DEFAULT_GPX_OPTS, options.gpx_options || {}); @@ -91,9 +101,10 @@ L.GPX = L.FeatureGroup.extend({ this._gpx = gpx; this._layers = {}; + this._prepare_markers(options.markers); this._init_info(); this.coords = []; - + if (gpx) { this._parse(gpx, options, this.options.async); } @@ -272,6 +283,31 @@ L.GPX = L.FeatureGroup.extend({ return r; }, + _prepare_markers: function(markers) { + function iconize(url) { + return new L.GPXTrackIcon({iconUrl: url}); + } + + Object.entries(markers).forEach(([key, value]) => { + if (key === 'wptIcons' || key === 'wptTypeIcons') { + markers[key] = this._prepare_markers(value); + } else if (key === 'pointMatchers') { + markers[key] = value.map(e => { + if (typeof(e.icon) === 'string') { + e.icon = iconize(e.icon); + } + return e; + }); + } else if (typeof(value) === 'string') { + markers[key] = iconize(value); + } else if (typeof(value) === 'object') { + markers[key] = this._prepare_markers(value); + } + }); + + return markers; + }, + _init_info: function() { this._info = { name: null, @@ -351,7 +387,10 @@ L.GPX = L.FeatureGroup.extend({ // routes are tags inside sections var routes = xml.getElementsByTagName('rte'); for (i = 0; i < routes.length; i++) { - layers = layers.concat(this._parse_segment(routes[i], options, {}, 'rtept')); + var route = routes[i]; + var base_style = this._extract_styling(route); + var polyline_options = this._get_polyline_options(options.polyline_options, i); + layers = layers.concat(this._parse_segment(routes[i], options, base_style, polyline_options, 'rtept')); } } @@ -360,14 +399,15 @@ L.GPX = L.FeatureGroup.extend({ var tracks = xml.getElementsByTagName('trk'); for (i = 0; i < tracks.length; i++) { var track = tracks[i]; - var polyline_options = this._extract_styling(track); + var base_style = this._extract_styling(track); + var polyline_options = this._get_polyline_options(options.polyline_options, i); if (options.gpx_options.joinTrackSegments) { - layers = layers.concat(this._parse_segment(track, options, polyline_options, 'trkpt')); + layers = layers.concat(this._parse_segment(track, options, base_style, polyline_options, 'trkpt')); } else { var segments = track.getElementsByTagName('trkseg'); for (j = 0; j < segments.length; j++) { - layers = layers.concat(this._parse_segment(segments[j], options, polyline_options, 'trkpt')); + layers = layers.concat(this._parse_segment(segments[j], options, base_style, polyline_options, 'trkpt')); } } } @@ -408,20 +448,14 @@ L.GPX = L.FeatureGroup.extend({ * Otherwise, fall back to the default icon if one was configured, or * finally to the default icon URL, if one was configured. */ - var wptIcons = options.marker_options.wptIcons; - var wptIconUrls = options.marker_options.wptIconUrls; - var wptIconsType = options.marker_options.wptIconsType; - var wptIconTypeUrls = options.marker_options.wptIconTypeUrls; - var ptMatchers = options.marker_options.pointMatchers || []; + var wptIcons = options.markers.wptIcons; + var wptTypeIcons = options.markers.wptTypeIcons; + var ptMatchers = options.markers.pointMatchers || []; var symIcon; if (wptIcons && symKey && wptIcons[symKey]) { symIcon = wptIcons[symKey]; - } else if (wptIconsType && typeKey && wptIconsType[typeKey]) { - symIcon = wptIconsType[typeKey]; - } else if (wptIconUrls && symKey && wptIconUrls[symKey]) { - symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls[symKey]}); - } else if (wptIconTypeUrls && typeKey && wptIconTypeUrls[typeKey]) { - symIcon = new L.GPXTrackIcon({iconUrl: wptIconTypeUrls[typeKey]}); + } else if (wptTypeIcons && typeKey && wptTypeIcons[typeKey]) { + symIcon = wptTypeIcons[typeKey]; } else if (ptMatchers.length > 0) { for (var j = 0; j < ptMatchers.length; j++) { if (ptMatchers[j].regex.test(name)) { @@ -431,8 +465,6 @@ L.GPX = L.FeatureGroup.extend({ } } else if (wptIcons && wptIcons['']) { symIcon = wptIcons['']; - } else if (wptIconUrls && wptIconUrls['']) { - symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls['']}); } if (!symIcon) { @@ -461,7 +493,7 @@ L.GPX = L.FeatureGroup.extend({ } }, - _parse_segment: function(line, options, polyline_options, tag) { + _parse_segment: function(line, options, base_style, polyline_options, tag) { var el = line.getElementsByTagName(tag); if (!el.length) return []; @@ -506,7 +538,7 @@ L.GPX = L.FeatureGroup.extend({ _ = el[i].getElementsByTagName('name'); if (_.length > 0) { var name = _[0].textContent; - var ptMatchers = options.marker_options.pointMatchers || []; + var ptMatchers = options.markers.pointMatchers || []; for (var j = 0; j < ptMatchers.length; j++) { if (ptMatchers[j].regex.test(name)) { @@ -516,6 +548,8 @@ L.GPX = L.FeatureGroup.extend({ } } + this._info.length += dist_3d; + _ = el[i].getElementsByTagNameNS('*', 'hr'); if (_.length > 0) { ll.meta.hr = parseInt(_[0].textContent); @@ -559,8 +593,6 @@ L.GPX = L.FeatureGroup.extend({ this._info.duration.moving += time_diff; } - this._info.length += dist_3d; - if (ele_diff > 0) { this._info.elevation.gain += ele_diff; } else { @@ -572,26 +604,26 @@ L.GPX = L.FeatureGroup.extend({ } // add track - var l = new L.Polyline(coords, this._extract_styling(line, polyline_options, options.polyline_options)); + var l = new L.Polyline(coords, this._extract_styling(line, base_style, polyline_options)); this.coords = coords; this.fire('addline', { line: l, element: line }); layers.push(l); - if (options.marker_options.startIcon || options.marker_options.startIconUrl) { + if (options.markers.startIcon) { // add start pin var marker = new L.Marker(coords[0], { clickable: options.marker_options.clickable, - icon: options.marker_options.startIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl}) + icon: options.markers.startIcon, }); this.fire('addpoint', { point: marker, point_type: 'start', element: el[0] }); layers.push(marker); } - if (options.marker_options.endIcon || options.marker_options.endIconUrl) { + if (options.markers.endIcon) { // add end pin var marker = new L.Marker(coords[coords.length-1], { clickable: options.marker_options.clickable, - icon: options.marker_options.endIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.endIconUrl}) + icon: options.markers.endIcon, }); this.fire('addpoint', { point: marker, point_type: 'end', element: el[el.length-1] }); layers.push(marker); @@ -611,6 +643,17 @@ L.GPX = L.FeatureGroup.extend({ return layers; }, + _get_polyline_options: function(polyline_options, i) { + /* + * Handle backwards compatibility with polyline_options being provided as a single object. + * In this situation, the provided style is expected to apply to all routes and tracks in the file. + */ + if (! Array.isArray(polyline_options)) { + return polyline_options; + } + return polyline_options[i] || {}; + }, + _extract_styling: function(el, base, overrides) { var style = this._merge_objs(_DEFAULT_POLYLINE_OPTS, base); var e = el.getElementsByTagNameNS(_GPX_STYLE_NS, 'line'); diff --git a/js/leafletChartJs/gpxTrackClass.js b/js/leafletChartJs/gpxTrackClass.js index 9ed1413..631f18e 100644 --- a/js/leafletChartJs/gpxTrackClass.js +++ b/js/leafletChartJs/gpxTrackClass.js @@ -39,39 +39,193 @@ class gpxTrackClass { * @param {string} [trackColour='#ff0000'] - The track colour to be assigned to the instance. Defaults to '#ff0000'. */ constructor(number, mapobject, tracks, trackNumber, trackColour = '#ff0000') { - this.tracks = tracks; + this.pageVariables = pageVarsForJs[number]; this.distSmoothing = parseInt(this.pageVariables.sw_options.gpx_distsmooth); this.eleSmoothing = parseFloat(this.pageVariables.sw_options.gpx_elesmooth); - this.number = number; this.mapobject = mapobject; this.trackNumber = trackNumber; this.trackColour = trackColour; - this.showTrack(this.trackNumber); + this.trackurl = tracks['track_'+ trackNumber.toString() ].url; // set track url : might be url or string in xml format + + if (this.#getTrackType(this.trackurl) === 'geojson') { + this.trackName = this.trackurl.properties.name; + this.showGeoJson(); + } + else { + this.showTrack(); // define a new function if the track data contains geojson. + } + } + + #getTrackType(input) { + + if (typeof(input) === 'string' && input.substr(0,1)==='<') { // direct XML has to start with a < + return 'xml' + } + else if (typeof(input) === 'object') { + return 'geojson'; + } + else { + return 'url'; + } + } + + /** + * Define Icons for the leaflet map. + * @param {string} path + * @param {string} iconpng + * @param {string} shadowpng + * @returns {object} icon leaflet.icon-object-type + */ + #setIcon(path, iconpng, shadowpng) { + let icon = L.icon({ + iconUrl: path + iconpng, + iconSize: [16, 22], + iconAnchor: [8, 22], + //popupAnchor: [0, -16], + shadowUrl: path + shadowpng, + shadowSize: [16, 22], + shadowAnchor: [8, 22], + }); + return icon; + } + + #handleMouseOver(e) { + if ( e.type === 'mouseover' && (this.trackNumber == this.mapobject.currentTrack ) ) { + // get id in coords. triggerEvent + const changed = new CustomEvent('mouseoverpath', { + detail: { + name: 'mouseoverpath', + track: this.trackNumber, // different + position: e.latlng, + index: this.getIndexForCoords(e.latlng), + } + }); + e.layer._map._container.dispatchEvent(changed); + + } else if ( e.type === 'mouseover' && (this.trackNumber != this.mapobject.currentTrack ) ) { + const changed = new CustomEvent('changetrack', { + detail: { + name: 'changetrack', + newtrack: this.trackNumber, + } + }); + e.layer._map._container.dispatchEvent(changed); + } + } + + showGeoJson() { + let coords = []; + let elevs = []; + let layers = []; + /* + const trackLayer = new L.LayerGroup(); + const routeLayer = new L.LayerGroup(); + const wptLayer = new L.LayerGroup(); + + function onEachFeature(feature, layer) { + switch (feature.geometry.type) { + // TODO: how to differ tracks and routes? + case 'Point': + wptLayer.addLayer(layer); + break; + case 'LineString': + trackLayer.addLayer(layer); + break; + default: //case 'Point': + routeLayer.addLayer(layer); + break; + } + } + */ + // showGeoJson(); on the map + L.Icon.Default.prototype.options.imagePath = this.pageVariables.imagepath; + this.gpxTracks = new L.geoJSON(this.trackurl, { // loads from url or parses xml directly + // options + //onEachFeature: onEachFeature, + }); + this.gpxTracks.setStyle({color :this.trackColour, stroke: parseInt(this.pageVariables.sw_options.trackwidth)}); + layers.push(this.gpxTracks); + + // get the data this.elev_data, this.coords + let i = 0; + this.trackurl.features[0].geometry.coordinates.forEach(element => { + let newElem = { + "lat": element[1], + "lng": element[0], + "meta": { + "time": null, + "ele": element[2], + "hr": null, + "cad": null, + "atemp": null, + "speed": 0 + } + }; + coords.push(newElem); + + let newelev = [ + i, // dist from null, also as string + element[2], + i.toFixed(2) + " km, " + element[2].toFixed(0) + " m", + ]; + elevs.push(newelev); + i+=1; + }); + this.coords = coords; + this.elev_data = elevs; + + // show start and end markers + this.myIcon1 = this.#setIcon(this.pageVariables.imagepath, 'pin-icon-start.png', 'pin-shadow.png'); + this.myIcon2 = this.#setIcon(this.pageVariables.imagepath, 'pin-icon-end.png', 'pin-shadow.png'); + + let marker1 = new L.marker(coords[0], { icon: this.myIcon1 }); + marker1.addTo(this.gpxTracks); + layers.push(marker1); + + let marker2 = new L.marker(coords[coords.length-1], { icon: this.myIcon2 }); + marker2.addTo(this.gpxTracks); + layers.push(marker2); + + this.gpxTracks.addTo(this.mapobject.map); + + // this.setTrackInfo(); // set track statistics + this.gpxTracks._info = {}; + this.gpxTracks._info.desc = this.trackurl.properties.description || ""; + this.setTrackInfo(); + + // set track name and bounds in leaflet + this.mapobject.controlLayer.addOverlay(this.gpxTracks, this.trackName); + this.mapobject.bounds = this.gpxTracks.getBounds(); + this.bounds = this.mapobject.bounds; + + // handle mouseover : same in both functions : seperate + this.gpxTracks.on('mouseover', (event) =>this.#handleMouseOver(event)) } /** * Shows a GPX-track on the leaflet map. (in Principle as part of the constructor). * Uses all class variables. * - * @param {number} trackNumber - The number of the track to be shown. * @return {void} This function does not return anything. */ - showTrack( trackNumber ) { - this.trackurl = this.tracks['track_'+ trackNumber.toString() ].url; // set track url : might be url or string in xml format + showTrack() { // show first track on map. track color, width, tooltip font color, background color - this.gpxTracks = new L.GPX(this.trackurl, { + this.gpxTracks = new L.GPX(this.trackurl, { // loads from url or parses xml directly async: this.asyncLoading, polyline_options: { color: this.trackColour, weight: parseInt(this.pageVariables.sw_options.trackwidth), }, - + markers: { + startIcon: this.pageVariables.imagepath +'/pin-icon-start.png', + endIcon: this.pageVariables.imagepath +'/pin-icon-end.png', + }, marker_options: { - startIconUrl: this.pageVariables.imagepath +'/pin-icon-start.png', - endIconUrl: this.pageVariables.imagepath +'/pin-icon-end.png', - shadowUrl: this.pageVariables.imagepath +'/pin-shadow.png', + //startIconUrl: this.pageVariables.imagepath +'/pin-icon-start.png', + //endIconUrl: this.pageVariables.imagepath +'/pin-icon-end.png', + //shadowUrl: this.pageVariables.imagepath +'/pin-shadow.png', iconSize: [16, 22], iconAnchor: [8, 22], shadowSize: [16, 22], @@ -83,40 +237,15 @@ class gpxTrackClass { this.coords = this.gpxTracks.get_coords(); // set info + this.trackName = this.gpxTracks._info.name; this.setTrackInfo(); this.mapobject.controlLayer.addOverlay(this.gpxTracks, this.gpxTracks._info.name); - //this.mapobject.map.fitBounds(this.gpxTracks.getBounds(), {padding: [150, 150]}); this.mapobject.bounds = this.gpxTracks.getBounds(); this.bounds = this.mapobject.bounds; - let classThis = this; - this.gpxTracks.on('mouseover', function(e) { - if ( e.type === 'mouseover' && (classThis.trackNumber == classThis.mapobject.currentTrack ) ) { - // let thecoords = e.propagatedFrom.latlngs; - // get id in coords. triggerEvent - // classThis.trackNumber : is the hovered track // classThis.mapobject.currentTrack : ist the current track - const changed = new CustomEvent('mouseoverpath', { - detail: { - name: 'mouseoverpath', - track: this._info.name, - position: e.latlng, - index: classThis.getIndexForCoords(e.latlng), - } - }); - - this._map._container.dispatchEvent(changed); - } else if ( e.type === 'mouseover' && (classThis.trackNumber != classThis.mapobject.currentTrack ) ) { - const changed = new CustomEvent('changetrack', { - detail: { - name: 'changetrack', - newtrack: classThis.trackNumber, - } - }); - - this._map._container.dispatchEvent(changed); - } - }) + // handle mouseover + this.gpxTracks.on('mouseover', (event) =>this.#handleMouseOver(event)); } /** diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/LICENSE b/js/leafletChartJs/leaflet-gpx-2.0.0/LICENSE new file mode 100644 index 0000000..ff83736 --- /dev/null +++ b/js/leafletChartJs/leaflet-gpx-2.0.0/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2011-2012 Pavel Shramov +Copyright (C) 2013 Maxime Petazzoni +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/README.md b/js/leafletChartJs/leaflet-gpx-2.0.0/README.md new file mode 100644 index 0000000..3ded30a --- /dev/null +++ b/js/leafletChartJs/leaflet-gpx-2.0.0/README.md @@ -0,0 +1,474 @@ +# GPX plugin for Leaflet + +[![CDNJS](https://img.shields.io/cdnjs/v/leaflet-gpx.svg)](https://cdnjs.com/libraries/leaflet-gpx) + +[Leaflet](http://www.leafletjs.com) is a Javascript library for displaying +interactive maps. This plugin, based on the work of [Pavel +Shramov](http://github.com/shramov) and his +[leaflet-plugins](http://github.com/shramov/leaflet-plugins), allows for +displaying and analyzing GPX tracks and their waypoints so they can be +displayed on a Leaflet map as a new layer. + +As it parses the GPX data, `leaflet-gpx` records information about the +GPX track, including total time, moving time, total distance, elevation +stats and heart-rate, and makes it accessible through an exhaustive set +of accessor methods. + +GPX parsing will automatically handle pauses in the track with a default +tolerance interval of 15 seconds between points. You can configure this +interval by setting `max_point_interval`, in milliseconds, in the options +passed to the `GPX` constructor. + +I've put together a complete example as a +[demo](http://mpetazzoni.github.io/leaflet-gpx/). + +## License + +`leaflet-gpx` is under the *BSD 2-clause license*. Please refer to the +attached LICENSE file and/or to the copyright header in gpx.js for more +information. + +## Usage + +Usage is very simple. First, include the Leaflet.js and Leaflet-GPX +scripts in your HTML page: + +```html + + + + + + + + + + + +``` + +Now, let's consider we have a Leaflet map: + +```javascript +var map = L.map('map'); +L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: 'Map data © OpenStreetMap' +}).addTo(map); +``` + +Displaying a GPX track on it is as easy as: + +```javascript +var url = '...'; // URL to your GPX file or the GPX itself +new L.GPX(url, {async: true}).on('loaded', function(e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +Some GPX tracks contain the actual route/track twice, both the `` and +`` elements are used. You can tell `leaflet-gpx` which tag to use or to +use both (which is the default setting for backwards compatibility) with the +`gpx_options` object in the second argument of the constructor. The member +`parseElements` controls this behavior, it should be an array that contains +`'route'` and/or `'track'`. + +### Available functions + +If you want to display additional information about the GPX track, you can do +so in the 'loaded' event handler, calling one of the following methods on the +`GPX` object `e.target`: + +* `get_name()`: returns the name of the GPX track +* `get_distance()`: returns the total track distance, in meters +* `get_start_time()`: returns a Javascript `Date` object representing the + starting time +* `get_end_time()`: returns a Javascript `Date` object representing when the + last point was recorded +* `get_moving_time()`: returns the moving time, in milliseconds +* `get_total_time()`: returns the total track time, in milliseconds +* `get_moving_pace()`: returns the average moving pace in milliseconds per km +* `get_moving_speed()`: returns the average moving speed in km per hour +* `get_total_speed()`: returns the average total speed in km per hour +* `get_elevation_min()`: returns the lowest elevation, in meters +* `get_elevation_max()`: returns the highest elevation, in meters +* `get_elevation_gain()`: returns the cumulative elevation gain, in meters +* `get_elevation_loss()`: returns the cumulative elevation loss, in meters +* `get_speed_max()`: returns the maximum speed in km per hour +* `get_average_hr()`: returns the average heart rate (if available) +* `get_average_cadence()`: returns the average cadence (if available) +* `get_average_temp()`: returns the average of the temperature (if available) + +If you're not a fan of the metric system, you also have the following methods +at your disposal: + +* `get_distance_imp()`: returns the total track distance in miles +* `get_moving_pace_imp()`: returns the average moving pace in milliseconds per + hour +* `get_moving_speed_imp()`: returns the average moving speed in miles per hour +* `get_total_speed_imp()`: returns the average total speed in miles per hour +* `get_elevation_min_imp()`: returns the lowest elevation, in feet +* `get_elevation_max_imp()`: returns the highest elevation, in feet +* `get_elevation_gain_imp()`: returns the cumulative elevation gain, in feet +* `get_elevation_loss_imp()`: returns the cumulative elevation loss, in feet +* `get_speed_max_imp()`: returns the maximum speed in miles per hour + +The reason why these methods return milliseconds is that you have at your +disposal nice helper methods to format a duration in milliseconds into a cool +string: + +* `get_duration_string(duration, hidems)` format to a string like `3:07'48"` + or `59'32.431`, where `duration` is in + milliseconds and `hidems` is an optional boolean you can use to request never + to display millisecond precision. +* `get_duration_string_iso(duration, hidems)` formats to an ISO like + representation like `3:07:48` or `59:32.431`, where `duration` is in + milliseconds and `hidems` is an optional boolean you can use to request never + to display millisecond precision. + +You can also get full elevation, heartrate, cadence and temperature data with: + +* `get_elevation_data()` and `get_elevation_data_imp()` +* `get_speed_data` and `get_speed_data_imp()` +* `get_heartrate_data()` and `get_heartrate_data_imp()` +* `get_cadence_data()` and `get_cadence_data_imp()` +* `get_temp_data()` and `get_temp_data_imp()` + +These methods all return an array of points `[distance, value, tooltip]` where +the distance is either in kilometers or in miles and the elevation in meters or +feet, depending on whether you use the `_imp` variant or not. Heart rate, +obviously, doesn't change. + +### Reloading + +You can make `leaflet-gpx` reload the source GPX file by calling the +`reload()` method. For example, to trigger a reload every 5 seconds, you +can do: + +```javascript +var gpx = new L.GPX(url); +setInterval(function() { + gpx.reload(); +}, 5000); +``` + +## About marker icons + +### Configuring markers + +By default, `leaflet-gpx` uses Leaflet's default icon image for all +markers. You can override this behavior by providing a Leaflet `Icon` +object, or the path or URL to an image to use as the marker, for any of +the markers supported by this plugin as part of the `markers` parameter: + +```javascript +new L.GPX(url, { + async: true, + markers: { + startIcon: ..., + endIcon: ... + wptIcons: { ... }, + wptTypeIcons: { ... }, + pointMatchers: [ ... ], + } +}).on('loaded', function(e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +* `startIcon` is used for the marker at the beginning of the GPX track; +* `endIcon` is used for the marker at the end of the GPX track; +* `wptIcons` and `wptTypeIcons` are mappings of waypoint symbols and + types to the icon you want to use for each; +* `pointMatchers` is an array of custom point matchers and their + respective icon (see below); + +You can also override any of those to `null` to disable the +corresponding marker altogether. + +Here is how you would override the URL of the provided icons for start +and end markers, but none of the other types of markers: + +```javascript +new L.GPX(url, { + async: true, + markers: { + startIcon: 'images/pin-icon-start.png', + endIcon: 'images/pin-icon-end.png', + } +}).on('loaded', function(e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +It's usually preferrable and more flexible to provide a Leaflet `Icon` +instance directly, for example from +[leaflet-awesome-markers](https://github.com/lennardv2/Leaflet.awesome-markers). See +https://leafletjs.com/examples/custom-icons/ for more information. + +```javascript +new L.GPX(url, { + async: true, + markers: { + wptIcons: { + 'Coffee shop': new L.AwesomeMarkers.icon({ + icon: 'coffee', + prefix: 'fa', + markerColor: 'blue', + iconColor: 'white' + }) + } + } +}).on('loaded', function (e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +### Marker options + +You can fine tune marker options using any of the parameters expected by +[Leaflet's base L.Icon class](https://leafletjs.com/reference.html#icon) +using the `marker_options` parameters: + +```javascript +new L.GPX(url, { + async: true, + marker_options: { + iconSize: [38, 95], + iconAnchor: [22, 94], + } +}).on('loaded', function(e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` +### Sensible defaults + +Note that you do not need to override all the marker definitions, or +marker options, when providing the `markers` and `marker_options` +parameters to the GPX constructor as this plugin will use sensible +defaults for all of those settings. + +## About waypoints + +By default, this plugin will parse all Waypoints from a GPX file. This +can be controlled via the value `waypoint` in `gpx_options`, e.g. +`parseElements: ['track', 'route', 'waypoint']`. + +The icon used in the marker representing each track waypoint is +determined based on the waypoint's properties, in this order: + +* If the waypoint has a `sym` attribute, the `markers.wptIcons[sym]` + icon is used; +* If the waypoint has a `type` attribute, the `markers.wptTypeIcons[type]` + icon is used; +* Point matchers are evaluated in order, if one matches the waypoint's + `name` attribute, its icon is used (see _Named markers_ below); +* If none of the above rules match, the default `''` (empty string) icon + entry in `wptIcons` is used. + +```javascript +new L.GPX(url, { + async: true, + markers: { + wptIcons: { + '': new L.Icon.Default, + 'Geocache Found': 'img/gpx/geocache.png', + 'Park': 'img/gpx/tree.png' + }, + } +}).on('loaded', function (e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +## Named points + +GPX points can be named, for example to denote certain POIs (points of +interest). You can setup rules to match point names to create labeled +markers for those points by providing a `pointMatchers` array in the +`markers` constructor parameter. + +Each element in this array must define a `regex` to match the point's +name and an `icon` definition (a `L.Icon` or subclass object, or the URL +to an icon image). + +Each named point in the GPX track is evaluated against those rules and +a marker is created with the point's name as label from the first +matching rule. This also applies to named waypoints, but keep in mind +that waypoint icons rules take precedence over point matchers. + +```javascript +new L.GPX(url, { + async: true, + marker_options: { + pointMatchers: [ + { + regex: /Coffee/, + icon: new L.AwesomeMarkers.icon({ + icon: 'coffee', + markerColor: 'blue', + iconColor: 'white' + }), + }, + { + regex: /Home/, + icon: new L.AwesomeMarkers.icon({ + icon: 'home', + markerColor: 'green', + iconColor: 'white' + }), + } + ] + } +}).on('loaded', function(e) { + map.fitToBounds(e.target.getBounds()); +}).addTo(map); +``` + +## Events + +Events are fired on the `L.GPX` object as the GPX data is being parsed +and the map layers generated. You can listen for those events by +attaching the corresponding event listener on the `L.GPX` object: + +```javascript +new L.GPX(url, async: true, { + // options +}).on('addpoint', function(e) { + console.log('Added ' + e.point_type + ' point: ' + e.point); +}).on('loaded', function(e) { + var gpx = e.target; + map.fitToBounds(gpx.getBounds()); +}).on('error', function(e) { + console.log('Error loading file: ' + e.err); +}).addTo(map); +``` + +Note that for your event listeners to be correctly triggered, you need +to pass `async: true` to the `L.GPX` constructor; otherwise the parsing +of the GPX happens synchronously in the constructor before you your +event listeners get registered! + +`addpoint` events are fired for every marker added to the map, in +particular for the start and end points, all the waypoints, and all the +named points that matched `pointMatchers` rules. Each `addpoint` event +contains the following properties: + +- `point`: the marker object itself, from which you can get or modify + the latitude and longitude of the point and any other attribute of the + marker. +- `point_type`: one of `start`, `end`, `waypoint` or `label`, allowing + you to identify what type of point the marker is for. +- `element`: the track point element the marker was created for. + +One use case for those events is for example to attach additional +content or behavior to the markers that were generated (popups, etc). + +`error` events are fired when no layers of the type(s) specified in +`options.gpx_options.parseElements` can be parsed out of the given +file. For instance, `error` would be fired if a file with no waypoints +was attempted to be loaded with `parseElements` set to `['waypoint']`. +Each `error` event contains the following property: + +- `err`: a message with details about the error that occurred. + +## Line styling + +`leaflet-gpx` understands the [GPX +Style](http://www.topografix.com/GPX/gpx_style/0/2) extension, and will +extract styling information defined on routes and track segments to use +for drawing the corresponding polyline. + +```xml + + + + FF0000 + 0.5 + 1 + square + square + 0,10 + 3 + + + + +``` + +You can override the style of the lines by passing a `polyline_options` +array into the `options` argument of the `L.GPX` constructor, each +element of the array defines the style for the corresponding route +and/or track in the file (in the same order). + +```javascript +new L.GPX(url, { + polyline_options: [{ + color: 'green', + opacity: 0.75, + weight: 3, + lineCap: 'round' + },{ + color: 'blue', + opacity: 0.75, + weight: 1 + }] +}).on('loaded', function(e) { + var gpx = e.target; + map.fitToBounds(gpx.getBounds()); +}).addTo(map); +``` + +If you have many routes or tracks in your GPX file and you want them to +share the same styling, you can pass `polyline_options` as a single +object rather than an array (this is also how `leaflet-gpx` worked +before the introduction of the array): + +```javascript +new L.GPX(url, { + polyline_options: { + color: 'green', + opacity: 0.75, + weight: 3, + lineCap: 'round' + } +}).on('loaded', function(e) { + var gpx = e.target; + map.fitToBounds(gpx.getBounds()); +}).addTo(map); +``` + +For more information on the available polyline styling options, refer to +the [Leaflet documentation on +Polyline](https://leafletjs.com/reference.html#polyline). By +default, if no styling is available, the line will be drawn in _blue_. + +## GPX parsing options + +### Multiple track segments within each track + +GPX file may contain multiple tracks represented by `` elements, +each track possibly composed of multiple segments with `` +elements. Although this plugin will always represent each GPX route and +each GPX track as distinct entities with their own start and end +markers, track segments will by default be joined into a single line. + +You can disable this behavior by setting the `joinTrackSegments` flag to +`false` in the `gpx_options`: + +```javascript +new L.GPX(url, { + gpx_options: { + joinTrackSegments: false + } +}).on('loaded', function(e) { + map.fitBounds(e.target.getBounds()); +}).addTo(map); +``` + +## Caveats + +* Distance calculation is relatively accurate, but elevation change + calculation is not topographically adjusted, so the total elevation + gain/loss/change might appear inaccurate in some situations. +* Currently doesn't seem to work in IE8/9. See #9 and #11 for + discussion. diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/gpx.js b/js/leafletChartJs/leaflet-gpx-2.0.0/gpx.js new file mode 100644 index 0000000..2208f81 --- /dev/null +++ b/js/leafletChartJs/leaflet-gpx-2.0.0/gpx.js @@ -0,0 +1,703 @@ +/** + * Copyright (C) 2011-2012 Pavel Shramov + * Copyright (C) 2013-2017 Maxime Petazzoni + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Thanks to Pavel Shramov who provided the initial implementation and Leaflet + * integration. Original code was at https://github.com/shramov/leaflet-plugins. + * + * It was then cleaned-up and modified to record and make available more + * information about the GPX track while it is being parsed so that the result + * can be used to display additional information about the track that is + * rendered on the Leaflet map. + */ + +var L = L || require('leaflet'); + +var _MAX_POINT_INTERVAL_MS = 15000; +var _SECOND_IN_MILLIS = 1000; +var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS; +var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS; +var _DAY_IN_MILLIS = 24 * _HOUR_IN_MILLIS; + +var _GPX_STYLE_NS = 'http://www.topografix.com/GPX/gpx_style/0/2'; +var _DEFAULT_ICON = new L.Icon.Default; + +var _DEFAULT_MARKERS = { + startIcon: _DEFAULT_ICON, + endIcon: _DEFAULT_ICON, + + // Based on 'sym' waypoint key + wptIcons: { + '': _DEFAULT_ICON, + }, + + // Based on 'type' waypoint key + wptTypeIcons: { + '': _DEFAULT_ICON + }, + + // Based on a regex over the waypoint's name + pointMatchers: [], +}; + +var _DEFAULT_MARKER_OPTS = { + iconSize: [33, 45], + iconAnchor: [16, 45], + clickable: false +}; + +var _DEFAULT_POLYLINE_OPTS = { + color: 'blue' +}; + +var _DEFAULT_GPX_OPTS = { + parseElements: ['track', 'route', 'waypoint'], + joinTrackSegments: true +}; + +L.GPX = L.FeatureGroup.extend({ + initialize: function(gpx, options) { + options.max_point_interval = options.max_point_interval || _MAX_POINT_INTERVAL_MS; + options.markers = this._merge_objs( + _DEFAULT_MARKERS, + options.markers || {}); + options.marker_options = this._merge_objs( + _DEFAULT_MARKER_OPTS, + options.marker_options || {}); + options.polyline_options = options.polyline_options || []; + options.gpx_options = this._merge_objs( + _DEFAULT_GPX_OPTS, + options.gpx_options || {}); + + L.Util.setOptions(this, options); + + // Base icon class for track pins. + L.GPXTrackIcon = L.Icon.extend({ options: options.marker_options }); + + this._gpx = gpx; + this._layers = {}; + this._prepare_markers(options.markers); + this._init_info(); + + if (gpx) { + this._parse(gpx, options, this.options.async); + } + }, + + get_duration_string: function(duration, hidems) { + var s = ''; + + if (duration >= _DAY_IN_MILLIS) { + s += Math.floor(duration / _DAY_IN_MILLIS) + 'd '; + duration = duration % _DAY_IN_MILLIS; + } + + if (duration >= _HOUR_IN_MILLIS) { + s += Math.floor(duration / _HOUR_IN_MILLIS) + ':'; + duration = duration % _HOUR_IN_MILLIS; + } + + var mins = Math.floor(duration / _MINUTE_IN_MILLIS); + duration = duration % _MINUTE_IN_MILLIS; + if (mins < 10) s += '0'; + s += mins + '\''; + + var secs = Math.floor(duration / _SECOND_IN_MILLIS); + duration = duration % _SECOND_IN_MILLIS; + if (secs < 10) s += '0'; + s += secs; + + if (!hidems && duration > 0) s += '.' + Math.round(Math.floor(duration)*1000)/1000; + else s += '"'; + + return s; + }, + + get_duration_string_iso: function(duration, hidems) { + var s = this.get_duration_string(duration, hidems); + return s.replace("'",':').replace('"',''); + }, + + // Public methods + to_miles: function(v) { return v / 1.60934; }, + to_ft: function(v) { return v * 3.28084; }, + m_to_km: function(v) { return v / 1000; }, + m_to_mi: function(v) { return v / 1609.34; }, + ms_to_kmh: function(v) { return v * 3.6; }, + ms_to_mih: function(v) { return v / 1609.34 * 3600; }, + + get_name: function() { return this._info.name; }, + get_desc: function() { return this._info.desc; }, + get_author: function() { return this._info.author; }, + get_copyright: function() { return this._info.copyright; }, + get_distance: function() { return this._info.length; }, + get_distance_imp: function() { return this.to_miles(this.m_to_km(this.get_distance())); }, + + get_start_time: function() { return this._info.duration.start; }, + get_end_time: function() { return this._info.duration.end; }, + get_moving_time: function() { return this._info.duration.moving; }, + get_total_time: function() { return this._info.duration.total; }, + + get_moving_pace: function() { return this.get_moving_time() / this.m_to_km(this.get_distance()); }, + get_moving_pace_imp: function() { return this.get_moving_time() / this.get_distance_imp(); }, + + get_moving_speed: function() { return this.m_to_km(this.get_distance()) / (this.get_moving_time() / (3600 * 1000)) ; }, + get_moving_speed_imp:function() { return this.to_miles(this.m_to_km(this.get_distance())) / (this.get_moving_time() / (3600 * 1000)) ; }, + + get_total_speed: function() { return this.m_to_km(this.get_distance()) / (this.get_total_time() / (3600 * 1000)); }, + get_total_speed_imp: function() { return this.to_miles(this.m_to_km(this.get_distance())) / (this.get_total_time() / (3600 * 1000)); }, + + get_elevation_gain: function() { return this._info.elevation.gain; }, + get_elevation_loss: function() { return this._info.elevation.loss; }, + get_elevation_gain_imp: function() { return this.to_ft(this.get_elevation_gain()); }, + get_elevation_loss_imp: function() { return this.to_ft(this.get_elevation_loss()); }, + get_elevation_data: function() { + var _this = this; + return this._info.elevation._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_km, null, + function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' m'; }); + }); + }, + get_elevation_data_imp: function() { + var _this = this; + return this._info.elevation._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_mi, _this.to_ft, + function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' ft'; }); + }); + }, + get_elevation_max: function() { return this._info.elevation.max; }, + get_elevation_min: function() { return this._info.elevation.min; }, + get_elevation_max_imp: function() { return this.to_ft(this.get_elevation_max()); }, + get_elevation_min_imp: function() { return this.to_ft(this.get_elevation_min()); }, + + get_speed_data: function() { + var _this = this; + return this._info.speed._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_km, _this.ms_to_kmh, + function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(2) + ' km/h'; }); + }); + }, + get_speed_data_imp: function() { + var _this = this; + return this._info.speed._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_mi, _this.ms_to_mih, + function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(2) + ' mi/h'; }); + }); + }, + get_speed_max: function() { return this.m_to_km(this._info.speed.max) * 3600; }, + get_speed_max_imp: function() { return this.to_miles(this.get_speed_max()); }, + + get_average_hr: function() { return this._info.hr.avg; }, + get_average_temp: function() { return this._info.atemp.avg; }, + get_average_cadence: function() { return this._info.cad.avg; }, + get_heartrate_data: function() { + var _this = this; + return this._info.hr._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_km, null, + function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' bpm'; }); + }); + }, + get_heartrate_data_imp: function() { + var _this = this; + return this._info.hr._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null, + function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' bpm'; }); + }); + }, + get_cadence_data: function() { + var _this = this; + return this._info.cad._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_km, null, + function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' rpm'; }); + }); + }, + get_temp_data: function() { + var _this = this; + return this._info.atemp._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_km, null, + function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' degrees'; }); + }); + }, + get_cadence_data_imp: function() { + var _this = this; + return this._info.cad._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null, + function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' rpm'; }); + }); + }, + get_temp_data_imp: function() { + var _this = this; + return this._info.atemp._points.map( + function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null, + function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' degrees'; }); + }); + }, + + reload: function() { + this._init_info(); + this.clearLayers(); + this._parse(this._gpx, this.options, this.options.async); + }, + + // Private methods + _merge_objs: function(a, b) { + var _ = {}; + for (var attr in a) { _[attr] = a[attr]; } + for (var attr in b) { _[attr] = b[attr]; } + return _; + }, + + _prepare_data_point: function(p, trans1, trans2, trans_tooltip) { + var r = [trans1 && trans1(p[0]) || p[0], trans2 && trans2(p[1]) || p[1]]; + r.push(trans_tooltip && trans_tooltip(r[0], r[1]) || (r[0] + ': ' + r[1])); + return r; + }, + + _prepare_markers: function(markers) { + function iconize(url) { + return new L.GPXTrackIcon({iconUrl: url}); + } + + Object.entries(markers).forEach(([key, value]) => { + if (key === 'wptIcons' || key === 'wptTypeIcons') { + markers[key] = this._prepare_markers(value); + } else if (key === 'pointMatchers') { + markers[key] = value.map(e => { + if (typeof(e.icon) === 'string') { + e.icon = iconize(e.icon); + } + return e; + }); + } else if (typeof(value) === 'string') { + markers[key] = iconize(value); + } else if (typeof(value) === 'object') { + markers[key] = this._prepare_markers(value); + } + }); + + return markers; + }, + + _init_info: function() { + this._info = { + name: null, + length: 0.0, + elevation: {gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _points: []}, + speed : {max: 0.0, _points: []}, + hr: {avg: 0, _total: 0, _points: []}, + duration: {start: null, end: null, moving: 0, total: 0}, + atemp: {avg: 0, _total: 0, _points: []}, + cad: {avg: 0, _total: 0, _points: []} + }; + }, + + _load_xml: function(url, cb, options, async) { + if (async == undefined) async = this.options.async; + if (options == undefined) options = this.options; + + var req = new window.XMLHttpRequest(); + req.open('GET', url, async); + try { + req.overrideMimeType('text/xml'); // unsupported by IE + } catch(e) {} + req.onreadystatechange = function() { + if (req.readyState != 4) return; + if(req.status == 200) cb(req.responseXML, options); + }; + req.send(null); + }, + + _parse: function(input, options, async) { + var _this = this; + var cb = function(gpx, options) { + var layers = _this._parse_gpx_data(gpx, options); + if (!layers) { + _this.fire('error', { err: 'No parseable layers of type(s) ' + JSON.stringify(options.gpx_options.parseElements) }); + return; + } + _this.addLayer(layers); + _this.fire('loaded', { layers: layers, element: gpx }); + } + if (input.substr(0,1)==='<') { // direct XML has to start with a < + var parser = new DOMParser(); + if (async) { + setTimeout(function() { + cb(parser.parseFromString(input, "text/xml"), options); + }); + } else { + cb(parser.parseFromString(input, "text/xml"), options); + } + } else { + this._load_xml(input, cb, options, async); + } + }, + + _parse_gpx_data: function(xml, options) { + var i, t, l, el, layers = []; + + var name = xml.getElementsByTagName('name'); + if (name.length > 0) { + this._info.name = name[0].textContent; + } + var desc = xml.getElementsByTagName('desc'); + if (desc.length > 0) { + this._info.desc = desc[0].textContent; + } + var author = xml.getElementsByTagName('author'); + if (author.length > 0) { + this._info.author = author[0].textContent; + } + var copyright = xml.getElementsByTagName('copyright'); + if (copyright.length > 0) { + this._info.copyright = copyright[0].textContent; + } + + var parseElements = options.gpx_options.parseElements; + if (parseElements.indexOf('route') > -1) { + // routes are tags inside sections + var routes = xml.getElementsByTagName('rte'); + for (i = 0; i < routes.length; i++) { + var route = routes[i]; + var base_style = this._extract_styling(route); + var polyline_options = this._get_polyline_options(options.polyline_options, i); + layers = layers.concat(this._parse_segment(routes[i], options, base_style, polyline_options, 'rtept')); + } + } + + if (parseElements.indexOf('track') > -1) { + // tracks are tags in one or more sections in each + var tracks = xml.getElementsByTagName('trk'); + for (i = 0; i < tracks.length; i++) { + var track = tracks[i]; + var base_style = this._extract_styling(track); + var polyline_options = this._get_polyline_options(options.polyline_options, i); + + if (options.gpx_options.joinTrackSegments) { + layers = layers.concat(this._parse_segment(track, options, base_style, polyline_options, 'trkpt')); + } else { + var segments = track.getElementsByTagName('trkseg'); + for (j = 0; j < segments.length; j++) { + layers = layers.concat(this._parse_segment(segments[j], options, base_style, polyline_options, 'trkpt')); + } + } + } + } + + this._info.hr.avg = Math.round(this._info.hr._total / this._info.hr._points.length); + this._info.cad.avg = Math.round(this._info.cad._total / this._info.cad._points.length); + this._info.atemp.avg = Math.round(this._info.atemp._total / this._info.atemp._points.length); + + // parse waypoints and add markers for each of them + if (parseElements.indexOf('waypoint') > -1) { + el = xml.getElementsByTagName('wpt'); + for (i = 0; i < el.length; i++) { + var ll = new L.LatLng( + el[i].getAttribute('lat'), + el[i].getAttribute('lon')); + + var nameEl = el[i].getElementsByTagName('name'); + var name = nameEl.length > 0 ? nameEl[0].textContent : ''; + + var descEl = el[i].getElementsByTagName('desc'); + var desc = descEl.length > 0 ? descEl[0].textContent : ''; + + var symEl = el[i].getElementsByTagName('sym'); + var symKey = symEl.length > 0 ? symEl[0].textContent : null; + + var typeEl = el[i].getElementsByTagName('type'); + var typeKey = typeEl.length > 0 ? typeEl[0].textContent : null; + + /* + * Add waypoint marker based on the waypoint symbol key. + * + * First look for a configured icon for that symKey. If not found, look + * for a configured icon URL for that symKey and build an icon from it. + * If none of those match, look through the point matchers for a match + * on the waypoint's name. + * + * Otherwise, fall back to the default icon if one was configured, or + * finally to the default icon URL, if one was configured. + */ + var wptIcons = options.markers.wptIcons; + var wptTypeIcons = options.markers.wptTypeIcons; + var ptMatchers = options.markers.pointMatchers || []; + var symIcon; + if (wptIcons && symKey && wptIcons[symKey]) { + symIcon = wptIcons[symKey]; + } else if (wptTypeIcons && typeKey && wptTypeIcons[typeKey]) { + symIcon = wptTypeIcons[typeKey]; + } else if (ptMatchers.length > 0) { + for (var j = 0; j < ptMatchers.length; j++) { + if (ptMatchers[j].regex.test(name)) { + symIcon = ptMatchers[j].icon; + break; + } + } + } else if (wptIcons && wptIcons['']) { + symIcon = wptIcons['']; + } + + if (!symIcon) { + console.log( + 'No waypoint icon could be matched for symKey=%s,typeKey=%s,name=%s on waypoint %o', + symKey, typeKey, name, el[i]); + continue; + } + + var marker = new L.Marker(ll, { + clickable: options.marker_options.clickable, + title: name, + icon: symIcon, + type: 'waypoint' + }); + marker.bindPopup("" + name + "" + (desc.length > 0 ? '
' + desc : '')).openPopup(); + this.fire('addpoint', { point: marker, point_type: 'waypoint', element: el[i] }); + layers.push(marker); + } + } + + if (layers.length > 1) { + return new L.FeatureGroup(layers); + } else if (layers.length == 1) { + return layers[0]; + } + }, + + _parse_segment: function(line, options, base_style, polyline_options, tag) { + var el = line.getElementsByTagName(tag); + if (!el.length) return []; + + var coords = []; + var markers = []; + var layers = []; + var last = null; + + for (var i = 0; i < el.length; i++) { + var _, ll = new L.LatLng( + el[i].getAttribute('lat'), + el[i].getAttribute('lon')); + ll.meta = { time: null, ele: null, hr: null, cad: null, atemp: null, speed: null }; + + _ = el[i].getElementsByTagName('time'); + if (_.length > 0) { + ll.meta.time = new Date(Date.parse(_[0].textContent)); + } else { + ll.meta.time = new Date('1970-01-01T00:00:00'); + } + var time_diff = last != null ? Math.abs(ll.meta.time - last.meta.time) : 0; + + _ = el[i].getElementsByTagName('ele'); + if (_.length > 0) { + ll.meta.ele = parseFloat(_[0].textContent); + } else { + // If the point doesn't have an tag, assume it has the same + // elevation as the point before it (if it had one). + ll.meta.ele = last != null ? last.meta.ele : null; + } + var ele_diff = last != null ? ll.meta.ele - last.meta.ele : 0; + var dist_3d = last != null ? this._dist3d(last, ll) : 0; + + _ = el[i].getElementsByTagName('speed'); + if (_.length > 0) { + ll.meta.speed = parseFloat(_[0].textContent); + } else { + // speed in meter per second + ll.meta.speed = time_diff > 0 ? 1000.0 * dist_3d / time_diff : 0; + } + + _ = el[i].getElementsByTagName('name'); + if (_.length > 0) { + var name = _[0].textContent; + var ptMatchers = options.markers.pointMatchers || []; + + for (var j = 0; j < ptMatchers.length; j++) { + if (ptMatchers[j].regex.test(name)) { + markers.push({ label: name, coords: ll, icon: ptMatchers[j].icon, element: el[i] }); + break; + } + } + } + + this._info.length += dist_3d; + + _ = el[i].getElementsByTagNameNS('*', 'hr'); + if (_.length > 0) { + ll.meta.hr = parseInt(_[0].textContent); + this._info.hr._points.push([this._info.length, ll.meta.hr]); + this._info.hr._total += ll.meta.hr; + } + + _ = el[i].getElementsByTagNameNS('*', 'cad'); + if (_.length > 0) { + ll.meta.cad = parseInt(_[0].textContent); + this._info.cad._points.push([this._info.length, ll.meta.cad]); + this._info.cad._total += ll.meta.cad; + } + + _ = el[i].getElementsByTagNameNS('*', 'atemp'); + if (_.length > 0) { + ll.meta.atemp = parseInt(_[0].textContent); + this._info.atemp._points.push([this._info.length, ll.meta.atemp]); + this._info.atemp._total += ll.meta.atemp; + } + + if (ll.meta.ele > this._info.elevation.max) { + this._info.elevation.max = ll.meta.ele; + } + if (ll.meta.ele < this._info.elevation.min) { + this._info.elevation.min = ll.meta.ele; + } + this._info.elevation._points.push([this._info.length, ll.meta.ele]); + + if (ll.meta.speed > this._info.speed.max) { + this._info.speed.max = ll.meta.speed; + } + this._info.speed._points.push([this._info.length, ll.meta.speed]); + + if ((last == null) && (this._info.duration.start == null)) { + this._info.duration.start = ll.meta.time; + } + this._info.duration.end = ll.meta.time; + this._info.duration.total += time_diff; + if (time_diff < options.max_point_interval) { + this._info.duration.moving += time_diff; + } + + if (ele_diff > 0) { + this._info.elevation.gain += ele_diff; + } else { + this._info.elevation.loss += Math.abs(ele_diff); + } + + last = ll; + coords.push(ll); + } + + // add track + var l = new L.Polyline(coords, this._extract_styling(line, base_style, polyline_options)); + this.fire('addline', { line: l, element: line }); + layers.push(l); + + if (options.markers.startIcon) { + // add start pin + var marker = new L.Marker(coords[0], { + clickable: options.marker_options.clickable, + icon: options.markers.startIcon, + }); + this.fire('addpoint', { point: marker, point_type: 'start', element: el[0] }); + layers.push(marker); + } + + if (options.markers.endIcon) { + // add end pin + var marker = new L.Marker(coords[coords.length-1], { + clickable: options.marker_options.clickable, + icon: options.markers.endIcon, + }); + this.fire('addpoint', { point: marker, point_type: 'end', element: el[el.length-1] }); + layers.push(marker); + } + + // add named markers + for (var i = 0; i < markers.length; i++) { + var marker = new L.Marker(markers[i].coords, { + clickable: options.marker_options.clickable, + title: markers[i].label, + icon: markers[i].icon + }); + this.fire('addpoint', { point: marker, point_type: 'label', element: markers[i].element }); + layers.push(marker); + } + + return layers; + }, + + _get_polyline_options: function(polyline_options, i) { + /* + * Handle backwards compatibility with polyline_options being provided as a single object. + * In this situation, the provided style is expected to apply to all routes and tracks in the file. + */ + if (! Array.isArray(polyline_options)) { + return polyline_options; + } + return polyline_options[i] || {}; + }, + + _extract_styling: function(el, base, overrides) { + var style = this._merge_objs(_DEFAULT_POLYLINE_OPTS, base); + var e = el.getElementsByTagNameNS(_GPX_STYLE_NS, 'line'); + if (e.length > 0) { + var _ = e[0].getElementsByTagName('color'); + if (_.length > 0) style.color = '#' + _[0].textContent; + var _ = e[0].getElementsByTagName('opacity'); + if (_.length > 0) style.opacity = _[0].textContent; + var _ = e[0].getElementsByTagName('weight'); + if (_.length > 0) style.weight = _[0].textContent; + var _ = e[0].getElementsByTagName('linecap'); + if (_.length > 0) style.lineCap = _[0].textContent; + var _ = e[0].getElementsByTagName('linejoin'); + if (_.length > 0) style.lineJoin = _[0].textContent; + var _ = e[0].getElementsByTagName('dasharray'); + if (_.length > 0) style.dashArray = _[0].textContent; + var _ = e[0].getElementsByTagName('dashoffset'); + if (_.length > 0) style.dashOffset = _[0].textContent; + } + return this._merge_objs(style, overrides) + }, + + _dist2d: function(a, b) { + var R = 6371000; + var dLat = this._deg2rad(b.lat - a.lat); + var dLon = this._deg2rad(b.lng - a.lng); + var r = Math.sin(dLat/2) * + Math.sin(dLat/2) + + Math.cos(this._deg2rad(a.lat)) * + Math.cos(this._deg2rad(b.lat)) * + Math.sin(dLon/2) * + Math.sin(dLon/2); + var c = 2 * Math.atan2(Math.sqrt(r), Math.sqrt(1-r)); + var d = R * c; + return d; + }, + + _dist3d: function(a, b) { + var planar = this._dist2d(a, b); + var height = Math.abs(b.meta.ele - a.meta.ele); + return Math.sqrt(Math.pow(planar, 2) + Math.pow(height, 2)); + }, + + _deg2rad: function(deg) { + return deg * Math.PI / 180; + } +}); + +if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = L; +} else if (typeof define === 'function' && define.amd) { + define(L); +} diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/package.json b/js/leafletChartJs/leaflet-gpx-2.0.0/package.json new file mode 100644 index 0000000..aba0052 --- /dev/null +++ b/js/leafletChartJs/leaflet-gpx-2.0.0/package.json @@ -0,0 +1,26 @@ +{ + "name": "leaflet-gpx", + "version": "2.0.0", + "description": "A Leaflet plugin for showing a GPX track on a map", + "main": "gpx.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mpetazzoni/leaflet-gpx.git" + }, + "keywords": [ + "leaflet", + "gpx", + "leaflet-gpx", + "map", + "gps" + ], + "author": "Maxime Petazzoni (https://www.bulix.org)", + "license": "BSD-2-Clause", + "bugs": { + "url": "https://github.com/mpetazzoni/leaflet-gpx/issues" + }, + "homepage": "https://github.com/mpetazzoni/leaflet-gpx#readme" +} diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-end.png b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-end.png new file mode 100644 index 0000000..190442f Binary files /dev/null and b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-end.png differ diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-start.png b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-start.png new file mode 100644 index 0000000..b1b9c7e Binary files /dev/null and b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-start.png differ diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-wpt.png b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-wpt.png new file mode 100644 index 0000000..393d49e Binary files /dev/null and b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-icon-wpt.png differ diff --git a/js/leafletChartJs/leaflet-gpx-2.0.0/pin-shadow.png b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-shadow.png new file mode 100644 index 0000000..948646e Binary files /dev/null and b/js/leafletChartJs/leaflet-gpx-2.0.0/pin-shadow.png differ diff --git a/readme.md b/readme.md index 069e3a5..3286454 100644 --- a/readme.md +++ b/readme.md @@ -312,7 +312,7 @@ This plugin uses the great work from: # Changelog = 0.27.0 = 11.07.2024 -Update chart.js and swiper.js to latest versions. +Update chart.js, swiper.js and gpx.js to latest versions. Added coords-functions to gpx.js. Added Functionality to Admin - GPX-Tracks: The Map + Track for the selected GPX-Track is shown on the Admin Page. Several filter options (statistics with mean height and speed change, Distance smoothing and simplify.js) were added. The GPX-File is now filtered in the browser and with Button Click "Save" sent as filtered file via http(s) to the server. The server-side (backend) from WP just stores the file now. Due to the new Functionality several bugs and inconsitencies were removed. Mainl in leafletChartJs-Files and in the AdminSettingsPage.php and admin_settings.php. The function was started with WebAssembly and Rust but later skipped, so the files are still in the repository. The translations in ./languages/*.json were updated for German only. diff --git a/webpack.config.js b/webpack.config.js index 21b94a4..5da2f06 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,6 @@ //const webpack = require('webpack'); const path = require('path'); -let _mode = 'development'; +let _mode = 'production'; // create bundle for fotorama module.exports = [ diff --git a/webpack.elevation.js b/webpack.elevation.js index 76ccf12..4ff243e 100644 --- a/webpack.elevation.js +++ b/webpack.elevation.js @@ -1,7 +1,7 @@ const path = require('path'); //const webpack = require('webpack'); //const TerserPlugin = require("terser-webpack-plugin"); -let _mode = 'development'; +let _mode = 'production'; // create bundle for fotorama module.exports = [ diff --git a/webpack.leaflet.js b/webpack.leaflet.js index ca1bd90..9f2f9ff 100644 --- a/webpack.leaflet.js +++ b/webpack.leaflet.js @@ -1,6 +1,6 @@ const path = require('path'); const TerserPlugin = require("terser-webpack-plugin"); -let _mode = 'development'; +let _mode = 'production'; // create bundle for fotorama module.exports = [