diff --git a/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx.js b/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx.js index 49a3728..da8d9d2 100644 --- a/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx.js +++ b/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx.js @@ -1,42 +1,4 @@ -/** - * 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 L = L || require("leaflet"); var _MAX_POINT_INTERVAL_MS = 15000; var _SECOND_IN_MILLIS = 1000; @@ -44,51 +6,62 @@ 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_MARKER_OPTS = { - startIconUrl: 'pin-icon-start.png', - endIconUrl: 'pin-icon-end.png', - shadowUrl: 'pin-shadow.png', - wptIconUrls : { - '': 'pin-icon-wpt.png', + startIconUrl: "pin-icon-start.png", + endIconUrl: "pin-icon-end.png", + shadowUrl: "pin-shadow.png", + wptIcons: [], + wptIconsType: [], + wptIconUrls: { + "": "pin-icon-wpt.png", + }, + wptIconTypeUrls: { + "": "pin-icon-wpt.png", }, + pointMatchers: [], iconSize: [33, 50], shadowSize: [50, 50], iconAnchor: [16, 45], shadowAnchor: [16, 47], - clickable: false + clickable: false, }; var _DEFAULT_POLYLINE_OPTS = { - color: 'blue' + color: "blue", }; var _DEFAULT_GPX_OPTS = { - parseElements: ['track', 'route', 'waypoint'], + parseElements: ["track", "route", "waypoint"], show_kilometer_point: false, - kilometer_point_options : { - kilometer_point_color: 'blue', - kilometer_point_color_text: 'white', + kilometer_point_options: { + kilometer_point_color: "blue", + kilometer_point_color_text: "white", kilometer_point_intervall: 1, kilometer_point_radius: 10, }, show_mile_point: true, - mile_point_options : { - mile_point_color: 'blue', - mile_point_color_text: 'white', + mile_point_options: { + mile_point_color: "blue", + mile_point_color_text: "white", mile_intervall: 1, mile_point_radius: 10, }, + joinTrackSegments: true, }; + L.GPX = L.FeatureGroup.extend({ - initialize: function(gpx, options) { - options.max_point_interval = options.max_point_interval || _MAX_POINT_INTERVAL_MS; + initialize: function (gpx, options) { + options.max_point_interval = + options.max_point_interval || _MAX_POINT_INTERVAL_MS; options.marker_options = this._merge_objs( _DEFAULT_MARKER_OPTS, - options.marker_options || {}); - options.polyline_options = this._merge_objs( - _DEFAULT_POLYLINE_OPTS, - options.polyline_options || {}); + options.marker_options || {} + ); + options.polyline_options = options.polyline_options || {}; options.gpx_options = this._merge_objs( _DEFAULT_GPX_OPTS, - options.gpx_options || {}); + options.gpx_options || {} + ); L.Util.setOptions(this, options); @@ -104,196 +77,297 @@ L.GPX = L.FeatureGroup.extend({ } }, - get_duration_string: function(duration, hidems) { - var s = ''; + get_duration_string: function (duration, hidems) { + var s = ""; if (duration >= _DAY_IN_MILLIS) { - s += Math.floor(duration / _DAY_IN_MILLIS) + 'd '; + 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) + ':'; + 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 + '\''; + 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'; + if (secs < 10) s += "0"; s += secs; - if (!hidems && duration > 0) s += '.' + Math.round(Math.floor(duration)*1000)/1000; + if (!hidems && duration > 0) + s += "." + Math.round(Math.floor(duration) * 1000) / 1000; else s += '"'; return s; }, - get_duration_string_iso: function(duration, hidems) { + get_duration_string_iso: function (duration, hidems) { var s = this.get_duration_string(duration, hidems); - return s.replace("'",':').replace('"',''); + 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; }, - - 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() { + 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; + }, + + 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'; }); + 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() { + 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'; }); + 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_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_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() { + 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'; }); + 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() { + 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'; }); + 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() { + 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'; }); + 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() { + 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'; }); + 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() { + 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'; }); + 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() { + 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'; }); + 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() { + reload: function () { this._init_info(); this.clearLayers(); this._parse(this._gpx, this.options, this.options.async); }, // Private methods - _merge_objs: function(a, b) { + _merge_objs: function (a, b) { var _ = {}; - for (var attr in a) { _[attr] = a[attr]; } - for (var attr in b) { _[attr] = b[attr]; } + 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])); + _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; }, - _init_info: function() { + _init_info: function () { this._info = { name: null, length: 0.0, - elevation: {gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _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: []} + elevation: { gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _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) { + _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); + req.open("GET", url, async); try { - req.overrideMimeType('text/xml'); // unsupported by IE - } catch(e) {} - req.onreadystatechange = function() { + 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); + if (req.status == 200) cb(req.responseXML, options); }; req.send(null); }, - _parse: function(input, options, async) { + _parse: function (input, options, async) { var _this = this; - var cb = function(gpx, options) { + var cb = function (gpx, options) { var layers = _this._parse_gpx_data(gpx, options); - if (!layers) return; + 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'); - } - if (input.substr(0,1)==='<') { // direct XML has to start with a < + _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() { + setTimeout(function () { cb(parser.parseFromString(input, "text/xml"), options); }); } else { @@ -304,98 +378,111 @@ L.GPX = L.FeatureGroup.extend({ } }, - _parse_gpx_data: function(xml, options) { - var j, i, el, layers = []; - var tags = []; - - var parseElements = options.gpx_options.parseElements; - if (parseElements.indexOf('route') > -1) { - tags.push(['rte','rtept']); - } - if (parseElements.indexOf('track') > -1) { - tags.push(['trkseg','trkpt']); - } + _parse_gpx_data: function (xml, options) { + var i, + t, + l, + el, + layers = []; - var name = xml.getElementsByTagName('name'); + var name = xml.getElementsByTagName("name"); if (name.length > 0) { this._info.name = name[0].textContent; } - var desc = xml.getElementsByTagName('desc'); + var desc = xml.getElementsByTagName("desc"); if (desc.length > 0) { this._info.desc = desc[0].textContent; } - var author = xml.getElementsByTagName('author'); + var author = xml.getElementsByTagName("author"); if (author.length > 0) { this._info.author = author[0].textContent; } - var copyright = xml.getElementsByTagName('copyright'); + var copyright = xml.getElementsByTagName("copyright"); if (copyright.length > 0) { this._info.copyright = copyright[0].textContent; } - for (j = 0; j < tags.length; j++) { - el = xml.getElementsByTagName(tags[j][0]); - for (i = 0; i < el.length; i++) { - var coords = this._parse_trkseg(el[i], xml, options, tags[j][1]); - if (coords.length === 0) continue; - - // add track - var l = new L.Polyline(coords, options.polyline_options); - this.fire('addline', { line: l }) - layers.push(l); - - if (options.marker_options.startIcon || options.marker_options.startIconUrl) { - // add start pin - var p = new L.Marker(coords[0], { - clickable: options.marker_options.clickable, - icon: options.marker_options.startIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl}) - }); - this.fire('addpoint', { point: p, point_type: 'start' }); - layers.push(p); - } + 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++) { + layers = layers.concat( + this._parse_segment(routes[i], options, {}, "rtept") + ); + } + } - if (options.marker_options.endIcon || options.marker_options.endIconUrl) { - // add end pin - p = 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}) - }); - this.fire('addpoint', { point: p, point_type: 'end' }); - layers.push(p); + 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 polyline_options = this._extract_styling(track); + + if (options.gpx_options.joinTrackSegments) { + layers = layers.concat( + this._parse_segment(track, options, 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" + ) + ); + } } } } - 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); + 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'); + 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')); + el[i].getAttribute("lat"), + el[i].getAttribute("lon") + ); - var nameEl = el[i].getElementsByTagName('name'); - var name = ''; + var nameEl = el[i].getElementsByTagName("name"); + var name = ""; if (nameEl.length > 0) { name = nameEl[0].textContent; } - var descEl = el[i].getElementsByTagName('desc'); - var desc = ''; + var descEl = el[i].getElementsByTagName("desc"); + var desc = ""; if (descEl.length > 0) { desc = descEl[0].textContent; } - var symEl = el[i].getElementsByTagName('sym'); - var symKey = ''; + var symEl = el[i].getElementsByTagName("sym"); + var symKey = ""; if (symEl.length > 0) { symKey = symEl[0].textContent; } + var typeEl = el[i].getElementsByTagName("type"); + var typeKey = ""; + if (typeEl.length > 0) { + typeKey = typeEl[0].textContent; + } + /* * Add waypoint marker based on the waypoint symbol key. * @@ -406,82 +493,120 @@ L.GPX = L.FeatureGroup.extend({ */ 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 symIcon; if (wptIcons && wptIcons[symKey]) { symIcon = wptIcons[symKey]; + } else if (wptIconsType && wptIconsType[typeKey]) { + symIcon = wptIconsType[typeKey]; } else if (wptIconUrls && wptIconUrls[symKey]) { - symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls[symKey]}); - } else if (wptIcons && wptIcons['']) { - symIcon = wptIcons['']; - } else if (wptIconUrls && wptIconUrls['']) { - symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls['']}); + symIcon = new L.GPXTrackIcon({ iconUrl: wptIconUrls[symKey] }); + } else if (wptIconTypeUrls && wptIconTypeUrls[typeKey]) { + symIcon = new L.GPXTrackIcon({ iconUrl: wptIconTypeUrls[typeKey] }); + } else if (wptIcons && wptIcons[""]) { + symIcon = wptIcons[""]; + } else if (wptIconUrls && wptIconUrls[""]) { + symIcon = new L.GPXTrackIcon({ iconUrl: wptIconUrls[""] }); } else { - console.log('No icon or icon URL configured for symbol type "' + symKey - + '", and no fallback configured; ignoring waypoint.'); + console.log( + 'No icon or icon URL configured for symbol type "' + + symKey + + '", and no fallback configured; ignoring waypoint.' + ); continue; } var marker = new L.Marker(ll, { - clickable: true, + clickable: options.marker_options.clickable, title: name, - icon: symIcon + icon: symIcon, + type: "waypoint", + }); + marker + .bindPopup( + "" + name + "" + (desc.length > 0 ? "
" + desc : "") + ) + .openPopup(); + this.fire("addpoint", { + point: marker, + point_type: "waypoint", + element: el[i], }); - marker.bindPopup("" + name + "" + (desc.length > 0 ? '
' + desc : '')).openPopup(); - this.fire('addpoint', { point: marker, point_type: 'waypoint' }); layers.push(marker); } } if (layers.length > 1) { - return new L.FeatureGroup(layers); + return new L.FeatureGroup(layers); } else if (layers.length == 1) { return layers[0]; } }, - _parse_trkseg: function(line, xml, options, tag) { + _parse_segment: function (line, options, polyline_options, tag) { var el = line.getElementsByTagName(tag); var kilometer_point_layers = []; var mile_point_layers = []; var _this = this; 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')); + 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 }; - _ = el[i].getElementsByTagName('time'); + _ = 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'); + ll.meta.time = new Date("1970-01-01T00:00:00"); } - _ = el[i].getElementsByTagName('ele'); + _ = el[i].getElementsByTagName("ele"); if (_.length > 0) { ll.meta.ele = parseFloat(_[0].textContent); } - _ = el[i].getElementsByTagNameNS('*', 'hr'); + _ = el[i].getElementsByTagName("name"); + if (_.length > 0) { + var name = _[0].textContent; + var ptMatchers = options.marker_options.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; + } + } + } + + _ = 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'); + _ = 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'); + _ = el[i].getElementsByTagNameNS("*", "atemp"); if (_.length > 0) { ll.meta.atemp = parseInt(_[0].textContent); this._info.atemp._points.push([this._info.length, ll.meta.atemp]); @@ -505,68 +630,98 @@ L.GPX = L.FeatureGroup.extend({ /* * Add points to the line. */ - if (options.gpx_options.show_kilometer_point || options.gpx_options.show_mile_point) { + if ( + options.gpx_options.show_kilometer_point || + options.gpx_options.show_mile_point + ) { if (this._parse_current_kilometer != null) { // Kilometer Point if (options.gpx_options.show_kilometer_point) { - if ((parseInt(this._info.length/1000) - this._parse_current_kilometer) > options.gpx_options.kilometer_point_options.kilometer_point_intervall-1) { - this._parse_current_kilometer = parseInt(this._info.length/1000); - var marker = new L.circleMarker(ll, { - radius: options.gpx_options.kilometer_point_options.kilometer_point_radius, - stroke: false, - fillColor: options.gpx_options.kilometer_point_options.kilometer_point_color, - fillOpacity: 1, - }).bindTooltip(this._parse_current_kilometer.toString(), { - direction: 'center', - permanent: true, - interactive: true, - className: 'kilometer_tooltip' - }); - kilometer_point_layers.push(marker); + if ( + parseInt(this._info.length / 1000) - + this._parse_current_kilometer > + options.gpx_options.kilometer_point_options + .kilometer_point_intervall - + 1 + ) { + this._parse_current_kilometer = parseInt( + this._info.length / 1000 + ); + var marker = new L.circleMarker(ll, { + radius: + options.gpx_options.kilometer_point_options + .kilometer_point_radius, + stroke: false, + fillColor: + options.gpx_options.kilometer_point_options + .kilometer_point_color, + fillOpacity: 1, + }).bindTooltip(this._parse_current_kilometer.toString(), { + direction: "center", + permanent: true, + interactive: true, + className: "kilometer_tooltip", + }); + kilometer_point_layers.push(marker); } } // Mile Point if (options.gpx_options.show_mile_point) { - if ((parseInt(this.to_miles(this._info.length)/1000) - this._parse_current_mile) > options.gpx_options.mile_point_options.mile_intervall) { - this._parse_current_mile = parseInt(this.to_miles(this._info.length)/1000); + if ( + parseInt(this.to_miles(this._info.length) / 1000) - + this._parse_current_mile > + options.gpx_options.mile_point_options.mile_intervall + ) { + this._parse_current_mile = parseInt( + this.to_miles(this._info.length) / 1000 + ); var marker = new L.circleMarker(ll, { - radius: options.gpx_options.mile_point_options.mile_point_radius, - stroke: false, - fillColor: options.gpx_options.mile_point_options.mile_point_color, + radius: + options.gpx_options.mile_point_options.mile_point_radius, + stroke: false, + fillColor: + options.gpx_options.mile_point_options.mile_point_color, fillOpacity: 1, }).bindTooltip(this._parse_current_mile.toString(), { - direction: 'center', + direction: "center", permanent: true, interactive: true, - className: 'mile_tooltip' + className: "mile_tooltip", }); mile_point_layers.push(marker); } } } else { - this._parse_current_kilometer = parseInt(this._info.length/1000); - this._parse_current_mile = parseInt(this._info.length/1000); + this._parse_current_kilometer = parseInt(this._info.length / 1000); + this._parse_current_mile = parseInt(this._info.length / 1000); // Append style element for the tooltip of the points - var element = document.createElement('style'); + var element = document.createElement("style"); document.head.appendChild(element); var sheet = element.sheet; - var styles = ''; - styles += '.kilometer_tooltip, .mile_tooltip {'; - styles += 'background: none!important;'; - styles += 'border: none!important;'; - styles += 'font-weight: 900!important;'; - styles += 'font-size: larger!important;'; - styles += 'box-shadow: none!important;'; - styles += '}'; + var styles = ""; + styles += ".kilometer_tooltip, .mile_tooltip {"; + styles += "background: none!important;"; + styles += "border: none!important;"; + styles += "font-weight: 900!important;"; + styles += "font-size: larger!important;"; + styles += "box-shadow: none!important;"; + styles += "}"; sheet.insertRule(styles, 0); - var styles_kilometer = '.kilometer_tooltip {'; - styles_kilometer += 'color: ' + options.gpx_options.kilometer_point_options.kilometer_point_color_text + ';'; - styles_kilometer += '}'; + var styles_kilometer = ".kilometer_tooltip {"; + styles_kilometer += + "color: " + + options.gpx_options.kilometer_point_options + .kilometer_point_color_text + + ";"; + styles_kilometer += "}"; sheet.insertRule(styles_kilometer, 0); - var styles_mile = '.mile_tooltip {'; - styles_mile += 'color: ' + options.gpx_options.mile_point_options.mile_point_color_text + ';'; - styles_mile += '}'; + var styles_mile = ".mile_tooltip {"; + styles_mile += + "color: " + + options.gpx_options.mile_point_options.mile_point_color_text + + ";"; + styles_mile += "}"; sheet.insertRule(styles_mile, 0); } } @@ -592,44 +747,118 @@ L.GPX = L.FeatureGroup.extend({ } if (kilometer_point_layers.length > 1) { - _this.addLayer(new L.FeatureGroup(kilometer_point_layers)); + _this.addLayer(new L.FeatureGroup(kilometer_point_layers)); } if (mile_point_layers.length > 1) { - _this.addLayer(new L.FeatureGroup(mile_point_layers)); + _this.addLayer(new L.FeatureGroup(mile_point_layers)); } - return coords; + // add track + var l = new L.Polyline( + coords, + this._extract_styling(line, polyline_options, options.polyline_options) + ); + this.fire("addline", { line: l, element: line }); + layers.push(l); + + if ( + options.marker_options.startIcon || + options.marker_options.startIconUrl + ) { + // 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 }), + }); + this.fire("addpoint", { + point: marker, + point_type: "start", + element: el[0], + }); + layers.push(marker); + } + + if (options.marker_options.endIcon || options.marker_options.endIconUrl) { + // 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 }), + }); + 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; }, - _dist2d: function(a, b) { + _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; + } + 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) + + 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)); + 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) { + _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; - } + _deg2rad: function (deg) { + return (deg * Math.PI) / 180; + }, }); -if (typeof module === 'object' && typeof module.exports === 'object') { +if (typeof module === "object" && typeof module.exports === "object") { module.exports = L; -} else if (typeof define === 'function' && define.amd) { +} else if (typeof define === "function" && define.amd) { define(L); } diff --git a/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx_meineVersion.js b/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx_meineVersion.js new file mode 100644 index 0000000..49a3728 --- /dev/null +++ b/src/media/plg_fields_aggpxtrack/leaflet-gpx/gpx_meineVersion.js @@ -0,0 +1,635 @@ +/** + * 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 _DEFAULT_MARKER_OPTS = { + startIconUrl: 'pin-icon-start.png', + endIconUrl: 'pin-icon-end.png', + shadowUrl: 'pin-shadow.png', + wptIconUrls : { + '': 'pin-icon-wpt.png', + }, + iconSize: [33, 50], + 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'], + show_kilometer_point: false, + kilometer_point_options : { + kilometer_point_color: 'blue', + kilometer_point_color_text: 'white', + kilometer_point_intervall: 1, + kilometer_point_radius: 10, + }, + show_mile_point: true, + mile_point_options : { + mile_point_color: 'blue', + mile_point_color_text: 'white', + mile_intervall: 1, + mile_point_radius: 10, + }, +}; +L.GPX = L.FeatureGroup.extend({ + initialize: function(gpx, options) { + options.max_point_interval = options.max_point_interval || _MAX_POINT_INTERVAL_MS; + options.marker_options = this._merge_objs( + _DEFAULT_MARKER_OPTS, + options.marker_options || {}); + options.polyline_options = this._merge_objs( + _DEFAULT_POLYLINE_OPTS, + 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._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; }, + + 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_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; + }, + + _init_info: function() { + this._info = { + name: null, + length: 0.0, + elevation: {gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _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) return; + _this.addLayer(layers); + _this.fire('loaded'); + } + 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 j, i, el, layers = []; + var tags = []; + + var parseElements = options.gpx_options.parseElements; + if (parseElements.indexOf('route') > -1) { + tags.push(['rte','rtept']); + } + if (parseElements.indexOf('track') > -1) { + tags.push(['trkseg','trkpt']); + } + + 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; + } + + for (j = 0; j < tags.length; j++) { + el = xml.getElementsByTagName(tags[j][0]); + for (i = 0; i < el.length; i++) { + var coords = this._parse_trkseg(el[i], xml, options, tags[j][1]); + if (coords.length === 0) continue; + + // add track + var l = new L.Polyline(coords, options.polyline_options); + this.fire('addline', { line: l }) + layers.push(l); + + if (options.marker_options.startIcon || options.marker_options.startIconUrl) { + // add start pin + var p = new L.Marker(coords[0], { + clickable: options.marker_options.clickable, + icon: options.marker_options.startIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl}) + }); + this.fire('addpoint', { point: p, point_type: 'start' }); + layers.push(p); + } + + if (options.marker_options.endIcon || options.marker_options.endIconUrl) { + // add end pin + p = 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}) + }); + this.fire('addpoint', { point: p, point_type: 'end' }); + layers.push(p); + } + } + } + + 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 = ''; + if (nameEl.length > 0) { + name = nameEl[0].textContent; + } + + var descEl = el[i].getElementsByTagName('desc'); + var desc = ''; + if (descEl.length > 0) { + desc = descEl[0].textContent; + } + + var symEl = el[i].getElementsByTagName('sym'); + var symKey = ''; + if (symEl.length > 0) { + symKey = symEl[0].textContent; + } + + /* + * 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. + * Otherwise, fall back to the default icon if one was configured, or + * finally to the default icon URL. + */ + var wptIcons = options.marker_options.wptIcons; + var wptIconUrls = options.marker_options.wptIconUrls; + var symIcon; + if (wptIcons && wptIcons[symKey]) { + symIcon = wptIcons[symKey]; + } else if (wptIconUrls && wptIconUrls[symKey]) { + symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls[symKey]}); + } else if (wptIcons && wptIcons['']) { + symIcon = wptIcons['']; + } else if (wptIconUrls && wptIconUrls['']) { + symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls['']}); + } else { + console.log('No icon or icon URL configured for symbol type "' + symKey + + '", and no fallback configured; ignoring waypoint.'); + continue; + } + + var marker = new L.Marker(ll, { + clickable: true, + title: name, + icon: symIcon + }); + marker.bindPopup("" + name + "" + (desc.length > 0 ? '
' + desc : '')).openPopup(); + this.fire('addpoint', { point: marker, point_type: 'waypoint' }); + layers.push(marker); + } + } + + if (layers.length > 1) { + return new L.FeatureGroup(layers); + } else if (layers.length == 1) { + return layers[0]; + } + }, + + _parse_trkseg: function(line, xml, options, tag) { + var el = line.getElementsByTagName(tag); + var kilometer_point_layers = []; + var mile_point_layers = []; + var _this = this; + + if (!el.length) return []; + var coords = []; + 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 }; + + _ = 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'); + } + + _ = el[i].getElementsByTagName('ele'); + if (_.length > 0) { + ll.meta.ele = parseFloat(_[0].textContent); + } + + _ = 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]); + this._info.duration.end = ll.meta.time; + + if (last != null) { + this._info.length += this._dist3d(last, ll); + + /* + * Add points to the line. + */ + if (options.gpx_options.show_kilometer_point || options.gpx_options.show_mile_point) { + if (this._parse_current_kilometer != null) { + // Kilometer Point + if (options.gpx_options.show_kilometer_point) { + if ((parseInt(this._info.length/1000) - this._parse_current_kilometer) > options.gpx_options.kilometer_point_options.kilometer_point_intervall-1) { + this._parse_current_kilometer = parseInt(this._info.length/1000); + var marker = new L.circleMarker(ll, { + radius: options.gpx_options.kilometer_point_options.kilometer_point_radius, + stroke: false, + fillColor: options.gpx_options.kilometer_point_options.kilometer_point_color, + fillOpacity: 1, + }).bindTooltip(this._parse_current_kilometer.toString(), { + direction: 'center', + permanent: true, + interactive: true, + className: 'kilometer_tooltip' + }); + kilometer_point_layers.push(marker); + } + } + // Mile Point + if (options.gpx_options.show_mile_point) { + if ((parseInt(this.to_miles(this._info.length)/1000) - this._parse_current_mile) > options.gpx_options.mile_point_options.mile_intervall) { + this._parse_current_mile = parseInt(this.to_miles(this._info.length)/1000); + var marker = new L.circleMarker(ll, { + radius: options.gpx_options.mile_point_options.mile_point_radius, + stroke: false, + fillColor: options.gpx_options.mile_point_options.mile_point_color, + fillOpacity: 1, + }).bindTooltip(this._parse_current_mile.toString(), { + direction: 'center', + permanent: true, + interactive: true, + className: 'mile_tooltip' + }); + mile_point_layers.push(marker); + } + } + } else { + this._parse_current_kilometer = parseInt(this._info.length/1000); + this._parse_current_mile = parseInt(this._info.length/1000); + + // Append style element for the tooltip of the points + var element = document.createElement('style'); + document.head.appendChild(element); + var sheet = element.sheet; + var styles = ''; + styles += '.kilometer_tooltip, .mile_tooltip {'; + styles += 'background: none!important;'; + styles += 'border: none!important;'; + styles += 'font-weight: 900!important;'; + styles += 'font-size: larger!important;'; + styles += 'box-shadow: none!important;'; + styles += '}'; + sheet.insertRule(styles, 0); + var styles_kilometer = '.kilometer_tooltip {'; + styles_kilometer += 'color: ' + options.gpx_options.kilometer_point_options.kilometer_point_color_text + ';'; + styles_kilometer += '}'; + sheet.insertRule(styles_kilometer, 0); + var styles_mile = '.mile_tooltip {'; + styles_mile += 'color: ' + options.gpx_options.mile_point_options.mile_point_color_text + ';'; + styles_mile += '}'; + sheet.insertRule(styles_mile, 0); + } + } + + var t = ll.meta.ele - last.meta.ele; + if (t > 0) { + this._info.elevation.gain += t; + } else { + this._info.elevation.loss += Math.abs(t); + } + + t = Math.abs(ll.meta.time - last.meta.time); + this._info.duration.total += t; + if (t < options.max_point_interval) { + this._info.duration.moving += t; + } + } else if (this._info.duration.start == null) { + this._info.duration.start = ll.meta.time; + } + + last = ll; + coords.push(ll); + } + + if (kilometer_point_layers.length > 1) { + _this.addLayer(new L.FeatureGroup(kilometer_point_layers)); + } + + if (mile_point_layers.length > 1) { + _this.addLayer(new L.FeatureGroup(mile_point_layers)); + } + + return coords; + }, + + _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); +}