diff --git a/js/Map.js b/js/Map.js
index ad35450..8c1f06b 100644
--- a/js/Map.js
+++ b/js/Map.js
@@ -41,7 +41,7 @@ BR.Map = {
attribution: '
World Imagery '
+ '©
Esri, sources: '
+ 'Esri, DigitalGlobe, Earthstar Geographics, CNES/Airbus DS, GeoEye, USDA FSA, USGS, Getmapping, Aerogrid, IGN, IGP, and the GIS User Community'
- });
+ });
var cycling = L.tileLayer('http://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png', {
maxNativeZoom: 18,
@@ -124,9 +124,14 @@ BR.Map = {
BR.debug = BR.debug || {};
BR.debug.map = map;
+ var layersAndOverlays = baseLayers;
+ for (var o in overlays) {
+ layersAndOverlays[o] = overlays[o];
+ }
return {
map: map,
- layersControl: layersControl
+ layersControl: layersControl,
+ layers: layersAndOverlays
};
}
diff --git a/js/control/RoutingOptions.js b/js/control/RoutingOptions.js
index 4d6c205..42441ca 100644
--- a/js/control/RoutingOptions.js
+++ b/js/control/RoutingOptions.js
@@ -17,9 +17,10 @@ BR.RoutingOptions = BR.Control.extend({
return BR.Control.prototype.onAdd.call(this, map);
},
- getOptions: function() {
+ refreshUI: function() {
var profile = $('#profile option:selected'),
alternative = $('#alternative option:selected');
+
$('#stat-profile').html(profile.text() + ' (' + alternative.text() +')');
// we do not allow to select more than one profile and/or alternative at a time
@@ -36,8 +37,13 @@ BR.RoutingOptions = BR.Control.extend({
if (custom.value === "Custom") {
custom.disabled = true;
}
-
$('.selectpicker').selectpicker('refresh')
+ },
+
+ getOptions: function() {
+ var profile = $('#profile option:selected'),
+ alternative = $('#alternative option:selected');
+ this.refreshUI();
return {
profile: profile.val(),
@@ -46,21 +52,19 @@ BR.RoutingOptions = BR.Control.extend({
},
setOptions: function(options) {
- var profiles_grp,
- profile = options.profile;
-
- if (profile) {
- profiles_grp = L.DomUtil.get('profile');
- profiles_grp.value = profile;
+ var values = [
+ options.profile ? options.profile : $('#profile option:selected').val(),
+ options.alternative ? options.alternative : $('#alternative option:selected').val()
+ ];
+ $('.selectpicker').selectpicker('val', values);
+ this.refreshUI();
+ if (options.profile) {
// profile got not selected = not in option values -> custom profile passed with permalink
- if (profiles_grp.value != profile) {
- this.setCustomProfile(profile, true);
+ if (L.DomUtil.get('profile').value != options.profile) {
+ this.setCustomProfile(options.profile, true);
}
}
- if (options.alternative) {
- L.DomUtil.get('alternative').value = options.alternative;
- }
},
setCustomProfile: function(profile, noUpdate) {
diff --git a/js/index.js b/js/index.js
index ea174fe..8c6c2a7 100644
--- a/js/index.js
+++ b/js/index.js
@@ -11,6 +11,7 @@
function initApp(mapContext) {
var map = mapContext.map,
layersControl = mapContext.layersControl,
+ mapLayers = mapContext.layers,
search,
router,
routing,
@@ -26,7 +27,7 @@
drawButton,
deleteButton,
drawToolbar,
- permalink,
+ urlHash,
saveWarningShown = false;
// By default bootstrap-select use glyphicons
@@ -70,7 +71,7 @@
if (result) {
routing.clear();
onUpdate();
- permalink._update_routing();
+ urlHash.onMapMove();
}
}
});
@@ -229,29 +230,70 @@
callback: L.bind(routing.setOpacity, routing)
}));
- // initial option settings (after controls are added and initialized with onAdd, before permalink)
+ // initial option settings (after controls are added and initialized with onAdd)
router.setOptions(nogos.getOptions());
router.setOptions(routingOptions.getOptions());
profile.update(routingOptions.getOptions());
- permalink = new L.Control.Permalink({
- text: 'Permalink',
- position: 'bottomright',
- layers: layersControl,
- routingOptions: routingOptions,
- nogos: nogos,
- router: router,
- routing: routing,
- profile: profile
- }).addTo(map);
+ var onHashChangeCb = function(url) {
+ var url2params = function (s) {
+ var p = {};
+ var sep = '&';
+ if (s.search('&') !== -1)
+ sep = '&';
+ var params = s.split(sep);
+ for (var i = 0; i < params.length; i++) {
+ var tmp = params[i].split('=');
+ if (tmp.length !== 2) continue;
+ p[tmp[0]] = decodeURIComponent(tmp[1]);
+ }
+ return p;
+ }
+ if (url == null) return;
+ var opts = router.parseUrlParams(url2params(url));
+ router.setOptions(opts);
+ routingOptions.setOptions(opts);
+ nogos.setOptions(opts);
+ profile.update(opts);
- // FIXME permalink temporary hack
- $('#permalink').on('click', function() {
- $('#permalink-input').val($('.leaflet-control-permalink a')[0].href)
- })
- $('#permalink-input').on('click', function() {
- $(this).select()
- })
+ if (opts.lonlats) {
+ routing.draw(false);
+ routing.clear();
+ routing.setWaypoints(opts.lonlats);
+ }
+ };
+
+ var onInvalidHashChangeCb = function(params) {
+ params = params.replace('zoom=', 'map=');
+ params = params.replace('&lat=', '/');
+ params = params.replace('&lon=', '/');
+ params = params.replace('&layer=', '/');
+ return params;
+ };
+
+ // do not initialize immediately
+ urlHash = new L.Hash(null, null);
+ urlHash.additionalCb = function() {
+ var url = router.getUrl(routing.getWaypoints(), null).substr('brouter?'.length+1);
+ return url.length > 0 ? '&' + url : null;
+ };
+ urlHash.onHashChangeCb = onHashChangeCb;
+ urlHash.onInvalidHashChangeCb = onInvalidHashChangeCb;
+ urlHash.layers = mapLayers;
+ urlHash.map = map;
+ urlHash.init(map, mapLayers);
+
+ routingOptions.on('update', urlHash.onMapMove, urlHash);
+ nogos.on('update', urlHash.onMapMove, urlHash);
+ // waypoint add, move, delete (but last)
+ routing.on('routing:routeWaypointEnd', urlHash.onMapMove, urlHash);
+ // delete last waypoint
+ routing.on('waypoint:click', function (evt) {
+ var r = evt.marker._routing;
+ if (!r.prevMarker && !r.nextMarker) {
+ urlHash.onMapMove();
+ }
+ }, urlHash);
$(window).resize(function () {
elevation.addBelow(map);
diff --git a/js/plugin/NogoAreas.js b/js/plugin/NogoAreas.js
index 4570ab3..d7ff4a3 100644
--- a/js/plugin/NogoAreas.js
+++ b/js/plugin/NogoAreas.js
@@ -60,8 +60,8 @@ BR.NogoAreas = L.Control.Draw.extend({
setOptions: function(options) {
var nogos = options.nogos;
+ this.drawnItems.clearLayers();
if (nogos) {
- this.drawnItems.clearLayers();
for (var i = 0; i < nogos.length; i++) {
this.drawnItems.addLayer(nogos[i]);
}
diff --git a/js/plugin/Permalink.Routing.js b/js/plugin/Permalink.Routing.js
deleted file mode 100644
index 240ddf5..0000000
--- a/js/plugin/Permalink.Routing.js
+++ /dev/null
@@ -1,105 +0,0 @@
-//#include "Permalink.js
-
-// patch to not encode URL (beside 'layer', better readable/hackable, Browser can handle)
-L.Control.Permalink.include({
- _update_href: function () {
- //var params = L.Util.getParamString(this._params);
- var params = this.getParamString(this._params);
-
- var sep = '?';
- if (this.options.useAnchor) sep = '#';
- var url = this._url_base + sep + params.slice(1);
- if (this._href) this._href.setAttribute('href', url);
- if (this.options.useLocation)
- location.replace('#' + params.slice(1));
- return url;
- },
-
- getParamString: function (obj, existingUrl, uppercase) {
- var params = [];
- for (var i in obj) {
- // do encode layer (e.g. spaces)
- if (i === 'layer') {
- params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
- } else {
- params.push(uppercase ? i.toUpperCase() : i + '=' + obj[i]);
- }
- }
- return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
- }
-});
-
-// patch: no animation when setting the map view, strange effects with nogo circles
-L.Control.Permalink.include({
- _set_center: function(e)
- {
- //console.info('Update center', e);
- var params = e.params;
- if (params.zoom === undefined ||
- params.lat === undefined ||
- params.lon === undefined) return;
- this._map.setView(new L.LatLng(params.lat, params.lon), params.zoom, { reset: true });
- }
-});
-
-L.Control.Permalink.include({
-
- initialize_routing: function () {
- this.on('update', this._set_routing, this);
- this.on('add', this._onadd_routing, this);
- },
-
- _onadd_routing: function (e) {
- this.options.routingOptions.on('update', this._update_routing, this);
- this.options.nogos.on('update', this._update_routing, this);
- // waypoint add, move, delete (but last)
- this.options.routing.on('routing:routeWaypointEnd', this._update_routing, this);
- // delete last waypoint
- this.options.routing.on('waypoint:click', function (evt) {
- var r = evt.marker._routing;
- if (!r.prevMarker && !r.nextMarker) {
- this._update_routing(evt);
- }
- }, this);
- },
-
- _update_routing: function (evt) {
- var router = this.options.router,
- routing = this.options.routing,
- routingOptions = this.options.routingOptions,
- latLngs = routing.getWaypoints(),
- params = router.getUrlParams(latLngs);
-
- if (evt && evt.options) {
- router.setOptions(evt.options);
- }
-
- // don't permalink to custom profile, as these are only stored temporarily
- if (params.profile && params.profile === routingOptions.getCustomProfile()) {
- params.profile = null;
- }
-
- this._update(params);
- //console.log('permalink: ' + this._href.href);
- },
-
- _set_routing: function (e) {
- var router = this.options.router,
- routing = this.options.routing,
- routingOptions = this.options.routingOptions,
- nogos = this.options.nogos,
- profile = this.options.profile;
-
- var opts = router.parseUrlParams(e.params);
- router.setOptions(opts);
- routingOptions.setOptions(opts);
- nogos.setOptions(opts);
- profile.update(opts);
-
- if (opts.lonlats) {
- routing.draw(false);
- routing.clear();
- routing.setWaypoints(opts.lonlats);
- }
- }
-});
diff --git a/js/plugin/leaflet-fullHash.js b/js/plugin/leaflet-fullHash.js
new file mode 100644
index 0000000..a9edd8a
--- /dev/null
+++ b/js/plugin/leaflet-fullHash.js
@@ -0,0 +1,239 @@
+(function(window) {
+ var HAS_HASHCHANGE = (function() {
+ var doc_mode = window.documentMode;
+ return ('onhashchange' in window) &&
+ (doc_mode === undefined || doc_mode > 7);
+ })();
+
+ L.Hash = function(map, options) {
+ this.onHashChange = L.Util.bind(this.onHashChange, this);
+
+ if (map) {
+ this.init(map, options);
+ }
+ };
+
+ L.Hash.parseHash = function(hash) {
+ if(hash.indexOf('#map=') === 0) {
+ hash = hash.substr(5);
+ }
+ var args = hash.split(/\&(.+)/);
+ var mapsArgs = args[0].split("/");
+ if (mapsArgs.length == 4) {
+ var zoom = parseInt(mapsArgs[0], 10),
+ lat = parseFloat(mapsArgs[1]),
+ lon = parseFloat(mapsArgs[2]),
+ layers = decodeURIComponent(mapsArgs[3]).split('-'),
+ additional = args[1];
+ if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
+ return false;
+ } else {
+ return {
+ center: new L.LatLng(lat, lon),
+ zoom: zoom,
+ layers: layers,
+ additional: additional
+ };
+ }
+ } else {
+ return false;
+ }
+ };
+
+ L.Hash.formatHash = function(map) {
+ var center = map.getCenter(),
+ zoom = map.getZoom(),
+ precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)),
+ layers = [];
+
+ //console.log(this.options);
+ var options = this.options;
+ //Check active layers
+ for(var key in options) {
+ if (options.hasOwnProperty(key)) {
+ if (map.hasLayer(options[key])) {
+ layers.push(key);
+ };
+ };
+ };
+ if (layers.length == 0) {
+ layers.push(Object.keys(options)[0]);
+ }
+ var params = [
+ zoom,
+ center.lat.toFixed(precision),
+ center.lng.toFixed(precision),
+ encodeURIComponent(layers.join("-"))
+ ];
+ url = "#map=" + params.join("/");
+ if (this.additionalCb != null) {
+ var additional = this.additionalCb();
+ if (additional != null) {
+ return url + additional;
+ }
+ }
+ return url;
+ },
+
+ L.Hash.prototype = {
+ map: null,
+ lastHash: null,
+
+ parseHash: L.Hash.parseHash,
+ formatHash: L.Hash.formatHash,
+
+ init: function(map, options) {
+ this.map = map;
+ L.Util.setOptions(this, options);
+
+ // reset the hash
+ this.lastHash = null;
+ this.onHashChange();
+
+ if (!this.isListening) {
+ this.startListening();
+ }
+ },
+
+ removeFrom: function(map) {
+ if (this.changeTimeout) {
+ clearTimeout(this.changeTimeout);
+ }
+
+ if (this.isListening) {
+ this.stopListening();
+ }
+
+ this.map = null;
+ },
+
+ onMapMove: function() {
+ // bail if we're moving the map (updating from a hash),
+ // or if the map is not yet loaded
+
+ if (this.movingMap || !this.map._loaded) {
+ return false;
+ }
+
+ var hash = this.formatHash(this.map);
+ if (this.lastHash != hash) {
+ location.replace(hash);
+ this.lastHash = hash;
+ }
+ },
+
+ movingMap: false,
+ update: function() {
+ var hash = location.hash;
+ if (hash === this.lastHash) {
+ return;
+ }
+ var parsed = this.parseHash(hash);
+ if (!parsed) {
+ // migration from old hash style to new one
+ if (this.onInvalidHashChangeCb != null) {
+ var newHash = this.onInvalidHashChangeCb(hash);
+ if (newHash != null && newHash != hash) {
+ parsed = this.parseHash(newHash);
+ }
+ }
+ }
+
+ if (parsed) {
+ this.movingMap = true;
+
+ this.map.setView(parsed.center, parsed.zoom);
+ var layers = parsed.layers,
+ options = this.options,
+ that = this;
+ //Add/remove layer
+ this.map.eachLayer(function(layer) {
+ for (alayer in that.layers) {
+ if (that.layers[alayer] == layer) {
+ that.map.removeLayer(layer);
+ break;
+ }
+ }
+ });
+ var added = false;
+ layers.forEach(function(element, index, array) {
+ if (element in options) {
+ added = true;
+ that.map.addLayer(options[element]);
+ }
+ });
+ if (!added) {
+ // if we couldn't add layers (custom ones or invalid name), add the default one
+ this.map.addLayer(options[Object.keys(options)[0]]);
+ }
+
+ if (this.onHashChangeCb != null) {
+ this.onHashChangeCb(parsed.additional);
+ }
+
+ this.movingMap = false;
+ } else {
+ this.onMapMove(this.map);
+ }
+ },
+
+ // defer hash change updates every 100ms
+ changeDefer: 100,
+ changeTimeout: null,
+ onHashChange: function() {
+ // throttle calls to update() so that they only happen every
+ // `changeDefer` ms
+ if (!this.changeTimeout) {
+ var that = this;
+ this.changeTimeout = setTimeout(function() {
+ that.update();
+ that.changeTimeout = null;
+ }, this.changeDefer);
+ }
+ },
+
+ isListening: false,
+ hashChangeInterval: null,
+ startListening: function() {
+ this.map.on("moveend layeradd layerremove", this.onMapMove, this);
+
+ if (HAS_HASHCHANGE) {
+ L.DomEvent.addListener(window, "hashchange", this.onHashChange);
+ } else {
+ clearInterval(this.hashChangeInterval);
+ this.hashChangeInterval = setInterval(this.onHashChange, 50);
+ }
+ this.isListening = true;
+ },
+
+ stopListening: function() {
+ this.map.off("moveend layeradd layerremove", this.onMapMove, this);
+
+ if (HAS_HASHCHANGE) {
+ L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
+ } else {
+ clearInterval(this.hashChangeInterval);
+ }
+ this.isListening = false;
+ },
+
+ _keyByValue: function(obj, value) {
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (obj[key] === value) {
+ return key;
+ } else { return null; };
+ };
+ };
+ }
+ };
+ L.hash = function(map, options) {
+ return new L.Hash(map, options);
+ };
+ L.Map.prototype.addHash = function() {
+ this._hash = L.hash(this, this.options);
+ };
+ L.Map.prototype.removeHash = function() {
+ this._hash.removeFrom();
+ };
+})(window);
diff --git a/js/router/BRouter.js b/js/router/BRouter.js
index 2ddbf39..5e55cd5 100644
--- a/js/router/BRouter.js
+++ b/js/router/BRouter.js
@@ -2,19 +2,17 @@ L.BRouter = L.Class.extend({
statics: {
// 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: BR.conf.host + '/brouter?lonlats={lonlats}&nogos={nogos}&profile={profile}&alternativeidx={alternativeidx}&format={format}',
+ URL_TEMPLATE: '/brouter?lonlats={lonlats}&nogos={nogos}&profile={profile}&alternativeidx={alternativeidx}&format={format}',
URL_PROFILE_UPLOAD: BR.conf.host + '/brouter/profile',
PRECISION: 6,
NUMBER_SEPARATOR: ',',
GROUP_SEPARATOR: '|',
ABORTED_ERROR: 'aborted'
},
-
+
options: {
},
- format: 'geojson',
-
initialize: function (options) {
L.setOptions(this, options);
@@ -38,13 +36,30 @@ L.BRouter = L.Class.extend({
},
getUrlParams: function(latLngs, format) {
- return {
- lonlats: this._getLonLatsString(latLngs),
- nogos: this._getNogosString(this.options.nogos),
- profile: this.options.profile,
- alternativeidx: this.options.alternative,
- format: format || this.format
- };
+ params = {};
+
+ if (this._getLonLatsString(latLngs) != null)
+ params.lonlats = this._getLonLatsString(latLngs);
+
+ if (this._getNogosString(this.options.nogos).length > 0)
+ params.nogos = this._getNogosString(this.options.nogos);
+
+ if (this.options.profile != null)
+ params.profile = this.options.profile;
+
+ params.alternativeidx = this.options.alternative;
+
+ if (format != null) {
+ params.format = format;
+ } else {
+ // do not put values in URL if this is the default value (format===null)
+ if (params.profile === BR.conf.profiles[0])
+ delete params.profile;
+ if (params.alternativeidx == 0)
+ delete params.alternativeidx;
+ }
+
+ return params;
},
parseUrlParams: function(params) {
@@ -66,12 +81,26 @@ L.BRouter = L.Class.extend({
getUrl: function(latLngs, format) {
var urlParams = this.getUrlParams(latLngs, format);
- var url = L.Util.template(L.BRouter.URL_TEMPLATE, urlParams);
- return url;
+
+ var args = []
+ if (urlParams.lonlats != null && urlParams.lonlats.length > 0)
+ args.push(L.Util.template('lonlats={lonlats}', urlParams));
+ if (urlParams.nogos != null)
+ args.push(L.Util.template('nogos={nogos}', urlParams));
+ if (urlParams.profile != null)
+ args.push(L.Util.template('profile={profile}', urlParams));
+ if (urlParams.alternativeidx != null)
+ args.push(L.Util.template('alternativeidx={alternativeidx}', urlParams));
+ if (urlParams.format != null)
+ args.push(L.Util.template('format={format}', urlParams));
+
+ var prepend_host = (format != null);
+
+ return (prepend_host ? BR.conf.host : '') + '/brouter?' + args.join('&');
},
getRoute: function(latLngs, cb) {
- var url = this.getUrl(latLngs),
+ var url = this.getUrl(latLngs, 'geojson'),
xhr = new XMLHttpRequest();
if (!url) {
@@ -199,14 +228,14 @@ L.BRouter = L.Class.extend({
if (!s) {
return nogos;
}
-
+
groups = s.split(L.BRouter.GROUP_SEPARATOR);
for (var i = 0; i < groups.length; i++) {
// lng,lat,radius
numbers = groups[i].split(L.BRouter.NUMBER_SEPARATOR);
// TODO refactor: pass simple obj, create circle in NogoAreas; use shapeOptions of instance
// [lat,lng],radius
- nogos.push(L.circle([numbers[1], numbers[0]], numbers[2], L.Draw.Circle.prototype.options.shapeOptions));
+ nogos.push(L.circle([numbers[1], numbers[0]], {radius: numbers[2]}));
}
return nogos;