PageSourceSearch

https://www.naturpark-aukrug.com/typo3conf/ext/npa/Resources/Public/JavaScript/Src/gpx.js?1692185445

js naturpark-aukrug.com collected 2026-10-02 19:51:48 UTC 23,561 bytes, 619 lines download raw bytes

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

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.