From cbae1d4496f9dcdf76f5dbcc20e6d98189301d61 Mon Sep 17 00:00:00 2001
From: Nikolaus Krismer <niko@krismer.de>
Date: Fri, 14 Feb 2014 17:54:41 +0100
Subject: [PATCH] now using requireJS to load js files (can be optimized using
 r.js for releases later on)

---
 src/main/webapp/index.html                    |  28 +-
 src/main/webapp/js/app.js                     |  46 ++
 src/main/webapp/js/guidelines.txt             |  13 +
 src/main/webapp/js/isochrone/configuration.js |  51 ++-
 src/main/webapp/js/isochrone/initHelper.js    | 112 ++---
 .../webapp/js/isochrone/searchExtender.js     | 132 +++---
 src/main/webapp/js/{ => lib}/console.js       |   0
 .../{ => lib}/jquery-ui-timepicker-addon.js   |   0
 src/main/webapp/js/map/bing.js                | 117 -----
 src/main/webapp/js/map/control/geosearch.js   | 412 +++++++++++++++++
 .../js/map/control/geosearchProvider/osm.js   |  97 ++++
 src/main/webapp/js/map/control/help.js        |  25 +
 src/main/webapp/js/map/control/settings.js    |  25 +
 src/main/webapp/js/map/controls.js            |  43 --
 src/main/webapp/js/map/geosearch.js           | 429 ------------------
 .../webapp/js/map/geosearch_provider_osm.js   |  81 ----
 src/main/webapp/js/map/google.js              | 176 -------
 src/main/webapp/js/map/ipLocator/ipLocator.js |  53 +++
 .../webapp/js/map/ipLocator/ipProvider.js     |  59 +++
 .../webapp/js/map/ipLocator/mapIncluder.js    |  49 ++
 src/main/webapp/js/map/isoMap.js              | 225 +++++++++
 src/main/webapp/js/map/isomap.js              | 212 ---------
 src/main/webapp/js/map/layer/bing.js          | 125 +++++
 src/main/webapp/js/map/layer/google.js        | 201 ++++++++
 src/main/webapp/js/map/locatebyip.js          | 161 -------
 .../webapp/js/service/serviceConfiguration.js | 124 ++---
 src/main/webapp/js/service/websocket.js       | 106 ++---
 src/main/webapp/js/versions.txt               |   2 +-
 28 files changed, 1611 insertions(+), 1493 deletions(-)
 create mode 100644 src/main/webapp/js/app.js
 create mode 100644 src/main/webapp/js/guidelines.txt
 rename src/main/webapp/js/{ => lib}/console.js (100%)
 rename src/main/webapp/js/{ => lib}/jquery-ui-timepicker-addon.js (100%)
 delete mode 100644 src/main/webapp/js/map/bing.js
 create mode 100644 src/main/webapp/js/map/control/geosearch.js
 create mode 100644 src/main/webapp/js/map/control/geosearchProvider/osm.js
 create mode 100644 src/main/webapp/js/map/control/help.js
 create mode 100644 src/main/webapp/js/map/control/settings.js
 delete mode 100644 src/main/webapp/js/map/controls.js
 delete mode 100644 src/main/webapp/js/map/geosearch.js
 delete mode 100644 src/main/webapp/js/map/geosearch_provider_osm.js
 delete mode 100644 src/main/webapp/js/map/google.js
 create mode 100644 src/main/webapp/js/map/ipLocator/ipLocator.js
 create mode 100644 src/main/webapp/js/map/ipLocator/ipProvider.js
 create mode 100644 src/main/webapp/js/map/ipLocator/mapIncluder.js
 create mode 100644 src/main/webapp/js/map/isoMap.js
 delete mode 100644 src/main/webapp/js/map/isomap.js
 create mode 100644 src/main/webapp/js/map/layer/bing.js
 create mode 100644 src/main/webapp/js/map/layer/google.js
 delete mode 100644 src/main/webapp/js/map/locatebyip.js

diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
index 6d980d68..f2cec7f7 100644
--- a/src/main/webapp/index.html
+++ b/src/main/webapp/index.html
@@ -14,34 +14,8 @@
     <link type="text/css" rel="stylesheet" href="http://domoritz.de/leaflet-locatecontrol/src/L.Control.Locate.ie.css" />
     <![endif]-->
 
+	<script data-main="js/app" src="js/lib/require.js"></script>
 	<script type="text/javascript" src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
-	<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
-	<script type="text/javascript" src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
-	<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
-	<script type="text/javascript" src="http://maps.stamen.com/js/tile.stamen.js"></script>
-	<script type="text/javascript" src="http://domoritz.de/leaflet-locatecontrol/src/L.Control.Locate.js"></script>
-	<script type="text/javascript" src="js/console.js"></script>
-	<script type="text/javascript" src="js/jquery-ui-timepicker-addon.js"></script>
-	<script type="text/javascript" src="js/map/locatebyip.js"></script>
-	<script type="text/javascript" src="js/map/geosearch.js"></script>
-	<script type="text/javascript" src="js/map/geosearch_provider_osm.js"></script>
-	<script type="text/javascript" src="js/map/controls.js"></script>
-	<script type="text/javascript" src="js/map/bing.js"></script>
-	<script type="text/javascript" src="js/map/google.js"></script>
-	<script type="text/javascript" src="js/map/isomap.js"></script>
-	<script type="text/javascript" src="js/service/serviceConfiguration.js"></script>
-	<script type="text/javascript" src="js/service/websocket.js"></script>
-	<script type="text/javascript" src="js/isochrone/configuration.js"></script>
-	<script type="text/javascript" src="js/isochrone/initHelper.js"></script>
-	<script type="text/javascript" src="js/isochrone/searchExtender.js"></script>
-	<script type="text/javascript">
-		$(function() {
-			var h = new InitHelper();
-
-			h.initPreferences();
-			h.initMap();
-		});
-	</script>
 </head>
 
 <body>
diff --git a/src/main/webapp/js/app.js b/src/main/webapp/js/app.js
new file mode 100644
index 00000000..e628c5dd
--- /dev/null
+++ b/src/main/webapp/js/app.js
@@ -0,0 +1,46 @@
+require.config({
+	baseUrl: 'js',
+	paths: {
+		'console': 'lib/console',
+		'jQuery': '//code.jquery.com/jquery-2.1.0.min',
+		'jQueryUI': '//code.jquery.com/ui/1.10.4/jquery-ui',
+		'jQueryUI-timepicker': 'lib/jquery-ui-timepicker-addon',
+		'leaflet': '//cdn.leafletjs.com/leaflet-0.7.2/leaflet',
+		'locateControl': '//domoritz.de/leaflet-locatecontrol/src/L.Control.Locate',
+		'stamenMap': '//maps.stamen.com/js/tile.stamen'
+	},
+	shim: {
+		'console': {
+			exports: 'console'
+		},
+		'jQuery': {
+			exports: '$'
+		},
+		'jQueryUI': {
+			deps: ['jQuery'],
+		},
+		'jQueryUI-timepicker': {
+			deps: ['jQueryUI'],
+		},
+		'leaflet': {
+			exports: 'L'
+		},
+		'jquery-ui-timepicker-addon': {
+			deps: ['jQueryUI']
+		},
+		'locateControl': {
+			deps: ['leaflet']
+		},
+		'stamenMap': {
+			deps: ['leaflet']
+		}
+	}
+});
+
+require(['jQuery', 'isochrone/initHelper', 'locateControl', 'map/ipLocator/mapIncluder'], function($, InitHelper) {
+	$(function() {
+		var h = new InitHelper();
+		h.initPreferences();
+		h.initMap();
+	});
+});
diff --git a/src/main/webapp/js/guidelines.txt b/src/main/webapp/js/guidelines.txt
new file mode 100644
index 00000000..a335dafa
--- /dev/null
+++ b/src/main/webapp/js/guidelines.txt
@@ -0,0 +1,13 @@
+JS coding guidelines:
+---------------------
+
+ - Use jsDoc and document your methods
+ - Use the gradle task "jsDoc" to check the code documentation
+ - Use the gradle task "jsHint" to check the coding styleguide
+
+Class Design:
+-------------
+https://gist.github.com/lucastan/5421897
+
+=> Pattern 1: Self-construction used and applied to AMD
+(for a documented example see IsoMap class in js/map/isoMap.js)
diff --git a/src/main/webapp/js/isochrone/configuration.js b/src/main/webapp/js/isochrone/configuration.js
index 05bd67f4..6d54ed3e 100644
--- a/src/main/webapp/js/isochrone/configuration.js
+++ b/src/main/webapp/js/isochrone/configuration.js
@@ -2,31 +2,38 @@
  * Singleton class configuration.
  * This is used to store the client's configuration
  */
