From 868fcb11005c091040328e0059c25a3f244bc4aa Mon Sep 17 00:00:00 2001 From: Brenschede Date: Mon, 25 Sep 2017 20:17:06 +0200 Subject: [PATCH 001/211] car-eco/fast + display energy/time --- config.js | 6 ++++-- js/control/TrackStats.js | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/config.js b/config.js index b0eb1ad..a33479d 100644 --- a/config.js +++ b/config.js @@ -19,7 +19,8 @@ BR.conf.profiles = [ 'trekking', 'fastbike', - 'car-test', + 'car-eco', + 'car-fast', 'safety', 'shortest', 'trekking-ignore-cr', @@ -48,7 +49,8 @@ 'fastbike', 'shortest', 'moped', - 'car-test' + 'car-eco', + 'car-fast' ]; BR.conf.host = 'http://localhost:17777'; diff --git a/js/control/TrackStats.js b/js/control/TrackStats.js index 34f5c79..784925d 100644 --- a/js/control/TrackStats.js +++ b/js/control/TrackStats.js @@ -14,14 +14,27 @@ BR.TrackStats = BR.Control.extend({ length1 = L.Util.formatNum(stats.trackLength/1000,1), length3 = L.Util.formatNum(stats.trackLength/1000,3), meanCostFactor = stats.trackLength ? L.Util.formatNum(stats.cost / stats.trackLength, 2) : '', + formattedTime = L.Util.formatNum(stats.totalTime / 60., 1), + formattedEnergy = L.Util.formatNum(stats.totalEnergy / 3600000., 2), + meanEnergy = stats.trackLength ? L.Util.formatNum(stats.totalEnergy / 36. / stats.trackLength, 2) : '', html = ''; html += ''; html += ''; - html += ''; - html += ''; - html += ''; - html += ''; + if ( stats.totalTime ) + { + html += ''; + html += ''; + html += ''; + html += ''; + } + else + { + html += ''; + html += ''; + html += ''; + html += ''; + } html += '
Length: ' + length1 + 'km
Ascent filtered:' + stats.filteredAscend + 'm
Ascent plain:' + stats.plainAscend + 'm
Cost: ' + stats.cost + '
Mean cost:' + meanCostFactor + '
Time:' + formattedTime + 'min
Energy:' + formattedEnergy + 'kWh (mean: ' + meanEnergy + ')
Ascent:' + stats.filteredAscend + 'm (plain: ' + stats.plainAscend + ')
Cost: ' + stats.cost + ' (mean: ' + meanCostFactor + ')
Ascent filtered:' + stats.filteredAscend + 'm
Ascent plain:' + stats.plainAscend + 'm
Cost: ' + stats.cost + '
Mean cost:' + meanCostFactor + '
'; this._content.innerHTML = html; @@ -32,6 +45,8 @@ BR.TrackStats = BR.Control.extend({ trackLength: 0, filteredAscend: 0, plainAscend: 0, + totalTime: 0, + totalEnergy: 0, cost: 0 }; var i, props; @@ -41,6 +56,8 @@ BR.TrackStats = BR.Control.extend({ stats.trackLength += +props['track-length']; stats.filteredAscend += +props['filtered ascend']; stats.plainAscend += +props['plain-ascend']; + stats.totalTime += +props['total-time']; + stats.totalEnergy += +props['total-energy']; stats.cost += +props['cost']; } From 69e53233bafcd4e6eae007b2a9d3d537da920bae Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Fri, 12 Jan 2018 15:38:47 +0100 Subject: [PATCH 002/211] Replace Leaflet.draw with Leaflet.Editable, closes #100 --- README.md | 4 +- bower.json | 13 +- css/style.css | 37 ++++ js/Browser.js | 9 +- js/index.js | 2 + js/plugin/NogoAreas.js | 419 ++++++++++++++++++++++++++++++++++++----- js/plugin/Routing.js | 4 + 7 files changed, 429 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index a7cfe8d..2ad33db 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,8 @@ Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten. All rights reserve Copyright (c) 2013 Felix Bache; [MIT License](https://github.com/MrMufflon/Leaflet.Elevation/blob/master/LICENSE) * [D3.js](https://github.com/mbostock/d3) 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) -Copyright 2012 Jacob Toye; [MIT License](https://github.com/Leaflet/Leaflet.draw/blob/master/MIT-LICENCE.txt) +* [Leaflet.Editable](https://github.com/Leaflet/Leaflet.Editable) +Yohan Boniface; WTFPL licence * [Leaflet Control Geocoder](https://github.com/perliedman/leaflet-control-geocoder) Copyright (c) 2012 [sa3m](https://github.com/sa3m), Copyright (c) 2013 Per Liedman; [2-clause BSD License](https://github.com/perliedman/leaflet-control-geocoder/blob/master/LICENSE) * [leaflet-plugins](https://github.com/shramov/leaflet-plugins) diff --git a/bower.json b/bower.json index 466c829..faab27b 100644 --- a/bower.json +++ b/bower.json @@ -15,7 +15,6 @@ "leaflet-routing": "nrenner/leaflet-routing#leaflet-1.0", "async": "~0.9.2", "d3": "~3.5.5", - "leaflet.draw": "~0.4.9", "bootstrap": "4.0.0-alpha.5", "DataTables": "~1.10.13", "leaflet.elevation": "MrMufflon/Leaflet.Elevation#master", @@ -29,7 +28,8 @@ "font-awesome": "^4.7.0", "bootstrap-select": "hugdx/bootstrap-select#patch-1", "leaflet-sidebar": "^0.1.9", - "autosize": "^3.0.20" + "autosize": "^3.0.20", + "leaflet.editable": "^1.1.0" }, "overrides": { "leaflet": { @@ -53,15 +53,6 @@ "src/L.Routing.Edit.js" ] }, - "leaflet.draw": { - "main": [ - "dist/leaflet.draw-src.js", - "dist/leaflet.draw.css", - "dist/images/*.png", - "dist/images/*.svg" - ], - "dependencies": null - }, "bootstrap-select": { "main": [ "js/bootstrap-select.js", diff --git a/css/style.css b/css/style.css index 7c6b033..b4c32bb 100644 --- a/css/style.css +++ b/css/style.css @@ -226,3 +226,40 @@ table.dataTable.display tbody tr.odd:hover, table.dataTable.display tbody tr.even:hover { background-color: rgba(255,255,0,0.2); } + +/* + * No-go areas + */ + +.nogo-delete-marker { + text-align: center; + line-height: 16px; + font-size: 11px; + border-radius: 8px; +} +.leaflet-touch .nogo-delete-marker { + border-radius: 12px; + line-height: 24px; +} + +/* tooltip */ + +.editing-tooltip, +.editing-tooltip-create { + color: #fff; + background-color: rgba(0,0,0,0.7); + /* for direction arrows that inherit */ + border-color: rgba(0,0,0,0.7); + /* no border but still set a color for direction arrows */ + border-width: 0px; +} +.editing-tooltip-create { + /* no (invisible) direction arrow for cursor tooltip */ + border-color: transparent; +} +.leaflet-tooltip-bottom:before { + border-bottom-color: inherit; +} +.leaflet-tooltip-right:before { + border-right-color: inherit; +} diff --git a/js/Browser.js b/js/Browser.js index 0c71316..1d7534d 100644 --- a/js/Browser.js +++ b/js/Browser.js @@ -13,12 +13,13 @@ return result; }()), - touchScreenDetectable = touchScreen !== null; - - + touchScreenDetectable = touchScreen !== null, + touch = touchScreenDetectable ? touchScreen : L.Browser.touch; + BR.Browser = { touchScreen: touchScreen, - touchScreenDetectable: touchScreenDetectable + touchScreenDetectable: touchScreenDetectable, + touch: touch }; }()); \ No newline at end of file diff --git a/js/index.js b/js/index.js index 1c66415..1f37894 100644 --- a/js/index.js +++ b/js/index.js @@ -235,6 +235,8 @@ map.addControl(sidebar); nogos.addTo(map); + nogos.preventRoutePointOnCreate(routing); + map.addControl(new BR.OpacitySlider({ callback: L.bind(routing.setOpacity, routing) })); diff --git a/js/plugin/NogoAreas.js b/js/plugin/NogoAreas.js index d7ff4a3..e3ae993 100644 --- a/js/plugin/NogoAreas.js +++ b/js/plugin/NogoAreas.js @@ -1,55 +1,115 @@ -L.drawLocal.draw.toolbar.buttons.circle = 'Draw no-go area (circle)'; -L.drawLocal.edit.toolbar.buttons.edit = 'Edit no-go areas'; -L.drawLocal.edit.toolbar.buttons.remove = 'Delete no-go areas'; - -BR.NogoAreas = L.Control.Draw.extend({ - initialize: function () { - this.drawnItems = new L.FeatureGroup(); +BR.NogoAreas = L.Control.extend({ + statics: { + MSG_BUTTON: 'Draw no-go area (circle)', + MSG_BUTTON_CANCEL: 'Cancel drawing no-go area', + MSG_CREATE: 'Click and drag to draw circle', + MSG_DISABLED: 'Click to edit', + MSG_ENABLED: '□ = move / resize, = delete,
click circle to quit editing', + STATE_CREATE: 'no-go-create', + STATE_CANCEL: 'cancel-no-go-create' + }, - L.Control.Draw.prototype.initialize.call(this, { - draw: { - position: 'topleft', - polyline: false, - polygon: false, - circle: true, - rectangle: false, - marker: false - }, - edit: { - featureGroup: this.drawnItems, - //edit: false, - edit: { - selectedPathOptions: { - //opacity: 0.8 - } - }, - remove: true - } - }); + style: { + color: '#f06eaa', + weight: 4, + opacity: 0.5, + fillColor: null, //same as color by default + fillOpacity: 0.2, + dashArray: null + }, + + editStyle: { + color: '#fe57a1', + opacity: 0.6, + dashArray: '10, 10', + fillOpacity: 0.1 + }, + + initialize: function () { + this._wasRouteDrawing = false; }, onAdd: function (map) { - map.addLayer(this.drawnItems); + var self = this; + + this.drawnItems = new L.FeatureGroup().addTo(map); + this.drawnItems.on('click', function(e) { + L.DomEvent.stop(e); + e.layer.toggleEdit(); + }); - map.on('draw:created', function (e) { - var layer = e.layer; - this.drawnItems.addLayer(layer); + var editTools = this.editTools = map.editTools = new L.Editable(map, { + circleEditorClass: BR.DeletableCircleEditor, + // FeatureGroup instead of LayerGroup to propagate events to members + editLayer: new L.FeatureGroup().addTo(map), + featuresLayer: this.drawnItems + }); + + var button = L.easyButton({ + states: [{ + stateName: BR.NogoAreas.STATE_CREATE, + icon: 'fa-ban', + title: BR.NogoAreas.MSG_BUTTON, + onClick: function (control) { + // initial radius of 0 to detect click, see DeletableCircleEditor.onDrawingMouseUp + var opts = L.extend({radius: 0}, self.style); + editTools.startCircle(null, opts); + + control.state('cancel-no-go-create'); + } + }, { + stateName: BR.NogoAreas.STATE_CANCEL, + icon: 'fa-ban active', + title: BR.NogoAreas.MSG_BUTTON_CANCEL, + onClick: function (control) { + editTools.stopDrawing(); + control.state('no-go-create'); + } + }] + }).addTo(map); + + this.editTools.on('editable:drawing:end', function (e) { + button.state(BR.NogoAreas.STATE_CREATE); + + setTimeout(L.bind(function () { + // turn editing off after create; async to still fire 'editable:vertex:dragend' + e.layer.disableEdit(); + }, this), 0); + }, this); + + this.editTools.on('editable:vertex:dragend editable:deleted', function (e) { this._fireUpdate(); }, this); - map.on('draw:editstart', function (e) { - this.drawnItems.eachLayer(function (layer) { - layer.on('edit', function(e) { - this._fireUpdate(); - }, this); - }, this); + this.editTools.on('editable:enable', function (e) { + e.layer.setStyle(this.editStyle); + }, this); + this.editTools.on('editable:disable', function (e) { + e.layer.setStyle(this.style); }, this); - map.on('draw:deleted', function (e) { - this._fireUpdate(); + this.tooltip = new BR.EditingTooltip(map, editTools, button); + this.tooltip.enable(); + + // dummy, no own representation, delegating to EasyButton + return L.DomUtil.create('div'); + }, + + // prevent route waypoint added after circle create (map click after up) + preventRoutePointOnCreate: function(routing) { + this.editTools.on('editable:drawing:start', function (e) { + this._wasRouteDrawing = routing.isDrawing(); + routing.draw(false); }, this); - return L.Control.Draw.prototype.onAdd.call(this, map); + // after create + this.editTools.on('editable:drawing:end', function (e) { + if (this._wasRouteDrawing) { + setTimeout(function () { + routing.draw(true); + }, 0); + } + }, this); }, getOptions: function() { @@ -60,17 +120,292 @@ BR.NogoAreas = L.Control.Draw.extend({ setOptions: function(options) { var nogos = options.nogos; - this.drawnItems.clearLayers(); + this._clear(); if (nogos) { for (var i = 0; i < nogos.length; i++) { + nogos[i].setStyle(this.style); this.drawnItems.addLayer(nogos[i]); } } }, + _clear: function () { + this.drawnItems.clearLayers(); + }, + + clear: function () { + this._clear(); + this._fireUpdate(); + }, + _fireUpdate: function () { this.fire('update', {options: this.getOptions()}); + }, + + getFeatureGroup: function() { + return this.drawnItems; + }, + + getEditGroup: function() { + return this.editTools.editLayer; } }); -BR.NogoAreas.include(L.Mixin.Events); \ No newline at end of file +BR.NogoAreas.include(L.Mixin.Events); + + +L.Editable.prototype.createVertexIcon = function (options) { + return BR.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options); +}; + + +BR.EditingTooltip = L.Handler.extend({ + options: { + closeTimeout: 2000 + }, + + initialize: function (map, editTools, button) { + this.map = map; + this.editTools = editTools; + this.button = button; + }, + + addHooks: function () { + // hack: listen to EasyButton click (instead of editable:drawing:start), + // to get mouse position from event for initial tooltip location + L.DomEvent.addListener(this.button.button, 'click', this._addCreate, this); + + this.editTools.featuresLayer.on('layeradd', this._bind, this); + + this.editTools.on('editable:drawing:end', this._postCreate, this); + this.editTools.on('editable:enable', this._enable, this); + this.editTools.on('editable:disable', this._disable, this); + }, + + removeHooks: function () { + L.DomEvent.removeListener(this.button.button, 'click', this._addCreate, this); + + this.editTools.featuresLayer.off('layeradd', this._bind, this); + + this.editTools.off('editable:drawing:end', this._postCreate, this); + this.editTools.off('editable:enable', this._enable, this); + this.editTools.off('editable:disable', this._disable, this); + }, + + _bind: function (e) { + // Position tooltip at bottom of circle, less distracting than + // sticky with cursor or at center. + + var layer = e.layer; + layer.bindTooltip(BR.NogoAreas.MSG_DISABLED, { + direction: 'bottom', + className: 'editing-tooltip' + }); + + // Override to set position to south instead of center (circle latlng); + // works better with zooming than updating offset to match radius + layer.openTooltip = function (layer, latlng) { + if (!latlng && layer instanceof L.Layer) { + latlng = L.latLng(layer.getBounds().getSouth(), layer.getLatLng().lng); + } + L.Layer.prototype.openTooltip.call(this, layer, latlng); + }; + }, + + _addCreate: function (e) { + // button cancel + if (!this.editTools.drawing()) return; + + var initialLatLng = this.map.mouseEventToLatLng(e); + var tooltip = L.tooltip({ + // no effect with map tooltip + sticky: true, + // offset wrong with 'auto' when switching direction + direction: 'right', + offset: L.point(5, 28), + className: 'editing-tooltip-create' + }); + + // self-reference hack for _moveTooltip, as tooltip is not bound to layer + tooltip._tooltip = tooltip; + + // simulate sticky feature (follow mouse) for map tooltip without layer + var onOffMove = function (e) { + var onOff = (e.type === 'tooltipclose') ? 'off' : 'on'; + this._map[onOff]('mousemove', this._moveTooltip, this); + } + this.map.on('tooltipopen', onOffMove, tooltip); + this.map.on('tooltipclose', onOffMove, tooltip); + + var onTooltipRemove = function (e) { + this.map.off('tooltipopen', onOffMove, e.tooltip); + this.map.off('tooltipclose', onOffMove, e.tooltip); + this.map.off('tooltipclose', onTooltipRemove, this); + e.tooltip._tooltip = null; + } + this.map.on('tooltipclose', onTooltipRemove, this); + + tooltip.setTooltipContent(BR.NogoAreas.MSG_CREATE); + this.map.openTooltip(tooltip, initialLatLng); + + var closeTooltip = function () { + this.map.closeTooltip(tooltip); + }; + this.editTools.once('editable:editing editable:drawing:cancel', closeTooltip, this); + + if (BR.Browser.touch) { + // can't move with cursor on touch devices, so show at start pos for a few seconds + setTimeout(L.bind(closeTooltip, this), this.options.closeTimeout); + } + }, + + _postCreate: function () { + // editing is disabled by another handler, tooltip won't stay open before + this.editTools.once('editable:disable', function (e) { + + // show for a few seconds, as mouse often not hovering circle after create + e.layer.openTooltip(e.layer); + setTimeout(function () { + e.layer.closeTooltip(); + }, this.options.closeTimeout); + }, this); + }, + + _enable: function (e) { + e.layer.setTooltipContent(BR.NogoAreas.MSG_ENABLED); + + this.editTools.once('editable:editing', function(e) { + e.layer.closeTooltip(); + }, this); + }, + + _disable: function (e) { + e.layer.setTooltipContent(BR.NogoAreas.MSG_DISABLED); + + setTimeout(function () { + e.layer.closeTooltip(); + }, this.options.closeTimeout); + } +}); + + +BR.DeletableCircleEditor = L.Editable.CircleEditor.extend({ + + _computeDeleteLatLng: function () { + // While circle is not added to the map, _radius is not set. + var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4), + point = this.map.project(this.feature._latlng); + return this.map.unproject([point.x - delta, point.y - delta]); + }, + + _updateDeleteLatLng: function () { + this._deleteLatLng.update(this._computeDeleteLatLng()); + this._deleteLatLng.__vertex.update(); + }, + + _addDeleteMarker: function() { + if (!this.enabled()) return; + this._deleteLatLng = this._computeDeleteLatLng(); + return new BR.DeleteMarker(this._deleteLatLng, this); + }, + + _delete: function() { + this.disable(); + this.tools.featuresLayer.removeLayer(this.feature); + }, + + delete: function() { + this._delete(); + this.fireAndForward('editable:deleted'); + }, + + initialize: function (map, feature, options) { + L.Editable.CircleEditor.prototype.initialize.call(this, map, feature, options); + this._deleteLatLng = this._computeDeleteLatLng(); + + // FeatureGroup instead of LayerGroup to propagate events to members + this.editLayer = new L.FeatureGroup(); + }, + + addHooks: function () { + L.Editable.CircleEditor.prototype.addHooks.call(this); + if (this.feature) { + this._addDeleteMarker(); + } + return this; + }, + + reset: function () { + L.Editable.CircleEditor.prototype.reset.call(this); + this._addDeleteMarker(); + }, + + onDrawingMouseDown: function (e) { + this._deleteLatLng.update(e.latlng); + L.Editable.CircleEditor.prototype.onDrawingMouseDown.call(this, e); + }, + + // override to cancel/remove created circle when added by click instead of drag, because: + // - without resize, edit handles stacked on top of each other + // - makes event handling more complicated (editable:vertex:dragend not called) + onDrawingMouseUp: function (e) { + if (this.feature.getRadius() > 0) { + this.commitDrawing(e); + } else { + this.cancelDrawing(e); + this._delete(); + } + e.originalEvent._simulated = false; + L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e); + }, + + onVertexMarkerDrag: function (e) { + this._updateDeleteLatLng(); + L.Editable.CircleEditor.prototype.onVertexMarkerDrag.call(this, e); + } + +}); + + +BR.DeleteMarker = L.Marker.extend({ + + options: { + draggable: false, + icon: L.divIcon({ + iconSize: BR.Browser.touch ? new L.Point(24, 24) : new L.Point(16, 16), + className: 'leaflet-div-icon fa fa-trash-o nogo-delete-marker' + }) + }, + + initialize: function (latlng, editor, options) { + // derived from L.Editable.VertexMarker.initialize + + // We don't use this._latlng, because on drag Leaflet replace it while + // we want to keep reference. + this.latlng = latlng; + this.editor = editor; + L.Marker.prototype.initialize.call(this, latlng, options); + + this.latlng.__vertex = this; + this.editor.editLayer.addLayer(this); + + // to keep small circles editable, make sure delete button is below drag handle + // (not using "+ 1" to place at bottom of other vertex markers) + this.setZIndexOffset(editor.tools._lastZIndex); + }, + + onAdd: function (map) { + L.Marker.prototype.onAdd.call(this, map); + this.on('click', this.onClick); + }, + + onRemove: function (map) { + delete this.latlng.__vertex; + this.off('click', this.onClick); + L.Marker.prototype.onRemove.call(this, map); + }, + + onClick: function (e) { + this.editor.delete(); + } +}); diff --git a/js/plugin/Routing.js b/js/plugin/Routing.js index fca3b55..4f45c29 100644 --- a/js/plugin/Routing.js +++ b/js/plugin/Routing.js @@ -295,4 +295,8 @@ BR.Routing = L.Routing.extend({ L.Routing.prototype._keyupListener.call(this, e); } } + + ,isDrawing: function () { + return this._draw._enabled; + } }); From 431c3e3f7f50d6595b1300eb292acac62f2cda08 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Fri, 26 Jan 2018 17:44:47 +0100 Subject: [PATCH 003/211] Refactor d-inline-block, !important overrides hidden attribute --- css/style.css | 1 + index.html | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/css/style.css b/css/style.css index b4c32bb..f9f4e6d 100644 --- a/css/style.css +++ b/css/style.css @@ -55,6 +55,7 @@ footer { } #stats li { margin: 0 1rem; + display: inline-block; } #elevation-chart { diff --git a/index.html b/index.html index 957343e..c5130b6 100644 --- a/index.html +++ b/index.html @@ -201,27 +201,27 @@