PageSourceSearch

https://vandersanden-limburgruns.be/wp-content/plugins/gpx-viewer/js/leaflet-gpx.js

js vandersanden-limburgruns.be collected 2026-10-08 09:26:10 UTC 18,781 bytes, 536 lines download raw bytes

1/**
2 * Corrections for GPX-Viewer
3 * 		- url to icons
4 * 		- name of track (not of first waypoint)
5 *
6 * Enhancements
7 * 		- Editable track points
8 *		- Context information of waypoints extended by duration from start
9 * 
10* Copyright (C) 2011-2012 Pavel Shramov
11* Copyright (C) 2013-2017 Maxime Petazzoni <[email protected]>
12* All Rights Reserved.
13*
14* Redistribution and use in source and binary forms, with or without
15* modification, are permitted provided that the following conditions are met:
16*
17* - Redistributions of source code must retain the above copyright notice,
18*   this list of conditions and the following disclaimer.
19*
20* - Redistributions in binary form must reproduce the above copyright notice,
21*   this list of conditions and the following disclaimer in the documentation
22*   and/or other materials provided with the distribution.
23*
24* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34* POSSIBILITY OF SUCH DAMAGE.
35*/
36
37/*
38* Thanks to Pavel Shramov who provided the initial implementation and Leaflet
39* integration. Original code was at https://github.com/shramov/leaflet-plugins.
40*
41* It was then cleaned-up and modified to record and make available more
42* information about the GPX track while it is being parsed so that the result
43* can be used to display additional information about the track that is
44* rendered on the Leaflet map.
45*/
46
47var L = L || require('leaflet');
48
49var _MAX_POINT_INTERVAL_MS = 15000;
50var _SECOND_IN_MILLIS = 1000;
51var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS;
52var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS;
53var _DAY_IN_MILLIS = 24 * _HOUR_IN_MILLIS;
54
55L.GPX = L.FeatureGroup.extend({
56	initialize: function(editable, options) {
57		options.max_point_interval = _MAX_POINT_INTERVAL_MS;
58
59		L.Util.setOptions(this, options);
60
61		// Base icon class for track pins.
62		L.GPXTrackIcon = L.Icon.extend({ options: options.marker_options });
63
64		this._editable = editable;
65		this.options = options;
66		this._layers = {};
67		this._init_info();
68	},
69
70	// Public methods
71	
72	load_track: function(gpx) {
73		if (gpx != '' && gpx != 'newtrack')
74			this._parse(gpx, this.options, this.options.async);
75		else if (gpx == 'newtrack' && this._editable) {
76			var l = new L.Polyline.PolylineEditor(new Array(0), this.options.polyline_options);
77			this.fire('addline', { line: l });
78			this.addLayer(l);
79			this.options.isLoaded = true;
80		}
81	},
82
83	get_duration_string: function(duration, hide_secs) {
84		var s = '';
85
86		if (duration >= _DAY_IN_MILLIS) {
87			s += Math.floor(duration / _DAY_IN_MILLIS) + ' d ';
88			duration = duration % _DAY_IN_MILLIS;
89		}
90
91		if (duration >= _HOUR_IN_MILLIS) {
92			s += Math.floor(duration / _HOUR_IN_MILLIS) + ':';
93			duration = duration % _HOUR_IN_MILLIS;
94		}
95		else
96			s += '00:';
97		
98		var mins = Math.floor(duration / _MINUTE_IN_MILLIS);
99		duration = duration % _MINUTE_IN_MILLIS;
100		if (mins < 10) s += '0';
101		s += mins + ' h ';
102
103		if (!hide_secs) {
104			var secs = Math.floor(duration / _SECOND_IN_MILLIS);
105			duration = duration % _SECOND_IN_MILLIS;
106			if (secs < 10) s += '0';
107			s += secs + ' s';
108		}
109		return s;
110	},
111
112	get_duration_string_iso: function(duration, hidems) {
113		var s = this.get_duration_string(duration, hidems);
114		return s.replace("'",':').replace('"','');
115	},
116
117	to_miles:            function(v) { return v / 1.60934; },
118	to_ft:               function(v) { return v * 3.28084; },
119	m_to_km:             function(v) { return v / 1000; },
120	m_to_mi:             function(v) { return v / 1609.34; },
121
122	get_name:            function() { return this._info.name; },
123	get_desc:            function() { return this._info.desc; },
124	get_author:          function() { return this._info.author; },
125	get_copyright:       function() { return this._info.copyright; },
126	get_distance:        function() { return this._info.length; },
127	get_distance_imp:    function() { return this.to_miles(this.m_to_km(this.get_distance())); },
128
129	get_start_time:      function() { return this._info.duration.start; },
130	get_end_time:        function() { return this._info.duration.end; },
131	get_moving_time:     function() { return this._info.duration.moving; },
132	get_total_time:      function() { return this._info.duration.total; },
133
134	get_moving_pace:     function() { return this.get_moving_time() / this.m_to_km(this.get_distance()); },
135	get_moving_pace_imp: function() { return this.get_moving_time() / this.get_distance_imp(); },
136
137	get_moving_speed:    function() { return this.m_to_km(this.get_distance()) / (this.get_moving_time() / (3600 * 1000)) ; },
138	get_moving_speed_imp:function() { return this.to_miles(this.m_to_km(this.get_distance())) / (this.get_moving_time() / (3600 * 1000)) ; },
139
140	get_total_speed:     function() { return this.m_to_km(this.get_distance()) / (this.get_total_time() / (3600 * 1000)); },
141	get_total_speed_imp: function() { return this.to_miles(this.m_to_km(this.get_distance())) / (this.get_total_time() / (3600 * 1000)); },
142
143	get_elevation_gain:     function() { return this._info.elevation.gain; },
144	get_elevation_loss:     function() { return this._info.elevation.loss; },
145	get_elevation_gain_imp: function() { return this.to_ft(this.get_elevation_gain()); },
146	get_elevation_loss_imp: function() { return this.to_ft(this.get_elevation_loss()); },
147	get_elevation_data:     function() {
148		var _this = this;
149		return this._info.elevation._points.map(
150		function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
151			function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' m'; });
152		});
153	},
154	get_elevation_data_imp: function() {
155		var _this = this;
156		return this._info.elevation._points.map(
157		function(p) { return _this._prepare_data_point(p, _this.m_to_mi, _this.to_ft,
158			function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' ft'; });
159		});
160	},
161	get_elevation_max:      function() { return this._info.elevation.max; },
162	get_elevation_min:      function() { return this._info.elevation.min; },
163	get_elevation_max_imp:  function() { return this.to_ft(this.get_elevation_max()); },
164	get_elevation_min_imp:  function() { return this.to_ft(this.get_elevation_min()); },
165
166	get_average_hr:         function() { return this._info.hr.avg; },
167	get_average_temp:       function() { return this._info.atemp.avg; },
168	get_average_cadence:    function() { return this._info.cad.avg; },
169	get_heartrate_data:     function() {
170		var _this = this;
171		return this._info.hr._points.map(
172		function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
173			function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' bpm'; });
174		});
175	},
176	get_heartrate_data_imp: function() {
177		var _this = this;
178		return this._info.hr._points.map(
179		function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null,
180			function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' bpm'; });
181		});
182	},
183	get_cadence_data:       function() {
184		var _this = this;
185		return this._info.cad._points.map(
186		function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
187			function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' rpm'; });
188		});
189	},
190	get_temp_data:          function() {
191		var _this = this;
192		return this._info.atemp._points.map(
193		function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
194			function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' degrees'; });
195		});
196	},
197	get_cadence_data_imp:   function() {
198		var _this = this;
199		return this._info.cad._points.map(
200		function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null,
201			function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' rpm'; });
202		});
203	},
204	get_temp_data_imp:      function() {
205		var _this = this;
206		return this._info.atemp._points.map(
207		function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null,
208			function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' degrees'; });
209		});
210	},
211
212	// Private methods
213	_merge_objs: function(a, b) {
214		var _ = {};
215		for (var attr in a) { _[attr] = a[attr]; }
216		for (var attr in b) { _[attr] = b[attr]; }
217		return _;
218	},
219
220	_prepare_data_point: function(p, trans1, trans2, trans_tooltip) {
221		var r = [trans1 && trans1(p[0]) || p[0], trans2 && trans2(p[1]) || p[1]];
222		r.push(trans_tooltip && trans_tooltip(r[0], r[1]) || (r[0] + ': ' + r[1]));
223		return r;
224	},
225
226	_init_info: function() {
227		this._info = {
228		name: null,
229		length: 0.0,
230		elevation: {gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _points: []},
231		hr: {avg: 0, _total: 0, _points: []},
232		duration: {start: null, end: null, moving: 0, total: 0},
233		atemp: {avg: 0, _total: 0, _points: []},
234		cad: {avg: 0, _total: 0, _points: []}
235		};
236	},
237
238	_load_xml: function(url, cb, options, async) {
239		if (async == undefined) async = this.options.async;
240		if (options == undefined) options = this.options;
241
242		var req = new window.XMLHttpRequest();
243		req.open('GET', url, async);
244		try {
245			req.overrideMimeType('text/xml'); // unsupported by IE
246		} catch(e) {}
247		req.onreadystatechange = function() {
248			if (req.readyState != 4) return;
249			if(req.status == 200) cb(req.responseXML, options);
250		};
251		req.send(null);
252	},
253
254	_parse: function(input, options, async) {
255		var _this = this;
256		var cb = function(gpx, options) {
257			var layers = _this._parse_gpx_data(gpx, options);
258			if (!layers)
259				return;
260			_this.addLayer(layers);
261			_this.fire('loaded');
262		}
263		if (input.substr(0,1) === '<') { // direct XML has to start with a <
264			var parser = new DOMParser();
265			if (async) {
266				setTimeout(function() {
267				cb(parser.parseFromString(input, "text/xml"), options);
268				});
269			} else
270				cb(parser.parseFromString(input, "text/xml"), options);
271		} else
272			this._load_xml(input, cb, options, async);
273	},
274
275	_parse_gpx_data: function(xml, options) {
276		var j, i, el, layers = [];
277		var tags = [];
278
279		var parseElements = options.gpx_options.parseElements;
280		if (parseElements.indexOf('route') > -1)
281			tags.push(['rte','rtept']);
282		if (parseElements.indexOf('track') > -1)
283			tags.push(['trkseg','trkpt']);
284		var trks = xml.getElementsByTagName('trk');
285		if (trks.length > 0) {
286			var names = trks[0].getElementsByTagName('name');
287			if (names.length > 0)
288				this._info.name = names[0].textContent;
289		}
290		var desc = xml.getElementsByTagName('desc');
291		if (desc.length > 0)
292			this._info.desc = desc[0].textContent;
293		var author = xml.getElementsByTagName('author');
294		if (author.length > 0)
295			this._info.author = author[0].textContent;
296		var copyright = xml.getElementsByTagName('copyright');
297		if (copyright.length > 0)
298			this._info.copyright = copyright[0].textContent;
299		
300		var all_coords = new Array(tags.length);
301		for (j = 0; j < tags.length; j++) {
302			el = xml.getElementsByTagName(tags[j][0]);
303			all_coords[j] = new Array(el.length);
304			for (i = 0; i < el.length; i++) {
305				var coords = this._parse_trkseg(el[i], xml, options, tags[j][1]);
306				if (coords.length === 0)
307					continue;
308				all_coords[j][i] = coords;
309
310				// add track
311				if (this._editable)
312					var l = new L.Polyline.PolylineEditor(coords, options.polyline_options);
313				else
314					var l = new L.Polyline(coords, options.polyline_options);
315				this.fire('addline', { line: l });
316				layers.push(l);
317
318				if (options.marker_options.startIcon || options.marker_options.startIconUrl) {
319					// add start pin
320					var p = new L.Marker(coords[0], {
321						icon: options.marker_options.startIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl}),
322						zIndexOffset: 5
323					});
324					this.fire('addpoint', { point: p, point_type: 'start' });
325					layers.push(p);
326				}
327				if (options.marker_options.endIcon || options.marker_options.endIconUrl) {
328					// add end pin
329					p = new L.Marker(coords[coords.length-1], {
330						icon: options.marker_options.endIcon || new L.GPXTrackIcon({iconUrl: options.marker_options.endIconUrl})
331					});
332					this.fire('addpoint', { point: p, point_type: 'end' });
333					layers.push(p);
334				}
335			}
336		}
337		this._info.hr.avg = Math.round(this._info.hr._total / this._info.hr._points.length);
338		this._info.cad.avg = Math.round(this._info.cad._total / this._info.cad._points.length);
339		this._info.atemp.avg = Math.round(this._info.atemp._total / this._info.atemp._points.length);
340
341		// parse waypoints and add markers for each of them
342		if (parseElements.indexOf('waypoint') > -1) {
343			el = xml.getElementsByTagName('wpt');
344			for (i = 0; i < el.length; i++) {
345				var ll = new L.LatLng(
346					el[i].getAttribute('lat'),
347					el[i].getAttribute('lon'));
348				var nameEl = el[i].getElementsByTagName('name');
349				var name = '';
350				if (nameEl.length > 0)
351					name = nameEl[0].textContent;
352				var timeEl = el[i].getElementsByTagName('time');
353				var time = '';
354				if (timeEl.length > 0)
355					time = new Date(Date.parse(timeEl[0].textContent));
356				else {
357					var min_dist = 6371000;
358					for (j = 0; j < all_coords.length; j++) {
359						for (k = 0; k < all_coords[j].length; k++) {
360							var coords = all_coords[j][k];
361							for(c = 0; c < coords.length; c++) {
362								var dist = this._dist2d(ll, coords[c]);
363								if (dist < min_dist) {
364									min_dist = dist;
365									time = coords[c].meta.time;
366								}
367							}
368						}
369					}
370					if (min_dist > 25)
371						time = '';
372				}
373				if (time != '') {
374					var duration = Math.abs(time - this._info.duration.start);
375					if (duration > 0)
376						name += '\n' + this.get_duration_string(duration, true);
377				}
378				var descEl = el[i].getElementsByTagName('desc');
379				var desc = '';
380				if (descEl.length > 0)
381					desc = descEl[0].textContent;
382				var symEl = el[i].getElementsByTagName('sym');
383				var symKey = '';
384				if (symEl.length > 0)
385					symKey = symEl[0].textContent;
386
387				/*
388				* Add waypoint marker based on the waypoint symbol key.
389				*
390				* First look for a configured icon for that symKey. If not found, look
391				* for a configured icon URL for that symKey and build an icon from it.
392				* Otherwise, fall back to the default icon if one was configured, or
393				* finally to the default icon URL.
394				*/
395				var wptIcons = options.marker_options.wptIcons;
396				var wptIconUrls = options.marker_options.wptIconUrls;
397				var symIcon;
398				if (wptIcons && wptIcons[symKey])
399					symIcon = wptIcons[symKey];
400				else if (wptIconUrls && wptIconUrls[symKey])
401					symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls[symKey]});
402				else if (wptIcons && wptIcons[''])
403					symIcon = wptIcons[''];
404				else if (wptIconUrls && wptIconUrls[''])
405					symIcon = new L.GPXTrackIcon({iconUrl: wptIconUrls['']});
406				else {
407					console.log('No icon or icon URL configured for symbol type "' + symKey
408						+ '", and no fallback configured; ignoring waypoint.');
409					continue;
410				}
411				var marker = new L.Marker(ll, {
412					clickable: true,
413					title: name,
414					icon: symIcon
415				});
416				marker.bindPopup("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : '')).openPopup();
417				this.fire('addpoint', { point: marker, point_type: 'waypoint' });
418				layers.push(marker);
419			}
420		}
421
422		if (layers.length > 1)
423			return new L.FeatureGroup(layers);
424		else if (layers.length == 1)
425			return layers[0];
426	},
427
428	_parse_trkseg: function(line, xml, options, tag) {
429		var el = line.getElementsByTagName(tag);
430		if (!el.length) return [];
431		var coords = [];
432		var last = null;
433		var last_time = null;
434
435		for (var i = 0; i < el.length; i++) {
436			var _, ll = new L.LatLng(
437				el[i].getAttribute('lat'),
438				el[i].getAttribute('lon'));
439			ll.meta = { time: null, ele: null, hr: null, cad: null, atemp: null };
440
441			_ = el[i].getElementsByTagName('time');
442			if (_.length > 0)
443				ll.meta.time = new Date(Date.parse(_[0].textContent));
444			else
445				ll.meta.time = new Date('1970-01-01T00:00:00');
446
447			_ = el[i].getElementsByTagName('ele');
448			if (_.length > 0)
449				ll.meta.ele = parseFloat(_[0].textContent);
450
451			_ = el[i].getElementsByTagNameNS('*', 'hr');
452			if (_.length > 0) {
453				ll.meta.hr = parseInt(_[0].textContent);
454				this._info.hr._points.push([this._info.length, ll.meta.hr]);
455				this._info.hr._total += ll.meta.hr;
456			}
457			_ = el[i].getElementsByTagNameNS('*', 'cad');
458			if (_.length > 0) {
459				ll.meta.cad = parseInt(_[0].textContent);
460				this._info.cad._points.push([this._info.length, ll.meta.cad]);
461				this._info.cad._total += ll.meta.cad;
462			}
463			_ = el[i].getElementsByTagNameNS('*', 'atemp');
464			if (_.length > 0) {
465				ll.meta.atemp = parseInt(_[0].textContent);
466				this._info.atemp._points.push([this._info.length, ll.meta.atemp]);
467				this._info.atemp._total += ll.meta.atemp;
468			}
469			if (ll.meta.ele > this._info.elevation.max)
470				this._info.elevation.max = ll.meta.ele;
471
472			if (ll.meta.ele < this._info.elevation.min)
473				this._info.elevation.min = ll.meta.ele;
474
475			this._info.elevation._points.push([this._info.length, ll.meta.ele]);
476			this._info.duration.end = ll.meta.time;
477
478			if (last != null) {
479				this._info.length += this._dist3d(last, ll);
480
481				var t = ll.meta.ele - last.meta.ele;
482				if (t > 0)
483					this._info.elevation.gain += t;
484				else
485					this._info.elevation.loss += Math.abs(t);
486			} else
487				this._info.duration.start = ll.meta.time;
488
489				
490			if (ll.meta.time.getTime() > 0) {
491				if (last_time != null) {
492					t = Math.abs(ll.meta.time - last_time);
493					this._info.duration.total += t;
494					if (t < options.max_point_interval)
495						this._info.duration.moving += t;
496				}
497				last_time = ll.meta.time;
498			}
499
500			last = ll;
501			coords.push(ll);
502		}
503		return coords;
504	},
505
506	_dist2d: function(a, b) {
507		var R = 6371000;
508		var dLat = this._deg2rad(b.lat - a.lat);
509		var dLon = this._deg2rad(b.lng - a.lng);
510		var r = Math.sin(dLat/2) *
511		Math.sin(dLat/2) +
512		Math.cos(this._deg2rad(a.lat)) *
513		Math.cos(this._deg2rad(b.lat)) *
514		Math.sin(dLon/2) *
515		Math.sin(dLon/2);
516		var c = 2 * Math.atan2(Math.sqrt(r), Math.sqrt(1-r));
517		var d = R * c;
518		return d;
519	},
520
521	_dist3d: function(a, b) {
522		var planar = this._dist2d(a, b);
523		var height = Math.abs(b.meta.ele - a.meta.ele);
524		return Math.sqrt(Math.pow(planar, 2) + Math.pow(height, 2));
525	},
526
527	_deg2rad: function(deg) {
528		return deg * Math.PI / 180;
529	}
530});
531
532if (typeof module === 'object' && typeof module.exports === 'object')
533	module.exports = L;
534else if (typeof define === 'function' && define.amd)
535	define(L);
536

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.