block by almccon 6057fcb80458c1dc2903

Side by side Leaflet (Toner comparison)

Full Screen

Side by side leaflet (Toner comparison)

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

index.html

<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.js

/*
 * 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.geosearch.provider.nominatim.js

/**
 * 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;
    }
});

l.geosearch.toner.css

.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;
}

leaflet-hash.js

(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);

spinner.gif

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;