Skip to content

Instantly share code, notes, and snippets.

@seyfro
Created May 10, 2018 18:13
Show Gist options
  • Save seyfro/9e3162ae5985d7168ffd97f5bec79f10 to your computer and use it in GitHub Desktop.
Save seyfro/9e3162ae5985d7168ffd97f5bec79f10 to your computer and use it in GitHub Desktop.
/*
* GPX plugin, Copyright (C) 2011-2012 Pavel Shramov, Copyright (C) 2013 Maxime Petazzoni <maxime.petazzoni@bulix.org>
* Last commit included: 4/8/2013 (Merge pull request #8 from rkusa/bugfix) - https://github.com/mpetazzoni/leaflet-gpx/commits/master
* added for Maps Marker Pro v3.1: 22.1.2017: replaced clickable with interactive due to leaflet 1.0.2 update - https://github.com/Leaflet/Leaflet/pull/2838
* not used anymore with Maps Marker Pro 4.0!
*/
var _SECOND_IN_MILLIS = 1000;
var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS;
var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS;
L.GPX = L.FeatureGroup.extend({
initialize: function(gpx, options) {
L.Util.setOptions(this, options);
if (mapsmarkerjspro.gpx_icons_status == 'show') { //info: added by RH
L.GPXTrackIcon = L.Icon.extend({ options: options.marker_options });
}
this._gpx = gpx;
this._layers = {};
this._info = {
name: null,
length: 0.0,
elevation: {gain: 0.0, loss: 0.0, _points: []},
hr: {avg: 0, _total: 0, _points: []},
duration: {start: null, end: null, moving: 0, total: 0},
};
if (gpx) {
this._prepare_parsing(gpx, options, this.options.async);
}
},
get_duration_string: function(duration, hidems) {
var s = '';
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;
},
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_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_elevation_gain: function() { return this._info.elevation.gain; },
get_elevation_loss: function() { return this._info.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_average_hr: function() { return this._info.hr.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';
});
});
},
//**************************************************************************/
// Private methods
//**************************************************************************/
_htmlspecialchars_decode: function htmlspecialchars_decode (string, quote_style) {
// http://kevin.vanzonneveld.net
// * example 1: htmlspecialchars_decode("<p>this -&gt; &quot;</p>", 'ENT_NOQUOTES');
// * returns 1: '<p>this -> &quot;</p>'
// * example 2: htmlspecialchars_decode("&amp;quot;");
// * returns 2: '&quot;'
var optTemp = 0,
i = 0,
noquotes = false;
if (typeof quote_style === 'undefined') {
quote_style = 2;
}
string = string.toString().replace(/&lt;/g, '<').replace(/&gt;/g, '>');
var OPTS = {
'ENT_NOQUOTES': 0,
'ENT_HTML_QUOTE_SINGLE': 1,
'ENT_HTML_QUOTE_DOUBLE': 2,
'ENT_COMPAT': 2,
'ENT_QUOTES': 3,
'ENT_IGNORE': 4
};
if (quote_style === 0) {
noquotes = true;
}
if (typeof quote_style !== 'number') { // Allow for a single string or an array of string flags
quote_style = [].concat(quote_style);
for (i = 0; i < quote_style.length; i++) {
// Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
if (OPTS[quote_style[i]] === 0) {
noquotes = true;
} else if (OPTS[quote_style[i]]) {
optTemp = optTemp | OPTS[quote_style[i]];
}
}
quote_style = optTemp;
}
if (quote_style && OPTS.ENT_HTML_QUOTE_SINGLE) {
string = string.replace(/&#0*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should
// string = string.replace(/&apos;|&#x0*27;/g, "'"); // This would also be useful here, but not a part of PHP
}
if (!noquotes) {
string = string.replace(/&quot;/g, '"');
}
// Put this in last place to avoid escape being double-decoded
string = string.replace(/&amp;/g, '&');
return string;
},
_merge_objs: function(a, b) {
var _ = {};
for (var attr in a) { _[attr] = a[attr]; }
for (var attr in b) { _[attr] = b[attr]; }
return _;
},
_prepare_data_point: function(p, trans1, trans2, trans_tooltip) {
var r = [trans1 && trans1(p[0]) || p[0], trans2 && trans2(p[1]) || p[1]];
r.push(trans_tooltip && trans_tooltip(r[0], r[1]) || (r[0] + ': ' + r[1]));
return r;
},
_prepare_parsing: 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);
setTimeout(function(){ _this.fire('gpx_loaded') }, 0);
}
if (async == undefined) async = this.options.async;
if (options == undefined) options = this.options;
var gpx_content_to_parse = this._htmlspecialchars_decode(this.options.gpx_content);
var xmlDoc = this._parse_xml(gpx_content_to_parse);
if(xmlDoc){
cb(this._parse_xml(gpx_content_to_parse), options);
} else{
if (window.console) { console.log('error parsing xml'); }
}
},
_parse_xml: function(xmlStr){
if (typeof window.DOMParser != "undefined") {
return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
} else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = "false";
xmlDoc.loadXML(xmlStr);
return xmlDoc;
} else {
throw new Error("No XML parser found");
}
},
_parse_gpx_data: function(xml, options) {
var j, i, el, layers = [];
var tags = [['rte','rtept'], ['trkseg','trkpt']];
var name = xml.getElementsByTagName('name');
if (name.length > 0) {
this._info.name = name[0].textContent || name[0].text || name[0].innerText;
}
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 (mapsmarkerjspro.gpx_icons_status == 'show') { //info: added by RH
// add start pin
var p = new L.Marker(coords[0], {
interactive: false,
icon: new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl})
});
this.fire('addpoint', { point: p });
layers.push(p);
// add end pin
p = new L.Marker(coords[coords.length-1], {
interactive: false,
icon: new L.GPXTrackIcon({iconUrl: options.marker_options.endIconUrl})
});
this.fire('addpoint', { point: p });
layers.push(p);
}
}
}
this._info.hr.avg = Math.round(this._info.hr._total / this._info.hr._points.length);
if (!layers.length) return;
var layer = layers[0];
if (layers.length > 1)
layer = new L.FeatureGroup(layers);
return layer;
},
_parseDate_for_IE8: function(input) {
var iso = /^(\d{4})(?:-?W(\d+)(?:-?(\d+)D?)?|(?:-(\d+))?-(\d+))(?:[T ](\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?)?(?:Z(-?\d*))?$/;
var parts = input.match(iso);
if (parts == null) {
throw new Error("Invalid Date");
}
var year = Number(parts[1]);
if (typeof parts[2] != "undefined") {
/* Convert weeks to days, months 0 */
var weeks = Number(parts[2]) - 1;
var days = Number(parts[3]);
if (typeof days == "undefined") {
days = 0;
}
days += weeks * 7;
var months = 0;
} else {
if (typeof parts[4] != "undefined") {
var months = Number(parts[4]) - 1;
} else {
/* it's an ordinal date... */
var months = 0;
}
var days = Number(parts[5]);
}
if (typeof parts[6] != "undefined" &&
typeof parts[7] != "undefined")
{
var hours = Number(parts[6]);
var minutes = Number(parts[7]);
if (typeof parts[8] != "undefined") {
var seconds = Number(parts[8]);
if (typeof parts[9] != "undefined") {
var fractional = Number(parts[9]);
var milliseconds = fractional / 100;
} else {
var milliseconds = 0
}
} else {
var seconds = 0;
var milliseconds = 0;
}
}
else {
var hours = 0;
var minutes = 0;
var seconds = 0;
var fractional = 0;
var milliseconds = 0;
}
if (typeof parts[10] != "undefined") {
/* Timezone adjustment, offset the minutes appropriately */
var localzone = -(new Date().getTimezoneOffset());
var timezone = parts[10] * 60;
minutes = Number(minutes) + (timezone - localzone);
}
return new Date(year, months, days, hours, minutes, seconds, milliseconds);
},
_parse_trkseg: function(line, xml, options, tag) {
var el = line.getElementsByTagName(tag);
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 };
_ = el[i].getElementsByTagName('time');
if (_.length > 0) {
if (window.addEventListener) {
var time_temp = Date.parse(_[0].textContent);
ll.meta.time = new Date(time_temp);
} else { //IE8
ll.meta.time = this._parseDate_for_IE8(_[0].text);
}
}
_ = el[i].getElementsByTagName('ele');
if (_.length > 0) {
ll.meta.ele = parseFloat(_[0].textContent || _[0].text || _[0].innerText); //IE8
}
/*IE9+only _ = el[i].getElementsByTagNameNS('*', 'hr');*/
_ = el[i].getElementsByTagName('hr');
if (_.length > 0) {
ll.meta.hr = parseInt(_[0].textContent || _[0].text || _[0].innerText); //IE8
this._info.hr._points.push([this._info.length, ll.meta.hr]);
this._info.hr._total += ll.meta.hr;
}
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 {
this._info.duration.start = ll.meta.time;
}
last = ll;
coords.push(ll);
}
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;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment