diff --git a/css/style.css b/css/style.css index aeea5d3..1c41f9f 100644 --- a/css/style.css +++ b/css/style.css @@ -197,6 +197,9 @@ input#trackname:focus:invalid { .pois-draw-enabled { cursor: cell; } +.circlego-draw-enabled { + cursor: pointer; +} #map { /* center error message horizontally */ diff --git a/index.html b/index.html index 1d15611..27566fd 100644 --- a/index.html +++ b/index.html @@ -373,6 +373,7 @@ Source
+
or
diff --git a/js/control/Export.js b/js/control/Export.js
index fd6b69c..31c6b0e 100644
--- a/js/control/Export.js
+++ b/js/control/Export.js
@@ -47,7 +47,7 @@ BR.Export = L.Class.extend({
var name = encodeURIComponent(exportForm['trackname'].value);
var includeWaypoints = exportForm['include-waypoints'].checked;
- var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), format, name, includeWaypoints);
+ var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), null, format, name, includeWaypoints);
e.preventDefault();
diff --git a/js/control/Message.js b/js/control/Message.js
index b71c5aa..426ebde 100644
--- a/js/control/Message.js
+++ b/js/control/Message.js
@@ -40,8 +40,16 @@ BR.Message = L.Class.extend({
},
showError: function(err) {
- if (err == 'Error: target island detected for section 0\n') {
+ if (err && err.message) err = err.message;
+
+ if (err == 'target island detected for section 0\n') {
err = i18next.t('warning.no-route-found');
+ } else if (err == 'no track found at pass=0\n') {
+ err = i18next.t('warning.no-route-found');
+ } else if (err == 'to-position not mapped in existing datafile\n') {
+ err = i18next.t('warning.invalid-route-to');
+ } else if (err == 'from-position not mapped in existing datafile\n') {
+ err = i18next.t('warning.invalid-route-from');
}
this._show(err, 'error');
},
diff --git a/js/index.js b/js/index.js
index 476e588..c964db4 100644
--- a/js/index.js
+++ b/js/index.js
@@ -34,6 +34,7 @@
drawButton,
deleteRouteButton,
pois,
+ circleGo,
urlHash;
// By default bootstrap-select use glyphicons
@@ -74,7 +75,10 @@
routing.draw(true);
control.state('deactivate-draw');
},
- title: i18next.t('keyboard.generic-shortcut', { action: '$t(map.draw-route-start)', key: 'D' })
+ title: i18next.t('keyboard.generic-shortcut', {
+ action: '$t(map.draw-route-start)',
+ key: 'D'
+ })
}
]
});
@@ -84,7 +88,10 @@
function() {
routing.reverse();
},
- i18next.t('keyboard.generic-shortcut', { action: '$t(map.reverse-route)', key: 'R' })
+ i18next.t('keyboard.generic-shortcut', {
+ action: '$t(map.reverse-route)',
+ key: 'R'
+ })
);
var deletePointButton = L.easyButton(
@@ -92,7 +99,10 @@
function() {
routing.deleteLastPoint();
},
- i18next.t('keyboard.generic-shortcut', { action: '$t(map.delete-last-point)', key: 'Z' })
+ i18next.t('keyboard.generic-shortcut', {
+ action: '$t(map.delete-last-point)',
+ key: 'Z'
+ })
);
deleteRouteButton = L.easyButton(
@@ -100,7 +110,10 @@
function() {
clearRoute();
},
- i18next.t('keyboard.generic-shortcut', { action: '$t(map.clear-route)', key: '$t(keyboard.backspace)' })
+ i18next.t('keyboard.generic-shortcut', {
+ action: '$t(map.clear-route)',
+ key: '$t(keyboard.backspace)'
+ })
);
L.DomEvent.addListener(
@@ -181,7 +194,10 @@
profile.update(evt.options);
});
- BR.NogoAreas.MSG_BUTTON = i18next.t('keyboard.generic-shortcut', { action: '$t(map.nogo.draw)', key: 'N' });
+ BR.NogoAreas.MSG_BUTTON = i18next.t('keyboard.generic-shortcut', {
+ action: '$t(map.nogo.draw)',
+ key: 'N'
+ });
BR.NogoAreas.MSG_BUTTON_CANCEL = i18next.t('keyboard.generic-shortcut', {
action: '$t(map.nogo.cancel)',
key: '$t(keyboard.escape)'
@@ -243,9 +259,9 @@
styles: BR.conf.routingStyles
});
- pois = new BR.PoiMarkers({
- routing: routing
- });
+ pois = new BR.PoiMarkers(routing);
+ circleGo = new BR.CircleGoArea(routing, nogos, pois);
+ pois.circlego = circleGo;
exportRoute = new BR.Export(router, pois);
@@ -305,7 +321,22 @@
}
nogos.addTo(map);
- L.easyBar([drawButton, reverseRouteButton, nogos.getButton(), deletePointButton, deleteRouteButton]).addTo(map);
+
+ var shouldAddCircleGo = false;
+ var lang = i18next.languages.length && i18next.languages[0];
+
+ if (lang.startsWith('fr')) {
+ circleGo.options.radius = 20000;
+ shouldAddCircleGo = true;
+ }
+
+ if (shouldAddCircleGo) circleGo.addTo(map);
+
+ var buttons = [drawButton, reverseRouteButton, nogos.getButton()];
+ if (shouldAddCircleGo) buttons.push(circleGo.getButton());
+ buttons.push(deletePointButton, deleteRouteButton);
+
+ L.easyBar(buttons).addTo(map);
nogos.preventRoutePointOnCreate(routing);
if (BR.keys.strava) {
@@ -322,7 +353,10 @@
map.addControl(
new BR.OpacitySliderControl({
id: 'route',
- title: i18next.t('map.opacity-slider-shortcut', { action: '$t(map.opacity-slider)', key: 'M' }),
+ title: i18next.t('map.opacity-slider-shortcut', {
+ action: '$t(map.opacity-slider)',
+ key: 'M'
+ }),
muteKeyCode: 77, // m
callback: L.bind(routing.setOpacity, routing)
})
@@ -358,6 +392,11 @@
var opts = router.parseUrlParams(url2params(url));
router.setOptions(opts);
routingOptions.setOptions(opts);
+ if (opts.circlego) {
+ // must be done before nogos!
+ circleGo.options.radius = opts.circlego[2];
+ circleGo.setCircle([opts.circlego[0], opts.circlego[1]]);
+ }
nogos.setOptions(opts);
profile.update(opts);
@@ -366,7 +405,6 @@
routing.clear();
routing.setWaypoints(opts.lonlats);
}
-
if (opts.pois) {
pois.setMarkers(opts.pois);
}
@@ -384,7 +422,9 @@
urlHash = new L.Hash(null, null);
// this callback is used to append anything in URL after L.Hash wrote #map=zoom/lat/lng/layer
urlHash.additionalCb = function() {
- var url = router.getUrl(routing.getWaypoints(), pois.getMarkers(), null).substr('brouter?'.length + 1);
+ var url = router
+ .getUrl(routing.getWaypoints(), pois.getMarkers(), circleGo.getCircle(), null)
+ .substr('brouter?'.length + 1);
// by default brouter use | as separator. To make URL more human-readable, we remplace them with ; for users
url = url.replace(/\|/g, ';');
diff --git a/js/plugin/CircleGoArea.js b/js/plugin/CircleGoArea.js
new file mode 100644
index 0000000..5fffebc
--- /dev/null
+++ b/js/plugin/CircleGoArea.js
@@ -0,0 +1,191 @@
+BR.CircleGoArea = L.Control.extend({
+ circleLayer: null,
+
+ options: {
+ radius: 1000, // in meters
+ shortcut: {
+ draw: {
+ enable: 73, // char code for 'i'
+ disable: 27 // char code for 'ESC'
+ }
+ }
+ },
+ initialize: function(routing, nogos, pois) {
+ this.routing = routing;
+ this.nogos = nogos;
+ this.pois = pois;
+ },
+
+ onAdd: function(map) {
+ var self = this;
+
+ this.map = map;
+ this.circleLayer = L.layerGroup([]).addTo(map);
+
+ var radiusKm = (this.options.radius / 1000).toFixed();
+ this.drawButton = L.easyButton({
+ states: [
+ {
+ stateName: 'activate-circlego',
+ icon: 'fa-circle-o',
+ onClick: function() {
+ self.draw(true);
+ },
+ title: i18next.t('keyboard.generic-shortcut', {
+ action: i18next.t('map.draw-circlego-start', { radius: radiusKm }),
+ key: 'I'
+ })
+ },
+ {
+ stateName: 'deactivate-circlego',
+ icon: 'fa-circle-o active',
+ onClick: function() {
+ self.draw(false);
+ },
+ title: i18next.t('keyboard.generic-shortcut', {
+ action: i18next.t('map.draw-circlego-stop', { radius: radiusKm }),
+ key: '$t(keyboard.escape)'
+ })
+ }
+ ]
+ });
+
+ map.on('routing:draw-start', function() {
+ self.draw(false);
+ });
+
+ L.DomEvent.addListener(document, 'keydown', this._keydownListener, this);
+
+ var container = new L.DomUtil.create('div');
+ return container;
+ },
+
+ draw: function(enable) {
+ this.drawButton.state(enable ? 'deactivate-circlego' : 'activate-circlego');
+ if (enable) {
+ this.routing.draw(false);
+ this.pois.draw(false);
+ this.map.on('click', this.onMapClick, this);
+ L.DomUtil.addClass(this.map.getContainer(), 'circlego-draw-enabled');
+ } else {
+ this.map.off('click', this.onMapClick, this);
+ L.DomUtil.removeClass(this.map.getContainer(), 'circlego-draw-enabled');
+ }
+ },
+
+ _keydownListener: function(e) {
+ if (!BR.Util.keyboardShortcutsAllowed(e)) {
+ return;
+ }
+ if (e.keyCode === this.options.shortcut.draw.disable) {
+ this.draw(false);
+ } else if (e.keyCode === this.options.shortcut.draw.enable) {
+ this.draw(true);
+ }
+ },
+
+ setNogoCircle: function(center) {
+ if (center) {
+ var polygon = this.circleToPolygon(center, this.options.radius);
+ $('#nogoJSON').val(JSON.stringify(polygon));
+ this.nogos.uploadNogos();
+ } else {
+ this.nogos.clear();
+ }
+ },
+
+ onMapClick: function(e) {
+ this.setCircle([e.latlng.lng, e.latlng.lat]);
+ },
+
+ setCircle: function(center) {
+ var self = this;
+ var icon = L.VectorMarkers.icon({
+ icon: 'home',
+ markerColor: BR.conf.markerColors.circlego
+ });
+ var marker = L.marker([center[1], center[0]], { icon: icon, draggable: true, name: name })
+ .on('dragend', function(e) {
+ self.setNogoCircle([e.target.getLatLng().lng, e.target.getLatLng().lat]);
+ })
+ .on('click', function() {
+ var drawing = self.drawButton.state() == 'deactivate-circlego';
+ if (drawing) {
+ self.circleLayer.removeLayer(marker);
+ self.setNogoCircle(undefined);
+ }
+ });
+
+ this.clear();
+ marker.addTo(this.circleLayer);
+ this.setNogoCircle(center);
+ this.draw(false);
+ },
+
+ clear: function() {
+ this.circleLayer.clearLayers();
+ },
+
+ getButton: function() {
+ return this.drawButton;
+ },
+
+ getCircle: function() {
+ var circle = this.circleLayer.getLayers().map(function(it) {
+ return it.getLatLng();
+ });
+ if (circle && circle.length) {
+ return [circle[0].lng.toFixed(6), circle[0].lat.toFixed(6), this.options.radius].join(',');
+ } else {
+ return null;
+ }
+ },
+
+ toRadians: function(angleInDegrees) {
+ return (angleInDegrees * Math.PI) / 180;
+ },
+
+ toDegrees: function(angleInRadians) {
+ return (angleInRadians * 180) / Math.PI;
+ },
+
+ offset: function(c1, distance, bearing) {
+ var lon1 = this.toRadians(c1[0]);
+ var lat1 = this.toRadians(c1[1]);
+ var dByR = distance / 6378137; // distance divided by 6378137 (radius of the earth) wgs84
+ var lat = Math.asin(Math.sin(lat1) * Math.cos(dByR) + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
+ var lon =
+ lon1 +
+ Math.atan2(
+ Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
+ Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)
+ );
+ return [this.toDegrees(lon), this.toDegrees(lat)];
+ },
+
+ circleToPolygon: function(center, radius, numberOfSegments) {
+ var n = numberOfSegments ? numberOfSegments : 64;
+
+ var inner = [];
+ for (var i = 0; i < n; ++i) {
+ inner.push(this.offset(center, radius, (2 * Math.PI * -i) / n));
+ }
+ inner.push(inner[0]);
+
+ return {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: inner
+ }
+ }
+ ]
+ };
+ }
+});
+
+BR.CircleGoArea.include(L.Evented.prototype);
diff --git a/js/plugin/NogoAreas.js b/js/plugin/NogoAreas.js
index 6cff5ec..2c539bb 100644
--- a/js/plugin/NogoAreas.js
+++ b/js/plugin/NogoAreas.js
@@ -162,9 +162,13 @@ BR.NogoAreas = L.Control.extend({
var self = this;
var geoJSONPromise;
+ var nogoJSON = $('#nogoJSON').val(); //hidden
var nogoURL = $('#nogoURL').val();
var nogoFile = $('#nogoFile')[0].files[0];
- if (nogoURL) {
+ if (nogoJSON) {
+ geoJSONPromise = Promise.resolve(JSON.parse(nogoJSON));
+ $('#nogoJSON').val(undefined);
+ } else if (nogoURL) {
// TODO: Handle {{bbox}}
geoJSONPromise = fetch(nogoURL).then(function(response) {
response.json();
diff --git a/js/plugin/POIMarkers.js b/js/plugin/POIMarkers.js
index c47990d..76d132a 100644
--- a/js/plugin/POIMarkers.js
+++ b/js/plugin/POIMarkers.js
@@ -1,8 +1,8 @@
BR.PoiMarkers = L.Control.extend({
markersLayer: null,
+ circlego: null,
options: {
- routing: null,
shortcut: {
draw: {
enable: 80, // char code for 'p'
@@ -10,6 +10,10 @@ BR.PoiMarkers = L.Control.extend({
}
}
},
+ initialize: function(routing) {
+ this.routing = routing;
+ this.circlego = null;
+ },
onAdd: function(map) {
var self = this;
@@ -54,7 +58,8 @@ BR.PoiMarkers = L.Control.extend({
draw: function(enable) {
this.drawButton.state(enable ? 'deactivate-poi' : 'activate-poi');
if (enable) {
- this.options.routing.draw(false);
+ this.routing.draw(false);
+ this.circlego.draw(false);
this.map.on('click', this.onMapClick, this);
L.DomUtil.addClass(this.map.getContainer(), 'pois-draw-enabled');
} else {
@@ -129,7 +134,7 @@ BR.PoiMarkers = L.Control.extend({
getMarkers: function() {
return this.markersLayer.getLayers().map(function(it) {
return {
- latlng: it._latlng,
+ latlng: it.getLatLng(),
name: it.options.name
};
});
diff --git a/js/router/BRouter.js b/js/router/BRouter.js
index d8985b1..f38ee1b 100644
--- a/js/router/BRouter.js
+++ b/js/router/BRouter.js
@@ -3,7 +3,7 @@ L.BRouter = L.Class.extend({
// NOTE: the routing API used here is not public!
// /brouter?lonlats=1.1,1.2|2.1,2.2|3.1,3.2|4.1,4.2&nogos=-1.1,-1.2,1|-2.1,-2.2,2&profile=shortest&alternativeidx=1&format=kml
URL_TEMPLATE:
- '/brouter?lonlats={lonlats}&nogos={nogos}&polylines={polylines}&polygons={polygons}&profile={profile}&alternativeidx={alternativeidx}&format={format}',
+ '/brouter?lonlats={lonlats}&profile={profile}&alternativeidx={alternativeidx}&format={format}&nogos={nogos}&polylines={polylines}&polygons={polygons}',
URL_PROFILE_UPLOAD: BR.conf.host + '/brouter/profile',
PRECISION: 6,
NUMBER_SEPARATOR: ',',
@@ -42,7 +42,7 @@ L.BRouter = L.Class.extend({
L.setOptions(this, options);
},
- getUrlParams: function(latLngs, pois, format) {
+ getUrlParams: function(latLngs, pois, circlego, format) {
params = {};
if (this._getLonLatsString(latLngs) != null) params.lonlats = this._getLonLatsString(latLngs);
@@ -59,6 +59,8 @@ L.BRouter = L.Class.extend({
if (pois && this._getLonLatsNameString(pois) != null) params.pois = this._getLonLatsNameString(pois);
+ if (circlego) params.circlego = circlego;
+
params.alternativeidx = this.options.alternative;
if (format != null) {
@@ -100,15 +102,27 @@ L.BRouter = L.Class.extend({
if (params.pois) {
opts.pois = this._parseLonLatNames(params.pois);
}
+ if (params.circlego) {
+ var circlego = params.circlego.split(',');
+ if (circlego.length == 3) {
+ circlego = [
+ Number.parseFloat(circlego[0]),
+ Number.parseFloat(circlego[1]),
+ Number.parseInt(circlego[2])
+ ];
+ opts.circlego = circlego;
+ }
+ }
return opts;
},
- getUrl: function(latLngs, pois, format, trackname, exportWaypoints) {
- var urlParams = this.getUrlParams(latLngs, pois, format);
+ getUrl: function(latLngs, pois, circlego, format, trackname, exportWaypoints) {
+ var urlParams = this.getUrlParams(latLngs, pois, circlego, format);
var args = [];
if (urlParams.lonlats != null && urlParams.lonlats.length > 0)
args.push(L.Util.template('lonlats={lonlats}', urlParams));
if (urlParams.pois != null && urlParams.pois.length > 0) args.push(L.Util.template('pois={pois}', urlParams));
+ if (urlParams.circlego != null) args.push(L.Util.template('circlego={circlego}', urlParams));
if (urlParams.nogos != null) args.push(L.Util.template('nogos={nogos}', urlParams));
if (urlParams.polylines != null) args.push(L.Util.template('polylines={polylines}', urlParams));
if (urlParams.polygons != null) args.push(L.Util.template('polygons={polygons}', urlParams));
@@ -129,7 +143,7 @@ L.BRouter = L.Class.extend({
},
getRoute: function(latLngs, cb) {
- var url = this.getUrl(latLngs, null, 'geojson'),
+ var url = this.getUrl(latLngs, null, null, 'geojson'),
xhr = new XMLHttpRequest();
if (!url) {
diff --git a/locales/en.json b/locales/en.json
index 28189a0..5374acc 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -94,6 +94,8 @@
"delete-nogo-areas": "Delete all no-go areas",
"delete-pois": "Delete all points of interest",
"delete-route": "Delete route",
+ "draw-circlego-start": "Draw limited {{radius}}km go-to zone",
+ "draw-circlego-stop": "Stop drawing limited {{radius}}km go-to zone",
"draw-poi-start": "Draw points of interest",
"draw-poi-stop": "Stop drawing points of interest",
"draw-route-start": "Draw route",
@@ -267,6 +269,8 @@
},
"warning": {
"cannot-get-route": "Error getting route URL",
+ "invalid-route-from": "Start marker is too far from a route.",
+ "invalid-route-to": "Destination marker is too far from a route.",
"no-response": "no response from server",
"no-route-found": "Error: cannot find a route for given points. Maybe try to move them closer to roads?",
"profile-error": "Profile error: no or empty response from server",
diff --git a/locales/fr.json b/locales/fr.json
index 4e8cf81..8a00f3c 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -61,7 +61,7 @@
"keyboard": {
"backspace": "Retour chariot",
"escape": "Échap",
- "generic-shortcut": "{{action}} (touche {{key}} )",
+ "generic-shortcut": "{{action}} (touche {{key}})",
"shift": "Maj"
},
"layers": {
@@ -94,6 +94,8 @@
"delete-nogo-areas": "Supprimer toutes les zones interdites",
"delete-pois": "Supprimer tous les points d'intérêt ",
"delete-route": "Supprimer l'itinéraire",
+ "draw-circlego-start": "Ajouter une zone limite de {{radius}} km",
+ "draw-circlego-stop": "Arrêter l'ajout d'une zone limit de {{radius}} km",
"draw-poi-start": "Ajouter des points d'intérêt",
"draw-poi-stop": "Arrêter l'ajout de points d'intérêt",
"draw-route-start": "Dessiner l'itinéraire",