diff --git a/application/assets/js/gpx.js b/application/assets/js/gpx.js index d1304fe3..9d40fa6c 100644 --- a/application/assets/js/gpx.js +++ b/application/assets/js/gpx.js @@ -37,6 +37,7 @@ */ var L = L || require("leaflet"); + var _MAX_POINT_INTERVAL_MS = 15000; var _SECOND_IN_MILLIS = 1000; var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS; @@ -46,775 +47,805 @@ 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", - 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, + 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, 45], + shadowSize: [50, 50], + iconAnchor: [16, 45], + shadowAnchor: [16, 47], + clickable: false, }; var _DEFAULT_POLYLINE_OPTS = { - color: "blue", + color: "blue", }; var _DEFAULT_GPX_OPTS = { - parseElements: ["track", "route", "waypoint"], - joinTrackSegments: true, + 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.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._init_info(); - - if (gpx) { - this._parse(gpx, options, this.options.async); + 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 = 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; + }, + 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_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; + ); + }); + }, + 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"; } - - if (duration >= _HOUR_IN_MILLIS) { - s += Math.floor(duration / _HOUR_IN_MILLIS) + ":"; - duration = duration % _HOUR_IN_MILLIS; + ); + }); + }, + 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"; } - - 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_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; + }, + + _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), }); - }, - 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"; - }); + 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); }); - }, - 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) { - _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 { + 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++) { + layers = layers.concat( + this._parse_segment(routes[i], 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 polyline_options = this._extract_styling(track); + + if (options.gpx_options.joinTrackSegments) { + layers = layers.concat( + this._parse_segment(track, options, polyline_options, "trkpt") + ); } 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 segments = track.getElementsByTagName("trkseg"); + for (j = 0; j < segments.length; j++) { + layers = layers.concat( + this._parse_segment( + segments[j], + options, + polyline_options, + "trkpt" + ) + ); + } } - - 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 (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"); + for (i = 0; i < el.length; i++) { + var ll = new L.LatLng( + el[i].getAttribute("lat"), + el[i].getAttribute("lon") ); - // 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; - } - - 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. - * - * 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 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 (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.' - ); - 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); + 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.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 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 (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[""]; + } else if (wptIconUrls && wptIconUrls[""]) { + symIcon = new L.GPXTrackIcon({ iconUrl: wptIconUrls[""] }); } - if (layers.length > 1) { - return new L.FeatureGroup(layers); - } else if (layers.length == 1) { - return layers[0]; + 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; } - }, - - _parse_segment: function(line, options, 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 - }; - - _ = 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].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"); - 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); - - 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); - } - - // add track - var l = new L.Polyline( - coords, - this._extract_styling(line, polyline_options, options.polyline_options) - ); - this.fire("addline", { - line: l, - element: line + var marker = new L.Marker(ll, { + clickable: options.marker_options.clickable, + title: name, + icon: symIcon, + type: "waypoint", }); - 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, + 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, 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.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], }); - layers.push(marker); - } - - return layers; - }, - - _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; + break; + } } - 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; - }, + } + + _ = 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; + } + + this._info.length += dist_3d; + + 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, 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; + }, + + _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; + module.exports = L; } else if (typeof define === "function" && define.amd) { - define(L); -} \ No newline at end of file + define(L); +} diff --git a/build/o.map-omnisd.zip b/build/o.map-omnisd.zip index 4eb27dc8..9620d1be 100644 Binary files a/build/o.map-omnisd.zip and b/build/o.map-omnisd.zip differ diff --git a/build/o.map.zip b/build/o.map.zip index 2f47a8bb..88474dcc 100644 Binary files a/build/o.map.zip and b/build/o.map.zip differ