-function Configuration() {
-	if (arguments.callee._singletonInstance) {
-		return arguments.callee._singletonInstance;
-	}
-	arguments.callee._singletonInstance = this;
+define(['console'], function(logger) {
+	instance = null;
 
-	var datasetConfigMap = {};
+	function Configuration() {
+		var datasetConfigMap = {};
 
-	this.addDatasetConfig = function(dSetConfig) {
-		if (!dSetConfig || !dSetConfig.datasetName) {
-			console.warn('Not adding invalid dataset configuration to client configuration singleton');
-			console.debug(' - dSetConfig given:', dSetConfig);
-			return;
-		}
+		this.addDatasetConfig = function(dSetConfig) {
+			if (!dSetConfig || !dSetConfig.datasetName) {
+				logger.warn('Not adding invalid dataset configuration to client configuration singleton');
+				logger.debug(' - dSetConfig given:', dSetConfig);
+				return;
+			}
 
-		datasetConfigMap[dSetConfig.datasetName] = dSetConfig;
-	};
+			datasetConfigMap[dSetConfig.datasetName] = dSetConfig;
+		};
 
-	this.getDatasetConfigMap = function() {
-		return datasetConfigMap;
-	};
+		this.getDatasetConfigMap = function() {
+			return datasetConfigMap;
+		};
 
-	this.getDatasetConfig = function(datasetName) {
-		return datasetConfigMap[datasetName];
-	};
-};
+		this.getDatasetConfig = function(datasetName) {
+			return datasetConfigMap[datasetName];
+		};
+	}
+
+	return {
+		getInstance: function() {
+			if (instance == null) {
+				instance = new Configuration();
+			}
 
-new Configuration();
+			return instance;
+		}
+	};
+});
diff --git a/src/main/webapp/js/isochrone/initHelper.js b/src/main/webapp/js/isochrone/initHelper.js
index a50e92d9..b5e64053 100644
--- a/src/main/webapp/js/isochrone/initHelper.js
+++ b/src/main/webapp/js/isochrone/initHelper.js
@@ -1,55 +1,61 @@
-function InitHelper() {
-	var config = null;
-	var isoMap = null;
-	var ws = null;
-
-	// Public methods
-
-	this.initMap = function() {
-		isoMap = new IsoMap('map');
-		isoMap.locateAndDraw();
-
-		ws = new Websocket();
-		config = new ServiceConfiguration(ws);
-		config.getFromServer();
-
-		$(document).on('isomap_draw', extendSearch.bind(this));
+define(['jQuery', 'console', 'map/isoMap', 'service/websocket', 'service/serviceConfiguration', 'isochrone/searchExtender', 'jQueryUI', 'jQueryUI-timepicker'], function($, logger, IsoMap, Websocket, ServiceConfiguration, SearchExtender) {
+	function InitHelper() {
+		var config = null;
+		var isoMap = null;
+		var ws = null;
+
+		// Public methods
+
+		this.initMap = function() {
+			logger.debug('Initializing map');
+
+			isoMap = new IsoMap('map');
+			isoMap.locateAndDraw();
+
+			ws = new Websocket();
+			config = new ServiceConfiguration(ws);
+			config.getFromServer();
+
+			$(document).on('isomap_draw', extendSearch.bind(this));
+		};
+
+		this.initPreferences = function() {
+			$('#help-dialog').dialog({
+				autoOpen: false,
+				modal: true,
+				resizable: false
+			});
+
+			$('#settings-dialog').dialog({
+				autoOpen: false,
+				modal: true,
+				resizable: false
+			});
+
+			$('#speed').spinner({
+				 step: 0.1,
+				 numberFormat: 'n'
+			});
+
+		    $('#settings-dialog select').css({'width':'100%'});
+		    $('#settings-dialog input').css({'width':'100%'});
+			$('#datetime').datetimepicker({
+				dateFormat: 'dd.mm.yy',
+				timeFormat: 'HH:mm'
+			});
+			$('#datetime .ui-datepicker-today').click();
+		};
+
+		// Private methods
+
+		extendSearch = function() {
+			var mapElem = isoMap.getMap(),
+				searchExtender;
+
+			searchExtender = new SearchExtender(mapElem);
+			searchExtender.extendResult(isoMap.getMap());
+		};
 	};
 
-	this.initPreferences = function() {
-		$('#help-dialog').dialog({
-			autoOpen: false,
-			modal: true,
-			resizable: false
-		});
-
-		$('#settings-dialog').dialog({
-			autoOpen: false,
-			modal: true,
-			resizable: false
-		});
-
-		$('#speed').spinner({
-			 step: 0.1,
-			 numberFormat: 'n'
-		});
-
-	    $('#settings-dialog select').css({'width':'100%'});
-	    $('#settings-dialog input').css({'width':'100%'});
-		$('#datetime').datetimepicker({
-			dateFormat: 'dd.mm.yy',
-			timeFormat: 'HH:mm'
-		});
-		$('#datetime .ui-datepicker-today').click();
-	};
-
-	// Private methods
-
-	extendSearch = function() {
-		var mapElem = isoMap.getMap(),
-			searchExtender;
-
-		searchExtender = new SearchExtender(mapElem);
-		searchExtender.extendResult(isoMap.getMap());
-	};
-};
\ No newline at end of file
+	return InitHelper;
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/isochrone/searchExtender.js b/src/main/webapp/js/isochrone/searchExtender.js
index 59b95d96..8d08ed42 100644
--- a/src/main/webapp/js/isochrone/searchExtender.js
+++ b/src/main/webapp/js/isochrone/searchExtender.js
@@ -1,76 +1,88 @@
-function SearchExtender(map) {
-	var geosearchEventName = 'geosearch_showresult';
-	var m = null;
+define(['jQuery', 'map/isoMap', 'isochrone/configuration'], function($, isoMap, Configuration) {
+	function SearchExtender(map) {
+		var geosearchEventName = 'geosearch_showresult';
+		var m = null;
 
-	// Constructors
+		// Constructors
 
-	m = map;
+		m = map;
 
-	// Public methods
+		this.extendResult = function() {
+			m.on(geosearchEventName, onSearchResult.bind(this));
+		};
 
-	this.extendResult = function() {
-		m.on(geosearchEventName, onSearchResult.bind(this));
-	};
+		// Private methods
 
-	// Private methods
+		getDSetConfigs = function(point) {
+			var config = Configuration.getInstance(),
+				configCandidates = [],
+				datasetConfigs = config.getDatasetConfigMap();
 
-	isochroneAvailable = function(point) {
-		var config = Configuration(),
-			configCandidates = [],
-			datasetConfigs = config.getDatasetConfigMap();
+			for (var dSetConfigName in datasetConfigs) {
+				var dSetConfig = datasetConfigs[dSetConfigName];
+				if (dSetConfig.latLngBBox.contains(point)) {
+					configCandidates[configCandidates.length] = dSetConfig;
+				}
+			}
 
-		for (var dSetConfigName in datasetConfigs) {
-			var dSetConfig = datasetConfigs[dSetConfigName];
-			if (dSetConfig.latLngBBox.contains(point)) {
-				configCandidates[configCandidates.length] = dSetConfig;
+			// Check if only one datasets bbox contains the query point.
+			// If so... return the matching dataset config
+			var resultLength = configCandidates.length;
+			if (resultLength == 0) {
+				return null;
 			}
-		}
-
-		// Check if only one datasets bbox contains the query point.
-		// If so... return the matching dataset config
-		var resultLength = configCandidates.length;
-		if (resultLength == 1) {
-			// TODO: Should we combine this with the dataset dropdown in the settings?
-			// Should we remove the combobox?
-			return configCandidates[0];
-		}
-
-		// at least two datasets are available for isochrone configuration
-
-		// FIXME: How do we handle this? Should we combine this with the dataset dropdown in the settings (if so... how)
-		// By now we use the smallest dataset containing the point
-		var tmpCandidate = configCandidates[0];
-		var tmpSize = tmpCandidate.bBox.getSize();
-		for (var i = 1; i < resultLength; ++i) {
-			var cSize = configCandidates[i].bBox.getSize();
-			if (cSize.x * cSize.y  < tmpSize.x * tmpSize.y) {
-				tmpCandidate = configCandidates[i];
-				tmpSize = cSize;
+
+			if (resultLength == 1) {
+				// TODO: Should we combine this with the dataset dropdown in the settings?
+				// Should we remove the combobox?
+				return configCandidates[0];
 			}
-		}
 
-		return tmpCandidate;
-	};
+			// at least two datasets are available for isochrone configuration
+
+			// FIXME: How do we handle this? Should we combine this with the dataset dropdown in the settings (if so... how)
+			// By now we use the smallest dataset containing the point
+			var tmpCandidate = configCandidates[0];
+			var tmpSize = tmpCandidate.bBox.getSize();
+			for (var i = 1; i < resultLength; ++i) {
+				var cSize = configCandidates[i].bBox.getSize();
+				if (cSize.x * cSize.y  < tmpSize.x * tmpSize.y) {
+					tmpCandidate = configCandidates[i];
+					tmpSize = cSize;
+				}
+			}
 
-	onSearchResult = function(data) {
-		var divIcons,
-        	iconIsochrone = null,
-        	li;
+			return tmpCandidate;
+		};
 
-		if (!data.queryPoint || !isochroneAvailable(data.queryPoint)) {
-			return;
-		}
+		onSearchResult = function(data) {
+			var divIcons,
+				dSetConfigs = null,
+	        	iconIsochrone = null,
+	        	li;
 
-		divIcons = document.createElement('div');
-		li = $(data.element).find('li:first div');
+			if (!data.queryPoint) {
+				return;
+			}
+
+			dSetConfigs = getDSetConfigs(data.queryPoint);
+			if (!dSetConfigs) {
+				return;
+			}
 
-		iconIsochrone = document.createElement('div');
-		iconIsochrone.id = 'icon-isochrone';
-		iconIsochrone.className = 'icon-isochrone';
-		iconIsochrone.appendChild(document.createTextNode('Isochrone'));
-		divIcons.className = 'geosearch-result-icons';
-		divIcons.appendChild(iconIsochrone);
+			divIcons = document.createElement('div');
+			li = $(data.element).find('li:first div');
 
-		$(divIcons).insertBefore(li);
+			iconIsochrone = document.createElement('div');
+			iconIsochrone.id = 'icon-isochrone';
+			iconIsochrone.className = 'icon-isochrone';
+			iconIsochrone.appendChild(document.createTextNode('Isochrone'));
+			divIcons.className = 'geosearch-result-icons';
+			divIcons.appendChild(iconIsochrone);
+
+			$(divIcons).insertBefore(li);
+		};
 	};
-};
+
+	return SearchExtender;
+});
diff --git a/src/main/webapp/js/console.js b/src/main/webapp/js/lib/console.js
similarity index 100%
rename from src/main/webapp/js/console.js
rename to src/main/webapp/js/lib/console.js
diff --git a/src/main/webapp/js/jquery-ui-timepicker-addon.js b/src/main/webapp/js/lib/jquery-ui-timepicker-addon.js
similarity index 100%
rename from src/main/webapp/js/jquery-ui-timepicker-addon.js
rename to src/main/webapp/js/lib/jquery-ui-timepicker-addon.js
diff --git a/src/main/webapp/js/map/bing.js b/src/main/webapp/js/map/bing.js
deleted file mode 100644
index 03f8845b..00000000
--- a/src/main/webapp/js/map/bing.js
+++ /dev/null
@@ -1,117 +0,0 @@
-L.BingLayer = L.TileLayer.extend({
-	options: {
-		subdomains: [0, 1, 2, 3],
-		type: 'Aerial',
-		attribution: 'Bing',
-		culture: ''
-	},
-
-	initialize: function(key, options) {
-		L.Util.setOptions(this, options);
-
-		this._key = key;
-		this._url = null;
-		this.meta = {};
-		this.loadMetadata();
-	},
-
-	tile2quad: function(x, y, z) {
-		var quad = '';
-		for (var i = z; i > 0; i--) {
-			var digit = 0;
-			var mask = 1 << (i - 1);
-			if ((x & mask) != 0) digit += 1;
-			if ((y & mask) != 0) digit += 2;
-			quad = quad + digit;
-		}
-		return quad;
-	},
-
-	getTileUrl: function(p, z) {
-		var z = this._getZoomForUrl();
-		var subdomains = this.options.subdomains,
-			s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)];
-		return this._url.replace('{subdomain}', s)
-				.replace('{quadkey}', this.tile2quad(p.x, p.y, z))
-				.replace('{culture}', this.options.culture);
-	},
-
-	loadMetadata: function() {
-		var _this = this;
-		var cbid = '_bing_metadata_' + L.Util.stamp(this);
-		window[cbid] = function (meta) {
-			_this.meta = meta;
-			window[cbid] = undefined;
-			var e = document.getElementById(cbid);
-			e.parentNode.removeChild(e);
-			if (meta.errorDetails) {
-				alert("Got metadata" + meta.errorDetails);
-				return;
-			}
-			_this.initMetadata();
-		};
-		var url = "http://dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.options.type + "?include=ImageryProviders&jsonp=" + cbid + "&key=" + this._key;
-		var script = document.createElement("script");
-		script.type = "text/javascript";
-		script.src = url;
-		script.id = cbid;
-		document.getElementsByTagName("head")[0].appendChild(script);
-	},
-
-	initMetadata: function() {
-		var r = this.meta.resourceSets[0].resources[0];
-		this.options.subdomains = r.imageUrlSubdomains;
-		this._url = r.imageUrl;
-		this._providers = [];
-		for (var i = 0; i < r.imageryProviders.length; i++) {
-			var p = r.imageryProviders[i];
-			for (var j = 0; j < p.coverageAreas.length; j++) {
-				var c = p.coverageAreas[j];
-				var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
-				var bounds = new L.LatLngBounds(
-						new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01),
-						new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01)
-				);
-				coverage.bounds = bounds;
-				coverage.attrib = p.attribution;
-				this._providers.push(coverage);
-			}
-		}
-		this._update();
-	},
-
-	_update: function() {
-		if (this._url == null || !this._map) return;
-		this._update_attribution();
-		L.TileLayer.prototype._update.apply(this, []);
-	},
-
-	_update_attribution: function() {
-		var bounds = this._map.getBounds();
-		var zoom = this._map.getZoom();
-		for (var i = 0; i < this._providers.length; i++) {
-			var p = this._providers[i];
-			if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
-					bounds.intersects(p.bounds)) {
-				if (!p.active)
-					this._map.attributionControl.addAttribution(p.attrib);
-				p.active = true;
-			} else {
-				if (p.active)
-					this._map.attributionControl.removeAttribution(p.attrib);
-				p.active = false;
-			}
-		}
-	},
-
-	onRemove: function(map) {
-		for (var i = 0; i < this._providers.length; i++) {
-			var p = this._providers[i];
-			if (p.active) {
-				this._map.attributionControl.removeAttribution(p.attrib);
-				p.active = false;
-			}
-		}
-        	L.TileLayer.prototype.onRemove.apply(this, [map]);
-	}
-});
\ No newline at end of file
diff --git a/src/main/webapp/js/map/control/geosearch.js b/src/main/webapp/js/map/control/geosearch.js
new file mode 100644
index 00000000..0e65ce23
--- /dev/null
+++ b/src/main/webapp/js/map/control/geosearch.js
@@ -0,0 +1,412 @@
+/**
+ * GeoSearch - search for an address and zoom to its location
+ *
+ * @see https://github.com/smeijer/leaflet.control.geosearch
+ */
+define(['leaflet'], function(L) {
+	L.Control.GeoSearch = L.Control.extend({
+	    options: {
+	        doReverseLookup: false,
+	        position: 'topleft',
+	        provider: null,
+	        showMarker: true
+	    },
+
+	    _config: {
+	        country: '',
+	        searchLabel: 'Search for address ...',
+	        notFoundMessage: 'Sorry, that address could not be found.',
+	        messageHideDelay: 3000,
+	        zoomLevel: 18
+	    },
+
+	    // Public methods
+
+	    initialize: function (options) {
+	        L.Util.extend(this.options, options);
+	        L.Util.extend(this._config, options);
+	    },
+
+	    geosearch: function (qry) {
+	        try {
+	            var provider = this._config.provider;
+
+	            if(typeof provider.GetLocations == 'function') {
+	                provider.GetLocations(qry, function(results) {
+	                    this._processResults(results);
+	                }.bind(this));
+	            } else {
+	                var url = provider.GetServiceUrl(qry);
+	                this.sendRequest(provider, url);
+	            }
+	        } catch (error) {
+	            this._printError(error);
+	        }
+	    },
+
+	    onAdd: function (map) {
+	        this._map = map;
+	        this._container = L.DomUtil.create('div', 'leaflet-control-geosearch');
+	        this._suggestSet = [];
+
+	        var searchdiv = document.createElement('div');
+	        searchdiv.id = 'leaflet-control-geosearch-searchdiv';
+	        searchdiv.className = 'leaflet-control-geosearch-searchdiv';
+	        this._searchdiv = searchdiv;
+
+	        var searchbox = document.createElement('input');
+	        searchbox.id = 'leaflet-control-geosearch-qry';
+	        searchbox.type = 'text';
+	        searchbox.placeholder = this._config.searchLabel;
+	        this._searchbox = searchbox;
+
+	        var searchbtn = document.createElement('button');
+	        searchbtn.id = 'leaflet-control-geosearch-btn';
+	        searchbtn.className = 'leaflet-control-geosearch-btn';
+	        this._searchbtn = searchbtn;
+
+	        var msgbox = document.createElement('div');
+	        msgbox.id = 'leaflet-control-geosearch-msg';
+	        msgbox.className = 'leaflet-control-geosearch-msg';
+	        this._msgbox = msgbox;
+
+	        var result = document.createElement('ul');
+	        result.id = 'leaflet-control-geosearch-result';
+	        result.className = 'leaflet-control-geosearch-result';
+	        this._result = result;
+
+	        var suggestlist = document.createElement('ul');
+	        suggestlist.id = 'leaflet-control-geosearch-suggests';
+	        suggestlist.className = 'leaflet-control-geosearch-suggests';
+	        this._suggestlist = suggestlist;
+
+	        this._msgbox.appendChild(this._suggestlist);
+	        this._msgbox.appendChild(this._result);
+	        this._searchdiv.appendChild(this._searchbtn);
+	        this._searchdiv.appendChild(this._searchbox);
+	        this._container.appendChild(this._searchdiv);
+	        this._container.appendChild(this._msgbox);
+
+	        L.DomEvent
+	          .addListener(this._container, 'click', L.DomEvent.stop)
+	          .addListener(this._searchbtn, 'click', this._onSearchClick, this)
+	          .addListener(this._searchbox, 'keypress', this._onKeyUp, this);
+
+	        if (this._config.doReverseLookup && this._config.provider.options.reverseable) {
+	            L.DomEvent.addListener(this._map, 'click', this._onMapClick, this);
+	        }
+
+	        L.DomEvent.disableClickPropagation(this._container);
+
+	        return this._container;
+	    },
+
+	    sendRequest: function (provider, url) {
+	        var that = this;
+
+	        window.parseLocation = function (response) {
+	            var results = provider.ParseJSON(response);
+	            that._processResults(results);
+
+	            document.body.removeChild(document.getElementById('getJsonP'));
+	            delete window.parseLocation;
+	        };
+
+	        function getJsonP (url) {
+	            url = url + '&callback=parseLocation';
+	            var script = document.createElement('script');
+	            script.id = 'getJsonP';
+	            script.src = url;
+	            script.async = true;
+	            document.body.appendChild(script);
+	        }
+
+	        if (XMLHttpRequest) {
+	            var xhr = new XMLHttpRequest();
+
+	            if ('withCredentials' in xhr) {
+	                var xhr = new XMLHttpRequest();
+
+	                xhr.onreadystatechange = function () {
+	                    if (xhr.readyState == 4) {
+	                        if (xhr.status == 200) {
+	                            var response = JSON.parse(xhr.responseText),
+	                                results = provider.ParseJSON(response);
+
+	                            that._processResults(results);
+	                        } else if (xhr.status == 0 || xhr.status == 400) {
+	                            getJsonP(url);
+	                        } else {
+	                            that._printError(xhr.responseText);
+	                        }
+	                    }
+	                };
+
+	                xhr.open('GET', url, true);
+	                xhr.send();
+	            } else if (XDomainRequest) {
+	                var xdr = new XDomainRequest();
+
+	                xdr.onerror = function (err) {
+	                    that._printError(err);
+	                };
+
+	                xdr.onload = function () {
+	                    var response = JSON.parse(xdr.responseText),
+	                        results = provider.ParseJSON(response);
+
+	                    that._processResults(results);
+	                };
+
+	                xdr.open('GET', url);
+	                xdr.send();
+	            } else {
+	                getJsonP(url);
+	            }
+	        }
+	    },
+
+	    // Private methods
+
+	    _addressToString : function(address) {
+	        if (!address) {
+	            return '';
+	        }
+
+	        var addressStr = '';
+	        if (address.road) addressStr += address.road + ', ';
+	        if (address.postcode) addressStr += address.postcode + ', ';
+	        if (address.country) addressStr += address.country;
+	        if (/, $/.test(addressStr)) addressStr = addressStr.substring(0, addressStr.length - 2);
+
+	        return addressStr;
+	    },
+
+	    _clearResult : function() {
+	        if (this._timeout != null) {
+	            clearTimeout(this._timeout);
+	            this._timeout = null;
+	        }
+
+	        var elem = this._result;
+	        elem.innerHTML = '';
+	        elem.style.display = 'none';
+	    },
+
+	    _clearSuggestList : function() {
+	        this._suggestSet = [];
+	        if (this._timeout != null) {
+	            clearTimeout(this._timeout);
+	            this._timeout = null;
+	        }
+
+	        var elem = this._suggestlist;
+	        elem.innerHTML = '';
+	        elem.style.display = 'none';
+	    },
+
+	    _isInSuggestSet : function(o) {
+	        var i = 0,
+	            set = this._suggestSet;
+
+	        for (i = 0; i < set.length; ++i) {
+	            if (set[i].lat == o.lat && set[i].lon == o.lon) {
+	                return true;
+	            }
+	        }
+
+	        return false;
+	    },
+
+	    _onKeyUp: function (e) {
+	        var esc = 27,
+	            enter = 13,
+	            queryBox = document.getElementById('leaflet-control-geosearch-qry');
+
+	        if (e.keyCode === esc) { // escape key detection is unreliable
+	            queryBox.value = '';
+	            this._map._container.focus();
+	        } else if (e.keyCode === enter) {
+	            this.geosearch(queryBox.value);
+	        }
+	    },
+
+	    _onMapClick: function (e) {
+	        var location = e.latlng,
+	            provider = this._config.provider;
+
+	        if (!location || !provider) {
+	            return;
+	        }
+
+	        try {
+	            var url = provider.GetServiceUrl(location.lat, location.lng);
+	            this.sendRequest(provider, url);
+	        } catch (error) {
+	            this._printError(error);
+	        }
+	    },
+
+	    _onSuggestClick : function (e) {
+	        var suggestId = e.target.id,
+	            suggestIndex = -1,
+	            suggest = null;
+
+	        if (suggestId) {
+	        	suggestIndex = suggestId.substring(suggestId.lastIndexOf('-') + 1);
+	        }
+
+	        if (suggestIndex >= 0) {
+	        	suggest = this._suggestSet[suggestIndex];
+	        }
+
+	        if (suggest) {
+	            this._showLocation(suggest);
+	        }
+	    },
+
+	    _onSearchClick: function (e) {
+	        var queryBox = document.getElementById('leaflet-control-geosearch-qry');
+
+	        this.geosearch(queryBox.value);
+	    },
+
+	    _processResults: function(results) {
+	        if (results instanceof Array) {
+	            this._processForwardResults(results);
+	        } else {
+	            this._processReverseResults(results);
+	        }
+	    },
+
+	    _processForwardResults: function(results) {
+	        var resultCount = results.length;
+	        if (resultCount <= 0) {
+	            this._printError(this._config.notFoundMessage);
+	            return;
+	        }
+
+	        this._map.fireEvent('geosearch_foundlocations', {Locations: results});
+	        if (resultCount == 1) {
+	            // only one result found... show it
+	            this._showLocation(results[0]);
+	        } else {
+	            // multiple results found... add to results list and let user choose which one to show
+	            this._setSuggestList(results);
+	        }
+	    },
+
+	    _processReverseResults: function(result) {
+	        if (!result) {
+	            this._printError(this._config.notFoundMessage);
+	        }
+
+	        this._map.fireEvent('geosearch_clicklocation', {Location: result});
+	        this._setResult(result);
+	    },
+
+	    _setResult: function(result) {
+	        var displayName = null,
+	            displayDescription = null,
+	            divText = document.createElement('div'),
+	            elem = this._result,
+	            index = -1;
+		        li = document.createElement('li'),
+		        txtDescription = null,
+		        txtName = null;
+
+	        if (result.lat == undefined || result.lon == undefined) {
+	            return;
+	        }
+
+	        this._clearResult();
+
+	        index = result.displayName.indexOf(',');
+	        displayName = result.displayName.substring(0, index);
+	        displayDescription = result.displayName.substring(index + 1);
+
+	        li.id = 'geosearch-result';
+	        li.className = 'geosearch-result';
+	        txtDescription = document.createElement('span');
+	        txtDescription.className = 'result-description';
+	        txtDescription.innerHTML = displayDescription.replace(/^\s+|\s+$/g,'');
+	        txtName = document.createElement('span');
+	        txtName.className = 'result-name';
+	        txtName.innerHTML = displayName.replace(/^\s+|\s+$/g,'');
+	        divText.className = 'geosearch-result-text';
+	        divText.appendChild(txtName);
+	        divText.appendChild(txtDescription);
+	        li.appendChild(divText);
+
+	        elem.appendChild(li);
+		    elem.style.display = 'block';
+
+		    this._map.fireEvent('geosearch_showresult', {element: elem, queryPoint: L.latLng({lat: parseFloat(result.lat), lon: parseFloat(result.lon)})});
+	    },
+
+		_setSuggestList: function(results) {
+	        var elem = this._suggestlist,
+	            i = 0,
+	            j = 0,
+	            li = null,
+	            liText = null,
+	            r = null;
+
+	        this._clearSuggestList();
+
+	        for (i = 0, j = 0; i < results.length; ++i) {
+	            r = results[i];
+	            if (r.lat == undefined || r.lon == undefined) {
+	                continue;
+	            }
+
+	            if (this._isInSuggestSet(r)) {
+	                continue;
+	            }
+
+	            this._suggestSet[this._suggestSet.length] = r;
+	            li = document.createElement('li');
+	            li.id = 'geosearch-suggest-' + j;
+	            li.className = 'geosearch-suggest';
+	            liText = document.createTextNode(r.displayName);
+	            li.appendChild(liText);
+	            elem.appendChild(li);
+	            L.DomEvent.addListener(li, 'click', this._onSuggestClick, this);
+
+	            ++j;
+	        }
+
+	        elem.style.display = 'block';
+	    },
+
+	    _showLocation: function (location) {
+	    	this._setResult(location);
+	    	this._clearSuggestList();
+
+	        if (this.options.showMarker == true) {
+	            if (typeof this._positionMarker === 'undefined') {
+	                this._positionMarker = L.marker([location.lat, location.lon]).addTo(this._map);
+	            } else {
+	                this._positionMarker.setLatLng([location.lat, location.lon]);
+	            }
+	        }
+
+	        this._map.setView([location.lat, location.lon], this._config.zoomLevel, false);
+	        this._map.fireEvent('geosearch_showlocation', {Location: location});
+	    },
+
+	    _printError: function(message) {
+	        var elem = this._result;
+	        elem.innerHTML = '<li>' + message + '</li>';
+	        elem.style.display = 'block';
+
+	        var self = this;
+	        self._timeout = setTimeout(function () {
+	            elem.style.display = 'none';
+	            self._timeout = null;
+	        }, 3000);
+	    }
+	});
+
+	return L.Control.GeoSearch;
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/map/control/geosearchProvider/osm.js b/src/main/webapp/js/map/control/geosearchProvider/osm.js
new file mode 100644
index 00000000..4edad207
--- /dev/null
+++ b/src/main/webapp/js/map/control/geosearchProvider/osm.js
@@ -0,0 +1,97 @@
+/**
+ * Uses openstreetmap geocoding service for providing geosearch
+ *
+ * @see https://github.com/smeijer/leaflet.control.geosearch
+ */
+define(['leaflet'], function(L) {
+	ReverseResult = function (lon, lat, address, displayName) {
+	    this.lon = lon;
+	    this.lat = lat;
+	    this.address = address;
+	    this.displayName = displayName;
+	};
+
+	Result = function (lon, lat, displayName) {
+	    this.lon = lon;
+	    this.lat = lat;
+	    this.displayName = displayName;
+	};
+
+	OsmProvider = L.Class.extend({
+	    options : {
+	        reverseable: true
+	    },
+
+	    initialize : function(options) {
+	        options = L.Util.setOptions(this, options);
+	    },
+
+	    GetServiceUrl : function(qry, lon) {
+	        if (!lon) {
+	            // only one parameter given (query).. perform a forward lookup
+	            return this._getForwardServiceUrl(qry);
+	        }
+
+	        // second parameter set... perform a reverse lookup
+	        return this._getReverseServiceUrl(qry, lon);
+	    },
+
+	    ParseJSON : function(data) {
+	        if (data instanceof Array) {
+	            return this._parseForwardJSON(data);
+	        }
+
+	        return this._parseReverseJSON(data);
+	    },
+
+	    _getReverseServiceUrl : function(lat, lon) {
+	        var parameters = L.Util.extend({
+	            lat: lat,
+	            lon: lon,
+	            format : 'json'
+	        }, this.options);
+
+	        return 'http://nominatim.openstreetmap.org/reverse'
+	                + L.Util.getParamString(parameters);
+	    },
+
+	    _getForwardServiceUrl: function(qry) {
+	        var parameters = L.Util.extend({
+	            q : qry,
+	            format : 'json'
+	        }, this.options);
+
+	        return 'http://nominatim.openstreetmap.org/search'
+	                + L.Util.getParamString(parameters);
+	    },
+
+	    _parseForwardJSON : function(data) {
+	        if (data.length == 0)
+	            return [];
+
+	        var results = [];
+	        for (var i = 0; i < data.length; i++)
+	            results.push(new Result(
+	                data[i].lon,
+	                data[i].lat,
+	                data[i].display_name
+	            ));
+
+	        return results;
+	    },
+
+	    _parseReverseJSON : function(data) {
+	        if (data.length == 0)
+	            return {};
+
+	        return new ReverseResult(
+	            data.lon,
+	            data.lat,
+	            data.address,
+	            data.display_name
+	        );
+	    }
+	});
+
+	return OsmProvider;
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/map/control/help.js b/src/main/webapp/js/map/control/help.js
new file mode 100644
index 00000000..e4274c14
--- /dev/null
+++ b/src/main/webapp/js/map/control/help.js
@@ -0,0 +1,25 @@
+define(['leaflet', 'console'], function(L, logger) {
+	L.HelpControl = L.Control.extend({
+	    options: {
+	        position: 'bottomright'
+	    },
+
+	    onAdd: function (map) {
+	    	this._container = L.DomUtil.create('div', 'help-control');
+	    	this._container.id = 'help-control-div';
+	    	this._container.className = 'help-control-div';
+
+	    	L.DomEvent.addListener(this._container, 'click', this._onOpen);
+	    	L.DomEvent.disableClickPropagation(this._container);
+
+	        return this._container;
+	    },
+
+	    _onOpen: function() {
+	    	logger.log('Showing helper dialog');
+	    	$('#help-dialog').dialog('open');
+	    }
+	});
+
+	return L.HelpControl;
+});
diff --git a/src/main/webapp/js/map/control/settings.js b/src/main/webapp/js/map/control/settings.js
new file mode 100644
index 00000000..ad6b413b
--- /dev/null
+++ b/src/main/webapp/js/map/control/settings.js
@@ -0,0 +1,25 @@
+define(['leaflet', 'console'], function(L, logger) {
+	L.SettingsControl = L.Control.extend({
+	    options: {
+	        position: 'bottomright'
+	    },
+
+	    onAdd: function (map) {
+	    	this._container = L.DomUtil.create('div', 'settings-control');
+	    	this._container.id = 'settings-control-div';
+	    	this._container.className = 'settings-control-div';
+
+	    	L.DomEvent.addListener(this._container, 'click', this._onOpen);
+	    	L.DomEvent.disableClickPropagation(this._container);
+
+	        return this._container;
+	    },
+
+	    _onOpen: function() {
+	    	logger.log('Showing settings');
+	    	$('#settings-dialog').dialog('open');
+	    }
+	});
+
+	return L.SettingsControl;
+});
diff --git a/src/main/webapp/js/map/controls.js b/src/main/webapp/js/map/controls.js
deleted file mode 100644
index 83ab18eb..00000000
--- a/src/main/webapp/js/map/controls.js
+++ /dev/null
@@ -1,43 +0,0 @@
-var HelpControl = L.Control.extend({
-    options: {
-        position: 'bottomright'
-    },
-
-    onAdd: function (map) {
-    	this._container = L.DomUtil.create('div', 'help-control');
-    	this._container.id = 'help-control-div';
-    	this._container.className = 'help-control-div';
-
-    	L.DomEvent.addListener(this._container, 'click', this._onOpen);
-    	L.DomEvent.disableClickPropagation(this._container);
-
-        return this._container;
-    },
-
-    _onOpen: function() {
-    	console.log('Showing helper dialog');
-    	$('#help-dialog').dialog('open');
-    }
-});
-
-var SettingsControl = L.Control.extend({
-    options: {
-        position: 'bottomright'
-    },
-
-    onAdd: function (map) {
-    	this._container = L.DomUtil.create('div', 'settings-control');
-    	this._container.id = 'settings-control-div';
-    	this._container.className = 'settings-control-div';
-
-    	L.DomEvent.addListener(this._container, 'click', this._onOpen);
-    	L.DomEvent.disableClickPropagation(this._container);
-
-        return this._container;
-    },
-
-    _onOpen: function() {
-    	console.log('Showing settings');
-    	$('#settings-dialog').dialog('open');
-    }
-});
diff --git a/src/main/webapp/js/map/geosearch.js b/src/main/webapp/js/map/geosearch.js
deleted file mode 100644
index 0dd3c68d..00000000
--- a/src/main/webapp/js/map/geosearch.js
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * 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.ReverseProvider = {};
-
-L.GeoSearch.ReverseResult = function (lon, lat, address, displayName) {
-    this.lon = lon;
-    this.lat = lat;
-    this.address = address;
-    this.displayName = displayName;
-};
-
-L.GeoSearch.Result = function (lon, lat, displayName) {
-    this.lon = lon;
-    this.lat = lat;
-    this.displayName = displayName;
-};
-
-L.Control.GeoSearch = L.Control.extend({
-    options: {
-        doReverseLookup: false,
-        position: 'topleft',
-        provider: null,
-        showMarker: true
-    },
-
-    _config: {
-        country: '',
-        searchLabel: 'Search for address ...',
-        notFoundMessage: 'Sorry, that address could not be found.',
-        messageHideDelay: 3000,
-        zoomLevel: 18
-    },
-
-    // Public methods
-
-    initialize: function (options) {
-        L.Util.extend(this.options, options);
-        L.Util.extend(this._config, options);
-    },
-
-    geosearch: function (qry) {
-        try {
-            var provider = this._config.provider;
-
-            if(typeof provider.GetLocations == 'function') {
-                var results = provider.GetLocations(qry, function(results) {
-                    this._processResults(results);
-                }.bind(this));
-            } else {
-                var url = provider.GetServiceUrl(qry);
-                this.sendRequest(provider, url);
-            }
-        } catch (error) {
-            this._printError(error);
-        }
-    },
-
-    onAdd: function (map) {
-        var $controlContainer = map._controlContainer,
-            nodes = $controlContainer.childNodes;
-
-        this._map = map;
-        this._container = L.DomUtil.create('div', 'leaflet-control-geosearch');
-        this._suggestSet = [];
-
-        var searchdiv = document.createElement('div');
-        searchdiv.id = 'leaflet-control-geosearch-searchdiv';
-        searchdiv.className = 'leaflet-control-geosearch-searchdiv';
-        this._searchdiv = searchdiv;
-
-        var searchbox = document.createElement('input');
-        searchbox.id = 'leaflet-control-geosearch-qry';
-        searchbox.type = 'text';
-        searchbox.placeholder = this._config.searchLabel;
-        this._searchbox = searchbox;
-
-        var searchbtn = document.createElement('button');
-        searchbtn.id = 'leaflet-control-geosearch-btn';
-        searchbtn.className = 'leaflet-control-geosearch-btn';
-        this._searchbtn = searchbtn;
-
-        var msgbox = document.createElement('div');
-        msgbox.id = 'leaflet-control-geosearch-msg';
-        msgbox.className = 'leaflet-control-geosearch-msg';
-        this._msgbox = msgbox;
-
-        var result = document.createElement('ul');
-        result.id = 'leaflet-control-geosearch-result';
-        result.className = 'leaflet-control-geosearch-result';
-        this._result = result;
-
-        var suggestlist = document.createElement('ul');
-        suggestlist.id = 'leaflet-control-geosearch-suggests';
-        suggestlist.className = 'leaflet-control-geosearch-suggests';
-        this._suggestlist = suggestlist;
-
-        this._msgbox.appendChild(this._suggestlist);
-        this._msgbox.appendChild(this._result);
-        this._searchdiv.appendChild(this._searchbtn);
-        this._searchdiv.appendChild(this._searchbox);
-        this._container.appendChild(this._searchdiv);
-        this._container.appendChild(this._msgbox);
-
-        L.DomEvent
-          .addListener(this._container, 'click', L.DomEvent.stop)
-          .addListener(this._searchbtn, 'click', this._onSearchClick, this)
-          .addListener(this._searchbox, 'keypress', this._onKeyUp, this);
-
-        if (this._config.doReverseLookup && this._config.provider.options.reverseable) {
-            L.DomEvent.addListener(this._map, 'click', this._onMapClick, this);
-        }
-
-        L.DomEvent.disableClickPropagation(this._container);
-
-        return this._container;
-    },
-
-    sendRequest: function (provider, url) {
-        var that = this;
-
-        window.parseLocation = function (response) {
-            var results = provider.ParseJSON(response);
-            that._processResults(results);
-
-            document.body.removeChild(document.getElementById('getJsonP'));
-            delete window.parseLocation;
-        };
-
-        function getJsonP (url) {
-            url = url + '&callback=parseLocation';
-            var script = document.createElement('script');
-            script.id = 'getJsonP';
-            script.src = url;
-            script.async = true;
-            document.body.appendChild(script);
-        }
-
-        if (XMLHttpRequest) {
-            var xhr = new XMLHttpRequest();
-
-            if ('withCredentials' in xhr) {
-                var xhr = new XMLHttpRequest();
-
-                xhr.onreadystatechange = function () {
-                    if (xhr.readyState == 4) {
-                        if (xhr.status == 200) {
-                            var response = JSON.parse(xhr.responseText),
-                                results = provider.ParseJSON(response);
-
-                            that._processResults(results);
-                        } else if (xhr.status == 0 || xhr.status == 400) {
-                            getJsonP(url);
-                        } else {
-                            that._printError(xhr.responseText);
-                        }
-                    }
-                };
-
-                xhr.open('GET', url, true);
-                xhr.send();
-            } else if (XDomainRequest) {
-                var xdr = new XDomainRequest();
-
-                xdr.onerror = function (err) {
-                    that._printError(err);
-                };
-
-                xdr.onload = function () {
-                    var response = JSON.parse(xdr.responseText),
-                        results = provider.ParseJSON(response);
-
-                    that._processResults(results);
-                };
-
-                xdr.open('GET', url);
-                xdr.send();
-            } else {
-                getJsonP(url);
-            }
-        }
-    },
-
-    // Private methods
-
-    _addressToString : function(address) {
-        if (!address) {
-            return '';
-        }
-
-        var addressStr = '';
-        if (address.road) addressStr += address.road + ', ';
-        if (address.postcode) addressStr += address.postcode + ', ';
-        if (address.country) addressStr += address.country;
-        if (/, $/.test(addressStr)) addressStr = addressStr.substring(0, addressStr.length - 2);
-
-        return addressStr;
-    },
-
-    _clearResult : function() {
-        if (this._timeout != null) {
-            clearTimeout(this._timeout);
-            this._timeout = null;
-        }
-
-        var elem = this._result;
-        elem.innerHTML = '';
-        elem.style.display = 'none';
-    },
-
-    _clearSuggestList : function() {
-        this._suggestSet = [];
-        if (this._timeout != null) {
-            clearTimeout(this._timeout);
-            this._timeout = null;
-        }
-
-        var elem = this._suggestlist;
-        elem.innerHTML = '';
-        elem.style.display = 'none';
-    },
-
-    _isInSuggestSet : function(o) {
-        var i = 0,
-            set = this._suggestSet;
-
-        for (i = 0; i < set.length; ++i) {
-            if (set[i].lat == o.lat && set[i].lon == o.lon) {
-                return true;
-            }
-        }
-
-        return false;
-    },
-
-    _onKeyUp: function (e) {
-        var esc = 27,
-            enter = 13,
-            queryBox = document.getElementById('leaflet-control-geosearch-qry');
-
-        if (e.keyCode === esc) { // escape key detection is unreliable
-            queryBox.value = '';
-            this._map._container.focus();
-        } else if (e.keyCode === enter) {
-            this.geosearch(queryBox.value);
-        }
-    },
-
-    _onMapClick: function (e) {
-        var location = e.latlng,
-            provider = this._config.provider;
-
-        if (!location || !provider) {
-            return;
-        }
-
-        try {
-            var url = provider.GetServiceUrl(location.lat, location.lng);
-            this.sendRequest(provider, url);
-        } catch (error) {
-            this._printError(error);
-        }
-    },
-
-    _onSuggestClick : function (e) {
-        var suggestId = e.target.id,
-            suggestIndex = -1,
-            suggest = null;
-
-        if (suggestId) {
-        	suggestIndex = suggestId.substring(suggestId.lastIndexOf('-') + 1);
-        }
-
-        if (suggestIndex >= 0) {
-        	suggest = this._suggestSet[suggestIndex];
-        }
-
-        if (suggest) {
-            this._showLocation(suggest);
-        }
-    },
-
-    _onSearchClick: function (e) {
-        var queryBox = document.getElementById('leaflet-control-geosearch-qry');
-
-        this.geosearch(queryBox.value);
-    },
-
-    _processResults: function(results) {
-        if (results instanceof Array) {
-            this._processForwardResults(results);
-        } else {
-            this._processReverseResults(results);
-        }
-    },
-
-    _processForwardResults: function(results) {
-        var resultCount = results.length;
-        if (resultCount <= 0) {
-            this._printError(this._config.notFoundMessage);
-            return;
-        }
-
-        this._map.fireEvent('geosearch_foundlocations', {Locations: results});
-        if (resultCount == 1) {
-            // only one result found... show it
-            this._showLocation(results[0]);
-        } else {
-            // multiple results found... add to results list and let user choose which one to show
-            this._setSuggestList(results);
-        }
-    },
-
-    _processReverseResults: function(result) {
-        if (!result) {
-            this._printError(this._config.notFoundMessage);
-        }
-
-        this._map.fireEvent('geosearch_clicklocation', {Location: result});
-        this._setResult(result);
-    },
-
-    _setResult: function(result) {
-        var displayName = null,
-            displayDescription = null,
-            divText = document.createElement('div'),
-            elem = this._result,
-            index = -1;
-	        li = document.createElement('li'),
-	        txtDescription = null,
-	        txtName = null;
-
-        if (result.lat == undefined || result.lon == undefined) {
-            return;
-        }
-
-        this._clearResult();
-
-        index = result.displayName.indexOf(',');
-        displayName = result.displayName.substring(0, index);
-        displayDescription = result.displayName.substring(index + 1);
-
-        li.id = 'geosearch-result';
-        li.className = 'geosearch-result';
-        txtDescription = document.createElement('span');
-        txtDescription.className = 'result-description';
-        txtDescription.innerHTML = displayDescription.replace(/^\s+|\s+$/g,'');
-        txtName = document.createElement('span');
-        txtName.className = 'result-name';
-        txtName.innerHTML = displayName.replace(/^\s+|\s+$/g,'');
-        divText.className = 'geosearch-result-text';
-        divText.appendChild(txtName);
-        divText.appendChild(txtDescription);
-        li.appendChild(divText);
-
-        elem.appendChild(li);
-	    elem.style.display = 'block';
-
-	    this._map.fireEvent('geosearch_showresult', {element: elem, queryPoint: L.latLng({lat: parseFloat(result.lat), lon: parseFloat(result.lon)})});
-    },
-
-	_setSuggestList: function(results) {
-        var elem = this._suggestlist,
-            i = 0,
-            j = 0,
-            li = null,
-            liText = null,
-            r = null;
-
-        this._clearSuggestList();
-
-        for (i = 0, j = 0; i < results.length; ++i) {
-            r = results[i];
-            if (r.lat == undefined || r.lon == undefined) {
-                continue;
-            }
-
-            if (this._isInSuggestSet(r)) {
-                continue;
-            }
-
-            this._suggestSet[this._suggestSet.length] = r;
-            li = document.createElement('li');
-            li.id = 'geosearch-suggest-' + j;
-            li.className = 'geosearch-suggest';
-            liText = document.createTextNode(r.displayName);
-            li.appendChild(liText);
-            elem.appendChild(li);
-            L.DomEvent.addListener(li, 'click', this._onSuggestClick, this);
-
-            ++j;
-        }
-
-        elem.style.display = 'block';
-    },
-
-    _showLocation: function (location) {
-    	this._setResult(location);
-    	this._clearSuggestList();
-
-        if (this.options.showMarker == true) {
-            if (typeof this._positionMarker === 'undefined') {
-                this._positionMarker = L.marker([location.lat, location.lon]).addTo(this._map);
-            } else {
-                this._positionMarker.setLatLng([location.lat, location.lon]);
-            }
-        }
-
-        this._map.setView([location.lat, location.lon], this._config.zoomLevel, false);
-        this._map.fireEvent('geosearch_showlocation', {Location: location});
-    },
-
-    _printError: function(message) {
-        var elem = this._result;
-        elem.innerHTML = '<li>' + message + '</li>';
-        elem.style.display = 'block';
-
-        var self = this;
-        self._timeout = setTimeout(function () {
-            elem.style.display = 'none';
-            self._timeout = null;
-        }, 3000);
-    }
-
-});
diff --git a/src/main/webapp/js/map/geosearch_provider_osm.js b/src/main/webapp/js/map/geosearch_provider_osm.js
deleted file mode 100644
index 33d5e12d..00000000
--- a/src/main/webapp/js/map/geosearch_provider_osm.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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.OpenStreetMap = L.Class.extend({
-    options : {
-        reverseable: true
-    },
-
-    initialize : function(options) {
-        options = L.Util.setOptions(this, options);
-    },
-
-    GetServiceUrl : function(qry, lon) {
-        if (!lon) {
-            // only one parameter given (query).. perform a forward lookup
-            return this._getForwardServiceUrl(qry);
-        }
-
-        // second parameter set... perform a reverse lookup
-        return this._getReverseServiceUrl(qry, lon);
-    },
-
-    ParseJSON : function(data) {
-        if (data instanceof Array) {
-            return this._parseForwardJSON(data);
-        }
-
-        return this._parseReverseJSON(data);
-    },
-
-    _getReverseServiceUrl : function(lat, lon) {
-        var parameters = L.Util.extend({
-            lat: lat,
-            lon: lon,
-            format : 'json'
-        }, this.options);
-
-        return 'http://nominatim.openstreetmap.org/reverse'
-                + L.Util.getParamString(parameters);
-    },
-
-    _getForwardServiceUrl: function(qry) {
-        var parameters = L.Util.extend({
-            q : qry,
-            format : 'json'
-        }, this.options);
-
-        return 'http://nominatim.openstreetmap.org/search'
-                + L.Util.getParamString(parameters);
-    },
-
-    _parseForwardJSON : 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;
-    },
-
-    _parseReverseJSON : function(data) {
-        if (data.length == 0)
-            return {};
-
-        return new L.GeoSearch.ReverseResult(
-            data.lon,
-            data.lat,
-            data.address,
-            data.display_name
-        );
-    }
-});
diff --git a/src/main/webapp/js/map/google.js b/src/main/webapp/js/map/google.js
deleted file mode 100644
index be69b226..00000000
--- a/src/main/webapp/js/map/google.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Google layer using Google Maps API
- */
-//(function (google, L) {
-
-L.Google = L.Class.extend({
-	includes: L.Mixin.Events,
-
-	options: {
-		minZoom: 0,
-		maxZoom: 18,
-		tileSize: 256,
-		subdomains: 'abc',
-		errorTileUrl: '',
-		attribution: '',
-		opacity: 1,
-		continuousWorld: false,
-		noWrap: false
-	},
-
-	// Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN
-	initialize: function(type, options) {
-		L.Util.setOptions(this, options);
-
-		this._ready = google.maps.Map != undefined;
-		if (!this._ready) L.Google.asyncWait.push(this);
-
-		this._type = type || 'SATELLITE';
-	},
-
-	onAdd: function(map, insertAtTheBottom) {
-		this._map = map;
-		this._insertAtTheBottom = insertAtTheBottom;
-
-		// create a container div for tiles
-		this._initContainer();
-		this._initMapObject();
-
-		// set up events
-		map.on('viewreset', this._resetCallback, this);
-
-		this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
-		map.on('move', this._update, this);
-		//map.on('moveend', this._update, this);
-
-		map._controlCorners['bottomright'].style.marginBottom = "1em";
-
-		this._reset();
-		this._update();
-	},
-
-	onRemove: function(map) {
-		this._map._container.removeChild(this._container);
-		//this._container = null;
-
-		this._map.off('viewreset', this._resetCallback, this);
-
-		this._map.off('move', this._update, this);
-		map._controlCorners['bottomright'].style.marginBottom = "0em";
-		//this._map.off('moveend', this._update, this);
-	},
-
-	getAttribution: function() {
-		return this.options.attribution;
-	},
-
-	setOpacity: function(opacity) {
-		this.options.opacity = opacity;
-		if (opacity < 1) {
-			L.DomUtil.setOpacity(this._container, opacity);
-		}
-	},
-
-	setElementSize: function(e, size) {
-		e.style.width = size.x + "px";
-		e.style.height = size.y + "px";
-	},
-
-	_initContainer: function() {
-		var tilePane = this._map._container,
-			first = tilePane.firstChild;
-
-		if (!this._container) {
-			this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left');
-			this._container.id = "_GMapContainer_" + L.Util.stamp(this);
-			this._container.style.zIndex = "auto";
-		}
-
-		if (true) {
-			tilePane.insertBefore(this._container, first);
-
-			this.setOpacity(this.options.opacity);
-			this.setElementSize(this._container, this._map.getSize());
-		}
-	},
-
-	_initMapObject: function() {
-		if (!this._ready) return;
-		this._google_center = new google.maps.LatLng(0, 0);
-		var map = new google.maps.Map(this._container, {
-		    center: this._google_center,
-		    zoom: 0,
-		    tilt: 0,
-		    mapTypeId: google.maps.MapTypeId[this._type],
-		    disableDefaultUI: true,
-		    keyboardShortcuts: false,
-		    draggable: false,
-		    disableDoubleClickZoom: true,
-		    scrollwheel: false,
-		    streetViewControl: false
-		});
-
-		var _this = this;
-		this._reposition = google.maps.event.addListenerOnce(map, "center_changed",
-			function() { _this.onReposition(); });
-
-		map.backgroundColor = '#ff0000';
-		this._google = map;
-	},
-
-	_resetCallback: function(e) {
-		this._reset(e.hard);
-	},
-
-	_reset: function(clearOldContainer) {
-		this._initContainer();
-	},
-
-	_update: function() {
-		if (!this._google) return;
-		this._resize();
-
-		var bounds = this._map.getBounds();
-		var ne = bounds.getNorthEast();
-		var sw = bounds.getSouthWest();
-		var google_bounds = new google.maps.LatLngBounds(
-			new google.maps.LatLng(sw.lat, sw.lng),
-			new google.maps.LatLng(ne.lat, ne.lng)
-		);
-		var center = this._map.getCenter();
-		var _center = new google.maps.LatLng(center.lat, center.lng);
-
-		this._google.setCenter(_center);
-		this._google.setZoom(this._map.getZoom());
-		//this._google.fitBounds(google_bounds);
-	},
-
-	_resize: function() {
-		var size = this._map.getSize();
-		if (this._container.style.width == size.x &&
-		    this._container.style.height == size.y)
-			return;
-		this.setElementSize(this._container, size);
-		this.onReposition();
-	},
-
-	onReposition: function() {
-		if (!this._google) return;
-		google.maps.event.trigger(this._google, "resize");
-	}
-});
-
-L.Google.asyncWait = [];
-L.Google.asyncInitialize = function() {
-	var i;
-	for (i = 0; i < L.Google.asyncWait.length; i++) {
-		var o = L.Google.asyncWait[i];
-		o._ready = true;
-		if (o._container) {
-			o._initMapObject();
-			o._update();
-		}
-	}
-	L.Google.asyncWait = [];
-}
-//})(window.google, L)
\ No newline at end of file
diff --git a/src/main/webapp/js/map/ipLocator/ipLocator.js b/src/main/webapp/js/map/ipLocator/ipLocator.js
new file mode 100644
index 00000000..eb02f837
--- /dev/null
+++ b/src/main/webapp/js/map/ipLocator/ipLocator.js
@@ -0,0 +1,53 @@
+define(['leaflet'], function IpLocator(L) {
+	L.IpLocator = L.Class.extend({
+		locateByIp: function (source, responseCallback, errorCallback) {
+			var handlerFn = L.bind(function (data) {
+				this._handleIpResponse(source, data, responseCallback, errorCallback);
+			}, this);
+
+			if (source.cbParam === undefined || source.cbParam === null || source.cbParam === '') {
+				this._loadScript(source.url, handlerFn);
+				return;
+			}
+
+			window.cbObject = {};
+			window.cbObject.fn = handlerFn;
+			this._loadScript(source.url + '?' + source.cbParam + '= window.cbObject.fn');
+		},
+
+		_handleIpResponse: function (source, data, responseCallback, errorCallback) {
+			window.cbObject = null;
+			delete window.cbObject;
+
+			var pos = source.buildLocationObject(data);
+			if (pos === null) {
+				if (errorCallback) { errorCallback('Could not get location.'); }
+			} else if (responseCallback) {
+				responseCallback(pos);
+			}
+		},
+
+		_loadScript: function (url, callback, type) {
+			var script = document.createElement('script');
+			script.type = (type === undefined) ? 'text/javascript' : type;
+
+			if (typeof callback === 'function') {
+				if (script.readyState) {
+					script.onreadystatechange = function () {
+						if (script.readyState === 'loaded' || script.readyState === 'complete') {
+							script.onreadystatechange = null;
+							callback();
+						}
+					};
+				} else {
+					script.onload = function () { callback(); };
+				}
+			}
+
+			script.src = url;
+			document.getElementsByTagName('head')[0].appendChild(script);
+		}
+	});
+
+	return L.IpLocator;
+});
diff --git a/src/main/webapp/js/map/ipLocator/ipProvider.js b/src/main/webapp/js/map/ipLocator/ipProvider.js
new file mode 100644
index 00000000..01bc7e7e
--- /dev/null
+++ b/src/main/webapp/js/map/ipLocator/ipProvider.js
@@ -0,0 +1,59 @@
+define({
+	None: null,
+	FreeGeoIp: {
+		url: 'http://freegeoip.net/json/',
+		cbParam: 'callback',
+		buildLocationObject: function (data) {
+			if (!data) {
+				return null;
+			}
+
+			return {
+				coords: {
+					accuracy: 10000,
+					latitude: parseFloat(data.latitude),
+					longitude: parseFloat(data.longitude)
+				},
+				timestamp: new Date().getTime()
+			};
+		}
+	},
+	GeoPlugin: {
+		url: 'http://www.geoplugin.net/json.gp',
+		cbParam: 'jsoncallback',
+		buildLocationObject: function (data) {
+			if (!data) {
+				return null;
+			}
+
+			return {
+				coords: {
+					accuracy: 10000,
+					/* jshint ignore:start */
+					latitude: parseFloat(data.geoplugin_latitude),
+					longitude: parseFloat(data.geoplugin_longitude)
+					/* jshint ignore:end */
+				},
+				timestamp: new Date().getTime()
+			};
+		}
+	},
+	Wikimedia: {
+		url: 'http://geoiplookup.wikimedia.org/',
+		cbParam: '',
+		buildLocationObject: function () {
+			var data = window.Geo,
+				result = {
+					coords: {
+						accuracy: 10000,
+						latitude: parseFloat(data.lat),
+						longitude: parseFloat(data.lon)
+					},
+					timestamp: new Date().getTime()
+				};
+
+			delete window.Geo;
+			return result;
+		}
+	}
+});
diff --git a/src/main/webapp/js/map/ipLocator/mapIncluder.js b/src/main/webapp/js/map/ipLocator/mapIncluder.js
new file mode 100644
index 00000000..30442d13
--- /dev/null
+++ b/src/main/webapp/js/map/ipLocator/mapIncluder.js
@@ -0,0 +1,49 @@
+define(['leaflet', 'map/ipLocator/ipProvider', 'map/ipLocator/ipLocator'], function(L, IpProvider, IpLocator) {
+	L.Map.include({
+		_defaultLocateOptions: {
+			ipProvider: IpProvider.Wikimedia,
+			timeout: 10000,
+			watch: false
+			// setView: false
+			// maxZoom: <Number>
+			// maximumAge: 0
+			// enableHighAccuracy: false
+		},
+
+		locate: function (/*Object*/ options) {
+			options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
+
+			if (!navigator.geolocation && !options.ipProvider) {
+				this._handleGeolocationError({
+					code: 0,
+					message: 'Geolocation not supported.'
+				});
+				return this;
+			}
+
+			var onResponse = L.bind(this._handleGeolocationResponse, this),
+				onError = L.bind((options.ipProvider) ? this._fallbackToIp : this._handleGeolocationError, this);
+
+			if (options.watch) {
+				this._locationWatchId =
+						navigator.geolocation.watchPosition(onResponse, onError, options);
+			} else {
+				navigator.geolocation.getCurrentPosition(onResponse, onError, options);
+			}
+
+			return this;
+		},
+
+		_fallbackToIp: function (errMsg) {
+			if (!this._locateOptions.ipProvider) {
+				this._handleGeolocationError(errMsg);
+				return;
+			}
+
+			var onResponse = L.bind(this._handleGeolocationResponse, this),
+				onError = L.bind(this._handleGeolocationError, this);
+
+			(new IpLocator).locateByIp(this._locateOptions.ipProvider, onResponse, onError);
+		}
+	});
+});
\ No newline at end of file
diff --git a/src/main/webapp/js/map/isoMap.js b/src/main/webapp/js/map/isoMap.js
new file mode 100644
index 00000000..4ed2db09
--- /dev/null
+++ b/src/main/webapp/js/map/isoMap.js
@@ -0,0 +1,225 @@
+define(['jQuery', 'leaflet', 'console', 'map/layer/bing','map/layer/google', 'map/control/settings', 'map/control/help', 'map/control/geosearch', 'map/control/geosearchProvider/osm', 'map/ipLocator/ipProvider', 'map/ipLocator/ipLocator'], function($, L, logger, BingLayer, GoogleLayer, SettingsControl, HelpControl, GeoSearch, OSMProvider, IpProvider, IpLocator) {
+	// Private static fields
+	var INNSBRUCK = [47.265718, 11.391342];
+	var VIENNA = [48.186312, 16.317615];
+
+	// Class function (a.k.a. constructor)
+	function IsoMap(div) {
+		// Private fields
+		var mDiv = null;
+		var map = null;
+		var mapOptions = {
+			attributionControl: false,
+			center: INNSBRUCK,
+			ipProvider: IpProvider.Wikimedia,
+			zoomControl: false, // hide default zoom control, so we can create a bottomright one
+			zoom: 12
+		};
+
+		// Constructor code
+
+		mDiv = div;
+
+		// Public methods
+
+		/**
+		 * Gets the internal map object.
+		 * This is only useful after the map has been drawn
+		 *
+		 * @return the map object. Null if the map has not been drawn.
+		 */
+		this.getMap = function() {
+			return map;
+		};
+
+		this.draw = function() {
+			var layerControl = L.control.layers(),
+				zoomControl = L.control.zoom({position: 'bottomright'});
+
+			logger.log('Drawing map');
+			logger.debug(' - options:', mapOptions);
+
+			// Base + Overlay map definition
+			mapOptions.layers = addOsmLayer(layerControl);
+			addStamenLayer(layerControl);
+			addBlueMarbleLayers(layerControl);
+			addBingLayers(layerControl);
+			addGoogleLayers(layerControl);
+
+			// Map creation and control assignment
+			map = L.map(mDiv, mapOptions);
+			map.addControl(new SettingsControl({position: 'bottomright'}));
+			map.addControl(new HelpControl({position: 'bottomright'}));
+			map.addControl(layerControl);
+			map.addControl(zoomControl);
+			map.addControl(L.control.locate({position: 'bottomright'}));
+			map.addControl(L.control.scale({position: 'bottomleft'}));
+			map.addControl(new GeoSearch({
+				doReverseLookup: true,
+				position: 'topleft',
+				provider: new OSMProvider(),
+				showMarker: true
+			}));
+
+			$(document).trigger('isomap_draw');
+		};
+
+		this.locateAndDraw = function() {
+			var locator = new IpLocator();
+		    locator.locateByIp(mapOptions.ipProvider, onLocationSuccess.bind(this), onLocationError.bind(this));
+		};
+
+		// Private methods
+
+		function addBingLayers(c) {
+			var apiKey = 'AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf',
+				bAerial,
+				bHybrid,
+				bRoad;
+
+			bRoad = new BingLayer(apiKey, {
+		        type: 'Road'
+		    });
+		    bHybrid = new BingLayer(apiKey, {
+		        type: 'AerialWithLabels'
+		    });
+		    bAerial = new BingLayer(apiKey, {
+		        type: 'Aerial'
+		    });
+
+		    logger.debug('Adding bing layers');
+
+		    c.addBaseLayer(bRoad, 'Bing Road');
+		    c.addBaseLayer(bHybrid, 'Bing Hybrid');
+		    c.addBaseLayer(bAerial, 'Bing Aerial');
+
+		    return bAerial;
+		}
+
+		function addBlueMarbleLayers(c) {
+			if (!L.tileLayer) {
+				return;
+			}
+
+			var blueMarbleJpg,
+				blueMarblePng,
+				openGeoOSM;
+
+			blueMarbleJpg = new L.tileLayer.wms(
+				'http://maps.opengeo.org/geowebcache/service/wms',
+				{layers: 'bluemarble'}
+		    );
+
+			blueMarblePng = new L.tileLayer.wms(
+				'http://maps.opengeo.org/geowebcache/service/wms',
+				{layers: 'bluemarble', format: 'image/png'}
+		    );
+
+			openGeoOSM = new L.tileLayer.wms(
+			    'http://maps.opengeo.org/geowebcache/service/wms',
+			    {layers: 'openstreetmap', format: 'image/png'}
+			);
+
+			logger.debug('Adding openGeo layers (blueMarbel and openGeo OSM)');
+
+			c.addBaseLayer(blueMarbleJpg, 'OpenGeo BlueMarble (jpg)');
+			c.addBaseLayer(blueMarblePng, 'OpenGeo BlueMarble (png)');
+			c.addBaseLayer(openGeoOSM, 'OpenGeo OSM (png)');
+
+			return openGeoOSM;
+		}
+
+		function addGoogleLayers(c) {
+			var gPhysical,
+				gStreet,
+				gHybrid,
+				gSatellite;
+
+		    gPhysical = new GoogleLayer('TERRAIN');
+		    gStreet = new GoogleLayer('ROADMAP',
+		        {maxZoom: 20 }
+		    );
+		    gHybrid = new GoogleLayer('HYBRID',
+		        {maxZoom: 20 }
+		    );
+		    gSatellite = new GoogleLayer('SATELLITE',
+		        {maxZoom: 22 }
+		    );
+
+		    logger.debug('Adding google layers');
+
+		    c.addBaseLayer(gPhysical, 'Google Physical');
+		    c.addBaseLayer(gStreet, 'Google Streets');
+		    c.addBaseLayer(gHybrid, 'Google Hybrid');
+		    c.addBaseLayer(gSatellite, 'Google Satellite');
+
+		    return gSatellite;
+		}
+
+		function addOsmLayer(c) {
+			if (!L.tileLayer) {
+				return;
+			}
+
+			var layerOsm = new L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
+			    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
+			});
+
+			logger.debug('Adding openstreetmap layer');
+
+			c.addBaseLayer(layerOsm, 'OpenStreetMap');
+			return layerOsm;
+		}
+
+		function addStamenLayer(c) {
+			if (!L.StamenTileLayer) {
+				return;
+			}
+
+			var layerStamen = new L.StamenTileLayer('watercolor');
+
+			logger.debug('Adding stamen watercolor layer');
+
+			c.addBaseLayer(layerStamen, 'Stamen Watercolor Map');
+			return layerStamen;
+		}
+
+		function onLocationError(message) {
+			logger.info('Could not get GeoLocation', message);
+			logger.debug(' - will continue with default location');
+
+			this.draw();
+		}
+
+		function onLocationSuccess(location) {
+		    if (!location || !location.coords) {
+		    	logger.debug('Invalid geoLocation found... no coordinates returned!');
+		    	return;
+		    }
+
+		    logger.log('GeoLocation found. It will be used as center');
+		    logger.debug(' - location:', location);
+
+		    mapOptions.center = [location.coords.latitude, location.coords.longitude];
+
+		    this.draw();
+		}
+	}
+
+	// Public static field
+//	IsoMap.staticVar = 0;
+
+	// Public static method
+//	IsoMap.capitalize = function(name){
+//		return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
+//	};
+
+	// Private static method
+//	function createWithName(name) {
+//		var obj = new cls();
+//		obj.setName(cls.capitalize(name));
+//		return obj;
+//	}
+
+	return IsoMap;
+});
diff --git a/src/main/webapp/js/map/isomap.js b/src/main/webapp/js/map/isomap.js
deleted file mode 100644
index c687d1c7..00000000
--- a/src/main/webapp/js/map/isomap.js
+++ /dev/null
@@ -1,212 +0,0 @@
-function IsoMap(divId) {
-	var INNSBRUCK = [47.265718, 11.391342];
-//	var VIENNA = [48.186312, 16.317615];
-	var mapDivId = null;
-	var map = null;
-	var mapOptions = {
-		attributionControl: false,
-		center: INNSBRUCK,
-		ipProvider: L.IpProvider.Wikimedia,
-		zoomControl: false, // hide default zoom control, so we can create a bottomright one
-		zoom: 12
-	};
-
-	// Constructor
-
-	mapDivId = divId;
-
-	// Getter
-
-	/**
-	 * Gets the internal map object.
-	 * This is only useful after the map has been drawn
-	 *
-	 * @return the map object. Null if the map has not been drawn.
-	 */
-	this.getMap = function() {
-		return map;
-	};
-
-	// Public methods
-
-	this.draw = function() {
-		var layerControl = L.control.layers({position: 'topright'}),
-			zoomControl = L.control.zoom({position: 'bottomright'});
-
-		console.log('Drawing map');
-		console.debug(' - options:', mapOptions);
-
-		// Base + Overlay map definition
-		mapOptions.layers = addOsmLayer(layerControl);
-		addStamenLayer(layerControl);
-		addBlueMarbleLayers(layerControl);
-		addBingLayers(layerControl);
-		addGoogleLayers(layerControl);
-
-		// Map creation and control assignment
-		map = L.map(mapDivId, mapOptions);
-		map.addControl(new SettingsControl({position: 'bottomright'}));
-		map.addControl(new HelpControl({position: 'bottomright'}));
-		map.addControl(layerControl);
-		map.addControl(zoomControl);
-		map.addControl(L.control.locate({position: 'bottomright'}));
-		map.addControl(L.control.scale({position: 'bottomleft'}));
-		map.addControl(new L.Control.GeoSearch({
-			doReverseLookup: true,
-			position: 'topleft',
-			provider: new L.GeoSearch.Provider.OpenStreetMap(),
-			showMarker: true
-		}));
-
-		$(document).trigger('isomap_draw');
-	};
-
-	this.locateAndDraw = function() {
-	    var locator = new L.IpLocator();
-	    locator.locateByIp(mapOptions.ipProvider, onLocationSuccess.bind(this), onLocationError.bind(this));
-	};
-
-	// Private methods
-
-	addBingLayers = function(c) {
-		if (!L.BingLayer) {
-			return;
-		}
-
-		var apiKey = 'AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf',
-			bAerial,
-			bHybrid,
-			bRoad;
-
-		bRoad = new L.BingLayer(apiKey, {
-	        type: 'Road'
-	    });
-	    bHybrid = new L.BingLayer(apiKey, {
-	        type: 'AerialWithLabels'
-	    });
-	    bAerial = new L.BingLayer(apiKey, {
-	        type: 'Aerial'
-	    });
-
-	    console.debug('Adding bing layers');
-
-	    c.addBaseLayer(bRoad, 'Bing Road');
-	    c.addBaseLayer(bHybrid, 'Bing Hybrid');
-	    c.addBaseLayer(bAerial, 'Bing Aerial');
-
-	    return bAerial;
-	};
-
-	addBlueMarbleLayers = function(c) {
-		if (!L.tileLayer) {
-			return;
-		}
-
-		var blueMarbleJpg,
-			blueMarblePng,
-			openGeoOSM;
-
-		blueMarbleJpg = new L.tileLayer.wms(
-			'http://maps.opengeo.org/geowebcache/service/wms',
-			{layers: 'bluemarble'}
-	    );
-
-		blueMarblePng = new L.tileLayer.wms(
-			'http://maps.opengeo.org/geowebcache/service/wms',
-			{layers: 'bluemarble', format: 'image/png'}
-	    );
-
-		openGeoOSM = new L.tileLayer.wms(
-		    'http://maps.opengeo.org/geowebcache/service/wms',
-		    {layers: 'openstreetmap', format: 'image/png'}
-		);
-
-		console.debug('Adding openGeo layers (blueMarbel and openGeo OSM)');
-
-		c.addBaseLayer(blueMarbleJpg, 'OpenGeo BlueMarble (jpg)');
-		c.addBaseLayer(blueMarblePng, 'OpenGeo BlueMarble (png)');
-		c.addBaseLayer(openGeoOSM, 'OpenGeo OSM (png)');
-
-		return openGeoOSM;
-	};
-
-	addGoogleLayers = function(c) {
-		if (!L.Google) {
-			return;
-		}
-
-		var gPhysical,
-			gStreet,
-			gHybrid,
-			gSatellite;
-
-	    gPhysical = new L.Google('TERRAIN');
-	    gStreet = new L.Google('ROADMAP',
-	        {maxZoom: 20 }
-	    );
-	    gHybrid = new L.Google('HYBRID',
-	        {maxZoom: 20 }
-	    );
-	    gSatellite = new L.Google('SATELLITE',
-	        {maxZoom: 22 }
-	    );
-
-	    console.debug('Adding google layers');
-
-	    c.addBaseLayer(gPhysical, 'Google Physical');
-	    c.addBaseLayer(gStreet, 'Google Streets');
-	    c.addBaseLayer(gHybrid, 'Google Hybrid');
-	    c.addBaseLayer(gSatellite, 'Google Satellite');
-
-	    return gSatellite;
-	};
-
-	addOsmLayer = function(c) {
-		if (!L.tileLayer) {
-			return;
-		}
-
-		var layerOsm = new L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
-		    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
-		});
-
-		console.debug('Adding openstreetmap layer');
-
-		c.addBaseLayer(layerOsm, 'OpenStreetMap');
-		return layerOsm;
-	};
-
-	addStamenLayer = function(c) {
-		if (!L.StamenTileLayer) {
-			return;
-		}
-
-		var layerStamen = new L.StamenTileLayer('watercolor');
-
-		console.debug('Adding stamen watercolor layer');
-
-		c.addBaseLayer(layerStamen, 'Stamen Watercolor Map');
-		return layerStamen;
-	};
-
-	onLocationError = function(message) {
-		console.info('Could not get GeoLocation', message);
-		console.debug(' - will continue with default location');
-
-		this.draw();
-	};
-
-	onLocationSuccess = function(location) {
-	    if (!location || !location.coords) {
-	    	console.debug('Invalid geoLocation found... no coordinates returned!');
-	    	return;
-	    }
-
-	    console.log('GeoLocation found. It will be used as center');
-	    console.debug(' - location:', location);
-
-	    mapOptions.center = [location.coords.latitude, location.coords.longitude];
-
-	    this.draw();
-	};
-};
diff --git a/src/main/webapp/js/map/layer/bing.js b/src/main/webapp/js/map/layer/bing.js
new file mode 100644
index 00000000..6fac75b1
--- /dev/null
+++ b/src/main/webapp/js/map/layer/bing.js
@@ -0,0 +1,125 @@
+define(['leaflet', 'console'], function(L, logger) {
+	L.BingLayer = L.TileLayer.extend({
+		options: {
+			subdomains: [0, 1, 2, 3],
+			type: 'Aerial',
+			attribution: 'Bing',
+			culture: ''
+		},
+
+		initialize: function(key, options) {
+			L.Util.setOptions(this, options);
+
+			this._key = key;
+			this._url = null;
+			this.meta = {};
+			this.loadMetadata();
+		},
+
+		tile2quad: function(x, y, z) {
+			var quad = '';
+			for (var i = z; i > 0; i--) {
+				var digit = 0;
+				var mask = 1 << (i - 1);
+				if ((x & mask) != 0) digit += 1;
+				if ((y & mask) != 0) digit += 2;
+				quad = quad + digit;
+			}
+			return quad;
+		},
+
+		getTileUrl: function(p, z) {
+			var z = this._getZoomForUrl();
+			var subdomains = this.options.subdomains,
+				s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)];
+			return this._url.replace('{subdomain}', s)
+					.replace('{quadkey}', this.tile2quad(p.x, p.y, z))
+					.replace('{culture}', this.options.culture);
+		},
+
+		loadMetadata: function() {
+			var _this = this;
+			var cbid = '_bing_metadata_' + L.Util.stamp(this);
+			window[cbid] = function (meta) {
+				_this.meta = meta;
+				window[cbid] = undefined;
+				var e = document.getElementById(cbid);
+				e.parentNode.removeChild(e);
+				if (meta.errorDetails) {
+					logger.log("Leaflet Bing Plugin Error - Got metadata: " + meta.errorDetails);
+					return;
+				}
+				_this.initMetadata();
+			};
+			var url = document.location.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.options.type + "?include=ImageryProviders&jsonp=" + cbid +
+			          "&key=" + this._key + "&UriScheme=" + document.location.protocol.slice(0, -1);
+			var script = document.createElement("script");
+			script.type = "text/javascript";
+			script.src = url;
+			script.id = cbid;
+			document.getElementsByTagName("head")[0].appendChild(script);
+		},
+
+		initMetadata: function() {
+			var r = this.meta.resourceSets[0].resources[0];
+			this.options.subdomains = r.imageUrlSubdomains;
+			this._url = r.imageUrl;
+			this._providers = [];
+			if (r.imageryProviders) {
+				for (var i = 0; i < r.imageryProviders.length; i++) {
+					var p = r.imageryProviders[i];
+					for (var j = 0; j < p.coverageAreas.length; j++) {
+						var c = p.coverageAreas[j];
+						var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
+						var bounds = new L.LatLngBounds(
+								new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01),
+								new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01)
+						);
+						coverage.bounds = bounds;
+						coverage.attrib = p.attribution;
+						this._providers.push(coverage);
+					}
+				}
+			}
+			this._update();
+		},
+
+		_update: function() {
+			if (this._url == null || !this._map) return;
+			this._update_attribution();
+			L.TileLayer.prototype._update.apply(this, []);
+		},
+
+		_update_attribution: function() {
+			var bounds = this._map.getBounds();
+			var zoom = this._map.getZoom();
+			for (var i = 0; i < this._providers.length; i++) {
+				var p = this._providers[i];
+				if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
+						bounds.intersects(p.bounds)) {
+					if (!p.active && this._map.attributionControl)
+						this._map.attributionControl.addAttribution(p.attrib);
+					p.active = true;
+				} else {
+					if (p.active && this._map.attributionControl)
+						this._map.attributionControl.removeAttribution(p.attrib);
+					p.active = false;
+				}
+			}
+		},
+
+		onRemove: function(map) {
+			for (var i = 0; i < this._providers.length; i++) {
+				var p = this._providers[i];
+				if (p.active && this._map.attributionControl) {
+					this._map.attributionControl.removeAttribution(p.attrib);
+					p.active = false;
+				}
+			}
+
+        	L.TileLayer.prototype.onRemove.apply(this, [map]);
+		}
+	});
+
+	return L.BingLayer;
+});
diff --git a/src/main/webapp/js/map/layer/google.js b/src/main/webapp/js/map/layer/google.js
new file mode 100644
index 00000000..7b802b54
--- /dev/null
+++ b/src/main/webapp/js/map/layer/google.js
@@ -0,0 +1,201 @@
+/*
+ * Google layer using Google Maps API
+ */
+define(['leaflet'], function(L) {
+	L.Google = L.Class.extend({
+		includes: L.Mixin.Events,
+
+		options: {
+			minZoom: 0,
+			maxZoom: 18,
+			tileSize: 256,
+			subdomains: 'abc',
+			errorTileUrl: '',
+			attribution: '',
+			opacity: 1,
+			continuousWorld: false,
+			noWrap: false,
+			mapOptions: {
+				backgroundColor: '#dddddd'
+			}
+		},
+
+		// Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN
+		initialize: function(type, options) {
+			L.Util.setOptions(this, options);
+
+			this._ready = google.maps.Map != undefined;
+			if (!this._ready) L.Google.asyncWait.push(this);
+
+			this._type = type || 'SATELLITE';
+		},
+
+		onAdd: function(map, insertAtTheBottom) {
+			this._map = map;
+			this._insertAtTheBottom = insertAtTheBottom;
+
+			// create a container div for tiles
+			this._initContainer();
+			this._initMapObject();
+
+			// set up events
+			map.on('viewreset', this._resetCallback, this);
+
+			this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+			map.on('move', this._update, this);
+
+			map.on('zoomanim', this._handleZoomAnim, this);
+
+			//20px instead of 1em to avoid a slight overlap with google's attribution
+			map._controlCorners['bottomright'].style.marginBottom = "20px";
+
+			this._reset();
+			this._update();
+		},
+
+		onRemove: function(map) {
+			this._map._container.removeChild(this._container);
+			//this._container = null;
+
+			this._map.off('viewreset', this._resetCallback, this);
+
+			this._map.off('move', this._update, this);
+
+			this._map.off('zoomanim', this._handleZoomAnim, this);
+
+			map._controlCorners['bottomright'].style.marginBottom = "0em";
+			//this._map.off('moveend', this._update, this);
+		},
+
+		getAttribution: function() {
+			return this.options.attribution;
+		},
+
+		setOpacity: function(opacity) {
+			this.options.opacity = opacity;
+			if (opacity < 1) {
+				L.DomUtil.setOpacity(this._container, opacity);
+			}
+		},
+
+		setElementSize: function(e, size) {
+			e.style.width = size.x + "px";
+			e.style.height = size.y + "px";
+		},
+
+		_initContainer: function() {
+			var tilePane = this._map._container,
+				first = tilePane.firstChild;
+
+			if (!this._container) {
+				this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left');
+				this._container.id = "_GMapContainer_" + L.Util.stamp(this);
+				this._container.style.zIndex = "auto";
+			}
+
+			tilePane.insertBefore(this._container, first);
+
+			this.setOpacity(this.options.opacity);
+			this.setElementSize(this._container, this._map.getSize());
+		},
+
+		_initMapObject: function() {
+			if (!this._ready) return;
+			this._google_center = new google.maps.LatLng(0, 0);
+			var map = new google.maps.Map(this._container, {
+			    center: this._google_center,
+			    zoom: 0,
+			    tilt: 0,
+			    mapTypeId: google.maps.MapTypeId[this._type],
+			    disableDefaultUI: true,
+			    keyboardShortcuts: false,
+			    draggable: false,
+			    disableDoubleClickZoom: true,
+			    scrollwheel: false,
+			    streetViewControl: false,
+			    styles: this.options.mapOptions.styles,
+			    backgroundColor: this.options.mapOptions.backgroundColor
+			});
+
+			var _this = this;
+			this._reposition = google.maps.event.addListenerOnce(map, "center_changed",
+				function() { _this.onReposition(); });
+			this._google = map;
+
+			google.maps.event.addListenerOnce(map, "idle",
+				function() { _this._checkZoomLevels(); });
+		},
+
+		_checkZoomLevels: function() {
+			//setting the zoom level on the Google map may result in a different zoom level than the one requested
+			//(it won't go beyond the level for which they have data).
+			// verify and make sure the zoom levels on both Leaflet and Google maps are consistent
+			if (this._google.getZoom() !== this._map.getZoom()) {
+				//zoom levels are out of sync. Set the leaflet zoom level to match the google one
+				this._map.setZoom( this._google.getZoom() );
+			}
+		},
+
+		_resetCallback: function(e) {
+			this._reset(e.hard);
+		},
+
+		_reset: function(clearOldContainer) {
+			this._initContainer();
+		},
+
+		_update: function(e) {
+			if (!this._google) return;
+			this._resize();
+
+			var center = e && e.latlng ? e.latlng : this._map.getCenter();
+			var _center = new google.maps.LatLng(center.lat, center.lng);
+
+			this._google.setCenter(_center);
+			this._google.setZoom(this._map.getZoom());
+
+			this._checkZoomLevels();
+			//this._google.fitBounds(google_bounds);
+		},
+
+		_resize: function() {
+			var size = this._map.getSize();
+			if (this._container.style.width == size.x &&
+			    this._container.style.height == size.y)
+				return;
+			this.setElementSize(this._container, size);
+			this.onReposition();
+		},
+
+
+		_handleZoomAnim: function (e) {
+			var center = e.center;
+			var _center = new google.maps.LatLng(center.lat, center.lng);
+
+			this._google.setCenter(_center);
+			this._google.setZoom(e.zoom);
+		},
+
+
+		onReposition: function() {
+			if (!this._google) return;
+			google.maps.event.trigger(this._google, "resize");
+		}
+	});
+
+	L.Google.asyncWait = [];
+	L.Google.asyncInitialize = function() {
+		var i;
+		for (i = 0; i < L.Google.asyncWait.length; i++) {
+			var o = L.Google.asyncWait[i];
+			o._ready = true;
+			if (o._container) {
+				o._initMapObject();
+				o._update();
+			}
+		}
+		L.Google.asyncWait = [];
+	};
+
+	return L.Google;
+});
diff --git a/src/main/webapp/js/map/locatebyip.js b/src/main/webapp/js/map/locatebyip.js
deleted file mode 100644
index 1dc278e7..00000000
--- a/src/main/webapp/js/map/locatebyip.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Provides L.Map with convenient shortcuts for using ip based geolocation features.
- */
-L.IpProvider = {};
-L.IpProvider.None = null;
-L.IpProvider.FreeGeoIp = {
-	url: 'http://freegeoip.net/json/',
-	cbParam: 'callback',
-	buildLocationObject: function (data) {
-		if (!data) {
-			return null;
-		}
-
-		return {
-			coords: {
-				accuracy: 10000,
-				latitude: parseFloat(data.latitude),
-				longitude: parseFloat(data.longitude)
-			},
-			timestamp: new Date().getTime()
-		};
-	}
-};
-L.IpProvider.GeoPlugin = {
-	url: 'http://www.geoplugin.net/json.gp',
-	cbParam: 'jsoncallback',
-	buildLocationObject: function (data) {
-		if (!data) {
-			return null;
-		}
-
-		return {
-			coords: {
-				accuracy: 10000,
-				/* jshint ignore:start */
-				latitude: parseFloat(data.geoplugin_latitude),
-				longitude: parseFloat(data.geoplugin_longitude)
-				/* jshint ignore:end */
-			},
-			timestamp: new Date().getTime()
-		};
-	}
-};
-L.IpProvider.Wikimedia = {
-	url: 'http://geoiplookup.wikimedia.org/',
-	cbParam: '',
-	buildLocationObject: function () {
-		var data = window.Geo,
-			result = {
-				coords: {
-					accuracy: 10000,
-					latitude: parseFloat(data.lat),
-					longitude: parseFloat(data.lon)
-				},
-				timestamp: new Date().getTime()
-			};
-
-		delete window.Geo;
-		return result;
-	}
-};
-
-L.IpLocator = L.Class.extend({
-
-	locateByIp: function (source, responseCallback, errorCallback) {
-		var handlerFn = L.bind(function (data) {
-			this._handleIpResponse(source, data, responseCallback, errorCallback);
-		}, this);
-
-		if (source.cbParam === undefined || source.cbParam === null || source.cbParam === '') {
-			this._loadScript(source.url, handlerFn);
-			return;
-		}
-
-		window.cbObject = {};
-		window.cbObject.fn = handlerFn;
-		this._loadScript(source.url + '?' + source.cbParam + '= window.cbObject.fn');
-	},
-
-	_handleIpResponse: function (source, data, responseCallback, errorCallback) {
-		window.cbObject = null;
-		delete window.cbObject;
-
-		var pos = source.buildLocationObject(data);
-		if (pos === null) {
-			if (errorCallback) { errorCallback('Could not get location.'); }
-		} else if (responseCallback) {
-			responseCallback(pos);
-		}
-	},
-
-	_loadScript: function (url, callback, type) {
-		var script = document.createElement('script');
-		script.type = (type === undefined) ? 'text/javascript' : type;
-
-		if (typeof callback === 'function') {
-			if (script.readyState) {
-				script.onreadystatechange = function () {
-					if (script.readyState === 'loaded' || script.readyState === 'complete') {
-						script.onreadystatechange = null;
-						callback();
-					}
-				};
-			} else {
-				script.onload = function () { callback(); };
-			}
-		}
-
-		script.src = url;
-		document.getElementsByTagName('head')[0].appendChild(script);
-	}
-});
-
-L.Map.include({
-	_defaultLocateOptions: {
-		ipProvider: L.IpProvider.Wikimedia,
-		timeout: 10000,
-		watch: false
-		// setView: false
-		// maxZoom: <Number>
-		// maximumAge: 0
-		// enableHighAccuracy: false
-	},
-
-	locate: function (/*Object*/ options) {
-		options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
-
-		if (!navigator.geolocation && !options.ipProvider) {
-			this._handleGeolocationError({
-				code: 0,
-				message: 'Geolocation not supported.'
-			});
-			return this;
-		}
-
-		var onResponse = L.bind(this._handleGeolocationResponse, this),
-			onError = L.bind((options.ipProvider) ? this._fallbackToIp : this._handleGeolocationError, this);
-
-		if (options.watch) {
-			this._locationWatchId =
-					navigator.geolocation.watchPosition(onResponse, onError, options);
-		} else {
-			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
-		}
-
-		return this;
-	},
-
-	_fallbackToIp: function (errMsg) {
-		if (!this._locateOptions.ipProvider) {
-			this._handleGeolocationError(errMsg);
-			return;
-		}
-
-		var onResponse = L.bind(this._handleGeolocationResponse, this),
-			onError = L.bind(this._handleGeolocationError, this);
-
-		(new L.IpLocator()).locateByIp(this._locateOptions.ipProvider, onResponse, onError);
-	}
-
-});
diff --git a/src/main/webapp/js/service/serviceConfiguration.js b/src/main/webapp/js/service/serviceConfiguration.js
index f2cab3f9..ba0d19f2 100644
--- a/src/main/webapp/js/service/serviceConfiguration.js
+++ b/src/main/webapp/js/service/serviceConfiguration.js
@@ -1,74 +1,78 @@
-function ServiceConfiguration(/* Websocket */ ws) {
-	var actionName = 'getConfiguration';
-	var websocket = null;
+define(['jQuery', 'leaflet', 'console', 'isochrone/configuration'], function($, L, logger, Configuration) {
+	function ServiceConfiguration(ws) {
+		var actionName = 'getConfiguration';
+		var websocket = null;
 
-	// Constructor
+		// Constructor
 
-	websocket = ws;
+		websocket = ws;
 
-	// Public methods
+		// Public methods
 
-	this.getFromServer = function() {
-		$(document).on(actionName, onServerResponse.bind(this));
-		websocket.sendWsMessage('{"action" : "' + actionName + '"}');
-	};
+		this.getFromServer = function() {
+			$(document).on(actionName, onServerResponse.bind(this));
+			websocket.sendWsMessage('{"action" : "' + actionName + '"}');
+		};
+
+		// Private methods
 
-	// Private methods
+		/**
+		 * Reads the enabled datasets and stores them in a client side configuration singleton
+		 * each dataset contains:
+		 * <ul>
+		 * <li>bbox the spatial extent of the specified area</li>
+		 * <li>queryPoint the a 2D-coordinate of the query point</li>
+		 * <li>time the arrival or departure time of the query point</li>
+		 * </ul>
+		 */
+		onServerResponse = function(data) {
+			var config = Configuration.getInstance(),
+				datasets = data.defaultDatasets,
+				result = {};
 
-	/**
-	 * Reads the enabled datasets and stores them in a client side configuration singleton
-	 * each dataset contains:
-	 * <ul>
-	 * <li>bbox the spatial extent of the specified area</li>
-	 * <li>queryPoint the a 2D-coordinate of the query point</li>
-	 * <li>time the arrival or departure time of the query point</li>
-	 * </ul>
-	 */
-	onServerResponse = function(data) {
-		var config = Configuration(),
-			datasets = data.defaultDatasets,
-			result = {};
+			logger.debug('Handling event "' + actionName + '":', data);
+			for (var i = 0; i < datasets.length; i++) {
+				var dataset = datasets[i];
+				var boundLower = new L.Point(dataset.bbox.minX, dataset.bbox.minY);
+				var boundUpper = new L.Point(dataset.bbox.maxX, dataset.bbox.maxY);
+				var latlngLower = convertToLatLng(boundLower);
+				var latlngUpper = convertToLatLng(boundUpper);
 
-		console.debug('Handling event "' + actionName + '":', data);
-		for (var i = 0; i < datasets.length; i++) {
-			var dataset = datasets[i];
-			var boundLower = new L.Point(dataset.bbox.minX, dataset.bbox.minY);
-			var boundUpper = new L.Point(dataset.bbox.maxX, dataset.bbox.maxY);
-			var latlngLower = convertToLatLng(boundLower);
-			var latlngUpper = convertToLatLng(boundUpper);
+				var dSetConfig = {
+					bBox: new L.Bounds(boundLower, boundUpper),
+					datasetName: dataset.name,
+					date: dataset.date,
+					isoEdgeLayer: dataset.isoEdgeLayer,
+					isoVertexLayer: dataset.isoVertexLayer,
+					isoCoverageLayer: dataset.isoCoverageLayer,
+					latLngBBox: new L.latLngBounds(latlngLower, latlngUpper),
+					prefix: dataset.prefix,
+					queryPoint: new L.LatLng(dataset.queryPoint.x, dataset.queryPoint.y),
+					time: dataset.time
+				};
 
-			var dSetConfig = {
-				bBox: new L.Bounds(boundLower, boundUpper),
-				datasetName: dataset.name,
-				date: dataset.date,
-				isoEdgeLayer: dataset.isoEdgeLayer,
-				isoVertexLayer: dataset.isoVertexLayer,
-				isoCoverageLayer: dataset.isoCoverageLayer,
-				latLngBBox: new L.latLngBounds(latlngLower, latlngUpper),
-				prefix: dataset.prefix,
-				queryPoint: new L.LatLng(dataset.queryPoint.x, dataset.queryPoint.y),
-				time: dataset.time
-			};
+				if (dataset.totalInhabitants) {
+					dSetConfig.totalInhabitants = dataset.totalInhabitants;
+				}
 
-			if (dataset.totalInhabitants) {
-				dSetConfig.totalInhabitants = dataset.totalInhabitants;
+				config.addDatasetConfig(dSetConfig);
 			}
 
-			config.addDatasetConfig(dSetConfig);
-		}
+			return result;
+		};
 
-		return result;
+		/**
+		 * Converts a point from projection EPSG:3857 to LatLng (EPSG:4326)
+		 *
+		 * @see http://developer.tomtom.com/docs/read/map_toolkit/javascript_sdk_2_0/Migration_Guide
+		 * @param L.Point the point to convert (containing EPSG:3857 x and y)
+		 * @return L.latLng the matching L.latLng object (coordinates in EPSG:4326)
+		 */
+		convertToLatLng = function(point) {
+			var earthRadius = 6378137;
+			return L.Projection.SphericalMercator.unproject(point.divideBy(earthRadius));
+		};
 	};
 
-	/**
-	 * Converts a point from projection EPSG:3857 to LatLng (EPSG:4326)
-	 *
-	 * @see http://developer.tomtom.com/docs/read/map_toolkit/javascript_sdk_2_0/Migration_Guide
-	 * @param L.Point the point to convert (containing EPSG:3857 x and y)
-	 * @return L.latLng the matching L.latLng object (coordinates in EPSG:4326)
-	 */
-	convertToLatLng = function(point) {
-		var earthRadius = 6378137;
-		return L.Projection.SphericalMercator.unproject(point.divideBy(earthRadius));
-	};
-};
+	return ServiceConfiguration;
+});
diff --git a/src/main/webapp/js/service/websocket.js b/src/main/webapp/js/service/websocket.js
index d5754372..cf93427d 100644
--- a/src/main/webapp/js/service/websocket.js
+++ b/src/main/webapp/js/service/websocket.js
@@ -1,53 +1,57 @@
-function Websocket() {
-	var wsUri = "ws://localhost:8090/isochrone/websocket";
-	var websocket = null;
-
-	// Constructor
-
-	websocket = new WebSocket(wsUri);
-	websocket.onopen = function(evt) { onOpen(evt); };
-	websocket.onclose = function(evt) { onClose(evt); };
-	websocket.onmessage = function(evt) { onMessage(evt); };
-	websocket.onerror = function(evt) { onError(evt); };
-
-    // Public methods
-
-	this.sendWsMessage = function(msg) {
-		if (websocket.readyState === websocket.OPEN) {
-			 websocket.send(msg);
-		} else if (websocket.readyState === websocket.CONNECTING) {
-		    setTimeout(this.sendWsMessage.bind(this, msg), 100);
-		} else {
-			console.error("Could not send message using websocket: socket closed (or closing)");
-		}
+define(['jQuery', 'console'], function($, logger) {
+	function Websocket() {
+		var wsUri = "ws://localhost:8090/isochrone/websocket";
+		var websocket = null;
+
+		// Constructor
+
+		websocket = new WebSocket(wsUri);
+		websocket.onopen = function(evt) { onOpen(evt); };
+		websocket.onclose = function(evt) { onClose(evt); };
+		websocket.onmessage = function(evt) { onMessage(evt); };
+		websocket.onerror = function(evt) { onError(evt); };
+
+	    // Public methods
+
+		this.sendWsMessage = function(msg) {
+			if (websocket.readyState === websocket.OPEN) {
+				 websocket.send(msg);
+			} else if (websocket.readyState === websocket.CONNECTING) {
+			    setTimeout(this.sendWsMessage.bind(this, msg), 100);
+			} else {
+				logger.error("Could not send message using websocket: socket closed (or closing)");
+			}
+		};
+
+		// Private methods
+
+		onOpen = function(evt) {
+			logger.debug("Opening connection to websocket");
+		};
+
+		onClose = function(evt) {
+			logger.debug("Closing connection to websocket");
+		};
+
+		onMessage = function(evt) {
+			var jsonString = evt.data;
+			logger.debug('Configuration returned by server: ', jsonString);
+
+			var jsonResponse = jQuery.parseJSON(jsonString);
+			if (!jsonResponse.action) {
+				logger.warn("Invalid server response. No action attribute set");
+				return;
+			}
+
+			var e = jQuery.Event(jsonResponse.action, jsonResponse);
+			$(document).trigger(e);
+		};
+
+		onError = function(evt) {
+			logger.error('Could not get data from configuration websocket');
+			logger.warn('Error data: ' + evt.data);
+		};
 	};
 
-	// Private methods
-
-	onOpen = function(evt) {
-	    console.debug("Opening connection to websocket");
-	};
-
-	onClose = function(evt) {
-	    console.debug("Closing connection to websocket");
-	};
-
-	onMessage = function(evt) {
-		var jsonString = evt.data;
-		console.debug('Configuration returned by server: ', jsonString);
-
-		var jsonResponse = jQuery.parseJSON(jsonString);
-		if (!jsonResponse.action) {
-			console.warn("Invalid server response. No action attribute set");
-			return;
-		}
-
-		var e = jQuery.Event(jsonResponse.action, jsonResponse);
-		$(document).trigger(e);
-	};
-
-	onError = function(evt) {
-	    console.error('Could not get data from configuration websocket');
-	    console.warn('Error data: ' + evt.data);
-	};
-};
+	return Websocket;
+});
diff --git a/src/main/webapp/js/versions.txt b/src/main/webapp/js/versions.txt
index 390c2b30..a4b356bc 100644
--- a/src/main/webapp/js/versions.txt
+++ b/src/main/webapp/js/versions.txt
@@ -1,5 +1,5 @@
 Sources:
-#########
+--------
 
 console.js: https://code.google.com/p/console-js/ (v. 0.1)
 bing.js: https://github.com/shramov/leaflet-plugins/
-- 
GitLab