Replace permalink/Share URL feature with automatic URL rewriting on change.
This commit is contained in:
parent
3cc0a3b9ee
commit
e82f360d9c
9 changed files with 308 additions and 170 deletions
|
|
@ -37,8 +37,6 @@
|
|||
},
|
||||
"leaflet-plugins": {
|
||||
"main": [
|
||||
"control/Permalink.js",
|
||||
"control/Permalink.Layer.js",
|
||||
"layer/tile/Bing.js"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -88,11 +88,6 @@ footer {
|
|||
cursor: crosshair;
|
||||
}
|
||||
|
||||
/* FIXME permalink temporary hack */
|
||||
.leaflet-control-permalink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#message {
|
||||
position: absolute;
|
||||
left: 446px; /* 400 + 10 + 26 + 10 */
|
||||
|
|
|
|||
20
index.html
20
index.html
|
|
@ -31,9 +31,6 @@
|
|||
<a class="dropdown-item" id="dl-csv" href="#" disabled>data CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="nav-item nav-link" href="" data-toggle="modal" data-target="#permalink-win" id="permalink">
|
||||
<span class="fa fa-lg fa-share-alt"></span> Share URL</a>
|
||||
|
||||
<form class="navbar-form">
|
||||
<div class="form-group">
|
||||
<select class="selectpicker show-tick" id="profile-alternative" multiple>
|
||||
|
|
@ -53,23 +50,6 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Permalink -->
|
||||
<div class="modal fade" id="permalink-win" tabindex="-1" role="dialog" aria-labelledby="Permalink window" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Permalink</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input class="form-control" type="text" id="permalink-input" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Credits modal window -->
|
||||
<div class="modal fade" id="credits" tabindex="-1" role="dialog" aria-labelledby="Credits window" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ BR.Map = {
|
|||
|
||||
return {
|
||||
map: map,
|
||||
layersControl: layersControl
|
||||
layersControl: layersControl,
|
||||
layers: baseLayers
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
72
js/index.js
72
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.updateHash();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -229,29 +230,60 @@
|
|||
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);
|
||||
urlHash = new L.Hash(map, mapLayers, function() {
|
||||
var latLngs = routing.getWaypoints();
|
||||
if (latLngs.length > 1) {
|
||||
return router.getUrl(latLngs, null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
routingOptions.on('update', urlHash.updateHash, urlHash);
|
||||
nogos.on('update', urlHash.updateHash, urlHash);
|
||||
// waypoint add, move, delete (but last)
|
||||
routing.on('routing:routeWaypointEnd', urlHash.updateHash, urlHash);
|
||||
// delete last waypoint
|
||||
routing.on('waypoint:click', function (evt) {
|
||||
var r = evt.marker._routing;
|
||||
if (!r.prevMarker && !r.nextMarker) {
|
||||
urlHash.updateHash();
|
||||
}
|
||||
}, urlHash);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
urlHash.onHashChangeCb = function(url) {
|
||||
if (url == null) return;
|
||||
url = url.substr('brouter?'.length);
|
||||
var opts = router.parseUrlParams(url2params(url));
|
||||
router.setOptions(opts);
|
||||
routingOptions.setOptions(opts);
|
||||
nogos.setOptions(opts);
|
||||
profile.update(opts);
|
||||
|
||||
if (opts.lonlats) {
|
||||
routing.draw(false);
|
||||
routing.clear();
|
||||
routing.setWaypoints(opts.lonlats);
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME permalink temporary hack
|
||||
$('#permalink').on('click', function() {
|
||||
$('#permalink-input').val($('.leaflet-control-permalink a')[0].href)
|
||||
})
|
||||
$('#permalink-input').on('click', function() {
|
||||
$(this).select()
|
||||
})
|
||||
|
||||
$(window).resize(function () {
|
||||
elevation.addBelow(map);
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ BR.NogoAreas = L.Control.Draw.extend({
|
|||
|
||||
setOptions: function(options) {
|
||||
var nogos = options.nogos;
|
||||
if (nogos) {
|
||||
this.drawnItems.clearLayers();
|
||||
if (nogos) {
|
||||
for (var i = 0; i < nogos.length; i++) {
|
||||
this.drawnItems.addLayer(nogos[i]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
212
js/plugin/leaflet-fullHash.js
Normal file
212
js/plugin/leaflet-fullHash.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
(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, layers, additionalCb, onHashChangeCb) {
|
||||
this.onHashChange = L.Util.bind(this.onHashChange, this);
|
||||
this.additionalCb = additionalCb;
|
||||
this.onHashChangeCb = onHashChangeCb;
|
||||
if (map) {
|
||||
this.init(map, layers);
|
||||
}
|
||||
};
|
||||
|
||||
L.Hash.parseHash = function(hash) {
|
||||
if(hash.indexOf('#map=') === 0) {
|
||||
hash = hash.substr(5);
|
||||
}
|
||||
var args = hash.split("/");
|
||||
if (args.length >= 4) {
|
||||
var zoom = parseInt(args[0], 10),
|
||||
lat = parseFloat(args[1]),
|
||||
lon = parseFloat(args[2]),
|
||||
layer = args[3];
|
||||
additional = args[4];
|
||||
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
|
||||
return false;
|
||||
} else {
|
||||
return {
|
||||
center: new L.LatLng(lat, lon),
|
||||
zoom: zoom,
|
||||
layer: layer,
|
||||
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)),
|
||||
layer = null;
|
||||
|
||||
var options = this.options;
|
||||
//Check active layer
|
||||
for(var key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
if (map.hasLayer(options[key])) {
|
||||
layer = key;
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
var params = [
|
||||
zoom,
|
||||
center.lat.toFixed(precision),
|
||||
center.lng.toFixed(precision),
|
||||
layer
|
||||
];
|
||||
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;
|
||||
},
|
||||
|
||||
updateHash: function() {
|
||||
// bail if we're moving the map (updating from a hash),
|
||||
// or if the map is not yet loaded
|
||||
|
||||
if (this.isUpdatingHash || !this.map._loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var hash = this.formatHash(this.map);
|
||||
if (this.lastHash != hash) {
|
||||
location.replace(hash);
|
||||
this.lastHash = hash;
|
||||
}
|
||||
},
|
||||
|
||||
isUpdatingHash: false,
|
||||
update: function() {
|
||||
var hash = location.hash;
|
||||
if (hash === this.lastHash) {
|
||||
return;
|
||||
}
|
||||
var parsed = this.parseHash(hash);
|
||||
if (parsed) {
|
||||
this.isUpdatingHash = true;
|
||||
|
||||
this.map.setView(parsed.center, parsed.zoom);
|
||||
|
||||
if (this.onHashChangeCb != null) {
|
||||
this.onHashChangeCb(parsed.additional);
|
||||
}
|
||||
|
||||
var options = this.options,
|
||||
layer = parsed.layer in options ? parsed.layer : Object.keys(options)[0],
|
||||
that = this;
|
||||
//Add/remove layer
|
||||
this.map.eachLayer(function(layer) {
|
||||
that.map.removeLayer(layer);
|
||||
});
|
||||
that.map.addLayer(options[layer]);
|
||||
|
||||
this.isUpdatingHash = false;
|
||||
} else {
|
||||
this.updateHash(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.updateHash, 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.updateHash, 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);
|
||||
|
|
@ -2,7 +2,7 @@ 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: ',',
|
||||
|
|
@ -13,8 +13,6 @@ L.BRouter = L.Class.extend({
|
|||
options: {
|
||||
},
|
||||
|
||||
format: 'geojson',
|
||||
|
||||
initialize: function (options) {
|
||||
L.setOptions(this, options);
|
||||
|
||||
|
|
@ -38,13 +36,26 @@ 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;
|
||||
|
||||
// do not put alternative in URL if it has its default value,
|
||||
// but always set it if we want to generate route because Brouter API requires it.
|
||||
if (this.options.alternative != 0 || format != null)
|
||||
params.alternativeidx = this.options.alternative;
|
||||
|
||||
if (format != null)
|
||||
params.format = format;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parseUrlParams: function(params) {
|
||||
|
|
@ -66,12 +77,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)
|
||||
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) {
|
||||
|
|
@ -206,7 +231,7 @@ L.BRouter = L.Class.extend({
|
|||
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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue