switch to GeoJSON response format instead of GPX:
- use BRouter track stats - remove leaflet-gpx plugin
This commit is contained in:
parent
d260414f6c
commit
726cf4bba6
16 changed files with 50 additions and 598 deletions
|
|
@ -50,9 +50,6 @@ Copyright (c) 2013 Felix Bache; [MIT License](https://github.com/MrMufflon/Leafl
|
||||||
Copyright (c) 2013, Michael Bostock. All rights reserved.; [3-clause BSD License](https://github.com/mbostock/d3/blob/master/LICENSE)
|
Copyright (c) 2013, Michael Bostock. All rights reserved.; [3-clause BSD License](https://github.com/mbostock/d3/blob/master/LICENSE)
|
||||||
* [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw)
|
* [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw)
|
||||||
Copyright 2012 Jacob Toye; [MIT License](https://github.com/Leaflet/Leaflet.draw/blob/master/MIT-LICENCE.txt)
|
Copyright 2012 Jacob Toye; [MIT License](https://github.com/Leaflet/Leaflet.draw/blob/master/MIT-LICENCE.txt)
|
||||||
* [leaflet-gpx](https://github.com/mpetazzoni/leaflet-gpx)
|
|
||||||
Copyright (C) 2011-2012 Pavel Shramov, Copyright (C) 2013 Maxime Petazzoni
|
|
||||||
All rights reserved. [2-clause BSD License](https://github.com/mpetazzoni/leaflet-gpx/blob/master/LICENSE)
|
|
||||||
* [Leaflet.Control.Search](https://github.com/stefanocudini/leaflet-search)
|
* [Leaflet.Control.Search](https://github.com/stefanocudini/leaflet-search)
|
||||||
Copyright (c) 2013 Stefano Cudini; [MIT License](https://github.com/stefanocudini/leaflet-search/blob/master/LICENSE.txt)
|
Copyright (c) 2013 Stefano Cudini; [MIT License](https://github.com/stefanocudini/leaflet-search/blob/master/LICENSE.txt)
|
||||||
* [leaflet-plugins](https://github.com/shramov/leaflet-plugins)
|
* [leaflet-plugins](https://github.com/shramov/leaflet-plugins)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-css": "*",
|
"normalize-css": "*",
|
||||||
"leaflet-gpx": "mpetazzoni/leaflet-gpx",
|
|
||||||
"leaflet-search": "*",
|
"leaflet-search": "*",
|
||||||
"leaflet-plugins": "*",
|
"leaflet-plugins": "*",
|
||||||
"leaflet-routing": "Turistforeningen/leaflet-routing#gh-pages",
|
"leaflet-routing": "Turistforeningen/leaflet-routing#gh-pages",
|
||||||
|
|
|
||||||
13
bower_components/leaflet-gpx/.bower.json
vendored
13
bower_components/leaflet-gpx/.bower.json
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"name": "leaflet-gpx",
|
|
||||||
"homepage": "https://github.com/mpetazzoni/leaflet-gpx",
|
|
||||||
"_release": "ad9d5adf0a",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "branch",
|
|
||||||
"branch": "master",
|
|
||||||
"commit": "ad9d5adf0ae5450bc5bc60e1e86307beb7667772"
|
|
||||||
},
|
|
||||||
"_source": "git://github.com/mpetazzoni/leaflet-gpx.git",
|
|
||||||
"_target": "*",
|
|
||||||
"_originalSource": "mpetazzoni/leaflet-gpx"
|
|
||||||
}
|
|
||||||
24
bower_components/leaflet-gpx/LICENSE
vendored
24
bower_components/leaflet-gpx/LICENSE
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
Copyright (C) 2011-2012 Pavel Shramov
|
|
||||||
Copyright (C) 2013 Maxime Petazzoni <maxime.petazzoni@bulix.org>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
- Redistributions in binary form must reproduce the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer in the documentation and/or
|
|
||||||
other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
147
bower_components/leaflet-gpx/README.md
vendored
147
bower_components/leaflet-gpx/README.md
vendored
|
|
@ -1,147 +0,0 @@
|
||||||
GPX plugin for Leaflet
|
|
||||||
======================
|
|
||||||
|
|
||||||
[Leaflet](http://www.leafletjs.com) is a Javascript library for displaying
|
|
||||||
interactive maps. This plugin, based on the work of [Pavel
|
|
||||||
Shramov](http://github.com/shramov) and his
|
|
||||||
[leaflet-plugins](http://github.com/shramov/leaflet-plugins), it allows for the
|
|
||||||
analysis and parsing of a GPX track in order to display it as a Leaflet map
|
|
||||||
layer. As it parses the GPX data, it will record information about the recorded
|
|
||||||
track, including total time, moving time, total distance, elevation stats and
|
|
||||||
heart-rate.
|
|
||||||
|
|
||||||
GPX parsing will automatically handle pauses in the track with a default
|
|
||||||
tolerance interval of 15 seconds between points. You can configure this
|
|
||||||
interval by setting `max_point_interval`, in milliseconds, in the options
|
|
||||||
passed to the `GPX` constructor.
|
|
||||||
|
|
||||||
I've put together a complete example as a
|
|
||||||
[demo](http://mpetazzoni.github.com/leaflet-gpx/).
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
`leaflet-gpx` is under the *BSD 2-clause license*. Please refer to the
|
|
||||||
attached LICENSE file and/or to the copyright header in gpx.js for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Usage is very simple. Let's consider we have a Leaflet map:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var map = L.map('map');
|
|
||||||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
attribution: 'Map data © <a href="http://www.osm.org">OpenStreetMap</a>'
|
|
||||||
}).addTo(map);
|
|
||||||
```
|
|
||||||
|
|
||||||
Displaying the GPX track is as easy as:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var gpx = '...'; // URL to your GPX file or the GPX itself
|
|
||||||
new L.GPX(gpx, {async: true}).on('loaded', function(e) {
|
|
||||||
map.fitBounds(e.target.getBounds());
|
|
||||||
}).addTo(map);
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to display additional information about the GPX track, you can do
|
|
||||||
so in the 'loaded' event handler, calling one of the following methods on the
|
|
||||||
`GPX` object `e.target`:
|
|
||||||
|
|
||||||
* `get_name()`: returns the name of the GPX track
|
|
||||||
* `get_distance()`: returns the total track distance, in meters
|
|
||||||
* `get_start_time()`: returns a Javascript `Date` object representing the
|
|
||||||
starting time
|
|
||||||
* `get_end_time()`: returns a Javascript `Date` object representing when the
|
|
||||||
last point was recorded
|
|
||||||
* `get_moving_time()`: returns the moving time, in milliseconds
|
|
||||||
* `get_total_time()`: returns the total track time, in milliseconds
|
|
||||||
* `get_moving_pace()`: returns the average moving pace in milliseconds per km
|
|
||||||
* `get_moving_speed()`: returns the average moving speed in km per hour
|
|
||||||
* `get_elevation_gain()`: returns the cumulative elevation gain, in meters
|
|
||||||
* `get_elevation_loss()`: returns the cumulative elevation loss, in meters
|
|
||||||
* `get_average_hr()`: returns the average heart rate (if available)
|
|
||||||
|
|
||||||
If you're not a fan of the metric system, you also have the following methods
|
|
||||||
at your disposal:
|
|
||||||
|
|
||||||
* `get_distance_imp()`: returns the total track distance in miles
|
|
||||||
* `get_moving_pace_imp()`: returns the average moving pace in milliseconds per
|
|
||||||
hour
|
|
||||||
* `get_moving_speed()`: returns the average moving pace in miles per
|
|
||||||
hour
|
|
||||||
|
|
||||||
The reason why these methods return milliseconds is that you have at your
|
|
||||||
disposal a nice helper method to format a duration in milliseconds into a cool
|
|
||||||
string like `3:07'48"` or `59'32.431`:
|
|
||||||
|
|
||||||
* `get_duration_string(duration, hidems)`, where `duration` is in
|
|
||||||
milliseconds and `hidems` is an optional boolean you can use to request never
|
|
||||||
to display millisecond precision.
|
|
||||||
|
|
||||||
You can also get full elevation and heartrate data with:
|
|
||||||
|
|
||||||
* `get_elevation_data()` and `get_elevation_data_imp()`
|
|
||||||
* `get_heartrate_data()` and `get_heartrate_data_imp()`
|
|
||||||
|
|
||||||
These methods all return an array of points `[distance, value, tooltip]` where
|
|
||||||
the distance is either in kilometers or in miles and the elevation in meters of
|
|
||||||
feet, depending on whether you use the `_imp` variant or not. Heart rate,
|
|
||||||
obviously, doesn't change.
|
|
||||||
|
|
||||||
You can reload remote gpx file every 5 seconds with:
|
|
||||||
```javascript
|
|
||||||
var gpxLayer = new L.GPX(gpxFile);
|
|
||||||
|
|
||||||
setInterval(function() {
|
|
||||||
gpxLayer.reload();
|
|
||||||
},5000);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
About marker icons
|
|
||||||
------------------
|
|
||||||
|
|
||||||
By default `gpx.js` will use `pin-icon-start.png`, `pin-icon-end.png` and
|
|
||||||
`pin-shadow.png` as the marker icons URLs for, respectively, the start marker,
|
|
||||||
the end marker and their drop shadow. Since it might not be convenient that
|
|
||||||
these images have to reside under the same directory as your HTML page, it is
|
|
||||||
possible to override the marker icon URLs and sizes by passing a
|
|
||||||
`marker_options` object to the `GPX` options object.
|
|
||||||
|
|
||||||
The field names are the same as for custom Leaflet icons, as explained in the
|
|
||||||
[Markers with custom icons](http://leafletjs.com/examples/custom-icons.html)
|
|
||||||
page in Leaflet's documentation. The only difference is that instead of
|
|
||||||
`iconUrl` you should specify `startIconUrl` and `endIconUrl` for the start and
|
|
||||||
end markers, respectively.
|
|
||||||
|
|
||||||
Note that you do not need to override all the marker icon options as `gpx.js`
|
|
||||||
will use sensible defaults with sizes matching the provided icon images. Here
|
|
||||||
is how you would override the URL of the provided icons if you decided to place
|
|
||||||
them in an `images/` directory:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var url = '...'; // URL to your GPX file
|
|
||||||
new L.GPX(url, {
|
|
||||||
async: true,
|
|
||||||
marker_options: {
|
|
||||||
startIconUrl: 'images/pin-icon-start.png',
|
|
||||||
endIconUrl: 'images/pin-icon-end.png',
|
|
||||||
shadowUrl: 'images/pin-shadow.png'
|
|
||||||
}
|
|
||||||
}).on('loaded', function(e) {
|
|
||||||
map.fitBounds(e.target.getBounds());
|
|
||||||
}).addTo(map);
|
|
||||||
```
|
|
||||||
|
|
||||||
Caveats
|
|
||||||
-------
|
|
||||||
|
|
||||||
* Distance calculation is relatively accurate, but elevation change
|
|
||||||
calculation is not topographically adjusted, so the total elevation
|
|
||||||
gain/loss/change might appear inaccurate in some situations.
|
|
||||||
* Currently doesn't seem to work in IE8/9. See #9 and #11 for
|
|
||||||
discussion.
|
|
||||||
361
bower_components/leaflet-gpx/gpx.js
vendored
361
bower_components/leaflet-gpx/gpx.js
vendored
|
|
@ -1,361 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2011-2012 Pavel Shramov
|
|
||||||
* Copyright (C) 2013 Maxime Petazzoni <maxime.petazzoni@bulix.org>
|
|
||||||
* All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* - Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Thanks to Pavel Shramov who provided the initial implementation and Leaflet
|
|
||||||
* integration. Original code was at https://github.com/shramov/leaflet-plugins.
|
|
||||||
*
|
|
||||||
* It was then cleaned-up and modified to record and make available more
|
|
||||||
* information about the GPX track while it is being parsed so that the result
|
|
||||||
* can be used to display additional information about the track that is
|
|
||||||
* rendered on the Leaflet map.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _MAX_POINT_INTERVAL_MS = 15000;
|
|
||||||
var _SECOND_IN_MILLIS = 1000;
|
|
||||||
var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS;
|
|
||||||
var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS;
|
|
||||||
|
|
||||||
var _DEFAULT_MARKER_OPTS = {
|
|
||||||
startIconUrl: 'pin-icon-start.png',
|
|
||||||
endIconUrl: 'pin-icon-end.png',
|
|
||||||
shadowUrl: 'pin-shadow.png',
|
|
||||||
iconSize: [33, 50],
|
|
||||||
shadowSize: [50, 50],
|
|
||||||
iconAnchor: [16, 45],
|
|
||||||
shadowAnchor: [16, 47]
|
|
||||||
};
|
|
||||||
var _DEFAULT_POLYLINE_OPTS = {
|
|
||||||
color:'blue'
|
|
||||||
};
|
|
||||||
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 = this._merge_objs(
|
|
||||||
_DEFAULT_POLYLINE_OPTS,
|
|
||||||
options.polyline_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._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._parse(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;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 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_desc: function() { return this._info.desc; },
|
|
||||||
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_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'; });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
reload: function() {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
|
|
||||||
_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) return;
|
|
||||||
_this.addLayer(layers);
|
|
||||||
_this.fire('loaded');
|
|
||||||
}
|
|
||||||
if (input.substr(0,1)==='<') { // direct XML has to start with a <
|
|
||||||
var parser = new DOMParser();
|
|
||||||
setTimeout(function() {
|
|
||||||
cb(parser.parseFromString(input, "text/xml"), options);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._load_xml(input, cb, options, async);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (options.marker_options.startIconUrl) {
|
|
||||||
// add start pin
|
|
||||||
var p = new L.Marker(coords[0], {
|
|
||||||
clickable: false,
|
|
||||||
icon: new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl})
|
|
||||||
});
|
|
||||||
this.fire('addpoint', { point: p });
|
|
||||||
layers.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.marker_options.endIconUrl) {
|
|
||||||
// add end pin
|
|
||||||
p = new L.Marker(coords[coords.length-1], {
|
|
||||||
clickable: 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;
|
|
||||||
},
|
|
||||||
|
|
||||||
_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) {
|
|
||||||
ll.meta.time = new Date(Date.parse(_[0].textContent));
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = el[i].getElementsByTagName('ele');
|
|
||||||
if (_.length > 0) {
|
|
||||||
ll.meta.ele = parseFloat(_[0].textContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
BIN
bower_components/leaflet-gpx/pin-icon-end.png
vendored
BIN
bower_components/leaflet-gpx/pin-icon-end.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 842 B |
BIN
bower_components/leaflet-gpx/pin-icon-start.png
vendored
BIN
bower_components/leaflet-gpx/pin-icon-start.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 945 B |
BIN
bower_components/leaflet-gpx/pin-shadow.png
vendored
BIN
bower_components/leaflet-gpx/pin-shadow.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -74,7 +74,7 @@ div.elevation {
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
max-width: 180px;
|
max-width: 176px;
|
||||||
/* normalize height, for absolute Profile control positioning */
|
/* normalize height, for absolute Profile control positioning */
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ td {
|
||||||
|
|
||||||
.heading, tr > td:first-child, .label {
|
.heading, tr > td:first-child, .label {
|
||||||
/* 1/4 of net info control width (370), so that values start at 50% */
|
/* 1/4 of net info control width (370), so that values start at 50% */
|
||||||
width: 92.5px;
|
width: 95px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.routing-draw-enabled {
|
.routing-draw-enabled {
|
||||||
|
|
@ -128,7 +128,7 @@ td {
|
||||||
|
|
||||||
.leaflet-leftpane .leaflet-control:last-child {
|
.leaflet-leftpane .leaflet-control:last-child {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 441px;
|
top: 461px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@
|
||||||
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet-src.js"></script>
|
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet-src.js"></script>
|
||||||
|
|
||||||
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw-src.js"></script>
|
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw-src.js"></script>
|
||||||
<script src="bower_components/leaflet-gpx/gpx.js"></script>
|
|
||||||
<!--
|
<!--
|
||||||
<script src="lib/spin.min.js"></script>
|
<script src="lib/spin.min.js"></script>
|
||||||
<script src="lib/leaflet.spin.js"></script>
|
<script src="lib/leaflet.spin.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -9,41 +9,37 @@ BR.TrackStats = BR.Control.extend({
|
||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function (polyline) {
|
update: function (polyline, segments) {
|
||||||
var stats = this.calcStats(polyline),
|
var stats = this.calcStats(polyline, segments),
|
||||||
html = '';
|
html = '';
|
||||||
|
|
||||||
html += '<table id="stats">';
|
html += '<table id="stats">';
|
||||||
html += '<tr><td>Length: </td><td>' + L.Util.formatNum(stats.distance/1000,1) + '</td><td>km</td></tr>';
|
html += '<tr><td>Length: </td><td>' + L.Util.formatNum(stats.trackLength/1000,1) + '</td><td>km</td></tr>';
|
||||||
html += '<tr><td>Ascent: </td><td>' + Math.round(stats.elevationGain) + '</td><td>m</td></tr>';
|
html += '<tr><td>Ascent filtered:</td><td>' + stats.filteredAscend + '</td><td>m</td></tr>';
|
||||||
html += '<tr><td>Descent: </td><td>' + Math.round(stats.elevationLoss) + '</td><td>m</td></tr>';
|
html += '<tr><td>Ascent plain:</td><td>' + stats.plainAscend + '</td><td>m</td></tr>';
|
||||||
|
html += '<tr><td>Cost: </td><td>' + stats.cost + '</td><td></td></tr>';
|
||||||
html += '</table>';
|
html += '</table>';
|
||||||
|
|
||||||
this._content.innerHTML = html;
|
this._content.innerHTML = html;
|
||||||
},
|
},
|
||||||
|
|
||||||
calcStats: function(polyline) {
|
calcStats: function(polyline, segments) {
|
||||||
var stats = {
|
var stats = {
|
||||||
distance: 0,
|
trackLength: 0,
|
||||||
elevationGain: 0,
|
filteredAscend: 0,
|
||||||
elevationLoss: 0
|
plainAscend: 0,
|
||||||
|
cost: 0
|
||||||
};
|
};
|
||||||
|
var i, props;
|
||||||
|
|
||||||
var latLngs = polyline ? polyline.getLatLngs() : [];
|
for (i = 0; segments && i < segments.length; i++) {
|
||||||
for (var i = 0, current, next, eleDiff; i < latLngs.length - 1; i++) {
|
props = segments[i].feature.properties;
|
||||||
current = latLngs[i];
|
stats.trackLength += +props['track-length'];
|
||||||
next = latLngs[i + 1];
|
stats.filteredAscend += +props['filtered ascend'];
|
||||||
stats.distance += current.distanceTo(next);
|
stats.plainAscend += +props['plain-ascend'];
|
||||||
|
stats.cost += +props['cost'];
|
||||||
// from Leaflet.gpx plugin (writes to LatLng.meta.ele, LatLng now supports ele)
|
|
||||||
eleDiff = (next.ele || next.meta.ele) - (current.ele || current.meta.ele);
|
|
||||||
if (eleDiff > 0) {
|
|
||||||
stats.elevationGain += eleDiff;
|
|
||||||
} else {
|
|
||||||
stats.elevationLoss += Math.abs(eleDiff);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -168,13 +168,14 @@
|
||||||
} else {
|
} else {
|
||||||
BR.message.hideError();
|
BR.message.hideError();
|
||||||
}
|
}
|
||||||
|
|
||||||
var track = routing.toPolyline(),
|
var track = routing.toPolyline(),
|
||||||
|
segments = routing.getSegments(),
|
||||||
latLngs = routing.getWaypoints(),
|
latLngs = routing.getWaypoints(),
|
||||||
urls = {};
|
urls = {};
|
||||||
|
|
||||||
elevation.update(track);
|
elevation.update(track);
|
||||||
stats.update(track);
|
stats.update(track, segments);
|
||||||
|
|
||||||
if (latLngs.length > 1) {
|
if (latLngs.length > 1) {
|
||||||
urls.gpx = router.getUrl(latLngs, 'gpx');
|
urls.gpx = router.getUrl(latLngs, 'gpx');
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ BR.Elevation = L.Control.Elevation.extend({
|
||||||
update: function(track) {
|
update: function(track) {
|
||||||
this.clear();
|
this.clear();
|
||||||
if (track && track.getLatLngs().length > 0) {
|
if (track && track.getLatLngs().length > 0) {
|
||||||
this.addData(track);
|
this.addData(track.toGeoJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -95,4 +95,14 @@ BR.Routing = L.Routing.extend({
|
||||||
cb(err, data);
|
cb(err, data);
|
||||||
}, this));
|
}, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
,getSegments: function() {
|
||||||
|
var segments = [];
|
||||||
|
|
||||||
|
this._eachSegment(function(m1, m2, line) {
|
||||||
|
segments.push(line);
|
||||||
|
});
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ L.BRouter = L.Class.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
format: 'gpx'
|
format: 'geojson'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
|
|
@ -83,8 +83,7 @@ L.BRouter = L.Class.extend({
|
||||||
|
|
||||||
getRoute: function(latLngs, cb) {
|
getRoute: function(latLngs, cb) {
|
||||||
var url = this.getUrl(latLngs),
|
var url = this.getUrl(latLngs),
|
||||||
xhr = new XMLHttpRequest(),
|
xhr = new XMLHttpRequest();
|
||||||
gpx;
|
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return cb(new Error('Error getting route URL'));
|
return cb(new Error('Error getting route URL'));
|
||||||
|
|
@ -99,23 +98,20 @@ L.BRouter = L.Class.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleRouteResponse: function(xhr, cb) {
|
_handleRouteResponse: function(xhr, cb) {
|
||||||
var gpx = xhr.responseXML;
|
var layer,
|
||||||
|
geojson;
|
||||||
|
|
||||||
|
if (xhr.status === 200
|
||||||
|
&& xhr.responseText
|
||||||
|
// application error when not GeoJSON format (text/plain for errors)
|
||||||
|
&& xhr.getResponseHeader('Content-Type').split(';')[0] === 'application/vnd.geo+json') {
|
||||||
|
|
||||||
if (xhr.status === 200 && gpx) {
|
|
||||||
// L.GPX has no XHR error handling, and expects either URL or text (not document),
|
|
||||||
// so bypass by passing null and call internal _parse_gpx_data directly
|
|
||||||
var gpxLayer = new L.GPX(null, {
|
|
||||||
polyline_options: {
|
|
||||||
opacity: 0.6
|
|
||||||
},
|
|
||||||
marker_options: {
|
|
||||||
startIconUrl: null,
|
|
||||||
endIconUrl: null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var layer = gpxLayer._parse_gpx_data(gpx, gpxLayer.options);
|
|
||||||
// leaflet.spin
|
// leaflet.spin
|
||||||
//gpxLayer.fire('data:loaded');
|
//gpxLayer.fire('data:loaded');
|
||||||
|
|
||||||
|
geojson = JSON.parse(xhr.responseText);
|
||||||
|
layer = L.geoJson(geojson).getLayers()[0];
|
||||||
|
|
||||||
return cb(null, layer);
|
return cb(null, layer);
|
||||||
} else {
|
} else {
|
||||||
cb(this._getError(xhr));
|
cb(this._getError(xhr));
|
||||||
|
|
@ -147,8 +143,7 @@ L.BRouter = L.Class.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleProfileResponse: function(xhr, cb) {
|
_handleProfileResponse: function(xhr, cb) {
|
||||||
var response,
|
var response;
|
||||||
profile;
|
|
||||||
|
|
||||||
if (xhr.status === 200 && xhr.responseText && xhr.responseText.length > 0) {
|
if (xhr.status === 200 && xhr.responseText && xhr.responseText.length > 0) {
|
||||||
response = JSON.parse(xhr.responseText);
|
response = JSON.parse(xhr.responseText);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue