PageSourceSearch

https://www.bubi.si/media/plg_fields_aggpxtrack/leaflet-gpx/gpx.js?7788aa

js bubi.si collected 2026-10-02 12:31:41 UTC 26,371 bytes, 872 lines download raw bytes

1var L = L || require("leaflet");
2
3var _MAX_POINT_INTERVAL_MS = 15000;
4var _SECOND_IN_MILLIS = 1000;
5var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS;
6var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS;
7var _DAY_IN_MILLIS = 24 * _HOUR_IN_MILLIS;
8
9var _GPX_STYLE_NS = "http://www.topografix.com/GPX/gpx_style/0/2";
10
11var _DEFAULT_MARKER_OPTS = {
12  startIconUrl: "pin-icon-start.png",
13  endIconUrl: "pin-icon-end.png",
14  shadowUrl: "pin-shadow.png",
15  wptIcons: [],
16  wptIconsType: [],
17  wptIconUrls: {
18    "": "pin-icon-wpt.png",
19  },
20  wptIconTypeUrls: {
21    "": "pin-icon-wpt.png",
22  },
23  pointMatchers: [],
24  iconSize: [33, 50],
25  shadowSize: [50, 50],
26  iconAnchor: [16, 45],
27  shadowAnchor: [16, 47],
28  clickable: false,
29};
30var _DEFAULT_POLYLINE_OPTS = {
31  color: "blue",
32};
33var _DEFAULT_GPX_OPTS = {
34  parseElements: ["track", "route", "waypoint"],
35  show_kilometer_point: false,
36  kilometer_point_options: {
37    kilometer_point_color: "blue",
38    kilometer_point_color_text: "white",
39    kilometer_point_intervall: 1,
40    kilometer_point_radius: 10,
41  },
42  show_mile_point: true,
43  mile_point_options: {
44    mile_point_color: "blue",
45    mile_point_color_text: "white",
46    mile_intervall: 1,
47    mile_point_radius: 10,
48  },
49  joinTrackSegments: true,
50};
51
52L.GPX = L.FeatureGroup.extend({
53  initialize: function (gpx, options) {
54    options.max_point_interval =
55      options.max_point_interval || _MAX_POINT_INTERVAL_MS;
56    options.marker_options = this._merge_objs(
57      _DEFAULT_MARKER_OPTS,
58      options.marker_options || {}
59    );
60    options.polyline_options = options.polyline_options || {};
61    options.gpx_options = this._merge_objs(
62      _DEFAULT_GPX_OPTS,
63      options.gpx_options || {}
64    );
65
66    L.Util.setOptions(this, options);
67
68    // Base icon class for track pins.
69    L.GPXTrackIcon = L.Icon.extend({ options: options.marker_options });
70
71    this._gpx = gpx;
72    this._layers = {};
73    this._init_info();
74
75    if (gpx) {
76      this._parse(gpx, options, this.options.async);
77    }
78  },
79
80  get_duration_string: function (duration, hidems) {
81    var s = "";
82
83    if (duration >= _DAY_IN_MILLIS) {
84      s += Math.floor(duration / _DAY_IN_MILLIS) + "d ";
85      duration = duration % _DAY_IN_MILLIS;
86    }
87
88    if (duration >= _HOUR_IN_MILLIS) {
89      s += Math.floor(duration / _HOUR_IN_MILLIS) + ":";
90      duration = duration % _HOUR_IN_MILLIS;
91    }
92
93    var mins = Math.floor(duration / _MINUTE_IN_MILLIS);
94    duration = duration % _MINUTE_IN_MILLIS;
95    if (mins < 10) s += "0";
96    s += mins + "'";
97
98    var secs = Math.floor(duration / _SECOND_IN_MILLIS);
99    duration = duration % _SECOND_IN_MILLIS;
100    if (secs < 10) s += "0";
101    s += secs;
102
103    if (!hidems && duration > 0)
104      s += "." + Math.round(Math.floor(duration) * 1000) / 1000;
105    else s += '"';
106
107    return s;
108  },
109
110  get_duration_string_iso: function (duration, hidems) {
111    var s = this.get_duration_string(duration, hidems);
112    return s.replace("'", ":").replace('"', "");
113  },
114
115  // Public methods
116  to_miles: function (v) {
117    return v / 1.60934;
118  },
119  to_ft: function (v) {
120    return v * 3.28084;
121  },
122  m_to_km: function (v) {
123    return v / 1000;
124  },
125  m_to_mi: function (v) {
126    return v / 1609.34;
127  },
128
129  get_name: function () {
130    return this._info.name;
131  },
132  get_desc: function () {
133    return this._info.desc;
134  },
135  get_author: function () {
136    return this._info.author;
137  },
138  get_copyright: function () {
139    return this._info.copyright;
140  },
141  get_distance: function () {
142    return this._info.length;
143  },
144  get_distance_imp: function () {
145    return this.to_miles(this.m_to_km(this.get_distance()));
146  },
147
148  get_start_time: function () {
149    return this._info.duration.start;
150  },
151  get_end_time: function () {
152    return this._info.duration.end;
153  },
154  get_moving_time: function () {
155    return this._info.duration.moving;
156  },
157  get_total_time: function () {
158    return this._info.duration.total;
159  },
160
161  get_moving_pace: function () {
162    return this.get_moving_time() / this.m_to_km(this.get_distance());
163  },
164  get_moving_pace_imp: function () {
165    return this.get_moving_time() / this.get_distance_imp();
166  },
167
168  get_moving_speed: function () {
169    return (
170      this.m_to_km(this.get_distance()) /
171      (this.get_moving_time() / (3600 * 1000))
172    );
173  },
174  get_moving_speed_imp: function () {
175    return (
176      this.to_miles(this.m_to_km(this.get_distance())) /
177      (this.get_moving_time() / (3600 * 1000))
178    );
179  },
180
181  get_total_speed: function () {
182    return (
183      this.m_to_km(this.get_distance()) /
184      (this.get_total_time() / (3600 * 1000))
185    );
186  },
187  get_total_speed_imp: function () {
188    return (
189      this.to_miles(this.m_to_km(this.get_distance())) /
190      (this.get_total_time() / (3600 * 1000))
191    );
192  },
193
194  get_elevation_gain: function () {
195    return this._info.elevation.gain;
196  },
197  get_elevation_loss: function () {
198    return this._info.elevation.loss;
199  },
200  get_elevation_gain_imp: function () {
201    return this.to_ft(this.get_elevation_gain());
202  },
203  get_elevation_loss_imp: function () {
204    return this.to_ft(this.get_elevation_loss());
205  },
206  get_elevation_data: function () {
207    var _this = this;
208    return this._info.elevation._points.map(function (p) {
209      return _this._prepare_data_point(p, _this.m_to_km, null, function (a, b) {
210        return a.toFixed(2) + " km, " + b.toFixed(0) + " m";
211      });
212    });
213  },
214  get_elevation_data_imp: function () {
215    var _this = this;
216    return this._info.elevation._points.map(function (p) {
217      return _this._prepare_data_point(p, _this.m_to_mi, _this.to_ft, function (
218        a,
219        b
220      ) {
221        return a.toFixed(2) + " mi, " + b.toFixed(0) + " ft";
222      });
223    });
224  },
225  get_elevation_max: function () {
226    return this._info.elevation.max;
227  },
228  get_elevation_min: function () {
229    return this._info.elevation.min;
230  },
231  get_elevation_max_imp: function () {
232    return this.to_ft(this.get_elevation_max());
233  },
234  get_elevation_min_imp: function () {
235    return this.to_ft(this.get_elevation_min());
236  },
237
238  get_average_hr: function () {
239    return this._info.hr.avg;
240  },
241  get_average_temp: function () {
242    return this._info.atemp.avg;
243  },
244  get_average_cadence: function () {
245    return this._info.cad.avg;
246  },
247  get_heartrate_data: function () {
248    var _this = this;
249    return this._info.hr._points.map(function (p) {
250      return _this._prepare_data_point(p, _this.m_to_km, null, function (a, b) {
251        return a.toFixed(2) + " km, " + b.toFixed(0) + " bpm";
252      });
253    });
254  },
255  get_heartrate_data_imp: function () {
256    var _this = this;
257    return this._info.hr._points.map(function (p) {
258      return _this._prepare_data_point(p, _this.m_to_mi, null, function (a, b) {
259        return a.toFixed(2) + " mi, " + b.toFixed(0) + " bpm";
260      });
261    });
262  },
263  get_cadence_data: function () {
264    var _this = this;
265    return this._info.cad._points.map(function (p) {
266      return _this._prepare_data_point(p, _this.m_to_km, null, function (a, b) {
267        return a.toFixed(2) + " km, " + b.toFixed(0) + " rpm";
268      });
269    });
270  },
271  get_temp_data: function () {
272    var _this = this;
273    return this._info.atemp._points.map(function (p) {
274      return _this._prepare_data_point(p, _this.m_to_km, null, function (a, b) {
275        return a.toFixed(2) + " km, " + b.toFixed(0) + " degrees";
276      });
277    });
278  },
279  get_cadence_data_imp: function () {
280    var _this = this;
281    return this._info.cad._points.map(function (p) {
282      return _this._prepare_data_point(p, _this.m_to_mi, null, function (a, b) {
283        return a.toFixed(2) + " mi, " + b.toFixed(0) + " rpm";
284      });
285    });
286  },
287  get_temp_data_imp: function () {
288    var _this = this;
289    return this._info.atemp._points.map(function (p) {
290      return _this._prepare_data_point(p, _this.m_to_mi, null, function (a, b) {
291        return a.toFixed(2) + " mi, " + b.toFixed(0) + " degrees";
292      });
293    });
294  },
295
296  reload: function () {
297    this._init_info();
298    this.clearLayers();
299    this._parse(this._gpx, this.options, this.options.async);
300  },
301
302  // Private methods
303  _merge_objs: function (a, b) {
304    var _ = {};
305    for (var attr in a) {
306      _[attr] = a[attr];
307    }
308    for (var attr in b) {
309      _[attr] = b[attr];
310    }
311    return _;
312  },
313
314  _prepare_data_point: function (p, trans1, trans2, trans_tooltip) {
315    var r = [
316      (trans1 && trans1(p[0])) || p[0],
317      (trans2 && trans2(p[1])) || p[1],
318    ];
319    r.push((trans_tooltip && trans_tooltip(r[0], r[1])) || r[0] + ": " + r[1]);
320    return r;
321  },
322
323  _init_info: function () {
324    this._info = {
325      name: null,
326      length: 0.0,
327      elevation: { gain: 0.0, loss: 0.0, max: 0.0, min: Infinity, _points: [] },
328      hr: { avg: 0, _total: 0, _points: [] },
329      duration: { start: null, end: null, moving: 0, total: 0 },
330      atemp: { avg: 0, _total: 0, _points: [] },
331      cad: { avg: 0, _total: 0, _points: [] },
332    };
333  },
334
335  _load_xml: function (url, cb, options, async) {
336    if (async == undefined) async = this.options.async;
337    if (options == undefined) options = this.options;
338
339    var req = new window.XMLHttpRequest();
340    req.open("GET", url, async);
341    try {
342      req.overrideMimeType("text/xml"); // unsupported by IE
343    } catch (e) {}
344    req.onreadystatechange = function () {
345      if (req.readyState != 4) return;
346      if (req.status == 200) cb(req.responseXML, options);
347    };
348    req.send(null);
349  },
350
351  _parse: function (input, options, async) {
352    var _this = this;
353    var cb = function (gpx, options) {
354      var layers = _this._parse_gpx_data(gpx, options);
355      if (!layers) {
356        _this.fire("error", {
357          err:
358            "No parseable layers of type(s) " +
359            JSON.stringify(options.gpx_options.parseElements),
360        });
361        return;
362      }
363      _this.addLayer(layers);
364      _this.fire("loaded", { layers: layers, element: gpx });
365    };
366    if (input.substr(0, 1) === "<") {
367      // direct XML has to start with a <
368      var parser = new DOMParser();
369      if (async) {
370        setTimeout(function () {
371          cb(parser.parseFromString(input, "text/xml"), options);
372        });
373      } else {
374        cb(parser.parseFromString(input, "text/xml"), options);
375      }
376    } else {
377      this._load_xml(input, cb, options, async);
378    }
379  },
380
381  _parse_gpx_data: function (xml, options) {
382    var i,
383      t,
384      l,
385      el,
386      layers = [];
387
388    var name = xml.getElementsByTagName("name");
389    if (name.length > 0) {
390      this._info.name = name[0].textContent;
391    }
392    var desc = xml.getElementsByTagName("desc");
393    if (desc.length > 0) {
394      this._info.desc = desc[0].textContent;
395    }
396    var author = xml.getElementsByTagName("author");
397    if (author.length > 0) {
398      this._info.author = author[0].textContent;
399    }
400    var copyright = xml.getElementsByTagName("copyright");
401    if (copyright.length > 0) {
402      this._info.copyright = copyright[0].textContent;
403    }
404
405    var parseElements = options.gpx_options.parseElements;
406    if (parseElements.indexOf("route") > -1) {
407      // routes are <rtept> tags inside <rte> sections
408      var routes = xml.getElementsByTagName("rte");
409      for (i = 0; i < routes.length; i++) {
410        layers = layers.concat(
411          this._parse_segment(routes[i], options, {}, "rtept")
412        );
413      }
414    }
415
416    if (parseElements.indexOf("track") > -1) {
417      // tracks are <trkpt> tags in one or more <trkseg> sections in each <trk>
418      var tracks = xml.getElementsByTagName("trk");
419      for (i = 0; i < tracks.length; i++) {
420        var track = tracks[i];
421        var polyline_options = this._extract_styling(track);
422
423        if (options.gpx_options.joinTrackSegments) {
424          layers = layers.concat(
425            this._parse_segment(track, options, polyline_options, "trkpt")
426          );
427        } else {
428          var segments = track.getElementsByTagName("trkseg");
429          for (j = 0; j < segments.length; j++) {
430            layers = layers.concat(
431              this._parse_segment(
432                segments[j],
433                options,
434                polyline_options,
435                "trkpt"
436              )
437            );
438          }
439        }
440      }
441    }
442
443    this._info.hr.avg = Math.round(
444      this._info.hr._total / this._info.hr._points.length
445    );
446    this._info.cad.avg = Math.round(
447      this._info.cad._total / this._info.cad._points.length
448    );
449    this._info.atemp.avg = Math.round(
450      this._info.atemp._total / this._info.atemp._points.length
451    );
452
453    // parse waypoints and add markers for each of them
454    if (parseElements.indexOf("waypoint") > -1) {
455      el = xml.getElementsByTagName("wpt");
456      for (i = 0; i < el.length; i++) {
457        var ll = new L.LatLng(
458          el[i].getAttribute("lat"),
459          el[i].getAttribute("lon")
460        );
461
462        var nameEl = el[i].getElementsByTagName("name");
463        var name = "";
464        if (nameEl.length > 0) {
465          name = nameEl[0].textContent;
466        }
467
468        var descEl = el[i].getElementsByTagName("desc");
469        var desc = "";
470        if (descEl.length > 0) {
471          desc = descEl[0].textContent;
472        }
473
474        var symEl = el[i].getElementsByTagName("sym");
475        var symKey = "";
476        if (symEl.length > 0) {
477          symKey = symEl[0].textContent;
478        }
479
480        var typeEl = el[i].getElementsByTagName("type");
481        var typeKey = "";
482        if (typeEl.length > 0) {
483          typeKey = typeEl[0].textContent;
484        }
485
486        /*
487         * Add waypoint marker based on the waypoint symbol key.
488         *
489         * First look for a configured icon for that symKey. If not found, look
490         * for a configured icon URL for that symKey and build an icon from it.
491         * Otherwise, fall back to the default icon if one was configured, or
492         * finally to the default icon URL.
493         */
494        var wptIcons = options.marker_options.wptIcons;
495        var wptIconUrls = options.marker_options.wptIconUrls;
496        var wptIconsType = options.marker_options.wptIconsType;
497        var wptIconTypeUrls = options.marker_options.wptIconTypeUrls;
498        
499        var symIcon;
500        if (wptIcons && wptIcons[symKey]) {
501          
502          symIcon = wptIcons[symKey];
503        } else if (wptIconsType && wptIconsType[typeKey]) {
504          
505          symIcon = wptIconsType[typeKey];
506        } else if (wptIconUrls && wptIconUrls[symKey]) {
507          
508          symIcon = new L.GPXTrackIcon({ iconUrl: wptIconUrls[symKey] });
509        } else if (wptIconTypeUrls && wptIconTypeUrls[typeKey]) {
510          
511          symIcon = new L.GPXTrackIcon({ iconUrl: wptIconTypeUrls[typeKey] });
512        } else if (wptIcons && wptIcons[""]) {
513          
514          symIcon = wptIcons[""];
515        } else if (wptIconUrls && wptIconUrls[""]) {
516          
517          symIcon = new L.GPXTrackIcon({ iconUrl: wptIconUrls[""] });
518        } else {
519          console.log(
520            'No icon or icon URL configured for symbol type "' +
521              symKey +
522              '", and no fallback configured; ignoring waypoint.'
523          );
524          continue;
525        }
526        
527
528        var marker = new L.Marker(ll, {
529          clickable: options.marker_options.clickable,
530          title: name,
531          icon: symIcon,
532          type: "waypoint",
533        });
534        marker
535          .bindPopup(
536            "<b>" + name + "</b>" + (desc.length > 0 ? "<br>" + desc : "")
537          )
538          .openPopup();
539        this.fire("addpoint", {
540          point: marker,
541          point_type: "waypoint",
542          element: el[i],
543        });
544        layers.push(marker);
545      }
546    }
547
548    if (layers.length > 1) {
549      return new L.FeatureGroup(layers);
550    } else if (layers.length == 1) {
551      return layers[0];
552    }
553  },
554
555  _parse_segment: function (line, options, polyline_options, tag) {
556    var el = line.getElementsByTagName(tag);
557    var kilometer_point_layers = [];
558    var mile_point_layers = [];
559    var _this = this;
560
561    if (!el.length) return [];
562
563    var coords = [];
564    var markers = [];
565    var layers = [];
566    var last = null;
567
568    for (var i = 0; i < el.length; i++) {
569      var _,
570        ll = new L.LatLng(el[i].getAttribute("lat"), el[i].getAttribute("lon"));
571      ll.meta = { time: null, ele: null, hr: null, cad: null, atemp: null };
572
573      _ = el[i].getElementsByTagName("time");
574      if (_.length > 0) {
575        ll.meta.time = new Date(Date.parse(_[0].textContent));
576      } else {
577        ll.meta.time = new Date("1970-01-01T00:00:00");
578      }
579
580      _ = el[i].getElementsByTagName("ele");
581      if (_.length > 0) {
582        ll.meta.ele = parseFloat(_[0].textContent);
583      }
584
585      _ = el[i].getElementsByTagName("name");
586      if (_.length > 0) {
587        var name = _[0].textContent;
588        var ptMatchers = options.marker_options.pointMatchers || [];
589
590        for (var j = 0; j < ptMatchers.length; j++) {
591          if (ptMatchers[j].regex.test(name)) {
592            markers.push({
593              label: name,
594              coords: ll,
595              icon: ptMatchers[j].icon,
596              element: el[i],
597            });
598            break;
599          }
600        }
601      }
602
603      _ = el[i].getElementsByTagNameNS("*", "hr");
604      if (_.length > 0) {
605        ll.meta.hr = parseInt(_[0].textContent);
606        this._info.hr._points.push([this._info.length, ll.meta.hr]);
607        this._info.hr._total += ll.meta.hr;
608      }
609
610      _ = el[i].getElementsByTagNameNS("*", "cad");
611      if (_.length > 0) {
612        ll.meta.cad = parseInt(_[0].textContent);
613        this._info.cad._points.push([this._info.length, ll.meta.cad]);
614        this._info.cad._total += ll.meta.cad;
615      }
616
617      _ = el[i].getElementsByTagNameNS("*", "atemp");
618      if (_.length > 0) {
619        ll.meta.atemp = parseInt(_[0].textContent);
620        this._info.atemp._points.push([this._info.length, ll.meta.atemp]);
621        this._info.atemp._total += ll.meta.atemp;
622      }
623
624      if (ll.meta.ele > this._info.elevation.max) {
625        this._info.elevation.max = ll.meta.ele;
626      }
627
628      if (ll.meta.ele < this._info.elevation.min) {
629        this._info.elevation.min = ll.meta.ele;
630      }
631
632      this._info.elevation._points.push([this._info.length, ll.meta.ele]);
633      this._info.duration.end = ll.meta.time;
634
635      if (last != null) {
636        this._info.length += this._dist3d(last, ll);
637
638        /*
639         * Add points to the line.
640         */
641        if (
642          options.gpx_options.show_kilometer_point ||
643          options.gpx_options.show_mile_point
644        ) {
645          if (this._parse_current_kilometer != null) {
646            // Kilometer Point
647            if (options.gpx_options.show_kilometer_point) {
648              if (
649                parseInt(this._info.length / 1000) -
650                  this._parse_current_kilometer >
651                options.gpx_options.kilometer_point_options
652                  .kilometer_point_intervall -
653                  1
654              ) {
655                this._parse_current_kilometer = parseInt(
656                  this._info.length / 1000
657                );
658                var marker = new L.circleMarker(ll, {
659                  radius:
660                    options.gpx_options.kilometer_point_options
661                      .kilometer_point_radius,
662                  stroke: false,
663                  fillColor:
664                    options.gpx_options.kilometer_point_options
665                      .kilometer_point_color,
666                  fillOpacity: 1,
667                }).bindTooltip(this._parse_current_kilometer.toString(), {
668                  direction: "center",
669                  permanent: true,
670                  interactive: true,
671                  className: "kilometer_tooltip",
672                });
673                kilometer_point_layers.push(marker);
674              }
675            }
676            // Mile Point
677            if (options.gpx_options.show_mile_point) {
678              if (
679                parseInt(this.to_miles(this._info.length) / 1000) -
680                  this._parse_current_mile >
681                options.gpx_options.mile_point_options.mile_intervall
682              ) {
683                this._parse_current_mile = parseInt(
684                  this.to_miles(this._info.length) / 1000
685                );
686                var marker = new L.circleMarker(ll, {
687                  radius:
688                    options.gpx_options.mile_point_options.mile_point_radius,
689                  stroke: false,
690                  fillColor:
691                    options.gpx_options.mile_point_options.mile_point_color,
692                  fillOpacity: 1,
693                }).bindTooltip(this._parse_current_mile.toString(), {
694                  direction: "center",
695                  permanent: true,
696                  interactive: true,
697                  className: "mile_tooltip",
698                });
699                mile_point_layers.push(marker);
700              }
701            }
702          } else {
703            this._parse_current_kilometer = parseInt(this._info.length / 1000);
704            this._parse_current_mile = parseInt(this._info.length / 1000);
705
706            // Append style element for the tooltip of the points
707            var element = document.createElement("style");
708            document.head.appendChild(element);
709            var sheet = element.sheet;
710            var styles = "";
711            styles += ".kilometer_tooltip, .mile_tooltip {";
712            styles += "background: none!important;";
713            styles += "border: none!important;";
714            styles += "font-weight: 900!important;";
715            styles += "font-size: larger!important;";
716            styles += "box-shadow: none!important;";
717            styles += "}";
718            sheet.insertRule(styles, 0);
719            var styles_kilometer = ".kilometer_tooltip {";
720            styles_kilometer +=
721              "color: " +
722              options.gpx_options.kilometer_point_options
723                .kilometer_point_color_text +
724              ";";
725            styles_kilometer += "}";
726            sheet.insertRule(styles_kilometer, 0);
727            var styles_mile = ".mile_tooltip {";
728            styles_mile +=
729              "color: " +
730              options.gpx_options.mile_point_options.mile_point_color_text +
731              ";";
732            styles_mile += "}";
733            sheet.insertRule(styles_mile, 0);
734          }
735        }
736
737        var t = ll.meta.ele - last.meta.ele;
738        if (t > 0) {
739          this._info.elevation.gain += t;
740        } else {
741          this._info.elevation.loss += Math.abs(t);
742        }
743
744        t = Math.abs(ll.meta.time - last.meta.time);
745        this._info.duration.total += t;
746        if (t < options.max_point_interval) {
747          this._info.duration.moving += t;
748        }
749      } else if (this._info.duration.start == null) {
750        this._info.duration.start = ll.meta.time;
751      }
752
753      last = ll;
754      coords.push(ll);
755    }
756
757    if (kilometer_point_layers.length > 1) {
758      _this.addLayer(new L.FeatureGroup(kilometer_point_layers));
759    }
760
761    if (mile_point_layers.length > 1) {
762      _this.addLayer(new L.FeatureGroup(mile_point_layers));
763    }
764
765    // add track
766    var l = new L.Polyline(
767      coords,
768      this._extract_styling(line, polyline_options, options.polyline_options)
769    );
770    this.fire("addline", { line: l, element: line });
771    layers.push(l);
772
773    if (
774      options.marker_options.startIcon ||
775      options.marker_options.startIconUrl
776    ) {
777      // add start pin
778      var marker = new L.Marker(coords[0], {
779        clickable: options.marker_options.clickable,
780        icon:
781          options.marker_options.startIcon ||
782          new L.GPXTrackIcon({ iconUrl: options.marker_options.startIconUrl }),
783      });
784      this.fire("addpoint", {
785        point: marker,
786        point_type: "start",
787        element: el[0],
788      });
789      layers.push(marker);
790    }
791
792    if (options.marker_options.endIcon || options.marker_options.endIconUrl) {
793      // add end pin
794      var marker = new L.Marker(coords[coords.length - 1], {
795        clickable: options.marker_options.clickable,
796        icon:
797          options.marker_options.endIcon ||
798          new L.GPXTrackIcon({ iconUrl: options.marker_options.endIconUrl }),
799      });
800      this.fire("addpoint", {
801        point: marker,
802        point_type: "end",
803        element: el[el.length - 1],
804      });
805      layers.push(marker);
806    }
807
808    // add named markers
809    for (var i = 0; i < markers.length; i++) {
810      var marker = new L.Marker(markers[i].coords, {
811        clickable: options.marker_options.clickable,
812        title: markers[i].label,
813        icon: markers[i].icon,
814      });
815      this.fire("addpoint", {
816        point: marker,
817        point_type: "label",
818        element: markers[i].element,
819      });
820      layers.push(marker);
821    }
822
823    return layers;
824  },
825
826  _extract_styling: function (el, base, overrides) {
827    var style = this._merge_objs(_DEFAULT_POLYLINE_OPTS, base);
828    var e = el.getElementsByTagNameNS(_GPX_STYLE_NS, "line");
829    if (e.length > 0) {
830      var _ = e[0].getElementsByTagName("color");
831      if (_.length > 0) style.color = "#" + _[0].textContent;
832      var _ = e[0].getElementsByTagName("opacity");
833      if (_.length > 0) style.opacity = _[0].textContent;
834      var _ = e[0].getElementsByTagName("weight");
835      if (_.length > 0) style.weight = _[0].textContent;
836      var _ = e[0].getElementsByTagName("linecap");
837      if (_.length > 0) style.lineCap = _[0].textContent;
838    }
839    return this._merge_objs(style, overrides);
840  },
841
842  _dist2d: function (a, b) {
843    var R = 6371000;
844    var dLat = this._deg2rad(b.lat - a.lat);
845    var dLon = this._deg2rad(b.lng - a.lng);
846    var r =
847      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
848      Math.cos(this._deg2rad(a.lat)) *
849        Math.cos(this._deg2rad(b.lat)) *
850        Math.sin(dLon / 2) *
851        Math.sin(dLon / 2);
852    var c = 2 * Math.atan2(Math.sqrt(r), Math.sqrt(1 - r));
853    var d = R * c;
854    return d;
855  },
856
857  _dist3d: function (a, b) {
858    var planar = this._dist2d(a, b);
859    var height = Math.abs(b.meta.ele - a.meta.ele);
860    return Math.sqrt(Math.pow(planar, 2) + Math.pow(height, 2));
861  },
862
863  _deg2rad: function (deg) {
864    return (deg * Math.PI) / 180;
865  },
866});
867
868if (typeof module === "object" && typeof module.exports === "object") {
869  module.exports = L;
870} else if (typeof define === "function" && define.amd) {
871  define(L);
872}

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.