Comparison of Stamen Design’s previous Toner style with new Toner data as of 2014.
Borrowed from Andrew Harvey’s gist here and his live demo.
Extended with Leaflet-hash by Michael Evans
Adapted to function on bl.ocks.org by Alan McConchie
<html xmlns="//www.w3.org/1999/xhtml">
<!--
This file is licenced CC0 //creativecommons.org/publicdomain/zero/1.0/
-->
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<title>Leaflet Maps Side by Side</title>
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" type="text/css" />
<style type="text/css">
body {
margin: 0;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
/* set the two maps side by side */
#mapA {
width: 50%;
height: 100%;
}
#mapB {
width: 50%;
height: 100%;
left: 50%;
top: 0;
position: absolute;
}
/* the cursor */
#cursor {
position: absolute;
z-index: 100;
}
/* map title bar along the top */
.title {
position: absolute;
z-index: 2;
opacity: 0.75;
top: 0px;
text-align: center;
font-weight: bold;
background-color: white;
}
/* position the individual title bars */
#mapATitle {
width: 50%;
}
#mapBTitle {
width: 50%;
left: 50%;
}
</style>
<script src="//cdn.leafletjs.com/leaflet-0.7.1/leaflet-src.js" type="text/javascript"></script>
<script src="https://cdn.rawgit.com/turban/Leaflet.Sync/master/L.Map.Sync.js" type="text/javascript"></script>
<script src="zepto.min.js" type="text/javascript"></script>
<script src="prototype.js" type="text/javascript"></script>
<script src="leaflet-hash.js" type="text/javascript"></script>
<script src="l.control.geosearch.js" type="text/javascript"></script>
<script src="l.geosearch.provider.nominatim.js" type="text/javascript"></script>
<link rel="stylesheet" href="l.geosearch.toner.css" type="text/css" />
<script type="text/javascript">
var mapA;
var mapB;
window.onload = function init(){
// predefined map layers
var provider = "//{s}.tile.stamen.com/toner/{z}/{x}/{y}.png";
var mediaQuery = "(-webkit-min-device-pixel-ratio: 1.5),\
(min--moz-device-pixel-ratio: 1.5),\
(-o-min-device-pixel-ratio: 3/2),\
(min-resolution: 1.5dppx)";
if (window.devicePixelRatio > 1 ||
(window.matchMedia && window.matchMedia(mediaQuery).matches)) {
// replace the last "." with "@2x."
provider = provider.replace(/\.(?!.*\.)/, "@2x.")
}
var tonernew = new L.TileLayer(provider,
{
attribution: 'Map tiles by <a href="//stamen.com">Stamen Design</a>, under <a href="//creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="//openstreetmap.org">OpenStreetMap</a>, under <a href="//opendatacommons.org/licenses/odbl/">ODbL</a>.',
maxZoom: 18
});
var tonerold = new L.TileLayer("//tilefarm.stamen.com/toner/{z}/{x}/{y}.png",
{
attribution: 'Map tiles by <a href="//stamen.com">Stamen Design</a>, under <a href="//creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="//openstreetmap.org">OpenStreetMap</a>, under <a href="//creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.',
maxZoom: 18
});
// give these layers names so users can reference them in the URI
var mapLayers = new Array();
mapLayers['tonernew'] = tonernew;
mapLayers['tonerold'] = tonerold;
var mapADefaultLayer = tonerold;
var mapBDefaultLayer = tonernew;
// make the map objects
var startLocation = new L.LatLng(0,0);
var startZoom = 2;
if (!location.hash) {
location.hash = "#2/0/0";
}
mapA = new L.Map('mapA',
{
//center: startLocation,
//zoom: startZoom,
layers: [mapADefaultLayer]
});
mapB = new L.Map('mapB',
{
//center: startLocation,
//zoom: startZoom,
layers: [mapBDefaultLayer],
zoomControl: false
});
var hash = new L.Hash(mapA);
/*
new L.Control.GeoSearch({
provider: new L.GeoSearch.Provider.Nominatim(),
showMarker: false
}).addTo(mapA);
*/
mapA.sync(mapB);
mapB.sync(mapA);
// update the location of the cursor
function updateCursorA(e) {
updateCursor(e, (window.innerWidth / 2));
}
function updateCursorB(e) {
updateCursor(e, -(window.innerWidth / 2));
}
function updateCursor(e, offset){
// the 15 is here to position to the center of the cursor icon in, not the top left of the cursor image
$('cursor').top = e.clientY - 15;
$('cursor').left = offset + e.clientX - 15;
$('cursor').setStyle({
left: $('cursor').left,
top: $('cursor').top
});
}
document.getElementById('mapA').onmousemove = updateCursorA;
document.getElementById('mapB').onmousemove = updateCursorB;
}
</script>
</head>
<body id="body">
<div id="mapA"></div>
<div id="mapB"></div>
<div id="mapATitle" class="title">Toner old</div>
<div id="mapbTitle" class="title">Toner 2014</div>
<!--
The following license applies to the following cross.png file originally from
//gitorious.org/opensuse/art/blobs/master/cursors/dmz/pngs/32x32/cross.png
(c) 2007-2010 Novell, Inc.
This work is licenced under the Creative Commons Attribution-Share Alike 3.0
United States License. To view a copy of this licence, visit
//creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
-->
<div><img id="cursor" src="cross.png" alt="X"/></div>
</body>
</html>
/*
* L.Control.GeoSearch - search for an address and zoom to its location
* https://github.com/smeijer/leaflet.control.geosearch
*/
L.GeoSearch = {};
L.GeoSearch.Provider = {};
L.GeoSearch.Result = function (x, y, label) {
this.X = x;
this.Y = y;
this.Label = label;
};
L.Control.GeoSearch = L.Control.extend({
options: {
position: 'topleft'
},
initialize: function (options) {
this._config = {};
L.Util.extend(this.options, options);
this.setConfig(options);
},
setConfig: function (options) {
this._config = {
'provider': options.provider,
'searchLabel': options.searchLabel || 'Enter address',
'notFoundMessage' : options.notFoundMessage || 'Sorry, that address could not be found.',
'zoomLevel': options.zoomLevel || 17,
'showMarker': typeof options.showMarker !== 'undefined' ? options.showMarker : true
};
},
resetLink: function(extraClass) {
var link = this._container.querySelector('a');
link.className = 'leaflet-bar-part leaflet-bar-part-single' + ' ' + extraClass;
},
onAdd: function (map) {
// create the container
this._container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-geosearch');
// create the link - this will contain one of the icons
var link = L.DomUtil.create('a', '', this._container);
link.href = '#';
link.title = this._config.searchLabel;
// set the link's icon to magnifying glass
this.resetLink('glass');
var displayNoneClass = 'displayNone';
// create the form that will contain the input
var form = L.DomUtil.create('form', displayNoneClass, this._container);
// create the input, and set its placeholder ("Enter address") text
var input = L.DomUtil.create('input', null, form);
input.placeholder = 'Enter address';
// create the error message div
var message = L.DomUtil.create('div', 'leaflet-bar message displayNone', this._container);
L.DomEvent
.on(link, 'click', L.DomEvent.stopPropagation)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', function() {
if (L.DomUtil.hasClass(form, displayNoneClass)) {
L.DomUtil.removeClass(form, 'displayNone'); // unhide form
input.focus();
} else {
L.DomUtil.addClass(form, 'displayNone'); // hide form
}
})
.on(link, 'dblclick', L.DomEvent.stopPropagation);
L.DomEvent
.on(input, 'keypress', this.onKeyPress, this)
.on(input, 'keyup', this.onKeyUp, this)
.on(input, 'input', this.onInput, this);
return this._container;
},
geosearch: function (qry) {
try {
var provider = this._config.provider;
if(typeof provider.GetLocations == 'function') {
var results = provider.GetLocations(qry, this._map, function(err, results) {
if (err) {
return this._printError(err);
}
this._processResults(results);
}.bind(this));
}
else {
var url = provider.GetServiceUrl(qry);
$.getJSON(url, function (data) {
try {
var results = provider.ParseJSON(data);
this._processResults(results);
}
catch (error) {
this._printError(error);
}
}.bind(this));
}
}
catch (error) {
this._printError(error);
}
},
_processResults: function(results) {
if (results.length === 0)
throw this._config.notFoundMessage;
this.cancelSearch();
this._showLocation(results[0]);
},
_showLocation: function (location) {
if (this._config.showMarker) {
if (typeof this._positionMarker === 'undefined')
this._positionMarker = L.marker([location.Y, location.X]).addTo(this._map);
else
this._positionMarker.setLatLng([location.Y, location.X]);
}
// this._map.setView([location.Y, location.X], this._config.zoomLevel, false);
},
_isShowingError: false,
_printError: function(error) {
var message = this._container.querySelector('.message');
message.innerHTML = error;
L.DomUtil.removeClass(message, 'displayNone');
// show alert icon
this.resetLink('alert');
this._isShowingError = true;
},
cancelSearch: function() {
var form = this._container.querySelector('form');
L.DomUtil.addClass(form, 'displayNone'); // hide form
var input = form.querySelector('input');
input.value = ''; // clear form
// show glass icon
this.resetLink('glass');
var message = this._container.querySelector('.message');
L.DomUtil.addClass(message, 'displayNone'); // hide message
},
startSearch: function() {
// show spinner icon
this.resetLink('spinner');
var input = this._container.querySelector('input');
this.geosearch(input.value);
},
onInput: function() {
if (this._isShowingError) {
// show glass icon
this.resetLink('glass');
var message = this._container.querySelector('.message');
L.DomUtil.addClass(message, 'displayNone'); // hide message
this._isShowingError = false;
}
},
onKeyPress: function (e) {
var enterKey = 13;
if (e.keyCode === enterKey) {
L.DomEvent.preventDefault(e); // prevent default form submission
this.startSearch();
}
},
onKeyUp: function (e) {
var escapeKey = 27;
if (e.keyCode === escapeKey) {
this.cancelSearch();
}
}
});
/**
* L.Control.GeoSearch - search for an address and zoom to it's location
* L.GeoSearch.Provider.OpenStreetMap uses openstreetmap geocoding service
* https://github.com/smeijer/leaflet.control.geosearch
*/
L.GeoSearch.Provider.Nominatim = L.Class.extend({
options: {
},
initialize: function(options) {
options = L.Util.setOptions(this, options);
},
GetLocations: function(query, map, callback) {
callback = callback || function() {};
var url = this.GetServiceUrl(query);
$.getJSON(url, function (data) {
var results;
try {
results = this.ParseJSON(data);
} catch (err) {
return callback(err);
}
if (data.length > 0) {
var bbox = data[0].boundingbox,
viewport = [
[bbox[0], bbox[2]],
[bbox[1], bbox[3]]
];
map.fitBounds(viewport, {
maxZoom: 15
});
}
return callback(null, results);
}.bind(this));
},
GetServiceUrl: function (qry) {
var parameters = L.Util.extend({
q: qry,
format: 'json'
}, this.options);
return 'http://nominatim.openstreetmap.org/search'
+ L.Util.getParamString(parameters);
},
ParseJSON: function (data) {
if (data.length == 0)
return [];
var results = [];
for (var i = 0; i < data.length; i++)
results.push(new L.GeoSearch.Result(
data[i].lon,
data[i].lat,
data[i].display_name
));
return results;
}
});
.displayNone {
display: none;
}
.leaflet-control-geosearch {
position: relative;
}
.leaflet-control-geosearch a {
-webkit-border-radius: 4px;
border-radius: 4px;
border-bottom: none;
}
.leaflet-control-geosearch a.glass {
background-image: url(geosearch.png);
background-size: 100% 100%;
}
.leaflet-control-geosearch a.spinner {
background-image: url(spinner.gif);
background-position: 50% 50%;
}
.leaflet-control-geosearch a.alert {
background-image: url(alert.png);
background-size: 64% 64%;
}
.leaflet-control-geosearch a:hover {
border-bottom: none;
}
.leaflet-control-geosearch form {
position: absolute;
top: 0;
left: 22px;
box-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
-webkit-border-radius: 4px;
border-radius: 0px 4px 4px 0px;
z-index: -1;
background: #FFF;
height: 26px;
padding: 0 6px 0 6px;
}
.leaflet-control-geosearch form input {
width: 200px;
border: none;
outline: none;
margin: 0;
padding: 0;
font-size: 12px;
margin-top: 5px;
}
.leaflet-control-geosearch .message {
position: absolute;
top: 26px;
left: 0px;
width: 226px;
color: #FFF;
background: rgb(40, 40, 40);
padding: 4px 0 4px 8px;
}
(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) {
this.onHashChange = L.Util.bind(this.onHashChange, this);
if (map) {
this.init(map);
}
};
L.Hash.parseHash = function(hash) {
if(hash.indexOf('#') === 0) {
hash = hash.substr(1);
}
var args = hash.split("/");
if (args.length == 3) {
var zoom = parseInt(args[0], 10),
lat = parseFloat(args[1]),
lon = parseFloat(args[2]);
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
return false;
} else {
return {
center: new L.LatLng(lat, lon),
zoom: zoom
};
}
} 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));
return "#" + [zoom,
center.lat.toFixed(precision),
center.lng.toFixed(precision)
].join("/");
},
L.Hash.prototype = {
map: null,
lastHash: null,
parseHash: L.Hash.parseHash,
formatHash: L.Hash.formatHash,
init: function(map) {
this.map = map;
// 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) {
this.movingMap = true;
this.map.setView(parsed.center, parsed.zoom, { animate: false });
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", 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", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
}
this.isListening = false;
}
};
L.hash = function(map) {
return new L.Hash(map);
};
L.Map.prototype.addHash = function() {
this._hash = L.hash(this);
};
L.Map.prototype.removeHash = function() {
this._hash.removeFrom();
};
})(window);
GIF89a � ������DBD�����줦����|z|,*,������������\Z\������|~|<><��������� !�NETSCAPE2.0 !� , 6`%�di�h:2�.��>B�I#5�
E@����� �"@0H$D�P:0� !� , ����������DBD���������lnl���$"$���������\^\���������|~|��� 4�$�di�h��F`�
���x���NP��!!
���@ 8D��!�<�B��l�B !� , �������DBD��伺�tvt���������,*,������|~|���\Z\��켾�|z|������<:<������ 4�%�dYJ�a� (�EU���O���ߊb�
�B)�`@$�@ �
�Ų� � !� , ����������DBD������dbd���������|~|$"$���������\^\������lnl������ 6`%V�cZ $P��LD�:R��#��� �����T�"PA��cip�� !� , �������DBD�����줦�tvt,*,������������|~|������\Z\������|z|<:<������ 8�%Z
)�(!�A
�<" ��4M
CTIL#E%U)(���"��J#���z���( !� , ����������DBD���dbd���������|~|$"$���������\^\���lnl��������� 5 %R�8�X �(�$�C�9� &�b$(H(� d�b. �������v��z�! !� , ������̼��DBD��줦����|z|,*,��������������Լ��\Z\������|~|<><������ 7�%Z̲0����<Gj�$�h:LJ%�����@(���T�"@�� �DiH ��Q !� , ����������DBD���������lnl������$"$���������\^\���������|~|��� 4 %���QI
Pq�bT40�@CM1���� �%��A C��H �@� ��B ;