Merge pull request #15 from nrenner/data

Data tab
This commit is contained in:
Norbert Renner 2015-03-08 12:14:38 +01:00
commit 84d81a7827
9 changed files with 412 additions and 54 deletions

View file

@ -54,7 +54,11 @@ Copyright 2012 Jacob Toye; [MIT License](https://github.com/Leaflet/Leaflet.draw
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)
Copyright (c) 2011-2012, Pavel Shramov; [2-clause BSD License](https://github.com/shramov/leaflet-plugins/blob/master/LICENSE) Copyright (c) 2011-2012, Pavel Shramov; [2-clause BSD License](https://github.com/shramov/leaflet-plugins/blob/master/LICENSE)
* [normalize.css](https://github.com/necolas/normalize.css)
Copyright (c) Nicolas Gallagher and Jonathan Neal; [MIT License](https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
* [Async.js](https://github.com/caolan/async) * [Async.js](https://github.com/caolan/async)
Copyright (c) 2010-2014 Caolan McMahon; [MIT License](https://github.com/caolan/async/blob/master/LICENSE) Copyright (c) 2010-2014 Caolan McMahon; [MIT License](https://github.com/caolan/async/blob/master/LICENSE)
* [Bootstrap](http://getbootstrap.com/)
Copyright (c) 2011-2014 Twitter, Inc; [MIT License](https://github.com/twbs/bootstrap/blob/master/LICENSE)
* [jQuery](https://github.com/jquery/jquery)
Copyright 2005, 2014 jQuery Foundation and other contributors; [MIT License](https://github.com/jquery/jquery/blob/master/LICENSE.txt)
* [DataTables](https://github.com/DataTables/DataTables)
Copyright (C) 2008-2014, SpryMedia Ltd.; [MIT License](http://www.datatables.net/license/mit)

View file

@ -7,7 +7,6 @@
"bower_components" "bower_components"
], ],
"dependencies": { "dependencies": {
"normalize-css": "*",
"leaflet-search": "*", "leaflet-search": "*",
"leaflet-plugins": "*", "leaflet-plugins": "*",
"leaflet-routing": "nrenner/leaflet-routing#styles", "leaflet-routing": "nrenner/leaflet-routing#styles",

View file

@ -15,11 +15,6 @@ html, body, #map {
div.elevation { div.elevation {
margin-bottom: -2px !important; margin-bottom: -2px !important;
} }
/*
.info, div.elevation {
display:table-row;
}
*/
.hidden { .hidden {
display: none; display: none;
@ -44,8 +39,6 @@ div.elevation {
} }
.title { .title {
padding-top: 4px; padding-top: 4px;
/* normalize height, for absolute Profile control positioning */
height: 17px;
} }
.title-name { .title-name {
font-size: larger; font-size: larger;
@ -75,8 +68,6 @@ div.elevation {
select { select {
max-width: 176px; max-width: 176px;
/* normalize height, for absolute Profile control positioning */
height: 24px;
} }
table { table {
@ -121,32 +112,56 @@ td {
.leaflet-leftpane .leaflet-control { .leaflet-leftpane .leaflet-control {
margin: 3px 5px; margin: 3px 5px;
width: 370px; width: 386px;
} }
/* Profile Control */ /* Profile Control */
.leaflet-leftpane .leaflet-control:last-child { /* flexbox layout: maximize textarea and data table (nested container/box path) */
position: absolute; .flex_container,
top: 461px; .leaflet-leftpane,
bottom: 0px; .leaflet-leftpane .leaflet-control:last-child,
#tabs_div,
.tab-content,
.tab-pane.active,
#profile_upload,
#datatable_wrapper,
.dataTables_scroll,
.dataTables_scrollBody {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
} }
#profile_upload input { .flex_box_none,
position: absolute; .leaflet-leftpane .leaflet-control,
top: 6px; #tab,
right: 8px; .dataTables_scrollHead {
-webkit-flex: none;
flex: none;
} }
/* http://snook.ca/archives/html_and_css/absolute-position-textarea */ .flex_box,
#textarea_container { .leaflet-leftpane .leaflet-control:last-child,
position: absolute; #tabs_div,
top: 32px; .tab-content,
bottom: 6px; .tab-pane.active,
left: 8px; #profile_upload,
right: 8px; textarea,
#datatable_wrapper,
.dataTables_scroll,
.dataTables_scrollBody,
#datatable {
-webkit-flex: auto;
flex: auto;
} }
#profile_buttons {
padding-top: 4px;
}
textarea { textarea {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -162,6 +177,11 @@ textarea:focus {
background-color: rgba(255,255,255,255); background-color: rgba(255,255,255,255);
} }
/* track messages (data tab) */
#tab_data {
font-size: x-small;
}
/* dashed line animation, derived from Chris Coyier and others /* dashed line animation, derived from Chris Coyier and others
http://css-tricks.com/svg-line-animation-works/ http://css-tricks.com/svg-line-animation-works/
*/ */
@ -185,3 +205,44 @@ textarea:focus {
stroke-dashoffset: 0; stroke-dashoffset: 0;
} }
} }
/*
* Bootstrap
*/
/* override Bootstrap label for Leaflet layer switcher */
.leaflet-control-layers label {
max-width: none;
margin-bottom: 0px;
font-weight: normal;
/* normalize label height
| Firefox | Chrome |
Leaflet only | 21 | 20 |
Bootstrap | 23 | 22 |*/
height: 21px;
}
/* smaller tab height */
.nav > li > a {
padding: 2px 15px;
}
/*
* DataTables
*/
table.dataTable.mini thead th,
table.dataTable.mini thead td {
padding: 3px 13px 3px 2px;
}
table.dataTable.mini tbody th,
table.dataTable.mini tbody td {
padding: 2px 2px;
white-space: nowrap;
}
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting {
background-position: center right -3px;
}

View file

@ -2,6 +2,8 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BRouter web client</title> <title>BRouter web client</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" /> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
@ -12,7 +14,9 @@
<link rel="stylesheet" href="bower_components/Leaflet.Elevation/dist/Leaflet.Elevation-0.0.2.css" /> <link rel="stylesheet" href="bower_components/Leaflet.Elevation/dist/Leaflet.Elevation-0.0.2.css" />
<link rel="stylesheet" href="bower_components/leaflet-search/dist/leaflet-search.min.css" /> <link rel="stylesheet" href="bower_components/leaflet-search/dist/leaflet-search.min.css" />
<link rel="stylesheet" href="css/leaflet-routing.css" /> <link rel="stylesheet" href="css/leaflet-routing.css" />
<link rel="stylesheet" href="bower_components/normalize-css/normalize.css" /> <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="http://cdn.datatables.net/1.10.2/css/jquery.dataTables.css">
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
</head> </head>
<body> <body>
@ -47,16 +51,29 @@
</td></tr> </td></tr>
</table> </table>
</div> </div>
<div id="profile_div" class="hidden"> <div id="tabs_div" class="hidden">
<ul id="tab" class="nav nav-tabs" role="tablist">
<li class="active"><a href="#tab_profile" role="tab">Profile</a></li>
<li><a href="#tab_data" role="tab">Data</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_profile">
<form id="profile_upload" name="profile_upload"> <form id="profile_upload" name="profile_upload">
<button id="clear">Clear</button>&nbsp;
<a href="http://brouter.de/brouter/costfunctions.html" target="_blank">Help</a>
<input type="submit" value="Upload" />
<div id="textarea_container">
<textarea type="text" name="profile" spellcheck="false" wrap="off" maxlength="100000" placeholder="... paste your custom routing profile here ..."></textarea> <textarea type="text" name="profile" spellcheck="false" wrap="off" maxlength="100000" placeholder="... paste your custom routing profile here ..."></textarea>
<div id="profile_buttons">
<button id="upload" type="button" class="btn btn-default btn-xs" data-uploading-text="Uploading..."><span class="glyphicon glyphicon-upload"></span> Upload</button>
<button id="clear" type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-ban-circle"></span> Clear</button>
<a href="http://brouter.de/brouter/costfunctions.html" target="_blank" class="btn-xs">Help</a>
</div> </div>
</form> </form>
</div> </div>
<div class="tab-pane" id="tab_data">
<table id="datatable" class="table mini cell-border hover stripe"></table>
</div>
</div>
</div>
<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>
@ -80,6 +97,11 @@
<script src="bower_components/Leaflet.Elevation/src/L.Control.Elevation.js"></script> <script src="bower_components/Leaflet.Elevation/src/L.Control.Elevation.js"></script>
<script src="bower_components/async/lib/async.js"></script> <script src="bower_components/async/lib/async.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="http://cdn.datatables.net/1.10.2/js/jquery.dataTables.js"></script>
<script> <script>
// global package prefix for BRouter web application // global package prefix for BRouter web application
BR = {}; BR = {};
@ -95,7 +117,9 @@
<script src="js/plugin/Permalink.Routing.js"></script> <script src="js/plugin/Permalink.Routing.js"></script>
<script src="js/control/TrackStats.js"></script> <script src="js/control/TrackStats.js"></script>
<script src="js/control/Download.js"></script> <script src="js/control/Download.js"></script>
<script src="js/control/Tabs.js"></script>
<script src="js/control/Profile.js"></script> <script src="js/control/Profile.js"></script>
<script src="js/control/TrackMessages.js"></script>
<script src="js/control/RoutingOptions.js"></script> <script src="js/control/RoutingOptions.js"></script>
<script src="js/control/Message.js"></script> <script src="js/control/Message.js"></script>

View file

@ -1,31 +1,33 @@
BR.Profile = BR.Control.extend({ BR.Profile = L.Class.extend({
options: { initialize: function () {
heading: 'Profile', L.DomUtil.get('upload').onclick = L.bind(this._upload, this);
divId: 'profile_div'
},
onAdd: function (map) {
L.DomUtil.get('profile_upload').onsubmit = L.bind(this._submit, this);
L.DomUtil.get('clear').onclick = L.bind(this.clear, this); L.DomUtil.get('clear').onclick = L.bind(this.clear, this);
return BR.Control.prototype.onAdd.call(this, map);
}, },
clear: function(evt) { clear: function(evt) {
var button = evt.target || evt.srcElement;
evt.preventDefault(); evt.preventDefault();
document.profile_upload.profile.value = null; document.profile_upload.profile.value = null;
this.fire('clear'); this.fire('clear');
button.blur();
}, },
_submit: function(evt) { _upload: function(evt) {
var form = evt.target || evt.srcElement, var button = evt.target || evt.srcElement,
profile = document.profile_upload.profile.value; profile = document.profile_upload.profile.value;
$(button).button('uploading');
evt.preventDefault(); evt.preventDefault();
this.fire('update', { profileText: profile }); this.fire('update', {
profileText: profile,
callback: function () {
$(button).button('reset');
$(button).blur();
}
});
} }
}); });

42
js/control/Tabs.js Normal file
View file

@ -0,0 +1,42 @@
BR.Tabs = BR.Control.extend({
options: {
divId: 'tabs_div',
// tab a.hash > instance
tabs: {}
},
initialize: function (options) {
L.setOptions(this, options);
},
onAdd: function (map) {
var tabs = this.options.tabs;
for (var key in tabs) {
if (tabs[key].onAdd) {
tabs[key].onAdd(map);
}
}
$('#tab a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
// e.target = activated tab
// e.relatedTarget = previous tab
$('#tab a').on('shown.bs.tab', L.bind(function (e) {
var tab = this.options.tabs[e.target.hash],
prevTab = this.options.tabs[e.relatedTarget.hash];
if (tab && tab.show) {
tab.show();
}
if (prevTab && prevTab.hide) {
prevTab.hide();
}
}, this));
return BR.Control.prototype.onAdd.call(this, map);
}
});

201
js/control/TrackMessages.js Normal file
View file

@ -0,0 +1,201 @@
BR.TrackMessages = L.Class.extend({
options: {
edgeStyle: {
color: 'yellow',
weight: 8
}
},
// true when tab is shown, false when hidden
active: false,
columnOptions: {
'Longitude': { visible: false },
'Latitude': { visible: false },
'Elevation': { title: 'elev.', className: 'dt-body-right' },
'Distance': { title: 'dist.', className: 'dt-body-right' },
'CostPerKm': { title: '$/km', className: 'dt-body-right' },
'ElevCost': { title: 'elev$', className: 'dt-body-right' },
'TurnCost': { title: 'turn$', className: 'dt-body-right' },
'NodeCost': { title: 'node$', className: 'dt-body-right' },
'InitialCost': { title: 'initial$', className: 'dt-body-right' },
'OsmTags': { title: 'tags'}
},
initialize: function (options) {
L.setOptions(this, options);
var table = document.getElementById('datatable');
this.tableClassName = table.className;
this.tableParent = table.parentElement;
},
onAdd: function (map) {
this._map = map;
},
update: function (polyline, segments) {
var i, messages, columns, headings,
data = [];
if (!this.active) {
return;
}
for (i = 0; segments && i < segments.length; i++) {
messages = segments[i].feature.properties.messages;
if (messages) {
data = data.concat(messages.slice(1));
}
}
this._destroyTable();
if (data.length === 0) {
return;
}
headings = messages[0];
columns = this._getColumns(headings, data);
console.time('datatable');
this._table = $('#datatable').DataTable({
destroy: true,
data: data,
columns: columns,
paging: false,
searching: false,
info: false,
scrollY: 330,
scrollX: 370,
scrollCollapse: true,
order: []
});
// highlight track segment (graph edge) on row hover
this._setEdges(polyline, segments);
$('#datatable tbody tr').hover(L.bind(this._handleHover, this), L.bind(this._handleHoverOut, this));
console.timeEnd('datatable');
},
show: function() {
this.active = true;
this.options.requestUpdate(this);
},
hide: function() {
this.active = false;
},
_destroyTable: function() {
var ele;
if ($.fn.DataTable.isDataTable('#datatable') ) {
// destroy option too slow on update, really remove elements with destroy method
$('#datatable').DataTable().destroy(true);
// recreate original table element, destroy removes all
ele = document.createElement('table');
ele.id = 'datatable';
ele.className = this.tableClassName;
this.tableParent.appendChild(ele);
}
return ele || document.getElementById('datatable');
},
_getColumns: function(headings, data) {
var columns = [],
defaultOptions,
options,
emptyColumns = this._getEmptyColumns(data);
for (k = 0; k < headings.length; k++) {
defaultOptions = {
title: headings[k],
visible: !emptyColumns[k]
};
options = L.extend(defaultOptions, this.columnOptions[headings[k]]);
columns.push(options);
}
return columns;
},
_getEmptyColumns: function(data) {
var empty = new Array(data[0].length),
i;
for (i = 0; i < empty.length; i++) {
empty[i] = true;
}
data.forEach(function(row) {
row.forEach(function(val, i) {
empty[i] = empty[i] && !val;
});
});
return empty;
},
_getMessageLatLng: function(message) {
var lon = message[0] / 1000000,
lat = message[1] / 1000000;
return L.latLng(lat, lon);
},
_setEdges: function(polyline, segments) {
var messages, segLatLngs, length, si, mi, latLng, i, segIndex,
baseIndex = 0;
// track latLngs index for end node of edge
this._edges = [];
this._track = polyline;
for (si = 0; si < segments.length; si++) {
messages = segments[si].feature.properties.messages;
segLatLngs = segments[si].getLatLngs();
length = segLatLngs.length;
segIndex = 0;
for (mi = 1; mi < messages.length; mi++) {
latLng = this._getMessageLatLng(messages[mi]);
for (i = segIndex; i < length; i++) {
if (latLng.equals(segLatLngs[i])) {
break;
}
}
if (i === length) {
i = length - 1;
if (mi !== messages.length - 1) debugger;
}
segIndex = i + 1;
this._edges.push(baseIndex + i);
}
baseIndex += length;
}
},
_handleHover: function(evt) {
var tr = $(evt.currentTarget),
row = this._table.row(tr),
trackLatLngs = this._track.getLatLngs(),
startIndex = row.index() > 0 ? this._edges[row.index() - 1] : 0,
endIndex = this._edges[row.index()],
edgeLatLngs = trackLatLngs.slice(startIndex, endIndex + 1);
this._selectedEdge = L.polyline(edgeLatLngs, this.options.edgeStyle).addTo(this._map);
},
_handleHoverOut: function(evt) {
this._map.removeLayer(this._selectedEdge);
this._selectedEdge = null;
}
});
BR.TrackMessages.include(L.Mixin.Events);

View file

@ -76,6 +76,10 @@
}).addTo(map); }).addTo(map);
map.addControl(new BR.Search()); map.addControl(new BR.Search());
// expose map instance for console debugging
BR.debug = BR.debug || {};
BR.debug.map = map;
} }
function initApp() { function initApp() {
@ -88,6 +92,7 @@
elevation, elevation,
download, download,
profile, profile,
trackMessages,
leftPaneId = 'leftpane', leftPaneId = 'leftpane',
saveWarningShown = false; saveWarningShown = false;
@ -106,6 +111,13 @@
routing.rerouteAllSegments(onUpdate); routing.rerouteAllSegments(onUpdate);
} }
function requestUpdate(updatable) {
var track = routing.toPolyline(),
segments = routing.getSegments();
updatable.update(track, segments);
}
routingOptions = new BR.RoutingOptions(); routingOptions = new BR.RoutingOptions();
routingOptions.on('update', updateRoute); routingOptions.on('update', updateRoute);
@ -134,12 +146,19 @@
router.setOptions(routingOptions.getOptions()); router.setOptions(routingOptions.getOptions());
} }
} }
if (evt.callback) {
evt.callback();
}
}); });
}); });
profile.on('clear', function(evt) { profile.on('clear', function(evt) {
BR.message.hideError(); BR.message.hideError();
routingOptions.setCustomProfile(null); routingOptions.setCustomProfile(null);
}); });
trackMessages = new BR.TrackMessages({
requestUpdate: requestUpdate
});
routing = new BR.Routing({ routing = new BR.Routing({
routing: { routing: {
@ -177,6 +196,7 @@
elevation.update(track); elevation.update(track);
stats.update(track, segments); stats.update(track, segments);
trackMessages.update(track, segments);
if (latLngs.length > 1) { if (latLngs.length > 1) {
urls.gpx = router.getUrl(latLngs, 'gpx'); urls.gpx = router.getUrl(latLngs, 'gpx');
@ -196,7 +216,12 @@
stats.addTo(map); stats.addTo(map);
download.addTo(map); download.addTo(map);
elevation.addTo(map); elevation.addTo(map);
profile.addTo(map); map.addControl(new BR.Tabs({
tabs: {
'#tab_profile': profile,
'#tab_data': trackMessages
}
}));
nogos.addTo(map); nogos.addTo(map);
routing.addTo(map); routing.addTo(map);

View file

@ -33,7 +33,7 @@ BR.Routing = L.Routing.extend({
this._edit._mouseMarker.setIcon(L.divIcon({ this._edit._mouseMarker.setIcon(L.divIcon({
className: 'line-mouse-marker' className: 'line-mouse-marker'
,iconAnchor: [8, 8] // size/2 + border/2 ,iconAnchor: [8, 8] // size/2 + border/2
,iconSize: [8, 8] ,iconSize: [16, 16]
})); }));
this._draw.on('enabled', function() { this._draw.on('enabled', function() {