From 54c8680e37ead365369bcad4c43e7116b99173ad Mon Sep 17 00:00:00 2001
From: David Manthey <david.manthey@kitware.com>
Date: Tue, 26 Jun 2018 11:41:45 -0400
Subject: [PATCH] Remove bower support.

This resolves issue #850.
---
 .gitignore |     1 -
 .npmignore |     1 -
 bower.json |    29 -
 geo.js     | 66946 ---------------------------------------------------
 4 files changed, 66977 deletions(-)
 delete mode 100644 bower.json
 delete mode 100644 geo.js

diff --git a/.gitignore b/.gitignore
index 517cafe493..b840ed0c1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,6 @@
 .DS_Store
 .*.swp
 /node_modules/*
-/bower_components/*
 .vimrc
 /geo.min.js
 *.py[ocd]
diff --git a/.npmignore b/.npmignore
index a263b5faa9..573411fbd7 100644
--- a/.npmignore
+++ b/.npmignore
@@ -7,7 +7,6 @@ scripts/
 built/
 *build*/
 .git/
-bower_components/
 dist/
 .eslintcache
 tutorials/
diff --git a/bower.json b/bower.json
deleted file mode 100644
index e3276b0622..0000000000
--- a/bower.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "name": "geojs",
-  "version": "0.16.0",
-  "description": "JavaScript Geo visualization and Analysis Library",
-  "homepage": "https://github.com/OpenGeoscience/geojs",
-  "main": "geo.js",
-  "bugs": {
-    "url": "https://github.com/OpenGeoscience/geojs/issues"
-  },
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/OpenGeoscience/geojs"
-  },
-  "dependencies": {
-    "d3": "^3.5.16"
-  },
-  "ignore": [
-    ".git",
-    ".gitignore",
-    ".travis.yml",
-    ".eslintcache",
-    "dist",
-    "test",
-    "cmake",
-    "testing",
-    "scripts",
-    "dashboard"
-  ]
-}
diff --git a/geo.js b/geo.js
deleted file mode 100644
index 864b482a58..0000000000
--- a/geo.js
+++ /dev/null
@@ -1,66946 +0,0 @@
-(function webpackUniversalModuleDefinition(root, factory) {
-	if(typeof exports === 'object' && typeof module === 'object')
-		module.exports = factory((function webpackLoadOptionalExternalModule() { try { return require("hammerjs"); } catch(e) {} }()), (function webpackLoadOptionalExternalModule() { try { return require("d3"); } catch(e) {} }()));
-	else if(typeof define === 'function' && define.amd)
-		define(["hammerjs", "d3"], factory);
-	else if(typeof exports === 'object')
-		exports["geo"] = factory((function webpackLoadOptionalExternalModule() { try { return require("hammerjs"); } catch(e) {} }()), (function webpackLoadOptionalExternalModule() { try { return require("d3"); } catch(e) {} }()));
-	else
-		root["geo"] = factory(root["Hammer"], root["d3"]);
-})(this, function(__WEBPACK_EXTERNAL_MODULE_224__, __WEBPACK_EXTERNAL_MODULE_230__) {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ 	// The module cache
-/******/ 	var installedModules = {};
-
-/******/ 	// The require function
-/******/ 	function __webpack_require__(moduleId) {
-
-/******/ 		// Check if module is in cache
-/******/ 		if(installedModules[moduleId])
-/******/ 			return installedModules[moduleId].exports;
-
-/******/ 		// Create a new module (and put it into the cache)
-/******/ 		var module = installedModules[moduleId] = {
-/******/ 			exports: {},
-/******/ 			id: moduleId,
-/******/ 			loaded: false
-/******/ 		};
-
-/******/ 		// Execute the module function
-/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-
-/******/ 		// Flag the module as loaded
-/******/ 		module.loaded = true;
-
-/******/ 		// Return the exports of the module
-/******/ 		return module.exports;
-/******/ 	}
-
-
-/******/ 	// expose the modules object (__webpack_modules__)
-/******/ 	__webpack_require__.m = modules;
-
-/******/ 	// expose the module cache
-/******/ 	__webpack_require__.c = installedModules;
-
-/******/ 	// __webpack_public_path__
-/******/ 	__webpack_require__.p = "dist/built";
-
-/******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(0);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/** @namespace geo */
-	// License headers that will be preserved in distributed bundles.
-	/**
-	 * GeoJS
-	 * @copyright 2013-2017, Kitware, Inc.
-	 * @license Apache-2.0
-	 *
-	 * Bundled with the following libraries:
-	 *
-	 * vgl
-	 * @copyright 2014-2016, Kitware, Inc.
-	 * @license Apache-2.0
-	 *
-	 * Proj4js
-	 * @copyright 2014, Mike Adair, Richard Greenwood, Didier Richard, Stephen Irons, Olivier Terral and Calvin Metcalf
-	 * @license MIT
-	 *
-	 * gl-matrix
-	 * @copyright 2015, Brandon Jones, Colin MacKenzie IV
-	 * @license MIT
-	 *
-	 * JQuery
-	 * @copyright jQuery Foundation and other contributors
-	 * @license MIT
-	 *
-	 * earcut
-	 * @copyright 2016, Mapbox
-	 * @license ISC
-	 *
-	 * kdbush
-	 * @copyright 2017, Vladimir Agafonkin
-	 * @license ISC
-	 */
-
-	var $ = __webpack_require__(1);
-	__webpack_require__(2);
-
-	__webpack_require__(3);
-
-	module.exports = $.extend({
-	  annotation: __webpack_require__(7),
-	  annotationLayer: __webpack_require__(219),
-	  camera: __webpack_require__(211),
-	  choroplethFeature: __webpack_require__(225),
-	  contourFeature: __webpack_require__(231),
-	  domRenderer: __webpack_require__(232),
-	  event: __webpack_require__(9),
-	  feature: __webpack_require__(207),
-	  featureLayer: __webpack_require__(220),
-	  fetchQueue: __webpack_require__(233),
-	  fileReader: __webpack_require__(234),
-	  geo_action: __webpack_require__(10),
-	  graphFeature: __webpack_require__(235),
-	  heatmapFeature: __webpack_require__(236),
-	  imageTile: __webpack_require__(237),
-	  jsonReader: __webpack_require__(239),
-	  layer: __webpack_require__(210),
-	  lineFeature: __webpack_require__(206),
-	  map: __webpack_require__(240),
-	  mapInteractor: __webpack_require__(222),
-	  object: __webpack_require__(203),
-	  osmLayer: __webpack_require__(242),
-	  pathFeature: __webpack_require__(245),
-	  pointFeature: __webpack_require__(212),
-	  polygonFeature: __webpack_require__(217),
-	  quadFeature: __webpack_require__(223),
-	  pixelmapFeature: __webpack_require__(246),
-	  renderer: __webpack_require__(202),
-	  sceneObject: __webpack_require__(208),
-	  textFeature: __webpack_require__(218),
-	  tile: __webpack_require__(238),
-	  tileCache: __webpack_require__(244),
-	  tileLayer: __webpack_require__(243),
-	  timestamp: __webpack_require__(209),
-	  transform: __webpack_require__(11),
-	  typedef: __webpack_require__(247),
-	  vectorFeature: __webpack_require__(248),
-	  inherit: __webpack_require__(8),
-	  version: __webpack_require__(249),
-	  sha: __webpack_require__(250),
-
-	  util: __webpack_require__(83),
-	  jQuery: $,
-	  d3: __webpack_require__(251),
-	  gl: __webpack_require__(259),
-	  canvas: __webpack_require__(269),
-	  gui: __webpack_require__(297)
-	}, __webpack_require__(201));
-
-	if (window && !window.$) {
-	  window.$ = $;
-	}
-	if (window && !window.jQuery) {
-	  window.jQuery = $;
-	}
-
-
-/***/ }),
-/* 1 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
-	 * jQuery JavaScript Library v3.3.1
-	 * https://jquery.com/
-	 *
-	 * Includes Sizzle.js
-	 * https://sizzlejs.com/
-	 *
-	 * Copyright JS Foundation and other contributors
-	 * Released under the MIT license
-	 * https://jquery.org/license
-	 *
-	 * Date: 2018-01-20T17:24Z
-	 */
-	( function( global, factory ) {
-
-		"use strict";
-
-		if ( typeof module === "object" && typeof module.exports === "object" ) {
-
-			// For CommonJS and CommonJS-like environments where a proper `window`
-			// is present, execute the factory and get jQuery.
-			// For environments that do not have a `window` with a `document`
-			// (such as Node.js), expose a factory as module.exports.
-			// This accentuates the need for the creation of a real `window`.
-			// e.g. var jQuery = require("jquery")(window);
-			// See ticket #14549 for more info.
-			module.exports = global.document ?
-				factory( global, true ) :
-				function( w ) {
-					if ( !w.document ) {
-						throw new Error( "jQuery requires a window with a document" );
-					}
-					return factory( w );
-				};
-		} else {
-			factory( global );
-		}
-
-	// Pass this if window is not defined yet
-	} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
-
-	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
-	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
-	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
-	// enough that all such attempts are guarded in a try block.
-	"use strict";
-
-	var arr = [];
-
-	var document = window.document;
-
-	var getProto = Object.getPrototypeOf;
-
-	var slice = arr.slice;
-
-	var concat = arr.concat;
-
-	var push = arr.push;
-
-	var indexOf = arr.indexOf;
-
-	var class2type = {};
-
-	var toString = class2type.toString;
-
-	var hasOwn = class2type.hasOwnProperty;
-
-	var fnToString = hasOwn.toString;
-
-	var ObjectFunctionString = fnToString.call( Object );
-
-	var support = {};
-
-	var isFunction = function isFunction( obj ) {
-
-	      // Support: Chrome <=57, Firefox <=52
-	      // In some browsers, typeof returns "function" for HTML <object> elements
-	      // (i.e., `typeof document.createElement( "object" ) === "function"`).
-	      // We don't want to classify *any* DOM node as a function.
-	      return typeof obj === "function" && typeof obj.nodeType !== "number";
-	  };
-
-
-	var isWindow = function isWindow( obj ) {
-			return obj != null && obj === obj.window;
-		};
-
-
-
-
-		var preservedScriptAttributes = {
-			type: true,
-			src: true,
-			noModule: true
-		};
-
-		function DOMEval( code, doc, node ) {
-			doc = doc || document;
-
-			var i,
-				script = doc.createElement( "script" );
-
-			script.text = code;
-			if ( node ) {
-				for ( i in preservedScriptAttributes ) {
-					if ( node[ i ] ) {
-						script[ i ] = node[ i ];
-					}
-				}
-			}
-			doc.head.appendChild( script ).parentNode.removeChild( script );
-		}
-
-
-	function toType( obj ) {
-		if ( obj == null ) {
-			return obj + "";
-		}
-
-		// Support: Android <=2.3 only (functionish RegExp)
-		return typeof obj === "object" || typeof obj === "function" ?
-			class2type[ toString.call( obj ) ] || "object" :
-			typeof obj;
-	}
-	/* global Symbol */
-	// Defining this global in .eslintrc.json would create a danger of using the global
-	// unguarded in another place, it seems safer to define global only for this module
-
-
-
-	var
-		version = "3.3.1",
-
-		// Define a local copy of jQuery
-		jQuery = function( selector, context ) {
-
-			// The jQuery object is actually just the init constructor 'enhanced'
-			// Need init if jQuery is called (just allow error to be thrown if not included)
-			return new jQuery.fn.init( selector, context );
-		},
-
-		// Support: Android <=4.0 only
-		// Make sure we trim BOM and NBSP
-		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
-
-	jQuery.fn = jQuery.prototype = {
-
-		// The current version of jQuery being used
-		jquery: version,
-
-		constructor: jQuery,
-
-		// The default length of a jQuery object is 0
-		length: 0,
-
-		toArray: function() {
-			return slice.call( this );
-		},
-
-		// Get the Nth element in the matched element set OR
-		// Get the whole matched element set as a clean array
-		get: function( num ) {
-
-			// Return all the elements in a clean array
-			if ( num == null ) {
-				return slice.call( this );
-			}
-
-			// Return just the one element from the set
-			return num < 0 ? this[ num + this.length ] : this[ num ];
-		},
-
-		// Take an array of elements and push it onto the stack
-		// (returning the new matched element set)
-		pushStack: function( elems ) {
-
-			// Build a new jQuery matched element set
-			var ret = jQuery.merge( this.constructor(), elems );
-
-			// Add the old object onto the stack (as a reference)
-			ret.prevObject = this;
-
-			// Return the newly-formed element set
-			return ret;
-		},
-
-		// Execute a callback for every element in the matched set.
-		each: function( callback ) {
-			return jQuery.each( this, callback );
-		},
-
-		map: function( callback ) {
-			return this.pushStack( jQuery.map( this, function( elem, i ) {
-				return callback.call( elem, i, elem );
-			} ) );
-		},
-
-		slice: function() {
-			return this.pushStack( slice.apply( this, arguments ) );
-		},
-
-		first: function() {
-			return this.eq( 0 );
-		},
-
-		last: function() {
-			return this.eq( -1 );
-		},
-
-		eq: function( i ) {
-			var len = this.length,
-				j = +i + ( i < 0 ? len : 0 );
-			return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
-		},
-
-		end: function() {
-			return this.prevObject || this.constructor();
-		},
-
-		// For internal use only.
-		// Behaves like an Array's method, not like a jQuery method.
-		push: push,
-		sort: arr.sort,
-		splice: arr.splice
-	};
-
-	jQuery.extend = jQuery.fn.extend = function() {
-		var options, name, src, copy, copyIsArray, clone,
-			target = arguments[ 0 ] || {},
-			i = 1,
-			length = arguments.length,
-			deep = false;
-
-		// Handle a deep copy situation
-		if ( typeof target === "boolean" ) {
-			deep = target;
-
-			// Skip the boolean and the target
-			target = arguments[ i ] || {};
-			i++;
-		}
-
-		// Handle case when target is a string or something (possible in deep copy)
-		if ( typeof target !== "object" && !isFunction( target ) ) {
-			target = {};
-		}
-
-		// Extend jQuery itself if only one argument is passed
-		if ( i === length ) {
-			target = this;
-			i--;
-		}
-
-		for ( ; i < length; i++ ) {
-
-			// Only deal with non-null/undefined values
-			if ( ( options = arguments[ i ] ) != null ) {
-
-				// Extend the base object
-				for ( name in options ) {
-					src = target[ name ];
-					copy = options[ name ];
-
-					// Prevent never-ending loop
-					if ( target === copy ) {
-						continue;
-					}
-
-					// Recurse if we're merging plain objects or arrays
-					if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
-						( copyIsArray = Array.isArray( copy ) ) ) ) {
-
-						if ( copyIsArray ) {
-							copyIsArray = false;
-							clone = src && Array.isArray( src ) ? src : [];
-
-						} else {
-							clone = src && jQuery.isPlainObject( src ) ? src : {};
-						}
-
-						// Never move original objects, clone them
-						target[ name ] = jQuery.extend( deep, clone, copy );
-
-					// Don't bring in undefined values
-					} else if ( copy !== undefined ) {
-						target[ name ] = copy;
-					}
-				}
-			}
-		}
-
-		// Return the modified object
-		return target;
-	};
-
-	jQuery.extend( {
-
-		// Unique for each copy of jQuery on the page
-		expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
-
-		// Assume jQuery is ready without the ready module
-		isReady: true,
-
-		error: function( msg ) {
-			throw new Error( msg );
-		},
-
-		noop: function() {},
-
-		isPlainObject: function( obj ) {
-			var proto, Ctor;
-
-			// Detect obvious negatives
-			// Use toString instead of jQuery.type to catch host objects
-			if ( !obj || toString.call( obj ) !== "[object Object]" ) {
-				return false;
-			}
-
-			proto = getProto( obj );
-
-			// Objects with no prototype (e.g., `Object.create( null )`) are plain
-			if ( !proto ) {
-				return true;
-			}
-
-			// Objects with prototype are plain iff they were constructed by a global Object function
-			Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
-			return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
-		},
-
-		isEmptyObject: function( obj ) {
-
-			/* eslint-disable no-unused-vars */
-			// See https://github.com/eslint/eslint/issues/6125
-			var name;
-
-			for ( name in obj ) {
-				return false;
-			}
-			return true;
-		},
-
-		// Evaluates a script in a global context
-		globalEval: function( code ) {
-			DOMEval( code );
-		},
-
-		each: function( obj, callback ) {
-			var length, i = 0;
-
-			if ( isArrayLike( obj ) ) {
-				length = obj.length;
-				for ( ; i < length; i++ ) {
-					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
-						break;
-					}
-				}
-			} else {
-				for ( i in obj ) {
-					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
-						break;
-					}
-				}
-			}
-
-			return obj;
-		},
-
-		// Support: Android <=4.0 only
-		trim: function( text ) {
-			return text == null ?
-				"" :
-				( text + "" ).replace( rtrim, "" );
-		},
-
-		// results is for internal usage only
-		makeArray: function( arr, results ) {
-			var ret = results || [];
-
-			if ( arr != null ) {
-				if ( isArrayLike( Object( arr ) ) ) {
-					jQuery.merge( ret,
-						typeof arr === "string" ?
-						[ arr ] : arr
-					);
-				} else {
-					push.call( ret, arr );
-				}
-			}
-
-			return ret;
-		},
-
-		inArray: function( elem, arr, i ) {
-			return arr == null ? -1 : indexOf.call( arr, elem, i );
-		},
-
-		// Support: Android <=4.0 only, PhantomJS 1 only
-		// push.apply(_, arraylike) throws on ancient WebKit
-		merge: function( first, second ) {
-			var len = +second.length,
-				j = 0,
-				i = first.length;
-
-			for ( ; j < len; j++ ) {
-				first[ i++ ] = second[ j ];
-			}
-
-			first.length = i;
-
-			return first;
-		},
-
-		grep: function( elems, callback, invert ) {
-			var callbackInverse,
-				matches = [],
-				i = 0,
-				length = elems.length,
-				callbackExpect = !invert;
-
-			// Go through the array, only saving the items
-			// that pass the validator function
-			for ( ; i < length; i++ ) {
-				callbackInverse = !callback( elems[ i ], i );
-				if ( callbackInverse !== callbackExpect ) {
-					matches.push( elems[ i ] );
-				}
-			}
-
-			return matches;
-		},
-
-		// arg is for internal usage only
-		map: function( elems, callback, arg ) {
-			var length, value,
-				i = 0,
-				ret = [];
-
-			// Go through the array, translating each of the items to their new values
-			if ( isArrayLike( elems ) ) {
-				length = elems.length;
-				for ( ; i < length; i++ ) {
-					value = callback( elems[ i ], i, arg );
-
-					if ( value != null ) {
-						ret.push( value );
-					}
-				}
-
-			// Go through every key on the object,
-			} else {
-				for ( i in elems ) {
-					value = callback( elems[ i ], i, arg );
-
-					if ( value != null ) {
-						ret.push( value );
-					}
-				}
-			}
-
-			// Flatten any nested arrays
-			return concat.apply( [], ret );
-		},
-
-		// A global GUID counter for objects
-		guid: 1,
-
-		// jQuery.support is not used in Core but other projects attach their
-		// properties to it so it needs to exist.
-		support: support
-	} );
-
-	if ( typeof Symbol === "function" ) {
-		jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
-	}
-
-	// Populate the class2type map
-	jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
-	function( i, name ) {
-		class2type[ "[object " + name + "]" ] = name.toLowerCase();
-	} );
-
-	function isArrayLike( obj ) {
-
-		// Support: real iOS 8.2 only (not reproducible in simulator)
-		// `in` check used to prevent JIT error (gh-2145)
-		// hasOwn isn't used here due to false negatives
-		// regarding Nodelist length in IE
-		var length = !!obj && "length" in obj && obj.length,
-			type = toType( obj );
-
-		if ( isFunction( obj ) || isWindow( obj ) ) {
-			return false;
-		}
-
-		return type === "array" || length === 0 ||
-			typeof length === "number" && length > 0 && ( length - 1 ) in obj;
-	}
-	var Sizzle =
-	/*!
-	 * Sizzle CSS Selector Engine v2.3.3
-	 * https://sizzlejs.com/
-	 *
-	 * Copyright jQuery Foundation and other contributors
-	 * Released under the MIT license
-	 * http://jquery.org/license
-	 *
-	 * Date: 2016-08-08
-	 */
-	(function( window ) {
-
-	var i,
-		support,
-		Expr,
-		getText,
-		isXML,
-		tokenize,
-		compile,
-		select,
-		outermostContext,
-		sortInput,
-		hasDuplicate,
-
-		// Local document vars
-		setDocument,
-		document,
-		docElem,
-		documentIsHTML,
-		rbuggyQSA,
-		rbuggyMatches,
-		matches,
-		contains,
-
-		// Instance-specific data
-		expando = "sizzle" + 1 * new Date(),
-		preferredDoc = window.document,
-		dirruns = 0,
-		done = 0,
-		classCache = createCache(),
-		tokenCache = createCache(),
-		compilerCache = createCache(),
-		sortOrder = function( a, b ) {
-			if ( a === b ) {
-				hasDuplicate = true;
-			}
-			return 0;
-		},
-
-		// Instance methods
-		hasOwn = ({}).hasOwnProperty,
-		arr = [],
-		pop = arr.pop,
-		push_native = arr.push,
-		push = arr.push,
-		slice = arr.slice,
-		// Use a stripped-down indexOf as it's faster than native
-		// https://jsperf.com/thor-indexof-vs-for/5
-		indexOf = function( list, elem ) {
-			var i = 0,
-				len = list.length;
-			for ( ; i < len; i++ ) {
-				if ( list[i] === elem ) {
-					return i;
-				}
-			}
-			return -1;
-		},
-
-		booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
-
-		// Regular expressions
-
-		// http://www.w3.org/TR/css3-selectors/#whitespace
-		whitespace = "[\\x20\\t\\r\\n\\f]",
-
-		// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-		identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
-
-		// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
-		attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
-			// Operator (capture 2)
-			"*([*^$|!~]?=)" + whitespace +
-			// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
-			"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
-			"*\\]",
-
-		pseudos = ":(" + identifier + ")(?:\\((" +
-			// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
-			// 1. quoted (capture 3; capture 4 or capture 5)
-			"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
-			// 2. simple (capture 6)
-			"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
-			// 3. anything else (capture 2)
-			".*" +
-			")\\)|)",
-
-		// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
-		rwhitespace = new RegExp( whitespace + "+", "g" ),
-		rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
-
-		rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
-		rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
-
-		rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
-
-		rpseudo = new RegExp( pseudos ),
-		ridentifier = new RegExp( "^" + identifier + "$" ),
-
-		matchExpr = {
-			"ID": new RegExp( "^#(" + identifier + ")" ),
-			"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
-			"TAG": new RegExp( "^(" + identifier + "|[*])" ),
-			"ATTR": new RegExp( "^" + attributes ),
-			"PSEUDO": new RegExp( "^" + pseudos ),
-			"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
-				"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
-				"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
-			"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
-			// For use in libraries implementing .is()
-			// We use this for POS matching in `select`
-			"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
-				whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
-		},
-
-		rinputs = /^(?:input|select|textarea|button)$/i,
-		rheader = /^h\d$/i,
-
-		rnative = /^[^{]+\{\s*\[native \w/,
-
-		// Easily-parseable/retrievable ID or TAG or CLASS selectors
-		rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
-
-		rsibling = /[+~]/,
-
-		// CSS escapes
-		// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
-		runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
-		funescape = function( _, escaped, escapedWhitespace ) {
-			var high = "0x" + escaped - 0x10000;
-			// NaN means non-codepoint
-			// Support: Firefox<24
-			// Workaround erroneous numeric interpretation of +"0x"
-			return high !== high || escapedWhitespace ?
-				escaped :
-				high < 0 ?
-					// BMP codepoint
-					String.fromCharCode( high + 0x10000 ) :
-					// Supplemental Plane codepoint (surrogate pair)
-					String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
-		},
-
-		// CSS string/identifier serialization
-		// https://drafts.csswg.org/cssom/#common-serializing-idioms
-		rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
-		fcssescape = function( ch, asCodePoint ) {
-			if ( asCodePoint ) {
-
-				// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
-				if ( ch === "\0" ) {
-					return "\uFFFD";
-				}
-
-				// Control characters and (dependent upon position) numbers get escaped as code points
-				return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
-			}
-
-			// Other potentially-special ASCII characters get backslash-escaped
-			return "\\" + ch;
-		},
-
-		// Used for iframes
-		// See setDocument()
-		// Removing the function wrapper causes a "Permission Denied"
-		// error in IE
-		unloadHandler = function() {
-			setDocument();
-		},
-
-		disabledAncestor = addCombinator(
-			function( elem ) {
-				return elem.disabled === true && ("form" in elem || "label" in elem);
-			},
-			{ dir: "parentNode", next: "legend" }
-		);
-
-	// Optimize for push.apply( _, NodeList )
-	try {
-		push.apply(
-			(arr = slice.call( preferredDoc.childNodes )),
-			preferredDoc.childNodes
-		);
-		// Support: Android<4.0
-		// Detect silently failing push.apply
-		arr[ preferredDoc.childNodes.length ].nodeType;
-	} catch ( e ) {
-		push = { apply: arr.length ?
-
-			// Leverage slice if possible
-			function( target, els ) {
-				push_native.apply( target, slice.call(els) );
-			} :
-
-			// Support: IE<9
-			// Otherwise append directly
-			function( target, els ) {
-				var j = target.length,
-					i = 0;
-				// Can't trust NodeList.length
-				while ( (target[j++] = els[i++]) ) {}
-				target.length = j - 1;
-			}
-		};
-	}
-
-	function Sizzle( selector, context, results, seed ) {
-		var m, i, elem, nid, match, groups, newSelector,
-			newContext = context && context.ownerDocument,
-
-			// nodeType defaults to 9, since context defaults to document
-			nodeType = context ? context.nodeType : 9;
-
-		results = results || [];
-
-		// Return early from calls with invalid selector or context
-		if ( typeof selector !== "string" || !selector ||
-			nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
-
-			return results;
-		}
-
-		// Try to shortcut find operations (as opposed to filters) in HTML documents
-		if ( !seed ) {
-
-			if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
-				setDocument( context );
-			}
-			context = context || document;
-
-			if ( documentIsHTML ) {
-
-				// If the selector is sufficiently simple, try using a "get*By*" DOM method
-				// (excepting DocumentFragment context, where the methods don't exist)
-				if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
-
-					// ID selector
-					if ( (m = match[1]) ) {
-
-						// Document context
-						if ( nodeType === 9 ) {
-							if ( (elem = context.getElementById( m )) ) {
-
-								// Support: IE, Opera, Webkit
-								// TODO: identify versions
-								// getElementById can match elements by name instead of ID
-								if ( elem.id === m ) {
-									results.push( elem );
-									return results;
-								}
-							} else {
-								return results;
-							}
-
-						// Element context
-						} else {
-
-							// Support: IE, Opera, Webkit
-							// TODO: identify versions
-							// getElementById can match elements by name instead of ID
-							if ( newContext && (elem = newContext.getElementById( m )) &&
-								contains( context, elem ) &&
-								elem.id === m ) {
-
-								results.push( elem );
-								return results;
-							}
-						}
-
-					// Type selector
-					} else if ( match[2] ) {
-						push.apply( results, context.getElementsByTagName( selector ) );
-						return results;
-
-					// Class selector
-					} else if ( (m = match[3]) && support.getElementsByClassName &&
-						context.getElementsByClassName ) {
-
-						push.apply( results, context.getElementsByClassName( m ) );
-						return results;
-					}
-				}
-
-				// Take advantage of querySelectorAll
-				if ( support.qsa &&
-					!compilerCache[ selector + " " ] &&
-					(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
-
-					if ( nodeType !== 1 ) {
-						newContext = context;
-						newSelector = selector;
-
-					// qSA looks outside Element context, which is not what we want
-					// Thanks to Andrew Dupont for this workaround technique
-					// Support: IE <=8
-					// Exclude object elements
-					} else if ( context.nodeName.toLowerCase() !== "object" ) {
-
-						// Capture the context ID, setting it first if necessary
-						if ( (nid = context.getAttribute( "id" )) ) {
-							nid = nid.replace( rcssescape, fcssescape );
-						} else {
-							context.setAttribute( "id", (nid = expando) );
-						}
-
-						// Prefix every selector in the list
-						groups = tokenize( selector );
-						i = groups.length;
-						while ( i-- ) {
-							groups[i] = "#" + nid + " " + toSelector( groups[i] );
-						}
-						newSelector = groups.join( "," );
-
-						// Expand context for sibling selectors
-						newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
-							context;
-					}
-
-					if ( newSelector ) {
-						try {
-							push.apply( results,
-								newContext.querySelectorAll( newSelector )
-							);
-							return results;
-						} catch ( qsaError ) {
-						} finally {
-							if ( nid === expando ) {
-								context.removeAttribute( "id" );
-							}
-						}
-					}
-				}
-			}
-		}
-
-		// All others
-		return select( selector.replace( rtrim, "$1" ), context, results, seed );
-	}
-
-	/**
-	 * Create key-value caches of limited size
-	 * @returns {function(string, object)} Returns the Object data after storing it on itself with
-	 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
-	 *	deleting the oldest entry
-	 */
-	function createCache() {
-		var keys = [];
-
-		function cache( key, value ) {
-			// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
-			if ( keys.push( key + " " ) > Expr.cacheLength ) {
-				// Only keep the most recent entries
-				delete cache[ keys.shift() ];
-			}
-			return (cache[ key + " " ] = value);
-		}
-		return cache;
-	}
-
-	/**
-	 * Mark a function for special use by Sizzle
-	 * @param {Function} fn The function to mark
-	 */
-	function markFunction( fn ) {
-		fn[ expando ] = true;
-		return fn;
-	}
-
-	/**
-	 * Support testing using an element
-	 * @param {Function} fn Passed the created element and returns a boolean result
-	 */
-	function assert( fn ) {
-		var el = document.createElement("fieldset");
-
-		try {
-			return !!fn( el );
-		} catch (e) {
-			return false;
-		} finally {
-			// Remove from its parent by default
-			if ( el.parentNode ) {
-				el.parentNode.removeChild( el );
-			}
-			// release memory in IE
-			el = null;
-		}
-	}
-
-	/**
-	 * Adds the same handler for all of the specified attrs
-	 * @param {String} attrs Pipe-separated list of attributes
-	 * @param {Function} handler The method that will be applied
-	 */
-	function addHandle( attrs, handler ) {
-		var arr = attrs.split("|"),
-			i = arr.length;
-
-		while ( i-- ) {
-			Expr.attrHandle[ arr[i] ] = handler;
-		}
-	}
-
-	/**
-	 * Checks document order of two siblings
-	 * @param {Element} a
-	 * @param {Element} b
-	 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
-	 */
-	function siblingCheck( a, b ) {
-		var cur = b && a,
-			diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
-				a.sourceIndex - b.sourceIndex;
-
-		// Use IE sourceIndex if available on both nodes
-		if ( diff ) {
-			return diff;
-		}
-
-		// Check if b follows a
-		if ( cur ) {
-			while ( (cur = cur.nextSibling) ) {
-				if ( cur === b ) {
-					return -1;
-				}
-			}
-		}
-
-		return a ? 1 : -1;
-	}
-
-	/**
-	 * Returns a function to use in pseudos for input types
-	 * @param {String} type
-	 */
-	function createInputPseudo( type ) {
-		return function( elem ) {
-			var name = elem.nodeName.toLowerCase();
-			return name === "input" && elem.type === type;
-		};
-	}
-
-	/**
-	 * Returns a function to use in pseudos for buttons
-	 * @param {String} type
-	 */
-	function createButtonPseudo( type ) {
-		return function( elem ) {
-			var name = elem.nodeName.toLowerCase();
-			return (name === "input" || name === "button") && elem.type === type;
-		};
-	}
-
-	/**
-	 * Returns a function to use in pseudos for :enabled/:disabled
-	 * @param {Boolean} disabled true for :disabled; false for :enabled
-	 */
-	function createDisabledPseudo( disabled ) {
-
-		// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
-		return function( elem ) {
-
-			// Only certain elements can match :enabled or :disabled
-			// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
-			// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
-			if ( "form" in elem ) {
-
-				// Check for inherited disabledness on relevant non-disabled elements:
-				// * listed form-associated elements in a disabled fieldset
-				//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
-				//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
-				// * option elements in a disabled optgroup
-				//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
-				// All such elements have a "form" property.
-				if ( elem.parentNode && elem.disabled === false ) {
-
-					// Option elements defer to a parent optgroup if present
-					if ( "label" in elem ) {
-						if ( "label" in elem.parentNode ) {
-							return elem.parentNode.disabled === disabled;
-						} else {
-							return elem.disabled === disabled;
-						}
-					}
-
-					// Support: IE 6 - 11
-					// Use the isDisabled shortcut property to check for disabled fieldset ancestors
-					return elem.isDisabled === disabled ||
-
-						// Where there is no isDisabled, check manually
-						/* jshint -W018 */
-						elem.isDisabled !== !disabled &&
-							disabledAncestor( elem ) === disabled;
-				}
-
-				return elem.disabled === disabled;
-
-			// Try to winnow out elements that can't be disabled before trusting the disabled property.
-			// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
-			// even exist on them, let alone have a boolean value.
-			} else if ( "label" in elem ) {
-				return elem.disabled === disabled;
-			}
-
-			// Remaining elements are neither :enabled nor :disabled
-			return false;
-		};
-	}
-
-	/**
-	 * Returns a function to use in pseudos for positionals
-	 * @param {Function} fn
-	 */
-	function createPositionalPseudo( fn ) {
-		return markFunction(function( argument ) {
-			argument = +argument;
-			return markFunction(function( seed, matches ) {
-				var j,
-					matchIndexes = fn( [], seed.length, argument ),
-					i = matchIndexes.length;
-
-				// Match elements found at the specified indexes
-				while ( i-- ) {
-					if ( seed[ (j = matchIndexes[i]) ] ) {
-						seed[j] = !(matches[j] = seed[j]);
-					}
-				}
-			});
-		});
-	}
-
-	/**
-	 * Checks a node for validity as a Sizzle context
-	 * @param {Element|Object=} context
-	 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
-	 */
-	function testContext( context ) {
-		return context && typeof context.getElementsByTagName !== "undefined" && context;
-	}
-
-	// Expose support vars for convenience
-	support = Sizzle.support = {};
-
-	/**
-	 * Detects XML nodes
-	 * @param {Element|Object} elem An element or a document
-	 * @returns {Boolean} True iff elem is a non-HTML XML node
-	 */
-	isXML = Sizzle.isXML = function( elem ) {
-		// documentElement is verified for cases where it doesn't yet exist
-		// (such as loading iframes in IE - #4833)
-		var documentElement = elem && (elem.ownerDocument || elem).documentElement;
-		return documentElement ? documentElement.nodeName !== "HTML" : false;
-	};
-
-	/**
-	 * Sets document-related variables once based on the current document
-	 * @param {Element|Object} [doc] An element or document object to use to set the document
-	 * @returns {Object} Returns the current document
-	 */
-	setDocument = Sizzle.setDocument = function( node ) {
-		var hasCompare, subWindow,
-			doc = node ? node.ownerDocument || node : preferredDoc;
-
-		// Return early if doc is invalid or already selected
-		if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
-			return document;
-		}
-
-		// Update global variables
-		document = doc;
-		docElem = document.documentElement;
-		documentIsHTML = !isXML( document );
-
-		// Support: IE 9-11, Edge
-		// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
-		if ( preferredDoc !== document &&
-			(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
-
-			// Support: IE 11, Edge
-			if ( subWindow.addEventListener ) {
-				subWindow.addEventListener( "unload", unloadHandler, false );
-
-			// Support: IE 9 - 10 only
-			} else if ( subWindow.attachEvent ) {
-				subWindow.attachEvent( "onunload", unloadHandler );
-			}
-		}
-
-		/* Attributes
-		---------------------------------------------------------------------- */
-
-		// Support: IE<8
-		// Verify that getAttribute really returns attributes and not properties
-		// (excepting IE8 booleans)
-		support.attributes = assert(function( el ) {
-			el.className = "i";
-			return !el.getAttribute("className");
-		});
-
-		/* getElement(s)By*
-		---------------------------------------------------------------------- */
-
-		// Check if getElementsByTagName("*") returns only elements
-		support.getElementsByTagName = assert(function( el ) {
-			el.appendChild( document.createComment("") );
-			return !el.getElementsByTagName("*").length;
-		});
-
-		// Support: IE<9
-		support.getElementsByClassName = rnative.test( document.getElementsByClassName );
-
-		// Support: IE<10
-		// Check if getElementById returns elements by name
-		// The broken getElementById methods don't pick up programmatically-set names,
-		// so use a roundabout getElementsByName test
-		support.getById = assert(function( el ) {
-			docElem.appendChild( el ).id = expando;
-			return !document.getElementsByName || !document.getElementsByName( expando ).length;
-		});
-
-		// ID filter and find
-		if ( support.getById ) {
-			Expr.filter["ID"] = function( id ) {
-				var attrId = id.replace( runescape, funescape );
-				return function( elem ) {
-					return elem.getAttribute("id") === attrId;
-				};
-			};
-			Expr.find["ID"] = function( id, context ) {
-				if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
-					var elem = context.getElementById( id );
-					return elem ? [ elem ] : [];
-				}
-			};
-		} else {
-			Expr.filter["ID"] =  function( id ) {
-				var attrId = id.replace( runescape, funescape );
-				return function( elem ) {
-					var node = typeof elem.getAttributeNode !== "undefined" &&
-						elem.getAttributeNode("id");
-					return node && node.value === attrId;
-				};
-			};
-
-			// Support: IE 6 - 7 only
-			// getElementById is not reliable as a find shortcut
-			Expr.find["ID"] = function( id, context ) {
-				if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
-					var node, i, elems,
-						elem = context.getElementById( id );
-
-					if ( elem ) {
-
-						// Verify the id attribute
-						node = elem.getAttributeNode("id");
-						if ( node && node.value === id ) {
-							return [ elem ];
-						}
-
-						// Fall back on getElementsByName
-						elems = context.getElementsByName( id );
-						i = 0;
-						while ( (elem = elems[i++]) ) {
-							node = elem.getAttributeNode("id");
-							if ( node && node.value === id ) {
-								return [ elem ];
-							}
-						}
-					}
-
-					return [];
-				}
-			};
-		}
-
-		// Tag
-		Expr.find["TAG"] = support.getElementsByTagName ?
-			function( tag, context ) {
-				if ( typeof context.getElementsByTagName !== "undefined" ) {
-					return context.getElementsByTagName( tag );
-
-				// DocumentFragment nodes don't have gEBTN
-				} else if ( support.qsa ) {
-					return context.querySelectorAll( tag );
-				}
-			} :
-
-			function( tag, context ) {
-				var elem,
-					tmp = [],
-					i = 0,
-					// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
-					results = context.getElementsByTagName( tag );
-
-				// Filter out possible comments
-				if ( tag === "*" ) {
-					while ( (elem = results[i++]) ) {
-						if ( elem.nodeType === 1 ) {
-							tmp.push( elem );
-						}
-					}
-
-					return tmp;
-				}
-				return results;
-			};
-
-		// Class
-		Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
-			if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
-				return context.getElementsByClassName( className );
-			}
-		};
-
-		/* QSA/matchesSelector
-		---------------------------------------------------------------------- */
-
-		// QSA and matchesSelector support
-
-		// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
-		rbuggyMatches = [];
-
-		// qSa(:focus) reports false when true (Chrome 21)
-		// We allow this because of a bug in IE8/9 that throws an error
-		// whenever `document.activeElement` is accessed on an iframe
-		// So, we allow :focus to pass through QSA all the time to avoid the IE error
-		// See https://bugs.jquery.com/ticket/13378
-		rbuggyQSA = [];
-
-		if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
-			// Build QSA regex
-			// Regex strategy adopted from Diego Perini
-			assert(function( el ) {
-				// Select is set to empty string on purpose
-				// This is to test IE's treatment of not explicitly
-				// setting a boolean content attribute,
-				// since its presence should be enough
-				// https://bugs.jquery.com/ticket/12359
-				docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
-					"<select id='" + expando + "-\r\\' msallowcapture=''>" +
-					"<option selected=''></option></select>";
-
-				// Support: IE8, Opera 11-12.16
-				// Nothing should be selected when empty strings follow ^= or $= or *=
-				// The test attribute must be unknown in Opera but "safe" for WinRT
-				// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
-				if ( el.querySelectorAll("[msallowcapture^='']").length ) {
-					rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
-				}
-
-				// Support: IE8
-				// Boolean attributes and "value" are not treated correctly
-				if ( !el.querySelectorAll("[selected]").length ) {
-					rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
-				}
-
-				// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
-				if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
-					rbuggyQSA.push("~=");
-				}
-
-				// Webkit/Opera - :checked should return selected option elements
-				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
-				// IE8 throws error here and will not see later tests
-				if ( !el.querySelectorAll(":checked").length ) {
-					rbuggyQSA.push(":checked");
-				}
-
-				// Support: Safari 8+, iOS 8+
-				// https://bugs.webkit.org/show_bug.cgi?id=136851
-				// In-page `selector#id sibling-combinator selector` fails
-				if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
-					rbuggyQSA.push(".#.+[+~]");
-				}
-			});
-
-			assert(function( el ) {
-				el.innerHTML = "<a href='' disabled='disabled'></a>" +
-					"<select disabled='disabled'><option/></select>";
-
-				// Support: Windows 8 Native Apps
-				// The type and name attributes are restricted during .innerHTML assignment
-				var input = document.createElement("input");
-				input.setAttribute( "type", "hidden" );
-				el.appendChild( input ).setAttribute( "name", "D" );
-
-				// Support: IE8
-				// Enforce case-sensitivity of name attribute
-				if ( el.querySelectorAll("[name=d]").length ) {
-					rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
-				}
-
-				// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
-				// IE8 throws error here and will not see later tests
-				if ( el.querySelectorAll(":enabled").length !== 2 ) {
-					rbuggyQSA.push( ":enabled", ":disabled" );
-				}
-
-				// Support: IE9-11+
-				// IE's :disabled selector does not pick up the children of disabled fieldsets
-				docElem.appendChild( el ).disabled = true;
-				if ( el.querySelectorAll(":disabled").length !== 2 ) {
-					rbuggyQSA.push( ":enabled", ":disabled" );
-				}
-
-				// Opera 10-11 does not throw on post-comma invalid pseudos
-				el.querySelectorAll("*,:x");
-				rbuggyQSA.push(",.*:");
-			});
-		}
-
-		if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
-			docElem.webkitMatchesSelector ||
-			docElem.mozMatchesSelector ||
-			docElem.oMatchesSelector ||
-			docElem.msMatchesSelector) )) ) {
-
-			assert(function( el ) {
-				// Check to see if it's possible to do matchesSelector
-				// on a disconnected node (IE 9)
-				support.disconnectedMatch = matches.call( el, "*" );
-
-				// This should fail with an exception
-				// Gecko does not error, returns false instead
-				matches.call( el, "[s!='']:x" );
-				rbuggyMatches.push( "!=", pseudos );
-			});
-		}
-
-		rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
-		rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
-
-		/* Contains
-		---------------------------------------------------------------------- */
-		hasCompare = rnative.test( docElem.compareDocumentPosition );
-
-		// Element contains another
-		// Purposefully self-exclusive
-		// As in, an element does not contain itself
-		contains = hasCompare || rnative.test( docElem.contains ) ?
-			function( a, b ) {
-				var adown = a.nodeType === 9 ? a.documentElement : a,
-					bup = b && b.parentNode;
-				return a === bup || !!( bup && bup.nodeType === 1 && (
-					adown.contains ?
-						adown.contains( bup ) :
-						a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
-				));
-			} :
-			function( a, b ) {
-				if ( b ) {
-					while ( (b = b.parentNode) ) {
-						if ( b === a ) {
-							return true;
-						}
-					}
-				}
-				return false;
-			};
-
-		/* Sorting
-		---------------------------------------------------------------------- */
-
-		// Document order sorting
-		sortOrder = hasCompare ?
-		function( a, b ) {
-
-			// Flag for duplicate removal
-			if ( a === b ) {
-				hasDuplicate = true;
-				return 0;
-			}
-
-			// Sort on method existence if only one input has compareDocumentPosition
-			var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
-			if ( compare ) {
-				return compare;
-			}
-
-			// Calculate position if both inputs belong to the same document
-			compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
-				a.compareDocumentPosition( b ) :
-
-				// Otherwise we know they are disconnected
-				1;
-
-			// Disconnected nodes
-			if ( compare & 1 ||
-				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
-
-				// Choose the first element that is related to our preferred document
-				if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
-					return -1;
-				}
-				if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
-					return 1;
-				}
-
-				// Maintain original order
-				return sortInput ?
-					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
-					0;
-			}
-
-			return compare & 4 ? -1 : 1;
-		} :
-		function( a, b ) {
-			// Exit early if the nodes are identical
-			if ( a === b ) {
-				hasDuplicate = true;
-				return 0;
-			}
-
-			var cur,
-				i = 0,
-				aup = a.parentNode,
-				bup = b.parentNode,
-				ap = [ a ],
-				bp = [ b ];
-
-			// Parentless nodes are either documents or disconnected
-			if ( !aup || !bup ) {
-				return a === document ? -1 :
-					b === document ? 1 :
-					aup ? -1 :
-					bup ? 1 :
-					sortInput ?
-					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
-					0;
-
-			// If the nodes are siblings, we can do a quick check
-			} else if ( aup === bup ) {
-				return siblingCheck( a, b );
-			}
-
-			// Otherwise we need full lists of their ancestors for comparison
-			cur = a;
-			while ( (cur = cur.parentNode) ) {
-				ap.unshift( cur );
-			}
-			cur = b;
-			while ( (cur = cur.parentNode) ) {
-				bp.unshift( cur );
-			}
-
-			// Walk down the tree looking for a discrepancy
-			while ( ap[i] === bp[i] ) {
-				i++;
-			}
-
-			return i ?
-				// Do a sibling check if the nodes have a common ancestor
-				siblingCheck( ap[i], bp[i] ) :
-
-				// Otherwise nodes in our document sort first
-				ap[i] === preferredDoc ? -1 :
-				bp[i] === preferredDoc ? 1 :
-				0;
-		};
-
-		return document;
-	};
-
-	Sizzle.matches = function( expr, elements ) {
-		return Sizzle( expr, null, null, elements );
-	};
-
-	Sizzle.matchesSelector = function( elem, expr ) {
-		// Set document vars if needed
-		if ( ( elem.ownerDocument || elem ) !== document ) {
-			setDocument( elem );
-		}
-
-		// Make sure that attribute selectors are quoted
-		expr = expr.replace( rattributeQuotes, "='$1']" );
-
-		if ( support.matchesSelector && documentIsHTML &&
-			!compilerCache[ expr + " " ] &&
-			( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
-			( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
-
-			try {
-				var ret = matches.call( elem, expr );
-
-				// IE 9's matchesSelector returns false on disconnected nodes
-				if ( ret || support.disconnectedMatch ||
-						// As well, disconnected nodes are said to be in a document
-						// fragment in IE 9
-						elem.document && elem.document.nodeType !== 11 ) {
-					return ret;
-				}
-			} catch (e) {}
-		}
-
-		return Sizzle( expr, document, null, [ elem ] ).length > 0;
-	};
-
-	Sizzle.contains = function( context, elem ) {
-		// Set document vars if needed
-		if ( ( context.ownerDocument || context ) !== document ) {
-			setDocument( context );
-		}
-		return contains( context, elem );
-	};
-
-	Sizzle.attr = function( elem, name ) {
-		// Set document vars if needed
-		if ( ( elem.ownerDocument || elem ) !== document ) {
-			setDocument( elem );
-		}
-
-		var fn = Expr.attrHandle[ name.toLowerCase() ],
-			// Don't get fooled by Object.prototype properties (jQuery #13807)
-			val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
-				fn( elem, name, !documentIsHTML ) :
-				undefined;
-
-		return val !== undefined ?
-			val :
-			support.attributes || !documentIsHTML ?
-				elem.getAttribute( name ) :
-				(val = elem.getAttributeNode(name)) && val.specified ?
-					val.value :
-					null;
-	};
-
-	Sizzle.escape = function( sel ) {
-		return (sel + "").replace( rcssescape, fcssescape );
-	};
-
-	Sizzle.error = function( msg ) {
-		throw new Error( "Syntax error, unrecognized expression: " + msg );
-	};
-
-	/**
-	 * Document sorting and removing duplicates
-	 * @param {ArrayLike} results
-	 */
-	Sizzle.uniqueSort = function( results ) {
-		var elem,
-			duplicates = [],
-			j = 0,
-			i = 0;
-
-		// Unless we *know* we can detect duplicates, assume their presence
-		hasDuplicate = !support.detectDuplicates;
-		sortInput = !support.sortStable && results.slice( 0 );
-		results.sort( sortOrder );
-
-		if ( hasDuplicate ) {
-			while ( (elem = results[i++]) ) {
-				if ( elem === results[ i ] ) {
-					j = duplicates.push( i );
-				}
-			}
-			while ( j-- ) {
-				results.splice( duplicates[ j ], 1 );
-			}
-		}
-
-		// Clear input after sorting to release objects
-		// See https://github.com/jquery/sizzle/pull/225
-		sortInput = null;
-
-		return results;
-	};
-
-	/**
-	 * Utility function for retrieving the text value of an array of DOM nodes
-	 * @param {Array|Element} elem
-	 */
-	getText = Sizzle.getText = function( elem ) {
-		var node,
-			ret = "",
-			i = 0,
-			nodeType = elem.nodeType;
-
-		if ( !nodeType ) {
-			// If no nodeType, this is expected to be an array
-			while ( (node = elem[i++]) ) {
-				// Do not traverse comment nodes
-				ret += getText( node );
-			}
-		} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
-			// Use textContent for elements
-			// innerText usage removed for consistency of new lines (jQuery #11153)
-			if ( typeof elem.textContent === "string" ) {
-				return elem.textContent;
-			} else {
-				// Traverse its children
-				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
-					ret += getText( elem );
-				}
-			}
-		} else if ( nodeType === 3 || nodeType === 4 ) {
-			return elem.nodeValue;
-		}
-		// Do not include comment or processing instruction nodes
-
-		return ret;
-	};
-
-	Expr = Sizzle.selectors = {
-
-		// Can be adjusted by the user
-		cacheLength: 50,
-
-		createPseudo: markFunction,
-
-		match: matchExpr,
-
-		attrHandle: {},
-
-		find: {},
-
-		relative: {
-			">": { dir: "parentNode", first: true },
-			" ": { dir: "parentNode" },
-			"+": { dir: "previousSibling", first: true },
-			"~": { dir: "previousSibling" }
-		},
-
-		preFilter: {
-			"ATTR": function( match ) {
-				match[1] = match[1].replace( runescape, funescape );
-
-				// Move the given value to match[3] whether quoted or unquoted
-				match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
-
-				if ( match[2] === "~=" ) {
-					match[3] = " " + match[3] + " ";
-				}
-
-				return match.slice( 0, 4 );
-			},
-
-			"CHILD": function( match ) {
-				/* matches from matchExpr["CHILD"]
-					1 type (only|nth|...)
-					2 what (child|of-type)
-					3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
-					4 xn-component of xn+y argument ([+-]?\d*n|)
-					5 sign of xn-component
-					6 x of xn-component
-					7 sign of y-component
-					8 y of y-component
-				*/
-				match[1] = match[1].toLowerCase();
-
-				if ( match[1].slice( 0, 3 ) === "nth" ) {
-					// nth-* requires argument
-					if ( !match[3] ) {
-						Sizzle.error( match[0] );
-					}
-
-					// numeric x and y parameters for Expr.filter.CHILD
-					// remember that false/true cast respectively to 0/1
-					match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
-					match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
-
-				// other types prohibit arguments
-				} else if ( match[3] ) {
-					Sizzle.error( match[0] );
-				}
-
-				return match;
-			},
-
-			"PSEUDO": function( match ) {
-				var excess,
-					unquoted = !match[6] && match[2];
-
-				if ( matchExpr["CHILD"].test( match[0] ) ) {
-					return null;
-				}
-
-				// Accept quoted arguments as-is
-				if ( match[3] ) {
-					match[2] = match[4] || match[5] || "";
-
-				// Strip excess characters from unquoted arguments
-				} else if ( unquoted && rpseudo.test( unquoted ) &&
-					// Get excess from tokenize (recursively)
-					(excess = tokenize( unquoted, true )) &&
-					// advance to the next closing parenthesis
-					(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
-
-					// excess is a negative index
-					match[0] = match[0].slice( 0, excess );
-					match[2] = unquoted.slice( 0, excess );
-				}
-
-				// Return only captures needed by the pseudo filter method (type and argument)
-				return match.slice( 0, 3 );
-			}
-		},
-
-		filter: {
-
-			"TAG": function( nodeNameSelector ) {
-				var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
-				return nodeNameSelector === "*" ?
-					function() { return true; } :
-					function( elem ) {
-						return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
-					};
-			},
-
-			"CLASS": function( className ) {
-				var pattern = classCache[ className + " " ];
-
-				return pattern ||
-					(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
-					classCache( className, function( elem ) {
-						return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
-					});
-			},
-
-			"ATTR": function( name, operator, check ) {
-				return function( elem ) {
-					var result = Sizzle.attr( elem, name );
-
-					if ( result == null ) {
-						return operator === "!=";
-					}
-					if ( !operator ) {
-						return true;
-					}
-
-					result += "";
-
-					return operator === "=" ? result === check :
-						operator === "!=" ? result !== check :
-						operator === "^=" ? check && result.indexOf( check ) === 0 :
-						operator === "*=" ? check && result.indexOf( check ) > -1 :
-						operator === "$=" ? check && result.slice( -check.length ) === check :
-						operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
-						operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
-						false;
-				};
-			},
-
-			"CHILD": function( type, what, argument, first, last ) {
-				var simple = type.slice( 0, 3 ) !== "nth",
-					forward = type.slice( -4 ) !== "last",
-					ofType = what === "of-type";
-
-				return first === 1 && last === 0 ?
-
-					// Shortcut for :nth-*(n)
-					function( elem ) {
-						return !!elem.parentNode;
-					} :
-
-					function( elem, context, xml ) {
-						var cache, uniqueCache, outerCache, node, nodeIndex, start,
-							dir = simple !== forward ? "nextSibling" : "previousSibling",
-							parent = elem.parentNode,
-							name = ofType && elem.nodeName.toLowerCase(),
-							useCache = !xml && !ofType,
-							diff = false;
-
-						if ( parent ) {
-
-							// :(first|last|only)-(child|of-type)
-							if ( simple ) {
-								while ( dir ) {
-									node = elem;
-									while ( (node = node[ dir ]) ) {
-										if ( ofType ?
-											node.nodeName.toLowerCase() === name :
-											node.nodeType === 1 ) {
-
-											return false;
-										}
-									}
-									// Reverse direction for :only-* (if we haven't yet done so)
-									start = dir = type === "only" && !start && "nextSibling";
-								}
-								return true;
-							}
-
-							start = [ forward ? parent.firstChild : parent.lastChild ];
-
-							// non-xml :nth-child(...) stores cache data on `parent`
-							if ( forward && useCache ) {
-
-								// Seek `elem` from a previously-cached index
-
-								// ...in a gzip-friendly way
-								node = parent;
-								outerCache = node[ expando ] || (node[ expando ] = {});
-
-								// Support: IE <9 only
-								// Defend against cloned attroperties (jQuery gh-1709)
-								uniqueCache = outerCache[ node.uniqueID ] ||
-									(outerCache[ node.uniqueID ] = {});
-
-								cache = uniqueCache[ type ] || [];
-								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
-								diff = nodeIndex && cache[ 2 ];
-								node = nodeIndex && parent.childNodes[ nodeIndex ];
-
-								while ( (node = ++nodeIndex && node && node[ dir ] ||
-
-									// Fallback to seeking `elem` from the start
-									(diff = nodeIndex = 0) || start.pop()) ) {
-
-									// When found, cache indexes on `parent` and break
-									if ( node.nodeType === 1 && ++diff && node === elem ) {
-										uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
-										break;
-									}
-								}
-
-							} else {
-								// Use previously-cached element index if available
-								if ( useCache ) {
-									// ...in a gzip-friendly way
-									node = elem;
-									outerCache = node[ expando ] || (node[ expando ] = {});
-
-									// Support: IE <9 only
-									// Defend against cloned attroperties (jQuery gh-1709)
-									uniqueCache = outerCache[ node.uniqueID ] ||
-										(outerCache[ node.uniqueID ] = {});
-
-									cache = uniqueCache[ type ] || [];
-									nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
-									diff = nodeIndex;
-								}
-
-								// xml :nth-child(...)
-								// or :nth-last-child(...) or :nth(-last)?-of-type(...)
-								if ( diff === false ) {
-									// Use the same loop as above to seek `elem` from the start
-									while ( (node = ++nodeIndex && node && node[ dir ] ||
-										(diff = nodeIndex = 0) || start.pop()) ) {
-
-										if ( ( ofType ?
-											node.nodeName.toLowerCase() === name :
-											node.nodeType === 1 ) &&
-											++diff ) {
-
-											// Cache the index of each encountered element
-											if ( useCache ) {
-												outerCache = node[ expando ] || (node[ expando ] = {});
-
-												// Support: IE <9 only
-												// Defend against cloned attroperties (jQuery gh-1709)
-												uniqueCache = outerCache[ node.uniqueID ] ||
-													(outerCache[ node.uniqueID ] = {});
-
-												uniqueCache[ type ] = [ dirruns, diff ];
-											}
-
-											if ( node === elem ) {
-												break;
-											}
-										}
-									}
-								}
-							}
-
-							// Incorporate the offset, then check against cycle size
-							diff -= last;
-							return diff === first || ( diff % first === 0 && diff / first >= 0 );
-						}
-					};
-			},
-
-			"PSEUDO": function( pseudo, argument ) {
-				// pseudo-class names are case-insensitive
-				// http://www.w3.org/TR/selectors/#pseudo-classes
-				// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
-				// Remember that setFilters inherits from pseudos
-				var args,
-					fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
-						Sizzle.error( "unsupported pseudo: " + pseudo );
-
-				// The user may use createPseudo to indicate that
-				// arguments are needed to create the filter function
-				// just as Sizzle does
-				if ( fn[ expando ] ) {
-					return fn( argument );
-				}
-
-				// But maintain support for old signatures
-				if ( fn.length > 1 ) {
-					args = [ pseudo, pseudo, "", argument ];
-					return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
-						markFunction(function( seed, matches ) {
-							var idx,
-								matched = fn( seed, argument ),
-								i = matched.length;
-							while ( i-- ) {
-								idx = indexOf( seed, matched[i] );
-								seed[ idx ] = !( matches[ idx ] = matched[i] );
-							}
-						}) :
-						function( elem ) {
-							return fn( elem, 0, args );
-						};
-				}
-
-				return fn;
-			}
-		},
-
-		pseudos: {
-			// Potentially complex pseudos
-			"not": markFunction(function( selector ) {
-				// Trim the selector passed to compile
-				// to avoid treating leading and trailing
-				// spaces as combinators
-				var input = [],
-					results = [],
-					matcher = compile( selector.replace( rtrim, "$1" ) );
-
-				return matcher[ expando ] ?
-					markFunction(function( seed, matches, context, xml ) {
-						var elem,
-							unmatched = matcher( seed, null, xml, [] ),
-							i = seed.length;
-
-						// Match elements unmatched by `matcher`
-						while ( i-- ) {
-							if ( (elem = unmatched[i]) ) {
-								seed[i] = !(matches[i] = elem);
-							}
-						}
-					}) :
-					function( elem, context, xml ) {
-						input[0] = elem;
-						matcher( input, null, xml, results );
-						// Don't keep the element (issue #299)
-						input[0] = null;
-						return !results.pop();
-					};
-			}),
-
-			"has": markFunction(function( selector ) {
-				return function( elem ) {
-					return Sizzle( selector, elem ).length > 0;
-				};
-			}),
-
-			"contains": markFunction(function( text ) {
-				text = text.replace( runescape, funescape );
-				return function( elem ) {
-					return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
-				};
-			}),
-
-			// "Whether an element is represented by a :lang() selector
-			// is based solely on the element's language value
-			// being equal to the identifier C,
-			// or beginning with the identifier C immediately followed by "-".
-			// The matching of C against the element's language value is performed case-insensitively.
-			// The identifier C does not have to be a valid language name."
-			// http://www.w3.org/TR/selectors/#lang-pseudo
-			"lang": markFunction( function( lang ) {
-				// lang value must be a valid identifier
-				if ( !ridentifier.test(lang || "") ) {
-					Sizzle.error( "unsupported lang: " + lang );
-				}
-				lang = lang.replace( runescape, funescape ).toLowerCase();
-				return function( elem ) {
-					var elemLang;
-					do {
-						if ( (elemLang = documentIsHTML ?
-							elem.lang :
-							elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
-
-							elemLang = elemLang.toLowerCase();
-							return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
-						}
-					} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
-					return false;
-				};
-			}),
-
-			// Miscellaneous
-			"target": function( elem ) {
-				var hash = window.location && window.location.hash;
-				return hash && hash.slice( 1 ) === elem.id;
-			},
-
-			"root": function( elem ) {
-				return elem === docElem;
-			},
-
-			"focus": function( elem ) {
-				return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
-			},
-
-			// Boolean properties
-			"enabled": createDisabledPseudo( false ),
-			"disabled": createDisabledPseudo( true ),
-
-			"checked": function( elem ) {
-				// In CSS3, :checked should return both checked and selected elements
-				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
-				var nodeName = elem.nodeName.toLowerCase();
-				return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
-			},
-
-			"selected": function( elem ) {
-				// Accessing this property makes selected-by-default
-				// options in Safari work properly
-				if ( elem.parentNode ) {
-					elem.parentNode.selectedIndex;
-				}
-
-				return elem.selected === true;
-			},
-
-			// Contents
-			"empty": function( elem ) {
-				// http://www.w3.org/TR/selectors/#empty-pseudo
-				// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
-				//   but not by others (comment: 8; processing instruction: 7; etc.)
-				// nodeType < 6 works because attributes (2) do not appear as children
-				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
-					if ( elem.nodeType < 6 ) {
-						return false;
-					}
-				}
-				return true;
-			},
-
-			"parent": function( elem ) {
-				return !Expr.pseudos["empty"]( elem );
-			},
-
-			// Element/input types
-			"header": function( elem ) {
-				return rheader.test( elem.nodeName );
-			},
-
-			"input": function( elem ) {
-				return rinputs.test( elem.nodeName );
-			},
-
-			"button": function( elem ) {
-				var name = elem.nodeName.toLowerCase();
-				return name === "input" && elem.type === "button" || name === "button";
-			},
-
-			"text": function( elem ) {
-				var attr;
-				return elem.nodeName.toLowerCase() === "input" &&
-					elem.type === "text" &&
-
-					// Support: IE<8
-					// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
-					( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
-			},
-
-			// Position-in-collection
-			"first": createPositionalPseudo(function() {
-				return [ 0 ];
-			}),
-
-			"last": createPositionalPseudo(function( matchIndexes, length ) {
-				return [ length - 1 ];
-			}),
-
-			"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
-				return [ argument < 0 ? argument + length : argument ];
-			}),
-
-			"even": createPositionalPseudo(function( matchIndexes, length ) {
-				var i = 0;
-				for ( ; i < length; i += 2 ) {
-					matchIndexes.push( i );
-				}
-				return matchIndexes;
-			}),
-
-			"odd": createPositionalPseudo(function( matchIndexes, length ) {
-				var i = 1;
-				for ( ; i < length; i += 2 ) {
-					matchIndexes.push( i );
-				}
-				return matchIndexes;
-			}),
-
-			"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
-				var i = argument < 0 ? argument + length : argument;
-				for ( ; --i >= 0; ) {
-					matchIndexes.push( i );
-				}
-				return matchIndexes;
-			}),
-
-			"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
-				var i = argument < 0 ? argument + length : argument;
-				for ( ; ++i < length; ) {
-					matchIndexes.push( i );
-				}
-				return matchIndexes;
-			})
-		}
-	};
-
-	Expr.pseudos["nth"] = Expr.pseudos["eq"];
-
-	// Add button/input type pseudos
-	for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
-		Expr.pseudos[ i ] = createInputPseudo( i );
-	}
-	for ( i in { submit: true, reset: true } ) {
-		Expr.pseudos[ i ] = createButtonPseudo( i );
-	}
-
-	// Easy API for creating new setFilters
-	function setFilters() {}
-	setFilters.prototype = Expr.filters = Expr.pseudos;
-	Expr.setFilters = new setFilters();
-
-	tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
-		var matched, match, tokens, type,
-			soFar, groups, preFilters,
-			cached = tokenCache[ selector + " " ];
-
-		if ( cached ) {
-			return parseOnly ? 0 : cached.slice( 0 );
-		}
-
-		soFar = selector;
-		groups = [];
-		preFilters = Expr.preFilter;
-
-		while ( soFar ) {
-
-			// Comma and first run
-			if ( !matched || (match = rcomma.exec( soFar )) ) {
-				if ( match ) {
-					// Don't consume trailing commas as valid
-					soFar = soFar.slice( match[0].length ) || soFar;
-				}
-				groups.push( (tokens = []) );
-			}
-
-			matched = false;
-
-			// Combinators
-			if ( (match = rcombinators.exec( soFar )) ) {
-				matched = match.shift();
-				tokens.push({
-					value: matched,
-					// Cast descendant combinators to space
-					type: match[0].replace( rtrim, " " )
-				});
-				soFar = soFar.slice( matched.length );
-			}
-
-			// Filters
-			for ( type in Expr.filter ) {
-				if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
-					(match = preFilters[ type ]( match ))) ) {
-					matched = match.shift();
-					tokens.push({
-						value: matched,
-						type: type,
-						matches: match
-					});
-					soFar = soFar.slice( matched.length );
-				}
-			}
-
-			if ( !matched ) {
-				break;
-			}
-		}
-
-		// Return the length of the invalid excess
-		// if we're just parsing
-		// Otherwise, throw an error or return tokens
-		return parseOnly ?
-			soFar.length :
-			soFar ?
-				Sizzle.error( selector ) :
-				// Cache the tokens
-				tokenCache( selector, groups ).slice( 0 );
-	};
-
-	function toSelector( tokens ) {
-		var i = 0,
-			len = tokens.length,
-			selector = "";
-		for ( ; i < len; i++ ) {
-			selector += tokens[i].value;
-		}
-		return selector;
-	}
-
-	function addCombinator( matcher, combinator, base ) {
-		var dir = combinator.dir,
-			skip = combinator.next,
-			key = skip || dir,
-			checkNonElements = base && key === "parentNode",
-			doneName = done++;
-
-		return combinator.first ?
-			// Check against closest ancestor/preceding element
-			function( elem, context, xml ) {
-				while ( (elem = elem[ dir ]) ) {
-					if ( elem.nodeType === 1 || checkNonElements ) {
-						return matcher( elem, context, xml );
-					}
-				}
-				return false;
-			} :
-
-			// Check against all ancestor/preceding elements
-			function( elem, context, xml ) {
-				var oldCache, uniqueCache, outerCache,
-					newCache = [ dirruns, doneName ];
-
-				// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
-				if ( xml ) {
-					while ( (elem = elem[ dir ]) ) {
-						if ( elem.nodeType === 1 || checkNonElements ) {
-							if ( matcher( elem, context, xml ) ) {
-								return true;
-							}
-						}
-					}
-				} else {
-					while ( (elem = elem[ dir ]) ) {
-						if ( elem.nodeType === 1 || checkNonElements ) {
-							outerCache = elem[ expando ] || (elem[ expando ] = {});
-
-							// Support: IE <9 only
-							// Defend against cloned attroperties (jQuery gh-1709)
-							uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
-
-							if ( skip && skip === elem.nodeName.toLowerCase() ) {
-								elem = elem[ dir ] || elem;
-							} else if ( (oldCache = uniqueCache[ key ]) &&
-								oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
-
-								// Assign to newCache so results back-propagate to previous elements
-								return (newCache[ 2 ] = oldCache[ 2 ]);
-							} else {
-								// Reuse newcache so results back-propagate to previous elements
-								uniqueCache[ key ] = newCache;
-
-								// A match means we're done; a fail means we have to keep checking
-								if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
-									return true;
-								}
-							}
-						}
-					}
-				}
-				return false;
-			};
-	}
-
-	function elementMatcher( matchers ) {
-		return matchers.length > 1 ?
-			function( elem, context, xml ) {
-				var i = matchers.length;
-				while ( i-- ) {
-					if ( !matchers[i]( elem, context, xml ) ) {
-						return false;
-					}
-				}
-				return true;
-			} :
-			matchers[0];
-	}
-
-	function multipleContexts( selector, contexts, results ) {
-		var i = 0,
-			len = contexts.length;
-		for ( ; i < len; i++ ) {
-			Sizzle( selector, contexts[i], results );
-		}
-		return results;
-	}
-
-	function condense( unmatched, map, filter, context, xml ) {
-		var elem,
-			newUnmatched = [],
-			i = 0,
-			len = unmatched.length,
-			mapped = map != null;
-
-		for ( ; i < len; i++ ) {
-			if ( (elem = unmatched[i]) ) {
-				if ( !filter || filter( elem, context, xml ) ) {
-					newUnmatched.push( elem );
-					if ( mapped ) {
-						map.push( i );
-					}
-				}
-			}
-		}
-
-		return newUnmatched;
-	}
-
-	function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
-		if ( postFilter && !postFilter[ expando ] ) {
-			postFilter = setMatcher( postFilter );
-		}
-		if ( postFinder && !postFinder[ expando ] ) {
-			postFinder = setMatcher( postFinder, postSelector );
-		}
-		return markFunction(function( seed, results, context, xml ) {
-			var temp, i, elem,
-				preMap = [],
-				postMap = [],
-				preexisting = results.length,
-
-				// Get initial elements from seed or context
-				elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
-
-				// Prefilter to get matcher input, preserving a map for seed-results synchronization
-				matcherIn = preFilter && ( seed || !selector ) ?
-					condense( elems, preMap, preFilter, context, xml ) :
-					elems,
-
-				matcherOut = matcher ?
-					// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
-					postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
-
-						// ...intermediate processing is necessary
-						[] :
-
-						// ...otherwise use results directly
-						results :
-					matcherIn;
-
-			// Find primary matches
-			if ( matcher ) {
-				matcher( matcherIn, matcherOut, context, xml );
-			}
-
-			// Apply postFilter
-			if ( postFilter ) {
-				temp = condense( matcherOut, postMap );
-				postFilter( temp, [], context, xml );
-
-				// Un-match failing elements by moving them back to matcherIn
-				i = temp.length;
-				while ( i-- ) {
-					if ( (elem = temp[i]) ) {
-						matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
-					}
-				}
-			}
-
-			if ( seed ) {
-				if ( postFinder || preFilter ) {
-					if ( postFinder ) {
-						// Get the final matcherOut by condensing this intermediate into postFinder contexts
-						temp = [];
-						i = matcherOut.length;
-						while ( i-- ) {
-							if ( (elem = matcherOut[i]) ) {
-								// Restore matcherIn since elem is not yet a final match
-								temp.push( (matcherIn[i] = elem) );
-							}
-						}
-						postFinder( null, (matcherOut = []), temp, xml );
-					}
-
-					// Move matched elements from seed to results to keep them synchronized
-					i = matcherOut.length;
-					while ( i-- ) {
-						if ( (elem = matcherOut[i]) &&
-							(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
-
-							seed[temp] = !(results[temp] = elem);
-						}
-					}
-				}
-
-			// Add elements to results, through postFinder if defined
-			} else {
-				matcherOut = condense(
-					matcherOut === results ?
-						matcherOut.splice( preexisting, matcherOut.length ) :
-						matcherOut
-				);
-				if ( postFinder ) {
-					postFinder( null, results, matcherOut, xml );
-				} else {
-					push.apply( results, matcherOut );
-				}
-			}
-		});
-	}
-
-	function matcherFromTokens( tokens ) {
-		var checkContext, matcher, j,
-			len = tokens.length,
-			leadingRelative = Expr.relative[ tokens[0].type ],
-			implicitRelative = leadingRelative || Expr.relative[" "],
-			i = leadingRelative ? 1 : 0,
-
-			// The foundational matcher ensures that elements are reachable from top-level context(s)
-			matchContext = addCombinator( function( elem ) {
-				return elem === checkContext;
-			}, implicitRelative, true ),
-			matchAnyContext = addCombinator( function( elem ) {
-				return indexOf( checkContext, elem ) > -1;
-			}, implicitRelative, true ),
-			matchers = [ function( elem, context, xml ) {
-				var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
-					(checkContext = context).nodeType ?
-						matchContext( elem, context, xml ) :
-						matchAnyContext( elem, context, xml ) );
-				// Avoid hanging onto element (issue #299)
-				checkContext = null;
-				return ret;
-			} ];
-
-		for ( ; i < len; i++ ) {
-			if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
-				matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
-			} else {
-				matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
-
-				// Return special upon seeing a positional matcher
-				if ( matcher[ expando ] ) {
-					// Find the next relative operator (if any) for proper handling
-					j = ++i;
-					for ( ; j < len; j++ ) {
-						if ( Expr.relative[ tokens[j].type ] ) {
-							break;
-						}
-					}
-					return setMatcher(
-						i > 1 && elementMatcher( matchers ),
-						i > 1 && toSelector(
-							// If the preceding token was a descendant combinator, insert an implicit any-element `*`
-							tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
-						).replace( rtrim, "$1" ),
-						matcher,
-						i < j && matcherFromTokens( tokens.slice( i, j ) ),
-						j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
-						j < len && toSelector( tokens )
-					);
-				}
-				matchers.push( matcher );
-			}
-		}
-
-		return elementMatcher( matchers );
-	}
-
-	function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
-		var bySet = setMatchers.length > 0,
-			byElement = elementMatchers.length > 0,
-			superMatcher = function( seed, context, xml, results, outermost ) {
-				var elem, j, matcher,
-					matchedCount = 0,
-					i = "0",
-					unmatched = seed && [],
-					setMatched = [],
-					contextBackup = outermostContext,
-					// We must always have either seed elements or outermost context
-					elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
-					// Use integer dirruns iff this is the outermost matcher
-					dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
-					len = elems.length;
-
-				if ( outermost ) {
-					outermostContext = context === document || context || outermost;
-				}
-
-				// Add elements passing elementMatchers directly to results
-				// Support: IE<9, Safari
-				// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
-				for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
-					if ( byElement && elem ) {
-						j = 0;
-						if ( !context && elem.ownerDocument !== document ) {
-							setDocument( elem );
-							xml = !documentIsHTML;
-						}
-						while ( (matcher = elementMatchers[j++]) ) {
-							if ( matcher( elem, context || document, xml) ) {
-								results.push( elem );
-								break;
-							}
-						}
-						if ( outermost ) {
-							dirruns = dirrunsUnique;
-						}
-					}
-
-					// Track unmatched elements for set filters
-					if ( bySet ) {
-						// They will have gone through all possible matchers
-						if ( (elem = !matcher && elem) ) {
-							matchedCount--;
-						}
-
-						// Lengthen the array for every element, matched or not
-						if ( seed ) {
-							unmatched.push( elem );
-						}
-					}
-				}
-
-				// `i` is now the count of elements visited above, and adding it to `matchedCount`
-				// makes the latter nonnegative.
-				matchedCount += i;
-
-				// Apply set filters to unmatched elements
-				// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
-				// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
-				// no element matchers and no seed.
-				// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
-				// case, which will result in a "00" `matchedCount` that differs from `i` but is also
-				// numerically zero.
-				if ( bySet && i !== matchedCount ) {
-					j = 0;
-					while ( (matcher = setMatchers[j++]) ) {
-						matcher( unmatched, setMatched, context, xml );
-					}
-
-					if ( seed ) {
-						// Reintegrate element matches to eliminate the need for sorting
-						if ( matchedCount > 0 ) {
-							while ( i-- ) {
-								if ( !(unmatched[i] || setMatched[i]) ) {
-									setMatched[i] = pop.call( results );
-								}
-							}
-						}
-
-						// Discard index placeholder values to get only actual matches
-						setMatched = condense( setMatched );
-					}
-
-					// Add matches to results
-					push.apply( results, setMatched );
-
-					// Seedless set matches succeeding multiple successful matchers stipulate sorting
-					if ( outermost && !seed && setMatched.length > 0 &&
-						( matchedCount + setMatchers.length ) > 1 ) {
-
-						Sizzle.uniqueSort( results );
-					}
-				}
-
-				// Override manipulation of globals by nested matchers
-				if ( outermost ) {
-					dirruns = dirrunsUnique;
-					outermostContext = contextBackup;
-				}
-
-				return unmatched;
-			};
-
-		return bySet ?
-			markFunction( superMatcher ) :
-			superMatcher;
-	}
-
-	compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
-		var i,
-			setMatchers = [],
-			elementMatchers = [],
-			cached = compilerCache[ selector + " " ];
-
-		if ( !cached ) {
-			// Generate a function of recursive functions that can be used to check each element
-			if ( !match ) {
-				match = tokenize( selector );
-			}
-			i = match.length;
-			while ( i-- ) {
-				cached = matcherFromTokens( match[i] );
-				if ( cached[ expando ] ) {
-					setMatchers.push( cached );
-				} else {
-					elementMatchers.push( cached );
-				}
-			}
-
-			// Cache the compiled function
-			cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
-
-			// Save selector and tokenization
-			cached.selector = selector;
-		}
-		return cached;
-	};
-
-	/**
-	 * A low-level selection function that works with Sizzle's compiled
-	 *  selector functions
-	 * @param {String|Function} selector A selector or a pre-compiled
-	 *  selector function built with Sizzle.compile
-	 * @param {Element} context
-	 * @param {Array} [results]
-	 * @param {Array} [seed] A set of elements to match against
-	 */
-	select = Sizzle.select = function( selector, context, results, seed ) {
-		var i, tokens, token, type, find,
-			compiled = typeof selector === "function" && selector,
-			match = !seed && tokenize( (selector = compiled.selector || selector) );
-
-		results = results || [];
-
-		// Try to minimize operations if there is only one selector in the list and no seed
-		// (the latter of which guarantees us context)
-		if ( match.length === 1 ) {
-
-			// Reduce context if the leading compound selector is an ID
-			tokens = match[0] = match[0].slice( 0 );
-			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
-					context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {
-
-				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
-				if ( !context ) {
-					return results;
-
-				// Precompiled matchers will still verify ancestry, so step up a level
-				} else if ( compiled ) {
-					context = context.parentNode;
-				}
-
-				selector = selector.slice( tokens.shift().value.length );
-			}
-
-			// Fetch a seed set for right-to-left matching
-			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
-			while ( i-- ) {
-				token = tokens[i];
-
-				// Abort if we hit a combinator
-				if ( Expr.relative[ (type = token.type) ] ) {
-					break;
-				}
-				if ( (find = Expr.find[ type ]) ) {
-					// Search, expanding context for leading sibling combinators
-					if ( (seed = find(
-						token.matches[0].replace( runescape, funescape ),
-						rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
-					)) ) {
-
-						// If seed is empty or no tokens remain, we can return early
-						tokens.splice( i, 1 );
-						selector = seed.length && toSelector( tokens );
-						if ( !selector ) {
-							push.apply( results, seed );
-							return results;
-						}
-
-						break;
-					}
-				}
-			}
-		}
-
-		// Compile and execute a filtering function if one is not provided
-		// Provide `match` to avoid retokenization if we modified the selector above
-		( compiled || compile( selector, match ) )(
-			seed,
-			context,
-			!documentIsHTML,
-			results,
-			!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
-		);
-		return results;
-	};
-
-	// One-time assignments
-
-	// Sort stability
-	support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
-
-	// Support: Chrome 14-35+
-	// Always assume duplicates if they aren't passed to the comparison function
-	support.detectDuplicates = !!hasDuplicate;
-
-	// Initialize against the default document
-	setDocument();
-
-	// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
-	// Detached nodes confoundingly follow *each other*
-	support.sortDetached = assert(function( el ) {
-		// Should return 1, but returns 4 (following)
-		return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
-	});
-
-	// Support: IE<8
-	// Prevent attribute/property "interpolation"
-	// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
-	if ( !assert(function( el ) {
-		el.innerHTML = "<a href='#'></a>";
-		return el.firstChild.getAttribute("href") === "#" ;
-	}) ) {
-		addHandle( "type|href|height|width", function( elem, name, isXML ) {
-			if ( !isXML ) {
-				return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
-			}
-		});
-	}
-
-	// Support: IE<9
-	// Use defaultValue in place of getAttribute("value")
-	if ( !support.attributes || !assert(function( el ) {
-		el.innerHTML = "<input/>";
-		el.firstChild.setAttribute( "value", "" );
-		return el.firstChild.getAttribute( "value" ) === "";
-	}) ) {
-		addHandle( "value", function( elem, name, isXML ) {
-			if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
-				return elem.defaultValue;
-			}
-		});
-	}
-
-	// Support: IE<9
-	// Use getAttributeNode to fetch booleans when getAttribute lies
-	if ( !assert(function( el ) {
-		return el.getAttribute("disabled") == null;
-	}) ) {
-		addHandle( booleans, function( elem, name, isXML ) {
-			var val;
-			if ( !isXML ) {
-				return elem[ name ] === true ? name.toLowerCase() :
-						(val = elem.getAttributeNode( name )) && val.specified ?
-						val.value :
-					null;
-			}
-		});
-	}
-
-	return Sizzle;
-
-	})( window );
-
-
-
-	jQuery.find = Sizzle;
-	jQuery.expr = Sizzle.selectors;
-
-	// Deprecated
-	jQuery.expr[ ":" ] = jQuery.expr.pseudos;
-	jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
-	jQuery.text = Sizzle.getText;
-	jQuery.isXMLDoc = Sizzle.isXML;
-	jQuery.contains = Sizzle.contains;
-	jQuery.escapeSelector = Sizzle.escape;
-
-
-
-
-	var dir = function( elem, dir, until ) {
-		var matched = [],
-			truncate = until !== undefined;
-
-		while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
-			if ( elem.nodeType === 1 ) {
-				if ( truncate && jQuery( elem ).is( until ) ) {
-					break;
-				}
-				matched.push( elem );
-			}
-		}
-		return matched;
-	};
-
-
-	var siblings = function( n, elem ) {
-		var matched = [];
-
-		for ( ; n; n = n.nextSibling ) {
-			if ( n.nodeType === 1 && n !== elem ) {
-				matched.push( n );
-			}
-		}
-
-		return matched;
-	};
-
-
-	var rneedsContext = jQuery.expr.match.needsContext;
-
-
-
-	function nodeName( elem, name ) {
-
-	  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
-
-	};
-	var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
-
-
-
-	// Implement the identical functionality for filter and not
-	function winnow( elements, qualifier, not ) {
-		if ( isFunction( qualifier ) ) {
-			return jQuery.grep( elements, function( elem, i ) {
-				return !!qualifier.call( elem, i, elem ) !== not;
-			} );
-		}
-
-		// Single element
-		if ( qualifier.nodeType ) {
-			return jQuery.grep( elements, function( elem ) {
-				return ( elem === qualifier ) !== not;
-			} );
-		}
-
-		// Arraylike of elements (jQuery, arguments, Array)
-		if ( typeof qualifier !== "string" ) {
-			return jQuery.grep( elements, function( elem ) {
-				return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
-			} );
-		}
-
-		// Filtered directly for both simple and complex selectors
-		return jQuery.filter( qualifier, elements, not );
-	}
-
-	jQuery.filter = function( expr, elems, not ) {
-		var elem = elems[ 0 ];
-
-		if ( not ) {
-			expr = ":not(" + expr + ")";
-		}
-
-		if ( elems.length === 1 && elem.nodeType === 1 ) {
-			return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
-		}
-
-		return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
-			return elem.nodeType === 1;
-		} ) );
-	};
-
-	jQuery.fn.extend( {
-		find: function( selector ) {
-			var i, ret,
-				len = this.length,
-				self = this;
-
-			if ( typeof selector !== "string" ) {
-				return this.pushStack( jQuery( selector ).filter( function() {
-					for ( i = 0; i < len; i++ ) {
-						if ( jQuery.contains( self[ i ], this ) ) {
-							return true;
-						}
-					}
-				} ) );
-			}
-
-			ret = this.pushStack( [] );
-
-			for ( i = 0; i < len; i++ ) {
-				jQuery.find( selector, self[ i ], ret );
-			}
-
-			return len > 1 ? jQuery.uniqueSort( ret ) : ret;
-		},
-		filter: function( selector ) {
-			return this.pushStack( winnow( this, selector || [], false ) );
-		},
-		not: function( selector ) {
-			return this.pushStack( winnow( this, selector || [], true ) );
-		},
-		is: function( selector ) {
-			return !!winnow(
-				this,
-
-				// If this is a positional/relative selector, check membership in the returned set
-				// so $("p:first").is("p:last") won't return true for a doc with two "p".
-				typeof selector === "string" && rneedsContext.test( selector ) ?
-					jQuery( selector ) :
-					selector || [],
-				false
-			).length;
-		}
-	} );
-
-
-	// Initialize a jQuery object
-
-
-	// A central reference to the root jQuery(document)
-	var rootjQuery,
-
-		// A simple way to check for HTML strings
-		// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
-		// Strict HTML recognition (#11290: must start with <)
-		// Shortcut simple #id case for speed
-		rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
-
-		init = jQuery.fn.init = function( selector, context, root ) {
-			var match, elem;
-
-			// HANDLE: $(""), $(null), $(undefined), $(false)
-			if ( !selector ) {
-				return this;
-			}
-
-			// Method init() accepts an alternate rootjQuery
-			// so migrate can support jQuery.sub (gh-2101)
-			root = root || rootjQuery;
-
-			// Handle HTML strings
-			if ( typeof selector === "string" ) {
-				if ( selector[ 0 ] === "<" &&
-					selector[ selector.length - 1 ] === ">" &&
-					selector.length >= 3 ) {
-
-					// Assume that strings that start and end with <> are HTML and skip the regex check
-					match = [ null, selector, null ];
-
-				} else {
-					match = rquickExpr.exec( selector );
-				}
-
-				// Match html or make sure no context is specified for #id
-				if ( match && ( match[ 1 ] || !context ) ) {
-
-					// HANDLE: $(html) -> $(array)
-					if ( match[ 1 ] ) {
-						context = context instanceof jQuery ? context[ 0 ] : context;
-
-						// Option to run scripts is true for back-compat
-						// Intentionally let the error be thrown if parseHTML is not present
-						jQuery.merge( this, jQuery.parseHTML(
-							match[ 1 ],
-							context && context.nodeType ? context.ownerDocument || context : document,
-							true
-						) );
-
-						// HANDLE: $(html, props)
-						if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
-							for ( match in context ) {
-
-								// Properties of context are called as methods if possible
-								if ( isFunction( this[ match ] ) ) {
-									this[ match ]( context[ match ] );
-
-								// ...and otherwise set as attributes
-								} else {
-									this.attr( match, context[ match ] );
-								}
-							}
-						}
-
-						return this;
-
-					// HANDLE: $(#id)
-					} else {
-						elem = document.getElementById( match[ 2 ] );
-
-						if ( elem ) {
-
-							// Inject the element directly into the jQuery object
-							this[ 0 ] = elem;
-							this.length = 1;
-						}
-						return this;
-					}
-
-				// HANDLE: $(expr, $(...))
-				} else if ( !context || context.jquery ) {
-					return ( context || root ).find( selector );
-
-				// HANDLE: $(expr, context)
-				// (which is just equivalent to: $(context).find(expr)
-				} else {
-					return this.constructor( context ).find( selector );
-				}
-
-			// HANDLE: $(DOMElement)
-			} else if ( selector.nodeType ) {
-				this[ 0 ] = selector;
-				this.length = 1;
-				return this;
-
-			// HANDLE: $(function)
-			// Shortcut for document ready
-			} else if ( isFunction( selector ) ) {
-				return root.ready !== undefined ?
-					root.ready( selector ) :
-
-					// Execute immediately if ready is not present
-					selector( jQuery );
-			}
-
-			return jQuery.makeArray( selector, this );
-		};
-
-	// Give the init function the jQuery prototype for later instantiation
-	init.prototype = jQuery.fn;
-
-	// Initialize central reference
-	rootjQuery = jQuery( document );
-
-
-	var rparentsprev = /^(?:parents|prev(?:Until|All))/,
-
-		// Methods guaranteed to produce a unique set when starting from a unique set
-		guaranteedUnique = {
-			children: true,
-			contents: true,
-			next: true,
-			prev: true
-		};
-
-	jQuery.fn.extend( {
-		has: function( target ) {
-			var targets = jQuery( target, this ),
-				l = targets.length;
-
-			return this.filter( function() {
-				var i = 0;
-				for ( ; i < l; i++ ) {
-					if ( jQuery.contains( this, targets[ i ] ) ) {
-						return true;
-					}
-				}
-			} );
-		},
-
-		closest: function( selectors, context ) {
-			var cur,
-				i = 0,
-				l = this.length,
-				matched = [],
-				targets = typeof selectors !== "string" && jQuery( selectors );
-
-			// Positional selectors never match, since there's no _selection_ context
-			if ( !rneedsContext.test( selectors ) ) {
-				for ( ; i < l; i++ ) {
-					for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
-
-						// Always skip document fragments
-						if ( cur.nodeType < 11 && ( targets ?
-							targets.index( cur ) > -1 :
-
-							// Don't pass non-elements to Sizzle
-							cur.nodeType === 1 &&
-								jQuery.find.matchesSelector( cur, selectors ) ) ) {
-
-							matched.push( cur );
-							break;
-						}
-					}
-				}
-			}
-
-			return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
-		},
-
-		// Determine the position of an element within the set
-		index: function( elem ) {
-
-			// No argument, return index in parent
-			if ( !elem ) {
-				return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
-			}
-
-			// Index in selector
-			if ( typeof elem === "string" ) {
-				return indexOf.call( jQuery( elem ), this[ 0 ] );
-			}
-
-			// Locate the position of the desired element
-			return indexOf.call( this,
-
-				// If it receives a jQuery object, the first element is used
-				elem.jquery ? elem[ 0 ] : elem
-			);
-		},
-
-		add: function( selector, context ) {
-			return this.pushStack(
-				jQuery.uniqueSort(
-					jQuery.merge( this.get(), jQuery( selector, context ) )
-				)
-			);
-		},
-
-		addBack: function( selector ) {
-			return this.add( selector == null ?
-				this.prevObject : this.prevObject.filter( selector )
-			);
-		}
-	} );
-
-	function sibling( cur, dir ) {
-		while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
-		return cur;
-	}
-
-	jQuery.each( {
-		parent: function( elem ) {
-			var parent = elem.parentNode;
-			return parent && parent.nodeType !== 11 ? parent : null;
-		},
-		parents: function( elem ) {
-			return dir( elem, "parentNode" );
-		},
-		parentsUntil: function( elem, i, until ) {
-			return dir( elem, "parentNode", until );
-		},
-		next: function( elem ) {
-			return sibling( elem, "nextSibling" );
-		},
-		prev: function( elem ) {
-			return sibling( elem, "previousSibling" );
-		},
-		nextAll: function( elem ) {
-			return dir( elem, "nextSibling" );
-		},
-		prevAll: function( elem ) {
-			return dir( elem, "previousSibling" );
-		},
-		nextUntil: function( elem, i, until ) {
-			return dir( elem, "nextSibling", until );
-		},
-		prevUntil: function( elem, i, until ) {
-			return dir( elem, "previousSibling", until );
-		},
-		siblings: function( elem ) {
-			return siblings( ( elem.parentNode || {} ).firstChild, elem );
-		},
-		children: function( elem ) {
-			return siblings( elem.firstChild );
-		},
-		contents: function( elem ) {
-	        if ( nodeName( elem, "iframe" ) ) {
-	            return elem.contentDocument;
-	        }
-
-	        // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
-	        // Treat the template element as a regular one in browsers that
-	        // don't support it.
-	        if ( nodeName( elem, "template" ) ) {
-	            elem = elem.content || elem;
-	        }
-
-	        return jQuery.merge( [], elem.childNodes );
-		}
-	}, function( name, fn ) {
-		jQuery.fn[ name ] = function( until, selector ) {
-			var matched = jQuery.map( this, fn, until );
-
-			if ( name.slice( -5 ) !== "Until" ) {
-				selector = until;
-			}
-
-			if ( selector && typeof selector === "string" ) {
-				matched = jQuery.filter( selector, matched );
-			}
-
-			if ( this.length > 1 ) {
-
-				// Remove duplicates
-				if ( !guaranteedUnique[ name ] ) {
-					jQuery.uniqueSort( matched );
-				}
-
-				// Reverse order for parents* and prev-derivatives
-				if ( rparentsprev.test( name ) ) {
-					matched.reverse();
-				}
-			}
-
-			return this.pushStack( matched );
-		};
-	} );
-	var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
-
-
-
-	// Convert String-formatted options into Object-formatted ones
-	function createOptions( options ) {
-		var object = {};
-		jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
-			object[ flag ] = true;
-		} );
-		return object;
-	}
-
-	/*
-	 * Create a callback list using the following parameters:
-	 *
-	 *	options: an optional list of space-separated options that will change how
-	 *			the callback list behaves or a more traditional option object
-	 *
-	 * By default a callback list will act like an event callback list and can be
-	 * "fired" multiple times.
-	 *
-	 * Possible options:
-	 *
-	 *	once:			will ensure the callback list can only be fired once (like a Deferred)
-	 *
-	 *	memory:			will keep track of previous values and will call any callback added
-	 *					after the list has been fired right away with the latest "memorized"
-	 *					values (like a Deferred)
-	 *
-	 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
-	 *
-	 *	stopOnFalse:	interrupt callings when a callback returns false
-	 *
-	 */
-	jQuery.Callbacks = function( options ) {
-
-		// Convert options from String-formatted to Object-formatted if needed
-		// (we check in cache first)
-		options = typeof options === "string" ?
-			createOptions( options ) :
-			jQuery.extend( {}, options );
-
-		var // Flag to know if list is currently firing
-			firing,
-
-			// Last fire value for non-forgettable lists
-			memory,
-
-			// Flag to know if list was already fired
-			fired,
-
-			// Flag to prevent firing
-			locked,
-
-			// Actual callback list
-			list = [],
-
-			// Queue of execution data for repeatable lists
-			queue = [],
-
-			// Index of currently firing callback (modified by add/remove as needed)
-			firingIndex = -1,
-
-			// Fire callbacks
-			fire = function() {
-
-				// Enforce single-firing
-				locked = locked || options.once;
-
-				// Execute callbacks for all pending executions,
-				// respecting firingIndex overrides and runtime changes
-				fired = firing = true;
-				for ( ; queue.length; firingIndex = -1 ) {
-					memory = queue.shift();
-					while ( ++firingIndex < list.length ) {
-
-						// Run callback and check for early termination
-						if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
-							options.stopOnFalse ) {
-
-							// Jump to end and forget the data so .add doesn't re-fire
-							firingIndex = list.length;
-							memory = false;
-						}
-					}
-				}
-
-				// Forget the data if we're done with it
-				if ( !options.memory ) {
-					memory = false;
-				}
-
-				firing = false;
-
-				// Clean up if we're done firing for good
-				if ( locked ) {
-
-					// Keep an empty list if we have data for future add calls
-					if ( memory ) {
-						list = [];
-
-					// Otherwise, this object is spent
-					} else {
-						list = "";
-					}
-				}
-			},
-
-			// Actual Callbacks object
-			self = {
-
-				// Add a callback or a collection of callbacks to the list
-				add: function() {
-					if ( list ) {
-
-						// If we have memory from a past run, we should fire after adding
-						if ( memory && !firing ) {
-							firingIndex = list.length - 1;
-							queue.push( memory );
-						}
-
-						( function add( args ) {
-							jQuery.each( args, function( _, arg ) {
-								if ( isFunction( arg ) ) {
-									if ( !options.unique || !self.has( arg ) ) {
-										list.push( arg );
-									}
-								} else if ( arg && arg.length && toType( arg ) !== "string" ) {
-
-									// Inspect recursively
-									add( arg );
-								}
-							} );
-						} )( arguments );
-
-						if ( memory && !firing ) {
-							fire();
-						}
-					}
-					return this;
-				},
-
-				// Remove a callback from the list
-				remove: function() {
-					jQuery.each( arguments, function( _, arg ) {
-						var index;
-						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
-							list.splice( index, 1 );
-
-							// Handle firing indexes
-							if ( index <= firingIndex ) {
-								firingIndex--;
-							}
-						}
-					} );
-					return this;
-				},
-
-				// Check if a given callback is in the list.
-				// If no argument is given, return whether or not list has callbacks attached.
-				has: function( fn ) {
-					return fn ?
-						jQuery.inArray( fn, list ) > -1 :
-						list.length > 0;
-				},
-
-				// Remove all callbacks from the list
-				empty: function() {
-					if ( list ) {
-						list = [];
-					}
-					return this;
-				},
-
-				// Disable .fire and .add
-				// Abort any current/pending executions
-				// Clear all callbacks and values
-				disable: function() {
-					locked = queue = [];
-					list = memory = "";
-					return this;
-				},
-				disabled: function() {
-					return !list;
-				},
-
-				// Disable .fire
-				// Also disable .add unless we have memory (since it would have no effect)
-				// Abort any pending executions
-				lock: function() {
-					locked = queue = [];
-					if ( !memory && !firing ) {
-						list = memory = "";
-					}
-					return this;
-				},
-				locked: function() {
-					return !!locked;
-				},
-
-				// Call all callbacks with the given context and arguments
-				fireWith: function( context, args ) {
-					if ( !locked ) {
-						args = args || [];
-						args = [ context, args.slice ? args.slice() : args ];
-						queue.push( args );
-						if ( !firing ) {
-							fire();
-						}
-					}
-					return this;
-				},
-
-				// Call all the callbacks with the given arguments
-				fire: function() {
-					self.fireWith( this, arguments );
-					return this;
-				},
-
-				// To know if the callbacks have already been called at least once
-				fired: function() {
-					return !!fired;
-				}
-			};
-
-		return self;
-	};
-
-
-	function Identity( v ) {
-		return v;
-	}
-	function Thrower( ex ) {
-		throw ex;
-	}
-
-	function adoptValue( value, resolve, reject, noValue ) {
-		var method;
-
-		try {
-
-			// Check for promise aspect first to privilege synchronous behavior
-			if ( value && isFunction( ( method = value.promise ) ) ) {
-				method.call( value ).done( resolve ).fail( reject );
-
-			// Other thenables
-			} else if ( value && isFunction( ( method = value.then ) ) ) {
-				method.call( value, resolve, reject );
-
-			// Other non-thenables
-			} else {
-
-				// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
-				// * false: [ value ].slice( 0 ) => resolve( value )
-				// * true: [ value ].slice( 1 ) => resolve()
-				resolve.apply( undefined, [ value ].slice( noValue ) );
-			}
-
-		// For Promises/A+, convert exceptions into rejections
-		// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
-		// Deferred#then to conditionally suppress rejection.
-		} catch ( value ) {
-
-			// Support: Android 4.0 only
-			// Strict mode functions invoked without .call/.apply get global-object context
-			reject.apply( undefined, [ value ] );
-		}
-	}
-
-	jQuery.extend( {
-
-		Deferred: function( func ) {
-			var tuples = [
-
-					// action, add listener, callbacks,
-					// ... .then handlers, argument index, [final state]
-					[ "notify", "progress", jQuery.Callbacks( "memory" ),
-						jQuery.Callbacks( "memory" ), 2 ],
-					[ "resolve", "done", jQuery.Callbacks( "once memory" ),
-						jQuery.Callbacks( "once memory" ), 0, "resolved" ],
-					[ "reject", "fail", jQuery.Callbacks( "once memory" ),
-						jQuery.Callbacks( "once memory" ), 1, "rejected" ]
-				],
-				state = "pending",
-				promise = {
-					state: function() {
-						return state;
-					},
-					always: function() {
-						deferred.done( arguments ).fail( arguments );
-						return this;
-					},
-					"catch": function( fn ) {
-						return promise.then( null, fn );
-					},
-
-					// Keep pipe for back-compat
-					pipe: function( /* fnDone, fnFail, fnProgress */ ) {
-						var fns = arguments;
-
-						return jQuery.Deferred( function( newDefer ) {
-							jQuery.each( tuples, function( i, tuple ) {
-
-								// Map tuples (progress, done, fail) to arguments (done, fail, progress)
-								var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
-
-								// deferred.progress(function() { bind to newDefer or newDefer.notify })
-								// deferred.done(function() { bind to newDefer or newDefer.resolve })
-								// deferred.fail(function() { bind to newDefer or newDefer.reject })
-								deferred[ tuple[ 1 ] ]( function() {
-									var returned = fn && fn.apply( this, arguments );
-									if ( returned && isFunction( returned.promise ) ) {
-										returned.promise()
-											.progress( newDefer.notify )
-											.done( newDefer.resolve )
-											.fail( newDefer.reject );
-									} else {
-										newDefer[ tuple[ 0 ] + "With" ](
-											this,
-											fn ? [ returned ] : arguments
-										);
-									}
-								} );
-							} );
-							fns = null;
-						} ).promise();
-					},
-					then: function( onFulfilled, onRejected, onProgress ) {
-						var maxDepth = 0;
-						function resolve( depth, deferred, handler, special ) {
-							return function() {
-								var that = this,
-									args = arguments,
-									mightThrow = function() {
-										var returned, then;
-
-										// Support: Promises/A+ section 2.3.3.3.3
-										// https://promisesaplus.com/#point-59
-										// Ignore double-resolution attempts
-										if ( depth < maxDepth ) {
-											return;
-										}
-
-										returned = handler.apply( that, args );
-
-										// Support: Promises/A+ section 2.3.1
-										// https://promisesaplus.com/#point-48
-										if ( returned === deferred.promise() ) {
-											throw new TypeError( "Thenable self-resolution" );
-										}
-
-										// Support: Promises/A+ sections 2.3.3.1, 3.5
-										// https://promisesaplus.com/#point-54
-										// https://promisesaplus.com/#point-75
-										// Retrieve `then` only once
-										then = returned &&
-
-											// Support: Promises/A+ section 2.3.4
-											// https://promisesaplus.com/#point-64
-											// Only check objects and functions for thenability
-											( typeof returned === "object" ||
-												typeof returned === "function" ) &&
-											returned.then;
-
-										// Handle a returned thenable
-										if ( isFunction( then ) ) {
-
-											// Special processors (notify) just wait for resolution
-											if ( special ) {
-												then.call(
-													returned,
-													resolve( maxDepth, deferred, Identity, special ),
-													resolve( maxDepth, deferred, Thrower, special )
-												);
-
-											// Normal processors (resolve) also hook into progress
-											} else {
-
-												// ...and disregard older resolution values
-												maxDepth++;
-
-												then.call(
-													returned,
-													resolve( maxDepth, deferred, Identity, special ),
-													resolve( maxDepth, deferred, Thrower, special ),
-													resolve( maxDepth, deferred, Identity,
-														deferred.notifyWith )
-												);
-											}
-
-										// Handle all other returned values
-										} else {
-
-											// Only substitute handlers pass on context
-											// and multiple values (non-spec behavior)
-											if ( handler !== Identity ) {
-												that = undefined;
-												args = [ returned ];
-											}
-
-											// Process the value(s)
-											// Default process is resolve
-											( special || deferred.resolveWith )( that, args );
-										}
-									},
-
-									// Only normal processors (resolve) catch and reject exceptions
-									process = special ?
-										mightThrow :
-										function() {
-											try {
-												mightThrow();
-											} catch ( e ) {
-
-												if ( jQuery.Deferred.exceptionHook ) {
-													jQuery.Deferred.exceptionHook( e,
-														process.stackTrace );
-												}
-
-												// Support: Promises/A+ section 2.3.3.3.4.1
-												// https://promisesaplus.com/#point-61
-												// Ignore post-resolution exceptions
-												if ( depth + 1 >= maxDepth ) {
-
-													// Only substitute handlers pass on context
-													// and multiple values (non-spec behavior)
-													if ( handler !== Thrower ) {
-														that = undefined;
-														args = [ e ];
-													}
-
-													deferred.rejectWith( that, args );
-												}
-											}
-										};
-
-								// Support: Promises/A+ section 2.3.3.3.1
-								// https://promisesaplus.com/#point-57
-								// Re-resolve promises immediately to dodge false rejection from
-								// subsequent errors
-								if ( depth ) {
-									process();
-								} else {
-
-									// Call an optional hook to record the stack, in case of exception
-									// since it's otherwise lost when execution goes async
-									if ( jQuery.Deferred.getStackHook ) {
-										process.stackTrace = jQuery.Deferred.getStackHook();
-									}
-									window.setTimeout( process );
-								}
-							};
-						}
-
-						return jQuery.Deferred( function( newDefer ) {
-
-							// progress_handlers.add( ... )
-							tuples[ 0 ][ 3 ].add(
-								resolve(
-									0,
-									newDefer,
-									isFunction( onProgress ) ?
-										onProgress :
-										Identity,
-									newDefer.notifyWith
-								)
-							);
-
-							// fulfilled_handlers.add( ... )
-							tuples[ 1 ][ 3 ].add(
-								resolve(
-									0,
-									newDefer,
-									isFunction( onFulfilled ) ?
-										onFulfilled :
-										Identity
-								)
-							);
-
-							// rejected_handlers.add( ... )
-							tuples[ 2 ][ 3 ].add(
-								resolve(
-									0,
-									newDefer,
-									isFunction( onRejected ) ?
-										onRejected :
-										Thrower
-								)
-							);
-						} ).promise();
-					},
-
-					// Get a promise for this deferred
-					// If obj is provided, the promise aspect is added to the object
-					promise: function( obj ) {
-						return obj != null ? jQuery.extend( obj, promise ) : promise;
-					}
-				},
-				deferred = {};
-
-			// Add list-specific methods
-			jQuery.each( tuples, function( i, tuple ) {
-				var list = tuple[ 2 ],
-					stateString = tuple[ 5 ];
-
-				// promise.progress = list.add
-				// promise.done = list.add
-				// promise.fail = list.add
-				promise[ tuple[ 1 ] ] = list.add;
-
-				// Handle state
-				if ( stateString ) {
-					list.add(
-						function() {
-
-							// state = "resolved" (i.e., fulfilled)
-							// state = "rejected"
-							state = stateString;
-						},
-
-						// rejected_callbacks.disable
-						// fulfilled_callbacks.disable
-						tuples[ 3 - i ][ 2 ].disable,
-
-						// rejected_handlers.disable
-						// fulfilled_handlers.disable
-						tuples[ 3 - i ][ 3 ].disable,
-
-						// progress_callbacks.lock
-						tuples[ 0 ][ 2 ].lock,
-
-						// progress_handlers.lock
-						tuples[ 0 ][ 3 ].lock
-					);
-				}
-
-				// progress_handlers.fire
-				// fulfilled_handlers.fire
-				// rejected_handlers.fire
-				list.add( tuple[ 3 ].fire );
-
-				// deferred.notify = function() { deferred.notifyWith(...) }
-				// deferred.resolve = function() { deferred.resolveWith(...) }
-				// deferred.reject = function() { deferred.rejectWith(...) }
-				deferred[ tuple[ 0 ] ] = function() {
-					deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
-					return this;
-				};
-
-				// deferred.notifyWith = list.fireWith
-				// deferred.resolveWith = list.fireWith
-				// deferred.rejectWith = list.fireWith
-				deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
-			} );
-
-			// Make the deferred a promise
-			promise.promise( deferred );
-
-			// Call given func if any
-			if ( func ) {
-				func.call( deferred, deferred );
-			}
-
-			// All done!
-			return deferred;
-		},
-
-		// Deferred helper
-		when: function( singleValue ) {
-			var
-
-				// count of uncompleted subordinates
-				remaining = arguments.length,
-
-				// count of unprocessed arguments
-				i = remaining,
-
-				// subordinate fulfillment data
-				resolveContexts = Array( i ),
-				resolveValues = slice.call( arguments ),
-
-				// the master Deferred
-				master = jQuery.Deferred(),
-
-				// subordinate callback factory
-				updateFunc = function( i ) {
-					return function( value ) {
-						resolveContexts[ i ] = this;
-						resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
-						if ( !( --remaining ) ) {
-							master.resolveWith( resolveContexts, resolveValues );
-						}
-					};
-				};
-
-			// Single- and empty arguments are adopted like Promise.resolve
-			if ( remaining <= 1 ) {
-				adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
-					!remaining );
-
-				// Use .then() to unwrap secondary thenables (cf. gh-3000)
-				if ( master.state() === "pending" ||
-					isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
-
-					return master.then();
-				}
-			}
-
-			// Multiple arguments are aggregated like Promise.all array elements
-			while ( i-- ) {
-				adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
-			}
-
-			return master.promise();
-		}
-	} );
-
-
-	// These usually indicate a programmer mistake during development,
-	// warn about them ASAP rather than swallowing them by default.
-	var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
-
-	jQuery.Deferred.exceptionHook = function( error, stack ) {
-
-		// Support: IE 8 - 9 only
-		// Console exists when dev tools are open, which can happen at any time
-		if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
-			window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
-		}
-	};
-
-
-
-
-	jQuery.readyException = function( error ) {
-		window.setTimeout( function() {
-			throw error;
-		} );
-	};
-
-
-
-
-	// The deferred used on DOM ready
-	var readyList = jQuery.Deferred();
-
-	jQuery.fn.ready = function( fn ) {
-
-		readyList
-			.then( fn )
-
-			// Wrap jQuery.readyException in a function so that the lookup
-			// happens at the time of error handling instead of callback
-			// registration.
-			.catch( function( error ) {
-				jQuery.readyException( error );
-			} );
-
-		return this;
-	};
-
-	jQuery.extend( {
-
-		// Is the DOM ready to be used? Set to true once it occurs.
-		isReady: false,
-
-		// A counter to track how many items to wait for before
-		// the ready event fires. See #6781
-		readyWait: 1,
-
-		// Handle when the DOM is ready
-		ready: function( wait ) {
-
-			// Abort if there are pending holds or we're already ready
-			if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
-				return;
-			}
-
-			// Remember that the DOM is ready
-			jQuery.isReady = true;
-
-			// If a normal DOM Ready event fired, decrement, and wait if need be
-			if ( wait !== true && --jQuery.readyWait > 0 ) {
-				return;
-			}
-
-			// If there are functions bound, to execute
-			readyList.resolveWith( document, [ jQuery ] );
-		}
-	} );
-
-	jQuery.ready.then = readyList.then;
-
-	// The ready event handler and self cleanup method
-	function completed() {
-		document.removeEventListener( "DOMContentLoaded", completed );
-		window.removeEventListener( "load", completed );
-		jQuery.ready();
-	}
-
-	// Catch cases where $(document).ready() is called
-	// after the browser event has already occurred.
-	// Support: IE <=9 - 10 only
-	// Older IE sometimes signals "interactive" too soon
-	if ( document.readyState === "complete" ||
-		( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
-
-		// Handle it asynchronously to allow scripts the opportunity to delay ready
-		window.setTimeout( jQuery.ready );
-
-	} else {
-
-		// Use the handy event callback
-		document.addEventListener( "DOMContentLoaded", completed );
-
-		// A fallback to window.onload, that will always work
-		window.addEventListener( "load", completed );
-	}
-
-
-
-
-	// Multifunctional method to get and set values of a collection
-	// The value/s can optionally be executed if it's a function
-	var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
-		var i = 0,
-			len = elems.length,
-			bulk = key == null;
-
-		// Sets many values
-		if ( toType( key ) === "object" ) {
-			chainable = true;
-			for ( i in key ) {
-				access( elems, fn, i, key[ i ], true, emptyGet, raw );
-			}
-
-		// Sets one value
-		} else if ( value !== undefined ) {
-			chainable = true;
-
-			if ( !isFunction( value ) ) {
-				raw = true;
-			}
-
-			if ( bulk ) {
-
-				// Bulk operations run against the entire set
-				if ( raw ) {
-					fn.call( elems, value );
-					fn = null;
-
-				// ...except when executing function values
-				} else {
-					bulk = fn;
-					fn = function( elem, key, value ) {
-						return bulk.call( jQuery( elem ), value );
-					};
-				}
-			}
-
-			if ( fn ) {
-				for ( ; i < len; i++ ) {
-					fn(
-						elems[ i ], key, raw ?
-						value :
-						value.call( elems[ i ], i, fn( elems[ i ], key ) )
-					);
-				}
-			}
-		}
-
-		if ( chainable ) {
-			return elems;
-		}
-
-		// Gets
-		if ( bulk ) {
-			return fn.call( elems );
-		}
-
-		return len ? fn( elems[ 0 ], key ) : emptyGet;
-	};
-
-
-	// Matches dashed string for camelizing
-	var rmsPrefix = /^-ms-/,
-		rdashAlpha = /-([a-z])/g;
-
-	// Used by camelCase as callback to replace()
-	function fcamelCase( all, letter ) {
-		return letter.toUpperCase();
-	}
-
-	// Convert dashed to camelCase; used by the css and data modules
-	// Support: IE <=9 - 11, Edge 12 - 15
-	// Microsoft forgot to hump their vendor prefix (#9572)
-	function camelCase( string ) {
-		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
-	}
-	var acceptData = function( owner ) {
-
-		// Accepts only:
-		//  - Node
-		//    - Node.ELEMENT_NODE
-		//    - Node.DOCUMENT_NODE
-		//  - Object
-		//    - Any
-		return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
-	};
-
-
-
-
-	function Data() {
-		this.expando = jQuery.expando + Data.uid++;
-	}
-
-	Data.uid = 1;
-
-	Data.prototype = {
-
-		cache: function( owner ) {
-
-			// Check if the owner object already has a cache
-			var value = owner[ this.expando ];
-
-			// If not, create one
-			if ( !value ) {
-				value = {};
-
-				// We can accept data for non-element nodes in modern browsers,
-				// but we should not, see #8335.
-				// Always return an empty object.
-				if ( acceptData( owner ) ) {
-
-					// If it is a node unlikely to be stringify-ed or looped over
-					// use plain assignment
-					if ( owner.nodeType ) {
-						owner[ this.expando ] = value;
-
-					// Otherwise secure it in a non-enumerable property
-					// configurable must be true to allow the property to be
-					// deleted when data is removed
-					} else {
-						Object.defineProperty( owner, this.expando, {
-							value: value,
-							configurable: true
-						} );
-					}
-				}
-			}
-
-			return value;
-		},
-		set: function( owner, data, value ) {
-			var prop,
-				cache = this.cache( owner );
-
-			// Handle: [ owner, key, value ] args
-			// Always use camelCase key (gh-2257)
-			if ( typeof data === "string" ) {
-				cache[ camelCase( data ) ] = value;
-
-			// Handle: [ owner, { properties } ] args
-			} else {
-
-				// Copy the properties one-by-one to the cache object
-				for ( prop in data ) {
-					cache[ camelCase( prop ) ] = data[ prop ];
-				}
-			}
-			return cache;
-		},
-		get: function( owner, key ) {
-			return key === undefined ?
-				this.cache( owner ) :
-
-				// Always use camelCase key (gh-2257)
-				owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
-		},
-		access: function( owner, key, value ) {
-
-			// In cases where either:
-			//
-			//   1. No key was specified
-			//   2. A string key was specified, but no value provided
-			//
-			// Take the "read" path and allow the get method to determine
-			// which value to return, respectively either:
-			//
-			//   1. The entire cache object
-			//   2. The data stored at the key
-			//
-			if ( key === undefined ||
-					( ( key && typeof key === "string" ) && value === undefined ) ) {
-
-				return this.get( owner, key );
-			}
-
-			// When the key is not a string, or both a key and value
-			// are specified, set or extend (existing objects) with either:
-			//
-			//   1. An object of properties
-			//   2. A key and value
-			//
-			this.set( owner, key, value );
-
-			// Since the "set" path can have two possible entry points
-			// return the expected data based on which path was taken[*]
-			return value !== undefined ? value : key;
-		},
-		remove: function( owner, key ) {
-			var i,
-				cache = owner[ this.expando ];
-
-			if ( cache === undefined ) {
-				return;
-			}
-
-			if ( key !== undefined ) {
-
-				// Support array or space separated string of keys
-				if ( Array.isArray( key ) ) {
-
-					// If key is an array of keys...
-					// We always set camelCase keys, so remove that.
-					key = key.map( camelCase );
-				} else {
-					key = camelCase( key );
-
-					// If a key with the spaces exists, use it.
-					// Otherwise, create an array by matching non-whitespace
-					key = key in cache ?
-						[ key ] :
-						( key.match( rnothtmlwhite ) || [] );
-				}
-
-				i = key.length;
-
-				while ( i-- ) {
-					delete cache[ key[ i ] ];
-				}
-			}
-
-			// Remove the expando if there's no more data
-			if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
-
-				// Support: Chrome <=35 - 45
-				// Webkit & Blink performance suffers when deleting properties
-				// from DOM nodes, so set to undefined instead
-				// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
-				if ( owner.nodeType ) {
-					owner[ this.expando ] = undefined;
-				} else {
-					delete owner[ this.expando ];
-				}
-			}
-		},
-		hasData: function( owner ) {
-			var cache = owner[ this.expando ];
-			return cache !== undefined && !jQuery.isEmptyObject( cache );
-		}
-	};
-	var dataPriv = new Data();
-
-	var dataUser = new Data();
-
-
-
-	//	Implementation Summary
-	//
-	//	1. Enforce API surface and semantic compatibility with 1.9.x branch
-	//	2. Improve the module's maintainability by reducing the storage
-	//		paths to a single mechanism.
-	//	3. Use the same single mechanism to support "private" and "user" data.
-	//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
-	//	5. Avoid exposing implementation details on user objects (eg. expando properties)
-	//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
-
-	var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
-		rmultiDash = /[A-Z]/g;
-
-	function getData( data ) {
-		if ( data === "true" ) {
-			return true;
-		}
-
-		if ( data === "false" ) {
-			return false;
-		}
-
-		if ( data === "null" ) {
-			return null;
-		}
-
-		// Only convert to a number if it doesn't change the string
-		if ( data === +data + "" ) {
-			return +data;
-		}
-
-		if ( rbrace.test( data ) ) {
-			return JSON.parse( data );
-		}
-
-		return data;
-	}
-
-	function dataAttr( elem, key, data ) {
-		var name;
-
-		// If nothing was found internally, try to fetch any
-		// data from the HTML5 data-* attribute
-		if ( data === undefined && elem.nodeType === 1 ) {
-			name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
-			data = elem.getAttribute( name );
-
-			if ( typeof data === "string" ) {
-				try {
-					data = getData( data );
-				} catch ( e ) {}
-
-				// Make sure we set the data so it isn't changed later
-				dataUser.set( elem, key, data );
-			} else {
-				data = undefined;
-			}
-		}
-		return data;
-	}
-
-	jQuery.extend( {
-		hasData: function( elem ) {
-			return dataUser.hasData( elem ) || dataPriv.hasData( elem );
-		},
-
-		data: function( elem, name, data ) {
-			return dataUser.access( elem, name, data );
-		},
-
-		removeData: function( elem, name ) {
-			dataUser.remove( elem, name );
-		},
-
-		// TODO: Now that all calls to _data and _removeData have been replaced
-		// with direct calls to dataPriv methods, these can be deprecated.
-		_data: function( elem, name, data ) {
-			return dataPriv.access( elem, name, data );
-		},
-
-		_removeData: function( elem, name ) {
-			dataPriv.remove( elem, name );
-		}
-	} );
-
-	jQuery.fn.extend( {
-		data: function( key, value ) {
-			var i, name, data,
-				elem = this[ 0 ],
-				attrs = elem && elem.attributes;
-
-			// Gets all values
-			if ( key === undefined ) {
-				if ( this.length ) {
-					data = dataUser.get( elem );
-
-					if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
-						i = attrs.length;
-						while ( i-- ) {
-
-							// Support: IE 11 only
-							// The attrs elements can be null (#14894)
-							if ( attrs[ i ] ) {
-								name = attrs[ i ].name;
-								if ( name.indexOf( "data-" ) === 0 ) {
-									name = camelCase( name.slice( 5 ) );
-									dataAttr( elem, name, data[ name ] );
-								}
-							}
-						}
-						dataPriv.set( elem, "hasDataAttrs", true );
-					}
-				}
-
-				return data;
-			}
-
-			// Sets multiple values
-			if ( typeof key === "object" ) {
-				return this.each( function() {
-					dataUser.set( this, key );
-				} );
-			}
-
-			return access( this, function( value ) {
-				var data;
-
-				// The calling jQuery object (element matches) is not empty
-				// (and therefore has an element appears at this[ 0 ]) and the
-				// `value` parameter was not undefined. An empty jQuery object
-				// will result in `undefined` for elem = this[ 0 ] which will
-				// throw an exception if an attempt to read a data cache is made.
-				if ( elem && value === undefined ) {
-
-					// Attempt to get data from the cache
-					// The key will always be camelCased in Data
-					data = dataUser.get( elem, key );
-					if ( data !== undefined ) {
-						return data;
-					}
-
-					// Attempt to "discover" the data in
-					// HTML5 custom data-* attrs
-					data = dataAttr( elem, key );
-					if ( data !== undefined ) {
-						return data;
-					}
-
-					// We tried really hard, but the data doesn't exist.
-					return;
-				}
-
-				// Set the data...
-				this.each( function() {
-
-					// We always store the camelCased key
-					dataUser.set( this, key, value );
-				} );
-			}, null, value, arguments.length > 1, null, true );
-		},
-
-		removeData: function( key ) {
-			return this.each( function() {
-				dataUser.remove( this, key );
-			} );
-		}
-	} );
-
-
-	jQuery.extend( {
-		queue: function( elem, type, data ) {
-			var queue;
-
-			if ( elem ) {
-				type = ( type || "fx" ) + "queue";
-				queue = dataPriv.get( elem, type );
-
-				// Speed up dequeue by getting out quickly if this is just a lookup
-				if ( data ) {
-					if ( !queue || Array.isArray( data ) ) {
-						queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
-					} else {
-						queue.push( data );
-					}
-				}
-				return queue || [];
-			}
-		},
-
-		dequeue: function( elem, type ) {
-			type = type || "fx";
-
-			var queue = jQuery.queue( elem, type ),
-				startLength = queue.length,
-				fn = queue.shift(),
-				hooks = jQuery._queueHooks( elem, type ),
-				next = function() {
-					jQuery.dequeue( elem, type );
-				};
-
-			// If the fx queue is dequeued, always remove the progress sentinel
-			if ( fn === "inprogress" ) {
-				fn = queue.shift();
-				startLength--;
-			}
-
-			if ( fn ) {
-
-				// Add a progress sentinel to prevent the fx queue from being
-				// automatically dequeued
-				if ( type === "fx" ) {
-					queue.unshift( "inprogress" );
-				}
-
-				// Clear up the last queue stop function
-				delete hooks.stop;
-				fn.call( elem, next, hooks );
-			}
-
-			if ( !startLength && hooks ) {
-				hooks.empty.fire();
-			}
-		},
-
-		// Not public - generate a queueHooks object, or return the current one
-		_queueHooks: function( elem, type ) {
-			var key = type + "queueHooks";
-			return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
-				empty: jQuery.Callbacks( "once memory" ).add( function() {
-					dataPriv.remove( elem, [ type + "queue", key ] );
-				} )
-			} );
-		}
-	} );
-
-	jQuery.fn.extend( {
-		queue: function( type, data ) {
-			var setter = 2;
-
-			if ( typeof type !== "string" ) {
-				data = type;
-				type = "fx";
-				setter--;
-			}
-
-			if ( arguments.length < setter ) {
-				return jQuery.queue( this[ 0 ], type );
-			}
-
-			return data === undefined ?
-				this :
-				this.each( function() {
-					var queue = jQuery.queue( this, type, data );
-
-					// Ensure a hooks for this queue
-					jQuery._queueHooks( this, type );
-
-					if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
-						jQuery.dequeue( this, type );
-					}
-				} );
-		},
-		dequeue: function( type ) {
-			return this.each( function() {
-				jQuery.dequeue( this, type );
-			} );
-		},
-		clearQueue: function( type ) {
-			return this.queue( type || "fx", [] );
-		},
-
-		// Get a promise resolved when queues of a certain type
-		// are emptied (fx is the type by default)
-		promise: function( type, obj ) {
-			var tmp,
-				count = 1,
-				defer = jQuery.Deferred(),
-				elements = this,
-				i = this.length,
-				resolve = function() {
-					if ( !( --count ) ) {
-						defer.resolveWith( elements, [ elements ] );
-					}
-				};
-
-			if ( typeof type !== "string" ) {
-				obj = type;
-				type = undefined;
-			}
-			type = type || "fx";
-
-			while ( i-- ) {
-				tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
-				if ( tmp && tmp.empty ) {
-					count++;
-					tmp.empty.add( resolve );
-				}
-			}
-			resolve();
-			return defer.promise( obj );
-		}
-	} );
-	var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
-
-	var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
-
-
-	var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
-
-	var isHiddenWithinTree = function( elem, el ) {
-
-			// isHiddenWithinTree might be called from jQuery#filter function;
-			// in that case, element will be second argument
-			elem = el || elem;
-
-			// Inline style trumps all
-			return elem.style.display === "none" ||
-				elem.style.display === "" &&
-
-				// Otherwise, check computed style
-				// Support: Firefox <=43 - 45
-				// Disconnected elements can have computed display: none, so first confirm that elem is
-				// in the document.
-				jQuery.contains( elem.ownerDocument, elem ) &&
-
-				jQuery.css( elem, "display" ) === "none";
-		};
-
-	var swap = function( elem, options, callback, args ) {
-		var ret, name,
-			old = {};
-
-		// Remember the old values, and insert the new ones
-		for ( name in options ) {
-			old[ name ] = elem.style[ name ];
-			elem.style[ name ] = options[ name ];
-		}
-
-		ret = callback.apply( elem, args || [] );
-
-		// Revert the old values
-		for ( name in options ) {
-			elem.style[ name ] = old[ name ];
-		}
-
-		return ret;
-	};
-
-
-
-
-	function adjustCSS( elem, prop, valueParts, tween ) {
-		var adjusted, scale,
-			maxIterations = 20,
-			currentValue = tween ?
-				function() {
-					return tween.cur();
-				} :
-				function() {
-					return jQuery.css( elem, prop, "" );
-				},
-			initial = currentValue(),
-			unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
-
-			// Starting value computation is required for potential unit mismatches
-			initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
-				rcssNum.exec( jQuery.css( elem, prop ) );
-
-		if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
-
-			// Support: Firefox <=54
-			// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
-			initial = initial / 2;
-
-			// Trust units reported by jQuery.css
-			unit = unit || initialInUnit[ 3 ];
-
-			// Iteratively approximate from a nonzero starting point
-			initialInUnit = +initial || 1;
-
-			while ( maxIterations-- ) {
-
-				// Evaluate and update our best guess (doubling guesses that zero out).
-				// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
-				jQuery.style( elem, prop, initialInUnit + unit );
-				if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
-					maxIterations = 0;
-				}
-				initialInUnit = initialInUnit / scale;
-
-			}
-
-			initialInUnit = initialInUnit * 2;
-			jQuery.style( elem, prop, initialInUnit + unit );
-
-			// Make sure we update the tween properties later on
-			valueParts = valueParts || [];
-		}
-
-		if ( valueParts ) {
-			initialInUnit = +initialInUnit || +initial || 0;
-
-			// Apply relative offset (+=/-=) if specified
-			adjusted = valueParts[ 1 ] ?
-				initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
-				+valueParts[ 2 ];
-			if ( tween ) {
-				tween.unit = unit;
-				tween.start = initialInUnit;
-				tween.end = adjusted;
-			}
-		}
-		return adjusted;
-	}
-
-
-	var defaultDisplayMap = {};
-
-	function getDefaultDisplay( elem ) {
-		var temp,
-			doc = elem.ownerDocument,
-			nodeName = elem.nodeName,
-			display = defaultDisplayMap[ nodeName ];
-
-		if ( display ) {
-			return display;
-		}
-
-		temp = doc.body.appendChild( doc.createElement( nodeName ) );
-		display = jQuery.css( temp, "display" );
-
-		temp.parentNode.removeChild( temp );
-
-		if ( display === "none" ) {
-			display = "block";
-		}
-		defaultDisplayMap[ nodeName ] = display;
-
-		return display;
-	}
-
-	function showHide( elements, show ) {
-		var display, elem,
-			values = [],
-			index = 0,
-			length = elements.length;
-
-		// Determine new display value for elements that need to change
-		for ( ; index < length; index++ ) {
-			elem = elements[ index ];
-			if ( !elem.style ) {
-				continue;
-			}
-
-			display = elem.style.display;
-			if ( show ) {
-
-				// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
-				// check is required in this first loop unless we have a nonempty display value (either
-				// inline or about-to-be-restored)
-				if ( display === "none" ) {
-					values[ index ] = dataPriv.get( elem, "display" ) || null;
-					if ( !values[ index ] ) {
-						elem.style.display = "";
-					}
-				}
-				if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
-					values[ index ] = getDefaultDisplay( elem );
-				}
-			} else {
-				if ( display !== "none" ) {
-					values[ index ] = "none";
-
-					// Remember what we're overwriting
-					dataPriv.set( elem, "display", display );
-				}
-			}
-		}
-
-		// Set the display of the elements in a second loop to avoid constant reflow
-		for ( index = 0; index < length; index++ ) {
-			if ( values[ index ] != null ) {
-				elements[ index ].style.display = values[ index ];
-			}
-		}
-
-		return elements;
-	}
-
-	jQuery.fn.extend( {
-		show: function() {
-			return showHide( this, true );
-		},
-		hide: function() {
-			return showHide( this );
-		},
-		toggle: function( state ) {
-			if ( typeof state === "boolean" ) {
-				return state ? this.show() : this.hide();
-			}
-
-			return this.each( function() {
-				if ( isHiddenWithinTree( this ) ) {
-					jQuery( this ).show();
-				} else {
-					jQuery( this ).hide();
-				}
-			} );
-		}
-	} );
-	var rcheckableType = ( /^(?:checkbox|radio)$/i );
-
-	var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
-
-	var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
-
-
-
-	// We have to close these tags to support XHTML (#13200)
-	var wrapMap = {
-
-		// Support: IE <=9 only
-		option: [ 1, "<select multiple='multiple'>", "</select>" ],
-
-		// XHTML parsers do not magically insert elements in the
-		// same way that tag soup parsers do. So we cannot shorten
-		// this by omitting <tbody> or other required elements.
-		thead: [ 1, "<table>", "</table>" ],
-		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
-		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
-		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
-
-		_default: [ 0, "", "" ]
-	};
-
-	// Support: IE <=9 only
-	wrapMap.optgroup = wrapMap.option;
-
-	wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-	wrapMap.th = wrapMap.td;
-
-
-	function getAll( context, tag ) {
-
-		// Support: IE <=9 - 11 only
-		// Use typeof to avoid zero-argument method invocation on host objects (#15151)
-		var ret;
-
-		if ( typeof context.getElementsByTagName !== "undefined" ) {
-			ret = context.getElementsByTagName( tag || "*" );
-
-		} else if ( typeof context.querySelectorAll !== "undefined" ) {
-			ret = context.querySelectorAll( tag || "*" );
-
-		} else {
-			ret = [];
-		}
-
-		if ( tag === undefined || tag && nodeName( context, tag ) ) {
-			return jQuery.merge( [ context ], ret );
-		}
-
-		return ret;
-	}
-
-
-	// Mark scripts as having already been evaluated
-	function setGlobalEval( elems, refElements ) {
-		var i = 0,
-			l = elems.length;
-
-		for ( ; i < l; i++ ) {
-			dataPriv.set(
-				elems[ i ],
-				"globalEval",
-				!refElements || dataPriv.get( refElements[ i ], "globalEval" )
-			);
-		}
-	}
-
-
-	var rhtml = /<|&#?\w+;/;
-
-	function buildFragment( elems, context, scripts, selection, ignored ) {
-		var elem, tmp, tag, wrap, contains, j,
-			fragment = context.createDocumentFragment(),
-			nodes = [],
-			i = 0,
-			l = elems.length;
-
-		for ( ; i < l; i++ ) {
-			elem = elems[ i ];
-
-			if ( elem || elem === 0 ) {
-
-				// Add nodes directly
-				if ( toType( elem ) === "object" ) {
-
-					// Support: Android <=4.0 only, PhantomJS 1 only
-					// push.apply(_, arraylike) throws on ancient WebKit
-					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
-
-				// Convert non-html into a text node
-				} else if ( !rhtml.test( elem ) ) {
-					nodes.push( context.createTextNode( elem ) );
-
-				// Convert html into DOM nodes
-				} else {
-					tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
-
-					// Deserialize a standard representation
-					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
-					wrap = wrapMap[ tag ] || wrapMap._default;
-					tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
-
-					// Descend through wrappers to the right content
-					j = wrap[ 0 ];
-					while ( j-- ) {
-						tmp = tmp.lastChild;
-					}
-
-					// Support: Android <=4.0 only, PhantomJS 1 only
-					// push.apply(_, arraylike) throws on ancient WebKit
-					jQuery.merge( nodes, tmp.childNodes );
-
-					// Remember the top-level container
-					tmp = fragment.firstChild;
-
-					// Ensure the created nodes are orphaned (#12392)
-					tmp.textContent = "";
-				}
-			}
-		}
-
-		// Remove wrapper from fragment
-		fragment.textContent = "";
-
-		i = 0;
-		while ( ( elem = nodes[ i++ ] ) ) {
-
-			// Skip elements already in the context collection (trac-4087)
-			if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
-				if ( ignored ) {
-					ignored.push( elem );
-				}
-				continue;
-			}
-
-			contains = jQuery.contains( elem.ownerDocument, elem );
-
-			// Append to fragment
-			tmp = getAll( fragment.appendChild( elem ), "script" );
-
-			// Preserve script evaluation history
-			if ( contains ) {
-				setGlobalEval( tmp );
-			}
-
-			// Capture executables
-			if ( scripts ) {
-				j = 0;
-				while ( ( elem = tmp[ j++ ] ) ) {
-					if ( rscriptType.test( elem.type || "" ) ) {
-						scripts.push( elem );
-					}
-				}
-			}
-		}
-
-		return fragment;
-	}
-
-
-	( function() {
-		var fragment = document.createDocumentFragment(),
-			div = fragment.appendChild( document.createElement( "div" ) ),
-			input = document.createElement( "input" );
-
-		// Support: Android 4.0 - 4.3 only
-		// Check state lost if the name is set (#11217)
-		// Support: Windows Web Apps (WWA)
-		// `name` and `type` must use .setAttribute for WWA (#14901)
-		input.setAttribute( "type", "radio" );
-		input.setAttribute( "checked", "checked" );
-		input.setAttribute( "name", "t" );
-
-		div.appendChild( input );
-
-		// Support: Android <=4.1 only
-		// Older WebKit doesn't clone checked state correctly in fragments
-		support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
-
-		// Support: IE <=11 only
-		// Make sure textarea (and checkbox) defaultValue is properly cloned
-		div.innerHTML = "<textarea>x</textarea>";
-		support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
-	} )();
-	var documentElement = document.documentElement;
-
-
-
-	var
-		rkeyEvent = /^key/,
-		rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
-		rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
-
-	function returnTrue() {
-		return true;
-	}
-
-	function returnFalse() {
-		return false;
-	}
-
-	// Support: IE <=9 only
-	// See #13393 for more info
-	function safeActiveElement() {
-		try {
-			return document.activeElement;
-		} catch ( err ) { }
-	}
-
-	function on( elem, types, selector, data, fn, one ) {
-		var origFn, type;
-
-		// Types can be a map of types/handlers
-		if ( typeof types === "object" ) {
-
-			// ( types-Object, selector, data )
-			if ( typeof selector !== "string" ) {
-
-				// ( types-Object, data )
-				data = data || selector;
-				selector = undefined;
-			}
-			for ( type in types ) {
-				on( elem, type, selector, data, types[ type ], one );
-			}
-			return elem;
-		}
-
-		if ( data == null && fn == null ) {
-
-			// ( types, fn )
-			fn = selector;
-			data = selector = undefined;
-		} else if ( fn == null ) {
-			if ( typeof selector === "string" ) {
-
-				// ( types, selector, fn )
-				fn = data;
-				data = undefined;
-			} else {
-
-				// ( types, data, fn )
-				fn = data;
-				data = selector;
-				selector = undefined;
-			}
-		}
-		if ( fn === false ) {
-			fn = returnFalse;
-		} else if ( !fn ) {
-			return elem;
-		}
-
-		if ( one === 1 ) {
-			origFn = fn;
-			fn = function( event ) {
-
-				// Can use an empty set, since event contains the info
-				jQuery().off( event );
-				return origFn.apply( this, arguments );
-			};
-
-			// Use same guid so caller can remove using origFn
-			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
-		}
-		return elem.each( function() {
-			jQuery.event.add( this, types, fn, data, selector );
-		} );
-	}
-
-	/*
-	 * Helper functions for managing events -- not part of the public interface.
-	 * Props to Dean Edwards' addEvent library for many of the ideas.
-	 */
-	jQuery.event = {
-
-		global: {},
-
-		add: function( elem, types, handler, data, selector ) {
-
-			var handleObjIn, eventHandle, tmp,
-				events, t, handleObj,
-				special, handlers, type, namespaces, origType,
-				elemData = dataPriv.get( elem );
-
-			// Don't attach events to noData or text/comment nodes (but allow plain objects)
-			if ( !elemData ) {
-				return;
-			}
-
-			// Caller can pass in an object of custom data in lieu of the handler
-			if ( handler.handler ) {
-				handleObjIn = handler;
-				handler = handleObjIn.handler;
-				selector = handleObjIn.selector;
-			}
-
-			// Ensure that invalid selectors throw exceptions at attach time
-			// Evaluate against documentElement in case elem is a non-element node (e.g., document)
-			if ( selector ) {
-				jQuery.find.matchesSelector( documentElement, selector );
-			}
-
-			// Make sure that the handler has a unique ID, used to find/remove it later
-			if ( !handler.guid ) {
-				handler.guid = jQuery.guid++;
-			}
-
-			// Init the element's event structure and main handler, if this is the first
-			if ( !( events = elemData.events ) ) {
-				events = elemData.events = {};
-			}
-			if ( !( eventHandle = elemData.handle ) ) {
-				eventHandle = elemData.handle = function( e ) {
-
-					// Discard the second event of a jQuery.event.trigger() and
-					// when an event is called after a page has unloaded
-					return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
-						jQuery.event.dispatch.apply( elem, arguments ) : undefined;
-				};
-			}
-
-			// Handle multiple events separated by a space
-			types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
-			t = types.length;
-			while ( t-- ) {
-				tmp = rtypenamespace.exec( types[ t ] ) || [];
-				type = origType = tmp[ 1 ];
-				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
-
-				// There *must* be a type, no attaching namespace-only handlers
-				if ( !type ) {
-					continue;
-				}
-
-				// If event changes its type, use the special event handlers for the changed type
-				special = jQuery.event.special[ type ] || {};
-
-				// If selector defined, determine special event api type, otherwise given type
-				type = ( selector ? special.delegateType : special.bindType ) || type;
-
-				// Update special based on newly reset type
-				special = jQuery.event.special[ type ] || {};
-
-				// handleObj is passed to all event handlers
-				handleObj = jQuery.extend( {
-					type: type,
-					origType: origType,
-					data: data,
-					handler: handler,
-					guid: handler.guid,
-					selector: selector,
-					needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
-					namespace: namespaces.join( "." )
-				}, handleObjIn );
-
-				// Init the event handler queue if we're the first
-				if ( !( handlers = events[ type ] ) ) {
-					handlers = events[ type ] = [];
-					handlers.delegateCount = 0;
-
-					// Only use addEventListener if the special events handler returns false
-					if ( !special.setup ||
-						special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
-
-						if ( elem.addEventListener ) {
-							elem.addEventListener( type, eventHandle );
-						}
-					}
-				}
-
-				if ( special.add ) {
-					special.add.call( elem, handleObj );
-
-					if ( !handleObj.handler.guid ) {
-						handleObj.handler.guid = handler.guid;
-					}
-				}
-
-				// Add to the element's handler list, delegates in front
-				if ( selector ) {
-					handlers.splice( handlers.delegateCount++, 0, handleObj );
-				} else {
-					handlers.push( handleObj );
-				}
-
-				// Keep track of which events have ever been used, for event optimization
-				jQuery.event.global[ type ] = true;
-			}
-
-		},
-
-		// Detach an event or set of events from an element
-		remove: function( elem, types, handler, selector, mappedTypes ) {
-
-			var j, origCount, tmp,
-				events, t, handleObj,
-				special, handlers, type, namespaces, origType,
-				elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
-
-			if ( !elemData || !( events = elemData.events ) ) {
-				return;
-			}
-
-			// Once for each type.namespace in types; type may be omitted
-			types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
-			t = types.length;
-			while ( t-- ) {
-				tmp = rtypenamespace.exec( types[ t ] ) || [];
-				type = origType = tmp[ 1 ];
-				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
-
-				// Unbind all events (on this namespace, if provided) for the element
-				if ( !type ) {
-					for ( type in events ) {
-						jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
-					}
-					continue;
-				}
-
-				special = jQuery.event.special[ type ] || {};
-				type = ( selector ? special.delegateType : special.bindType ) || type;
-				handlers = events[ type ] || [];
-				tmp = tmp[ 2 ] &&
-					new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
-
-				// Remove matching events
-				origCount = j = handlers.length;
-				while ( j-- ) {
-					handleObj = handlers[ j ];
-
-					if ( ( mappedTypes || origType === handleObj.origType ) &&
-						( !handler || handler.guid === handleObj.guid ) &&
-						( !tmp || tmp.test( handleObj.namespace ) ) &&
-						( !selector || selector === handleObj.selector ||
-							selector === "**" && handleObj.selector ) ) {
-						handlers.splice( j, 1 );
-
-						if ( handleObj.selector ) {
-							handlers.delegateCount--;
-						}
-						if ( special.remove ) {
-							special.remove.call( elem, handleObj );
-						}
-					}
-				}
-
-				// Remove generic event handler if we removed something and no more handlers exist
-				// (avoids potential for endless recursion during removal of special event handlers)
-				if ( origCount && !handlers.length ) {
-					if ( !special.teardown ||
-						special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
-
-						jQuery.removeEvent( elem, type, elemData.handle );
-					}
-
-					delete events[ type ];
-				}
-			}
-
-			// Remove data and the expando if it's no longer used
-			if ( jQuery.isEmptyObject( events ) ) {
-				dataPriv.remove( elem, "handle events" );
-			}
-		},
-
-		dispatch: function( nativeEvent ) {
-
-			// Make a writable jQuery.Event from the native event object
-			var event = jQuery.event.fix( nativeEvent );
-
-			var i, j, ret, matched, handleObj, handlerQueue,
-				args = new Array( arguments.length ),
-				handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
-				special = jQuery.event.special[ event.type ] || {};
-
-			// Use the fix-ed jQuery.Event rather than the (read-only) native event
-			args[ 0 ] = event;
-
-			for ( i = 1; i < arguments.length; i++ ) {
-				args[ i ] = arguments[ i ];
-			}
-
-			event.delegateTarget = this;
-
-			// Call the preDispatch hook for the mapped type, and let it bail if desired
-			if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
-				return;
-			}
-
-			// Determine handlers
-			handlerQueue = jQuery.event.handlers.call( this, event, handlers );
-
-			// Run delegates first; they may want to stop propagation beneath us
-			i = 0;
-			while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
-				event.currentTarget = matched.elem;
-
-				j = 0;
-				while ( ( handleObj = matched.handlers[ j++ ] ) &&
-					!event.isImmediatePropagationStopped() ) {
-
-					// Triggered event must either 1) have no namespace, or 2) have namespace(s)
-					// a subset or equal to those in the bound event (both can have no namespace).
-					if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
-
-						event.handleObj = handleObj;
-						event.data = handleObj.data;
-
-						ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
-							handleObj.handler ).apply( matched.elem, args );
-
-						if ( ret !== undefined ) {
-							if ( ( event.result = ret ) === false ) {
-								event.preventDefault();
-								event.stopPropagation();
-							}
-						}
-					}
-				}
-			}
-
-			// Call the postDispatch hook for the mapped type
-			if ( special.postDispatch ) {
-				special.postDispatch.call( this, event );
-			}
-
-			return event.result;
-		},
-
-		handlers: function( event, handlers ) {
-			var i, handleObj, sel, matchedHandlers, matchedSelectors,
-				handlerQueue = [],
-				delegateCount = handlers.delegateCount,
-				cur = event.target;
-
-			// Find delegate handlers
-			if ( delegateCount &&
-
-				// Support: IE <=9
-				// Black-hole SVG <use> instance trees (trac-13180)
-				cur.nodeType &&
-
-				// Support: Firefox <=42
-				// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
-				// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
-				// Support: IE 11 only
-				// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
-				!( event.type === "click" && event.button >= 1 ) ) {
-
-				for ( ; cur !== this; cur = cur.parentNode || this ) {
-
-					// Don't check non-elements (#13208)
-					// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
-					if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
-						matchedHandlers = [];
-						matchedSelectors = {};
-						for ( i = 0; i < delegateCount; i++ ) {
-							handleObj = handlers[ i ];
-
-							// Don't conflict with Object.prototype properties (#13203)
-							sel = handleObj.selector + " ";
-
-							if ( matchedSelectors[ sel ] === undefined ) {
-								matchedSelectors[ sel ] = handleObj.needsContext ?
-									jQuery( sel, this ).index( cur ) > -1 :
-									jQuery.find( sel, this, null, [ cur ] ).length;
-							}
-							if ( matchedSelectors[ sel ] ) {
-								matchedHandlers.push( handleObj );
-							}
-						}
-						if ( matchedHandlers.length ) {
-							handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
-						}
-					}
-				}
-			}
-
-			// Add the remaining (directly-bound) handlers
-			cur = this;
-			if ( delegateCount < handlers.length ) {
-				handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
-			}
-
-			return handlerQueue;
-		},
-
-		addProp: function( name, hook ) {
-			Object.defineProperty( jQuery.Event.prototype, name, {
-				enumerable: true,
-				configurable: true,
-
-				get: isFunction( hook ) ?
-					function() {
-						if ( this.originalEvent ) {
-								return hook( this.originalEvent );
-						}
-					} :
-					function() {
-						if ( this.originalEvent ) {
-								return this.originalEvent[ name ];
-						}
-					},
-
-				set: function( value ) {
-					Object.defineProperty( this, name, {
-						enumerable: true,
-						configurable: true,
-						writable: true,
-						value: value
-					} );
-				}
-			} );
-		},
-
-		fix: function( originalEvent ) {
-			return originalEvent[ jQuery.expando ] ?
-				originalEvent :
-				new jQuery.Event( originalEvent );
-		},
-
-		special: {
-			load: {
-
-				// Prevent triggered image.load events from bubbling to window.load
-				noBubble: true
-			},
-			focus: {
-
-				// Fire native event if possible so blur/focus sequence is correct
-				trigger: function() {
-					if ( this !== safeActiveElement() && this.focus ) {
-						this.focus();
-						return false;
-					}
-				},
-				delegateType: "focusin"
-			},
-			blur: {
-				trigger: function() {
-					if ( this === safeActiveElement() && this.blur ) {
-						this.blur();
-						return false;
-					}
-				},
-				delegateType: "focusout"
-			},
-			click: {
-
-				// For checkbox, fire native event so checked state will be right
-				trigger: function() {
-					if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
-						this.click();
-						return false;
-					}
-				},
-
-				// For cross-browser consistency, don't fire native .click() on links
-				_default: function( event ) {
-					return nodeName( event.target, "a" );
-				}
-			},
-
-			beforeunload: {
-				postDispatch: function( event ) {
-
-					// Support: Firefox 20+
-					// Firefox doesn't alert if the returnValue field is not set.
-					if ( event.result !== undefined && event.originalEvent ) {
-						event.originalEvent.returnValue = event.result;
-					}
-				}
-			}
-		}
-	};
-
-	jQuery.removeEvent = function( elem, type, handle ) {
-
-		// This "if" is needed for plain objects
-		if ( elem.removeEventListener ) {
-			elem.removeEventListener( type, handle );
-		}
-	};
-
-	jQuery.Event = function( src, props ) {
-
-		// Allow instantiation without the 'new' keyword
-		if ( !( this instanceof jQuery.Event ) ) {
-			return new jQuery.Event( src, props );
-		}
-
-		// Event object
-		if ( src && src.type ) {
-			this.originalEvent = src;
-			this.type = src.type;
-
-			// Events bubbling up the document may have been marked as prevented
-			// by a handler lower down the tree; reflect the correct value.
-			this.isDefaultPrevented = src.defaultPrevented ||
-					src.defaultPrevented === undefined &&
-
-					// Support: Android <=2.3 only
-					src.returnValue === false ?
-				returnTrue :
-				returnFalse;
-
-			// Create target properties
-			// Support: Safari <=6 - 7 only
-			// Target should not be a text node (#504, #13143)
-			this.target = ( src.target && src.target.nodeType === 3 ) ?
-				src.target.parentNode :
-				src.target;
-
-			this.currentTarget = src.currentTarget;
-			this.relatedTarget = src.relatedTarget;
-
-		// Event type
-		} else {
-			this.type = src;
-		}
-
-		// Put explicitly provided properties onto the event object
-		if ( props ) {
-			jQuery.extend( this, props );
-		}
-
-		// Create a timestamp if incoming event doesn't have one
-		this.timeStamp = src && src.timeStamp || Date.now();
-
-		// Mark it as fixed
-		this[ jQuery.expando ] = true;
-	};
-
-	// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-	// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-	jQuery.Event.prototype = {
-		constructor: jQuery.Event,
-		isDefaultPrevented: returnFalse,
-		isPropagationStopped: returnFalse,
-		isImmediatePropagationStopped: returnFalse,
-		isSimulated: false,
-
-		preventDefault: function() {
-			var e = this.originalEvent;
-
-			this.isDefaultPrevented = returnTrue;
-
-			if ( e && !this.isSimulated ) {
-				e.preventDefault();
-			}
-		},
-		stopPropagation: function() {
-			var e = this.originalEvent;
-
-			this.isPropagationStopped = returnTrue;
-
-			if ( e && !this.isSimulated ) {
-				e.stopPropagation();
-			}
-		},
-		stopImmediatePropagation: function() {
-			var e = this.originalEvent;
-
-			this.isImmediatePropagationStopped = returnTrue;
-
-			if ( e && !this.isSimulated ) {
-				e.stopImmediatePropagation();
-			}
-
-			this.stopPropagation();
-		}
-	};
-
-	// Includes all common event props including KeyEvent and MouseEvent specific props
-	jQuery.each( {
-		altKey: true,
-		bubbles: true,
-		cancelable: true,
-		changedTouches: true,
-		ctrlKey: true,
-		detail: true,
-		eventPhase: true,
-		metaKey: true,
-		pageX: true,
-		pageY: true,
-		shiftKey: true,
-		view: true,
-		"char": true,
-		charCode: true,
-		key: true,
-		keyCode: true,
-		button: true,
-		buttons: true,
-		clientX: true,
-		clientY: true,
-		offsetX: true,
-		offsetY: true,
-		pointerId: true,
-		pointerType: true,
-		screenX: true,
-		screenY: true,
-		targetTouches: true,
-		toElement: true,
-		touches: true,
-
-		which: function( event ) {
-			var button = event.button;
-
-			// Add which for key events
-			if ( event.which == null && rkeyEvent.test( event.type ) ) {
-				return event.charCode != null ? event.charCode : event.keyCode;
-			}
-
-			// Add which for click: 1 === left; 2 === middle; 3 === right
-			if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
-				if ( button & 1 ) {
-					return 1;
-				}
-
-				if ( button & 2 ) {
-					return 3;
-				}
-
-				if ( button & 4 ) {
-					return 2;
-				}
-
-				return 0;
-			}
-
-			return event.which;
-		}
-	}, jQuery.event.addProp );
-
-	// Create mouseenter/leave events using mouseover/out and event-time checks
-	// so that event delegation works in jQuery.
-	// Do the same for pointerenter/pointerleave and pointerover/pointerout
-	//
-	// Support: Safari 7 only
-	// Safari sends mouseenter too often; see:
-	// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
-	// for the description of the bug (it existed in older Chrome versions as well).
-	jQuery.each( {
-		mouseenter: "mouseover",
-		mouseleave: "mouseout",
-		pointerenter: "pointerover",
-		pointerleave: "pointerout"
-	}, function( orig, fix ) {
-		jQuery.event.special[ orig ] = {
-			delegateType: fix,
-			bindType: fix,
-
-			handle: function( event ) {
-				var ret,
-					target = this,
-					related = event.relatedTarget,
-					handleObj = event.handleObj;
-
-				// For mouseenter/leave call the handler if related is outside the target.
-				// NB: No relatedTarget if the mouse left/entered the browser window
-				if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
-					event.type = handleObj.origType;
-					ret = handleObj.handler.apply( this, arguments );
-					event.type = fix;
-				}
-				return ret;
-			}
-		};
-	} );
-
-	jQuery.fn.extend( {
-
-		on: function( types, selector, data, fn ) {
-			return on( this, types, selector, data, fn );
-		},
-		one: function( types, selector, data, fn ) {
-			return on( this, types, selector, data, fn, 1 );
-		},
-		off: function( types, selector, fn ) {
-			var handleObj, type;
-			if ( types && types.preventDefault && types.handleObj ) {
-
-				// ( event )  dispatched jQuery.Event
-				handleObj = types.handleObj;
-				jQuery( types.delegateTarget ).off(
-					handleObj.namespace ?
-						handleObj.origType + "." + handleObj.namespace :
-						handleObj.origType,
-					handleObj.selector,
-					handleObj.handler
-				);
-				return this;
-			}
-			if ( typeof types === "object" ) {
-
-				// ( types-object [, selector] )
-				for ( type in types ) {
-					this.off( type, selector, types[ type ] );
-				}
-				return this;
-			}
-			if ( selector === false || typeof selector === "function" ) {
-
-				// ( types [, fn] )
-				fn = selector;
-				selector = undefined;
-			}
-			if ( fn === false ) {
-				fn = returnFalse;
-			}
-			return this.each( function() {
-				jQuery.event.remove( this, types, fn, selector );
-			} );
-		}
-	} );
-
-
-	var
-
-		/* eslint-disable max-len */
-
-		// See https://github.com/eslint/eslint/issues/3229
-		rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
-
-		/* eslint-enable */
-
-		// Support: IE <=10 - 11, Edge 12 - 13 only
-		// In IE/Edge using regex groups here causes severe slowdowns.
-		// See https://connect.microsoft.com/IE/feedback/details/1736512/
-		rnoInnerhtml = /<script|<style|<link/i,
-
-		// checked="checked" or checked
-		rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
-		rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
-
-	// Prefer a tbody over its parent table for containing new rows
-	function manipulationTarget( elem, content ) {
-		if ( nodeName( elem, "table" ) &&
-			nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
-
-			return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
-		}
-
-		return elem;
-	}
-
-	// Replace/restore the type attribute of script elements for safe DOM manipulation
-	function disableScript( elem ) {
-		elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
-		return elem;
-	}
-	function restoreScript( elem ) {
-		if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
-			elem.type = elem.type.slice( 5 );
-		} else {
-			elem.removeAttribute( "type" );
-		}
-
-		return elem;
-	}
-
-	function cloneCopyEvent( src, dest ) {
-		var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
-
-		if ( dest.nodeType !== 1 ) {
-			return;
-		}
-
-		// 1. Copy private data: events, handlers, etc.
-		if ( dataPriv.hasData( src ) ) {
-			pdataOld = dataPriv.access( src );
-			pdataCur = dataPriv.set( dest, pdataOld );
-			events = pdataOld.events;
-
-			if ( events ) {
-				delete pdataCur.handle;
-				pdataCur.events = {};
-
-				for ( type in events ) {
-					for ( i = 0, l = events[ type ].length; i < l; i++ ) {
-						jQuery.event.add( dest, type, events[ type ][ i ] );
-					}
-				}
-			}
-		}
-
-		// 2. Copy user data
-		if ( dataUser.hasData( src ) ) {
-			udataOld = dataUser.access( src );
-			udataCur = jQuery.extend( {}, udataOld );
-
-			dataUser.set( dest, udataCur );
-		}
-	}
-
-	// Fix IE bugs, see support tests
-	function fixInput( src, dest ) {
-		var nodeName = dest.nodeName.toLowerCase();
-
-		// Fails to persist the checked state of a cloned checkbox or radio button.
-		if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
-			dest.checked = src.checked;
-
-		// Fails to return the selected option to the default selected state when cloning options
-		} else if ( nodeName === "input" || nodeName === "textarea" ) {
-			dest.defaultValue = src.defaultValue;
-		}
-	}
-
-	function domManip( collection, args, callback, ignored ) {
-
-		// Flatten any nested arrays
-		args = concat.apply( [], args );
-
-		var fragment, first, scripts, hasScripts, node, doc,
-			i = 0,
-			l = collection.length,
-			iNoClone = l - 1,
-			value = args[ 0 ],
-			valueIsFunction = isFunction( value );
-
-		// We can't cloneNode fragments that contain checked, in WebKit
-		if ( valueIsFunction ||
-				( l > 1 && typeof value === "string" &&
-					!support.checkClone && rchecked.test( value ) ) ) {
-			return collection.each( function( index ) {
-				var self = collection.eq( index );
-				if ( valueIsFunction ) {
-					args[ 0 ] = value.call( this, index, self.html() );
-				}
-				domManip( self, args, callback, ignored );
-			} );
-		}
-
-		if ( l ) {
-			fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
-			first = fragment.firstChild;
-
-			if ( fragment.childNodes.length === 1 ) {
-				fragment = first;
-			}
-
-			// Require either new content or an interest in ignored elements to invoke the callback
-			if ( first || ignored ) {
-				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
-				hasScripts = scripts.length;
-
-				// Use the original fragment for the last item
-				// instead of the first because it can end up
-				// being emptied incorrectly in certain situations (#8070).
-				for ( ; i < l; i++ ) {
-					node = fragment;
-
-					if ( i !== iNoClone ) {
-						node = jQuery.clone( node, true, true );
-
-						// Keep references to cloned scripts for later restoration
-						if ( hasScripts ) {
-
-							// Support: Android <=4.0 only, PhantomJS 1 only
-							// push.apply(_, arraylike) throws on ancient WebKit
-							jQuery.merge( scripts, getAll( node, "script" ) );
-						}
-					}
-
-					callback.call( collection[ i ], node, i );
-				}
-
-				if ( hasScripts ) {
-					doc = scripts[ scripts.length - 1 ].ownerDocument;
-
-					// Reenable scripts
-					jQuery.map( scripts, restoreScript );
-
-					// Evaluate executable scripts on first document insertion
-					for ( i = 0; i < hasScripts; i++ ) {
-						node = scripts[ i ];
-						if ( rscriptType.test( node.type || "" ) &&
-							!dataPriv.access( node, "globalEval" ) &&
-							jQuery.contains( doc, node ) ) {
-
-							if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {
-
-								// Optional AJAX dependency, but won't run scripts if not present
-								if ( jQuery._evalUrl ) {
-									jQuery._evalUrl( node.src );
-								}
-							} else {
-								DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node );
-							}
-						}
-					}
-				}
-			}
-		}
-
-		return collection;
-	}
-
-	function remove( elem, selector, keepData ) {
-		var node,
-			nodes = selector ? jQuery.filter( selector, elem ) : elem,
-			i = 0;
-
-		for ( ; ( node = nodes[ i ] ) != null; i++ ) {
-			if ( !keepData && node.nodeType === 1 ) {
-				jQuery.cleanData( getAll( node ) );
-			}
-
-			if ( node.parentNode ) {
-				if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
-					setGlobalEval( getAll( node, "script" ) );
-				}
-				node.parentNode.removeChild( node );
-			}
-		}
-
-		return elem;
-	}
-
-	jQuery.extend( {
-		htmlPrefilter: function( html ) {
-			return html.replace( rxhtmlTag, "<$1></$2>" );
-		},
-
-		clone: function( elem, dataAndEvents, deepDataAndEvents ) {
-			var i, l, srcElements, destElements,
-				clone = elem.cloneNode( true ),
-				inPage = jQuery.contains( elem.ownerDocument, elem );
-
-			// Fix IE cloning issues
-			if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
-					!jQuery.isXMLDoc( elem ) ) {
-
-				// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
-				destElements = getAll( clone );
-				srcElements = getAll( elem );
-
-				for ( i = 0, l = srcElements.length; i < l; i++ ) {
-					fixInput( srcElements[ i ], destElements[ i ] );
-				}
-			}
-
-			// Copy the events from the original to the clone
-			if ( dataAndEvents ) {
-				if ( deepDataAndEvents ) {
-					srcElements = srcElements || getAll( elem );
-					destElements = destElements || getAll( clone );
-
-					for ( i = 0, l = srcElements.length; i < l; i++ ) {
-						cloneCopyEvent( srcElements[ i ], destElements[ i ] );
-					}
-				} else {
-					cloneCopyEvent( elem, clone );
-				}
-			}
-
-			// Preserve script evaluation history
-			destElements = getAll( clone, "script" );
-			if ( destElements.length > 0 ) {
-				setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
-			}
-
-			// Return the cloned set
-			return clone;
-		},
-
-		cleanData: function( elems ) {
-			var data, elem, type,
-				special = jQuery.event.special,
-				i = 0;
-
-			for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
-				if ( acceptData( elem ) ) {
-					if ( ( data = elem[ dataPriv.expando ] ) ) {
-						if ( data.events ) {
-							for ( type in data.events ) {
-								if ( special[ type ] ) {
-									jQuery.event.remove( elem, type );
-
-								// This is a shortcut to avoid jQuery.event.remove's overhead
-								} else {
-									jQuery.removeEvent( elem, type, data.handle );
-								}
-							}
-						}
-
-						// Support: Chrome <=35 - 45+
-						// Assign undefined instead of using delete, see Data#remove
-						elem[ dataPriv.expando ] = undefined;
-					}
-					if ( elem[ dataUser.expando ] ) {
-
-						// Support: Chrome <=35 - 45+
-						// Assign undefined instead of using delete, see Data#remove
-						elem[ dataUser.expando ] = undefined;
-					}
-				}
-			}
-		}
-	} );
-
-	jQuery.fn.extend( {
-		detach: function( selector ) {
-			return remove( this, selector, true );
-		},
-
-		remove: function( selector ) {
-			return remove( this, selector );
-		},
-
-		text: function( value ) {
-			return access( this, function( value ) {
-				return value === undefined ?
-					jQuery.text( this ) :
-					this.empty().each( function() {
-						if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
-							this.textContent = value;
-						}
-					} );
-			}, null, value, arguments.length );
-		},
-
-		append: function() {
-			return domManip( this, arguments, function( elem ) {
-				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
-					var target = manipulationTarget( this, elem );
-					target.appendChild( elem );
-				}
-			} );
-		},
-
-		prepend: function() {
-			return domManip( this, arguments, function( elem ) {
-				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
-					var target = manipulationTarget( this, elem );
-					target.insertBefore( elem, target.firstChild );
-				}
-			} );
-		},
-
-		before: function() {
-			return domManip( this, arguments, function( elem ) {
-				if ( this.parentNode ) {
-					this.parentNode.insertBefore( elem, this );
-				}
-			} );
-		},
-
-		after: function() {
-			return domManip( this, arguments, function( elem ) {
-				if ( this.parentNode ) {
-					this.parentNode.insertBefore( elem, this.nextSibling );
-				}
-			} );
-		},
-
-		empty: function() {
-			var elem,
-				i = 0;
-
-			for ( ; ( elem = this[ i ] ) != null; i++ ) {
-				if ( elem.nodeType === 1 ) {
-
-					// Prevent memory leaks
-					jQuery.cleanData( getAll( elem, false ) );
-
-					// Remove any remaining nodes
-					elem.textContent = "";
-				}
-			}
-
-			return this;
-		},
-
-		clone: function( dataAndEvents, deepDataAndEvents ) {
-			dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
-			deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
-			return this.map( function() {
-				return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
-			} );
-		},
-
-		html: function( value ) {
-			return access( this, function( value ) {
-				var elem = this[ 0 ] || {},
-					i = 0,
-					l = this.length;
-
-				if ( value === undefined && elem.nodeType === 1 ) {
-					return elem.innerHTML;
-				}
-
-				// See if we can take a shortcut and just use innerHTML
-				if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
-					!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
-
-					value = jQuery.htmlPrefilter( value );
-
-					try {
-						for ( ; i < l; i++ ) {
-							elem = this[ i ] || {};
-
-							// Remove element nodes and prevent memory leaks
-							if ( elem.nodeType === 1 ) {
-								jQuery.cleanData( getAll( elem, false ) );
-								elem.innerHTML = value;
-							}
-						}
-
-						elem = 0;
-
-					// If using innerHTML throws an exception, use the fallback method
-					} catch ( e ) {}
-				}
-
-				if ( elem ) {
-					this.empty().append( value );
-				}
-			}, null, value, arguments.length );
-		},
-
-		replaceWith: function() {
-			var ignored = [];
-
-			// Make the changes, replacing each non-ignored context element with the new content
-			return domManip( this, arguments, function( elem ) {
-				var parent = this.parentNode;
-
-				if ( jQuery.inArray( this, ignored ) < 0 ) {
-					jQuery.cleanData( getAll( this ) );
-					if ( parent ) {
-						parent.replaceChild( elem, this );
-					}
-				}
-
-			// Force callback invocation
-			}, ignored );
-		}
-	} );
-
-	jQuery.each( {
-		appendTo: "append",
-		prependTo: "prepend",
-		insertBefore: "before",
-		insertAfter: "after",
-		replaceAll: "replaceWith"
-	}, function( name, original ) {
-		jQuery.fn[ name ] = function( selector ) {
-			var elems,
-				ret = [],
-				insert = jQuery( selector ),
-				last = insert.length - 1,
-				i = 0;
-
-			for ( ; i <= last; i++ ) {
-				elems = i === last ? this : this.clone( true );
-				jQuery( insert[ i ] )[ original ]( elems );
-
-				// Support: Android <=4.0 only, PhantomJS 1 only
-				// .get() because push.apply(_, arraylike) throws on ancient WebKit
-				push.apply( ret, elems.get() );
-			}
-
-			return this.pushStack( ret );
-		};
-	} );
-	var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
-
-	var getStyles = function( elem ) {
-
-			// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
-			// IE throws on elements created in popups
-			// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
-			var view = elem.ownerDocument.defaultView;
-
-			if ( !view || !view.opener ) {
-				view = window;
-			}
-
-			return view.getComputedStyle( elem );
-		};
-
-	var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
-
-
-
-	( function() {
-
-		// Executing both pixelPosition & boxSizingReliable tests require only one layout
-		// so they're executed at the same time to save the second computation.
-		function computeStyleTests() {
-
-			// This is a singleton, we need to execute it only once
-			if ( !div ) {
-				return;
-			}
-
-			container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
-				"margin-top:1px;padding:0;border:0";
-			div.style.cssText =
-				"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
-				"margin:auto;border:1px;padding:1px;" +
-				"width:60%;top:1%";
-			documentElement.appendChild( container ).appendChild( div );
-
-			var divStyle = window.getComputedStyle( div );
-			pixelPositionVal = divStyle.top !== "1%";
-
-			// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
-			reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
-
-			// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
-			// Some styles come back with percentage values, even though they shouldn't
-			div.style.right = "60%";
-			pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
-
-			// Support: IE 9 - 11 only
-			// Detect misreporting of content dimensions for box-sizing:border-box elements
-			boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
-
-			// Support: IE 9 only
-			// Detect overflow:scroll screwiness (gh-3699)
-			div.style.position = "absolute";
-			scrollboxSizeVal = div.offsetWidth === 36 || "absolute";
-
-			documentElement.removeChild( container );
-
-			// Nullify the div so it wouldn't be stored in the memory and
-			// it will also be a sign that checks already performed
-			div = null;
-		}
-
-		function roundPixelMeasures( measure ) {
-			return Math.round( parseFloat( measure ) );
-		}
-
-		var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
-			reliableMarginLeftVal,
-			container = document.createElement( "div" ),
-			div = document.createElement( "div" );
-
-		// Finish early in limited (non-browser) environments
-		if ( !div.style ) {
-			return;
-		}
-
-		// Support: IE <=9 - 11 only
-		// Style of cloned element affects source element cloned (#8908)
-		div.style.backgroundClip = "content-box";
-		div.cloneNode( true ).style.backgroundClip = "";
-		support.clearCloneStyle = div.style.backgroundClip === "content-box";
-
-		jQuery.extend( support, {
-			boxSizingReliable: function() {
-				computeStyleTests();
-				return boxSizingReliableVal;
-			},
-			pixelBoxStyles: function() {
-				computeStyleTests();
-				return pixelBoxStylesVal;
-			},
-			pixelPosition: function() {
-				computeStyleTests();
-				return pixelPositionVal;
-			},
-			reliableMarginLeft: function() {
-				computeStyleTests();
-				return reliableMarginLeftVal;
-			},
-			scrollboxSize: function() {
-				computeStyleTests();
-				return scrollboxSizeVal;
-			}
-		} );
-	} )();
-
-
-	function curCSS( elem, name, computed ) {
-		var width, minWidth, maxWidth, ret,
-
-			// Support: Firefox 51+
-			// Retrieving style before computed somehow
-			// fixes an issue with getting wrong values
-			// on detached elements
-			style = elem.style;
-
-		computed = computed || getStyles( elem );
-
-		// getPropertyValue is needed for:
-		//   .css('filter') (IE 9 only, #12537)
-		//   .css('--customProperty) (#3144)
-		if ( computed ) {
-			ret = computed.getPropertyValue( name ) || computed[ name ];
-
-			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
-				ret = jQuery.style( elem, name );
-			}
-
-			// A tribute to the "awesome hack by Dean Edwards"
-			// Android Browser returns percentage for some values,
-			// but width seems to be reliably pixels.
-			// This is against the CSSOM draft spec:
-			// https://drafts.csswg.org/cssom/#resolved-values
-			if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
-
-				// Remember the original values
-				width = style.width;
-				minWidth = style.minWidth;
-				maxWidth = style.maxWidth;
-
-				// Put in the new values to get a computed value out
-				style.minWidth = style.maxWidth = style.width = ret;
-				ret = computed.width;
-
-				// Revert the changed values
-				style.width = width;
-				style.minWidth = minWidth;
-				style.maxWidth = maxWidth;
-			}
-		}
-
-		return ret !== undefined ?
-
-			// Support: IE <=9 - 11 only
-			// IE returns zIndex value as an integer.
-			ret + "" :
-			ret;
-	}
-
-
-	function addGetHookIf( conditionFn, hookFn ) {
-
-		// Define the hook, we'll check on the first run if it's really needed.
-		return {
-			get: function() {
-				if ( conditionFn() ) {
-
-					// Hook not needed (or it's not possible to use it due
-					// to missing dependency), remove it.
-					delete this.get;
-					return;
-				}
-
-				// Hook needed; redefine it so that the support test is not executed again.
-				return ( this.get = hookFn ).apply( this, arguments );
-			}
-		};
-	}
-
-
-	var
-
-		// Swappable if display is none or starts with table
-		// except "table", "table-cell", or "table-caption"
-		// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
-		rdisplayswap = /^(none|table(?!-c[ea]).+)/,
-		rcustomProp = /^--/,
-		cssShow = { position: "absolute", visibility: "hidden", display: "block" },
-		cssNormalTransform = {
-			letterSpacing: "0",
-			fontWeight: "400"
-		},
-
-		cssPrefixes = [ "Webkit", "Moz", "ms" ],
-		emptyStyle = document.createElement( "div" ).style;
-
-	// Return a css property mapped to a potentially vendor prefixed property
-	function vendorPropName( name ) {
-
-		// Shortcut for names that are not vendor prefixed
-		if ( name in emptyStyle ) {
-			return name;
-		}
-
-		// Check for vendor prefixed names
-		var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
-			i = cssPrefixes.length;
-
-		while ( i-- ) {
-			name = cssPrefixes[ i ] + capName;
-			if ( name in emptyStyle ) {
-				return name;
-			}
-		}
-	}
-
-	// Return a property mapped along what jQuery.cssProps suggests or to
-	// a vendor prefixed property.
-	function finalPropName( name ) {
-		var ret = jQuery.cssProps[ name ];
-		if ( !ret ) {
-			ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name;
-		}
-		return ret;
-	}
-
-	function setPositiveNumber( elem, value, subtract ) {
-
-		// Any relative (+/-) values have already been
-		// normalized at this point
-		var matches = rcssNum.exec( value );
-		return matches ?
-
-			// Guard against undefined "subtract", e.g., when used as in cssHooks
-			Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
-			value;
-	}
-
-	function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
-		var i = dimension === "width" ? 1 : 0,
-			extra = 0,
-			delta = 0;
-
-		// Adjustment may not be necessary
-		if ( box === ( isBorderBox ? "border" : "content" ) ) {
-			return 0;
-		}
-
-		for ( ; i < 4; i += 2 ) {
-
-			// Both box models exclude margin
-			if ( box === "margin" ) {
-				delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
-			}
-
-			// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
-			if ( !isBorderBox ) {
-
-				// Add padding
-				delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
-
-				// For "border" or "margin", add border
-				if ( box !== "padding" ) {
-					delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
-
-				// But still keep track of it otherwise
-				} else {
-					extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
-				}
-
-			// If we get here with a border-box (content + padding + border), we're seeking "content" or
-			// "padding" or "margin"
-			} else {
-
-				// For "content", subtract padding
-				if ( box === "content" ) {
-					delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
-				}
-
-				// For "content" or "padding", subtract border
-				if ( box !== "margin" ) {
-					delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
-				}
-			}
-		}
-
-		// Account for positive content-box scroll gutter when requested by providing computedVal
-		if ( !isBorderBox && computedVal >= 0 ) {
-
-			// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
-			// Assuming integer scroll gutter, subtract the rest and round down
-			delta += Math.max( 0, Math.ceil(
-				elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
-				computedVal -
-				delta -
-				extra -
-				0.5
-			) );
-		}
-
-		return delta;
-	}
-
-	function getWidthOrHeight( elem, dimension, extra ) {
-
-		// Start with computed style
-		var styles = getStyles( elem ),
-			val = curCSS( elem, dimension, styles ),
-			isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
-			valueIsBorderBox = isBorderBox;
-
-		// Support: Firefox <=54
-		// Return a confounding non-pixel value or feign ignorance, as appropriate.
-		if ( rnumnonpx.test( val ) ) {
-			if ( !extra ) {
-				return val;
-			}
-			val = "auto";
-		}
-
-		// Check for style in case a browser which returns unreliable values
-		// for getComputedStyle silently falls back to the reliable elem.style
-		valueIsBorderBox = valueIsBorderBox &&
-			( support.boxSizingReliable() || val === elem.style[ dimension ] );
-
-		// Fall back to offsetWidth/offsetHeight when value is "auto"
-		// This happens for inline elements with no explicit setting (gh-3571)
-		// Support: Android <=4.1 - 4.3 only
-		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
-		if ( val === "auto" ||
-			!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) {
-
-			val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
-
-			// offsetWidth/offsetHeight provide border-box values
-			valueIsBorderBox = true;
-		}
-
-		// Normalize "" and auto
-		val = parseFloat( val ) || 0;
-
-		// Adjust for the element's box model
-		return ( val +
-			boxModelAdjustment(
-				elem,
-				dimension,
-				extra || ( isBorderBox ? "border" : "content" ),
-				valueIsBorderBox,
-				styles,
-
-				// Provide the current computed size to request scroll gutter calculation (gh-3589)
-				val
-			)
-		) + "px";
-	}
-
-	jQuery.extend( {
-
-		// Add in style property hooks for overriding the default
-		// behavior of getting and setting a style property
-		cssHooks: {
-			opacity: {
-				get: function( elem, computed ) {
-					if ( computed ) {
-
-						// We should always get a number back from opacity
-						var ret = curCSS( elem, "opacity" );
-						return ret === "" ? "1" : ret;
-					}
-				}
-			}
-		},
-
-		// Don't automatically add "px" to these possibly-unitless properties
-		cssNumber: {
-			"animationIterationCount": true,
-			"columnCount": true,
-			"fillOpacity": true,
-			"flexGrow": true,
-			"flexShrink": true,
-			"fontWeight": true,
-			"lineHeight": true,
-			"opacity": true,
-			"order": true,
-			"orphans": true,
-			"widows": true,
-			"zIndex": true,
-			"zoom": true
-		},
-
-		// Add in properties whose names you wish to fix before
-		// setting or getting the value
-		cssProps: {},
-
-		// Get and set the style property on a DOM Node
-		style: function( elem, name, value, extra ) {
-
-			// Don't set styles on text and comment nodes
-			if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
-				return;
-			}
-
-			// Make sure that we're working with the right name
-			var ret, type, hooks,
-				origName = camelCase( name ),
-				isCustomProp = rcustomProp.test( name ),
-				style = elem.style;
-
-			// Make sure that we're working with the right name. We don't
-			// want to query the value if it is a CSS custom property
-			// since they are user-defined.
-			if ( !isCustomProp ) {
-				name = finalPropName( origName );
-			}
-
-			// Gets hook for the prefixed version, then unprefixed version
-			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
-			// Check if we're setting a value
-			if ( value !== undefined ) {
-				type = typeof value;
-
-				// Convert "+=" or "-=" to relative numbers (#7345)
-				if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
-					value = adjustCSS( elem, name, ret );
-
-					// Fixes bug #9237
-					type = "number";
-				}
-
-				// Make sure that null and NaN values aren't set (#7116)
-				if ( value == null || value !== value ) {
-					return;
-				}
-
-				// If a number was passed in, add the unit (except for certain CSS properties)
-				if ( type === "number" ) {
-					value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
-				}
-
-				// background-* props affect original clone's values
-				if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
-					style[ name ] = "inherit";
-				}
-
-				// If a hook was provided, use that value, otherwise just set the specified value
-				if ( !hooks || !( "set" in hooks ) ||
-					( value = hooks.set( elem, value, extra ) ) !== undefined ) {
-
-					if ( isCustomProp ) {
-						style.setProperty( name, value );
-					} else {
-						style[ name ] = value;
-					}
-				}
-
-			} else {
-
-				// If a hook was provided get the non-computed value from there
-				if ( hooks && "get" in hooks &&
-					( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
-
-					return ret;
-				}
-
-				// Otherwise just get the value from the style object
-				return style[ name ];
-			}
-		},
-
-		css: function( elem, name, extra, styles ) {
-			var val, num, hooks,
-				origName = camelCase( name ),
-				isCustomProp = rcustomProp.test( name );
-
-			// Make sure that we're working with the right name. We don't
-			// want to modify the value if it is a CSS custom property
-			// since they are user-defined.
-			if ( !isCustomProp ) {
-				name = finalPropName( origName );
-			}
-
-			// Try prefixed name followed by the unprefixed name
-			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
-			// If a hook was provided get the computed value from there
-			if ( hooks && "get" in hooks ) {
-				val = hooks.get( elem, true, extra );
-			}
-
-			// Otherwise, if a way to get the computed value exists, use that
-			if ( val === undefined ) {
-				val = curCSS( elem, name, styles );
-			}
-
-			// Convert "normal" to computed value
-			if ( val === "normal" && name in cssNormalTransform ) {
-				val = cssNormalTransform[ name ];
-			}
-
-			// Make numeric if forced or a qualifier was provided and val looks numeric
-			if ( extra === "" || extra ) {
-				num = parseFloat( val );
-				return extra === true || isFinite( num ) ? num || 0 : val;
-			}
-
-			return val;
-		}
-	} );
-
-	jQuery.each( [ "height", "width" ], function( i, dimension ) {
-		jQuery.cssHooks[ dimension ] = {
-			get: function( elem, computed, extra ) {
-				if ( computed ) {
-
-					// Certain elements can have dimension info if we invisibly show them
-					// but it must have a current display style that would benefit
-					return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
-
-						// Support: Safari 8+
-						// Table columns in Safari have non-zero offsetWidth & zero
-						// getBoundingClientRect().width unless display is changed.
-						// Support: IE <=11 only
-						// Running getBoundingClientRect on a disconnected node
-						// in IE throws an error.
-						( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
-							swap( elem, cssShow, function() {
-								return getWidthOrHeight( elem, dimension, extra );
-							} ) :
-							getWidthOrHeight( elem, dimension, extra );
-				}
-			},
-
-			set: function( elem, value, extra ) {
-				var matches,
-					styles = getStyles( elem ),
-					isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
-					subtract = extra && boxModelAdjustment(
-						elem,
-						dimension,
-						extra,
-						isBorderBox,
-						styles
-					);
-
-				// Account for unreliable border-box dimensions by comparing offset* to computed and
-				// faking a content-box to get border and padding (gh-3699)
-				if ( isBorderBox && support.scrollboxSize() === styles.position ) {
-					subtract -= Math.ceil(
-						elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
-						parseFloat( styles[ dimension ] ) -
-						boxModelAdjustment( elem, dimension, "border", false, styles ) -
-						0.5
-					);
-				}
-
-				// Convert to pixels if value adjustment is needed
-				if ( subtract && ( matches = rcssNum.exec( value ) ) &&
-					( matches[ 3 ] || "px" ) !== "px" ) {
-
-					elem.style[ dimension ] = value;
-					value = jQuery.css( elem, dimension );
-				}
-
-				return setPositiveNumber( elem, value, subtract );
-			}
-		};
-	} );
-
-	jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
-		function( elem, computed ) {
-			if ( computed ) {
-				return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
-					elem.getBoundingClientRect().left -
-						swap( elem, { marginLeft: 0 }, function() {
-							return elem.getBoundingClientRect().left;
-						} )
-					) + "px";
-			}
-		}
-	);
-
-	// These hooks are used by animate to expand properties
-	jQuery.each( {
-		margin: "",
-		padding: "",
-		border: "Width"
-	}, function( prefix, suffix ) {
-		jQuery.cssHooks[ prefix + suffix ] = {
-			expand: function( value ) {
-				var i = 0,
-					expanded = {},
-
-					// Assumes a single number if not a string
-					parts = typeof value === "string" ? value.split( " " ) : [ value ];
-
-				for ( ; i < 4; i++ ) {
-					expanded[ prefix + cssExpand[ i ] + suffix ] =
-						parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
-				}
-
-				return expanded;
-			}
-		};
-
-		if ( prefix !== "margin" ) {
-			jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
-		}
-	} );
-
-	jQuery.fn.extend( {
-		css: function( name, value ) {
-			return access( this, function( elem, name, value ) {
-				var styles, len,
-					map = {},
-					i = 0;
-
-				if ( Array.isArray( name ) ) {
-					styles = getStyles( elem );
-					len = name.length;
-
-					for ( ; i < len; i++ ) {
-						map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
-					}
-
-					return map;
-				}
-
-				return value !== undefined ?
-					jQuery.style( elem, name, value ) :
-					jQuery.css( elem, name );
-			}, name, value, arguments.length > 1 );
-		}
-	} );
-
-
-	function Tween( elem, options, prop, end, easing ) {
-		return new Tween.prototype.init( elem, options, prop, end, easing );
-	}
-	jQuery.Tween = Tween;
-
-	Tween.prototype = {
-		constructor: Tween,
-		init: function( elem, options, prop, end, easing, unit ) {
-			this.elem = elem;
-			this.prop = prop;
-			this.easing = easing || jQuery.easing._default;
-			this.options = options;
-			this.start = this.now = this.cur();
-			this.end = end;
-			this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
-		},
-		cur: function() {
-			var hooks = Tween.propHooks[ this.prop ];
-
-			return hooks && hooks.get ?
-				hooks.get( this ) :
-				Tween.propHooks._default.get( this );
-		},
-		run: function( percent ) {
-			var eased,
-				hooks = Tween.propHooks[ this.prop ];
-
-			if ( this.options.duration ) {
-				this.pos = eased = jQuery.easing[ this.easing ](
-					percent, this.options.duration * percent, 0, 1, this.options.duration
-				);
-			} else {
-				this.pos = eased = percent;
-			}
-			this.now = ( this.end - this.start ) * eased + this.start;
-
-			if ( this.options.step ) {
-				this.options.step.call( this.elem, this.now, this );
-			}
-
-			if ( hooks && hooks.set ) {
-				hooks.set( this );
-			} else {
-				Tween.propHooks._default.set( this );
-			}
-			return this;
-		}
-	};
-
-	Tween.prototype.init.prototype = Tween.prototype;
-
-	Tween.propHooks = {
-		_default: {
-			get: function( tween ) {
-				var result;
-
-				// Use a property on the element directly when it is not a DOM element,
-				// or when there is no matching style property that exists.
-				if ( tween.elem.nodeType !== 1 ||
-					tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
-					return tween.elem[ tween.prop ];
-				}
-
-				// Passing an empty string as a 3rd parameter to .css will automatically
-				// attempt a parseFloat and fallback to a string if the parse fails.
-				// Simple values such as "10px" are parsed to Float;
-				// complex values such as "rotate(1rad)" are returned as-is.
-				result = jQuery.css( tween.elem, tween.prop, "" );
-
-				// Empty strings, null, undefined and "auto" are converted to 0.
-				return !result || result === "auto" ? 0 : result;
-			},
-			set: function( tween ) {
-
-				// Use step hook for back compat.
-				// Use cssHook if its there.
-				// Use .style if available and use plain properties where available.
-				if ( jQuery.fx.step[ tween.prop ] ) {
-					jQuery.fx.step[ tween.prop ]( tween );
-				} else if ( tween.elem.nodeType === 1 &&
-					( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
-						jQuery.cssHooks[ tween.prop ] ) ) {
-					jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
-				} else {
-					tween.elem[ tween.prop ] = tween.now;
-				}
-			}
-		}
-	};
-
-	// Support: IE <=9 only
-	// Panic based approach to setting things on disconnected nodes
-	Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
-		set: function( tween ) {
-			if ( tween.elem.nodeType && tween.elem.parentNode ) {
-				tween.elem[ tween.prop ] = tween.now;
-			}
-		}
-	};
-
-	jQuery.easing = {
-		linear: function( p ) {
-			return p;
-		},
-		swing: function( p ) {
-			return 0.5 - Math.cos( p * Math.PI ) / 2;
-		},
-		_default: "swing"
-	};
-
-	jQuery.fx = Tween.prototype.init;
-
-	// Back compat <1.8 extension point
-	jQuery.fx.step = {};
-
-
-
-
-	var
-		fxNow, inProgress,
-		rfxtypes = /^(?:toggle|show|hide)$/,
-		rrun = /queueHooks$/;
-
-	function schedule() {
-		if ( inProgress ) {
-			if ( document.hidden === false && window.requestAnimationFrame ) {
-				window.requestAnimationFrame( schedule );
-			} else {
-				window.setTimeout( schedule, jQuery.fx.interval );
-			}
-
-			jQuery.fx.tick();
-		}
-	}
-
-	// Animations created synchronously will run synchronously
-	function createFxNow() {
-		window.setTimeout( function() {
-			fxNow = undefined;
-		} );
-		return ( fxNow = Date.now() );
-	}
-
-	// Generate parameters to create a standard animation
-	function genFx( type, includeWidth ) {
-		var which,
-			i = 0,
-			attrs = { height: type };
-
-		// If we include width, step value is 1 to do all cssExpand values,
-		// otherwise step value is 2 to skip over Left and Right
-		includeWidth = includeWidth ? 1 : 0;
-		for ( ; i < 4; i += 2 - includeWidth ) {
-			which = cssExpand[ i ];
-			attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
-		}
-
-		if ( includeWidth ) {
-			attrs.opacity = attrs.width = type;
-		}
-
-		return attrs;
-	}
-
-	function createTween( value, prop, animation ) {
-		var tween,
-			collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
-			index = 0,
-			length = collection.length;
-		for ( ; index < length; index++ ) {
-			if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
-
-				// We're done with this property
-				return tween;
-			}
-		}
-	}
-
-	function defaultPrefilter( elem, props, opts ) {
-		var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
-			isBox = "width" in props || "height" in props,
-			anim = this,
-			orig = {},
-			style = elem.style,
-			hidden = elem.nodeType && isHiddenWithinTree( elem ),
-			dataShow = dataPriv.get( elem, "fxshow" );
-
-		// Queue-skipping animations hijack the fx hooks
-		if ( !opts.queue ) {
-			hooks = jQuery._queueHooks( elem, "fx" );
-			if ( hooks.unqueued == null ) {
-				hooks.unqueued = 0;
-				oldfire = hooks.empty.fire;
-				hooks.empty.fire = function() {
-					if ( !hooks.unqueued ) {
-						oldfire();
-					}
-				};
-			}
-			hooks.unqueued++;
-
-			anim.always( function() {
-
-				// Ensure the complete handler is called before this completes
-				anim.always( function() {
-					hooks.unqueued--;
-					if ( !jQuery.queue( elem, "fx" ).length ) {
-						hooks.empty.fire();
-					}
-				} );
-			} );
-		}
-
-		// Detect show/hide animations
-		for ( prop in props ) {
-			value = props[ prop ];
-			if ( rfxtypes.test( value ) ) {
-				delete props[ prop ];
-				toggle = toggle || value === "toggle";
-				if ( value === ( hidden ? "hide" : "show" ) ) {
-
-					// Pretend to be hidden if this is a "show" and
-					// there is still data from a stopped show/hide
-					if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
-						hidden = true;
-
-					// Ignore all other no-op show/hide data
-					} else {
-						continue;
-					}
-				}
-				orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
-			}
-		}
-
-		// Bail out if this is a no-op like .hide().hide()
-		propTween = !jQuery.isEmptyObject( props );
-		if ( !propTween && jQuery.isEmptyObject( orig ) ) {
-			return;
-		}
-
-		// Restrict "overflow" and "display" styles during box animations
-		if ( isBox && elem.nodeType === 1 ) {
-
-			// Support: IE <=9 - 11, Edge 12 - 15
-			// Record all 3 overflow attributes because IE does not infer the shorthand
-			// from identically-valued overflowX and overflowY and Edge just mirrors
-			// the overflowX value there.
-			opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
-
-			// Identify a display type, preferring old show/hide data over the CSS cascade
-			restoreDisplay = dataShow && dataShow.display;
-			if ( restoreDisplay == null ) {
-				restoreDisplay = dataPriv.get( elem, "display" );
-			}
-			display = jQuery.css( elem, "display" );
-			if ( display === "none" ) {
-				if ( restoreDisplay ) {
-					display = restoreDisplay;
-				} else {
-
-					// Get nonempty value(s) by temporarily forcing visibility
-					showHide( [ elem ], true );
-					restoreDisplay = elem.style.display || restoreDisplay;
-					display = jQuery.css( elem, "display" );
-					showHide( [ elem ] );
-				}
-			}
-
-			// Animate inline elements as inline-block
-			if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
-				if ( jQuery.css( elem, "float" ) === "none" ) {
-
-					// Restore the original display value at the end of pure show/hide animations
-					if ( !propTween ) {
-						anim.done( function() {
-							style.display = restoreDisplay;
-						} );
-						if ( restoreDisplay == null ) {
-							display = style.display;
-							restoreDisplay = display === "none" ? "" : display;
-						}
-					}
-					style.display = "inline-block";
-				}
-			}
-		}
-
-		if ( opts.overflow ) {
-			style.overflow = "hidden";
-			anim.always( function() {
-				style.overflow = opts.overflow[ 0 ];
-				style.overflowX = opts.overflow[ 1 ];
-				style.overflowY = opts.overflow[ 2 ];
-			} );
-		}
-
-		// Implement show/hide animations
-		propTween = false;
-		for ( prop in orig ) {
-
-			// General show/hide setup for this element animation
-			if ( !propTween ) {
-				if ( dataShow ) {
-					if ( "hidden" in dataShow ) {
-						hidden = dataShow.hidden;
-					}
-				} else {
-					dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
-				}
-
-				// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
-				if ( toggle ) {
-					dataShow.hidden = !hidden;
-				}
-
-				// Show elements before animating them
-				if ( hidden ) {
-					showHide( [ elem ], true );
-				}
-
-				/* eslint-disable no-loop-func */
-
-				anim.done( function() {
-
-				/* eslint-enable no-loop-func */
-
-					// The final step of a "hide" animation is actually hiding the element
-					if ( !hidden ) {
-						showHide( [ elem ] );
-					}
-					dataPriv.remove( elem, "fxshow" );
-					for ( prop in orig ) {
-						jQuery.style( elem, prop, orig[ prop ] );
-					}
-				} );
-			}
-
-			// Per-property setup
-			propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
-			if ( !( prop in dataShow ) ) {
-				dataShow[ prop ] = propTween.start;
-				if ( hidden ) {
-					propTween.end = propTween.start;
-					propTween.start = 0;
-				}
-			}
-		}
-	}
-
-	function propFilter( props, specialEasing ) {
-		var index, name, easing, value, hooks;
-
-		// camelCase, specialEasing and expand cssHook pass
-		for ( index in props ) {
-			name = camelCase( index );
-			easing = specialEasing[ name ];
-			value = props[ index ];
-			if ( Array.isArray( value ) ) {
-				easing = value[ 1 ];
-				value = props[ index ] = value[ 0 ];
-			}
-
-			if ( index !== name ) {
-				props[ name ] = value;
-				delete props[ index ];
-			}
-
-			hooks = jQuery.cssHooks[ name ];
-			if ( hooks && "expand" in hooks ) {
-				value = hooks.expand( value );
-				delete props[ name ];
-
-				// Not quite $.extend, this won't overwrite existing keys.
-				// Reusing 'index' because we have the correct "name"
-				for ( index in value ) {
-					if ( !( index in props ) ) {
-						props[ index ] = value[ index ];
-						specialEasing[ index ] = easing;
-					}
-				}
-			} else {
-				specialEasing[ name ] = easing;
-			}
-		}
-	}
-
-	function Animation( elem, properties, options ) {
-		var result,
-			stopped,
-			index = 0,
-			length = Animation.prefilters.length,
-			deferred = jQuery.Deferred().always( function() {
-
-				// Don't match elem in the :animated selector
-				delete tick.elem;
-			} ),
-			tick = function() {
-				if ( stopped ) {
-					return false;
-				}
-				var currentTime = fxNow || createFxNow(),
-					remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
-
-					// Support: Android 2.3 only
-					// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
-					temp = remaining / animation.duration || 0,
-					percent = 1 - temp,
-					index = 0,
-					length = animation.tweens.length;
-
-				for ( ; index < length; index++ ) {
-					animation.tweens[ index ].run( percent );
-				}
-
-				deferred.notifyWith( elem, [ animation, percent, remaining ] );
-
-				// If there's more to do, yield
-				if ( percent < 1 && length ) {
-					return remaining;
-				}
-
-				// If this was an empty animation, synthesize a final progress notification
-				if ( !length ) {
-					deferred.notifyWith( elem, [ animation, 1, 0 ] );
-				}
-
-				// Resolve the animation and report its conclusion
-				deferred.resolveWith( elem, [ animation ] );
-				return false;
-			},
-			animation = deferred.promise( {
-				elem: elem,
-				props: jQuery.extend( {}, properties ),
-				opts: jQuery.extend( true, {
-					specialEasing: {},
-					easing: jQuery.easing._default
-				}, options ),
-				originalProperties: properties,
-				originalOptions: options,
-				startTime: fxNow || createFxNow(),
-				duration: options.duration,
-				tweens: [],
-				createTween: function( prop, end ) {
-					var tween = jQuery.Tween( elem, animation.opts, prop, end,
-							animation.opts.specialEasing[ prop ] || animation.opts.easing );
-					animation.tweens.push( tween );
-					return tween;
-				},
-				stop: function( gotoEnd ) {
-					var index = 0,
-
-						// If we are going to the end, we want to run all the tweens
-						// otherwise we skip this part
-						length = gotoEnd ? animation.tweens.length : 0;
-					if ( stopped ) {
-						return this;
-					}
-					stopped = true;
-					for ( ; index < length; index++ ) {
-						animation.tweens[ index ].run( 1 );
-					}
-
-					// Resolve when we played the last frame; otherwise, reject
-					if ( gotoEnd ) {
-						deferred.notifyWith( elem, [ animation, 1, 0 ] );
-						deferred.resolveWith( elem, [ animation, gotoEnd ] );
-					} else {
-						deferred.rejectWith( elem, [ animation, gotoEnd ] );
-					}
-					return this;
-				}
-			} ),
-			props = animation.props;
-
-		propFilter( props, animation.opts.specialEasing );
-
-		for ( ; index < length; index++ ) {
-			result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
-			if ( result ) {
-				if ( isFunction( result.stop ) ) {
-					jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
-						result.stop.bind( result );
-				}
-				return result;
-			}
-		}
-
-		jQuery.map( props, createTween, animation );
-
-		if ( isFunction( animation.opts.start ) ) {
-			animation.opts.start.call( elem, animation );
-		}
-
-		// Attach callbacks from options
-		animation
-			.progress( animation.opts.progress )
-			.done( animation.opts.done, animation.opts.complete )
-			.fail( animation.opts.fail )
-			.always( animation.opts.always );
-
-		jQuery.fx.timer(
-			jQuery.extend( tick, {
-				elem: elem,
-				anim: animation,
-				queue: animation.opts.queue
-			} )
-		);
-
-		return animation;
-	}
-
-	jQuery.Animation = jQuery.extend( Animation, {
-
-		tweeners: {
-			"*": [ function( prop, value ) {
-				var tween = this.createTween( prop, value );
-				adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
-				return tween;
-			} ]
-		},
-
-		tweener: function( props, callback ) {
-			if ( isFunction( props ) ) {
-				callback = props;
-				props = [ "*" ];
-			} else {
-				props = props.match( rnothtmlwhite );
-			}
-
-			var prop,
-				index = 0,
-				length = props.length;
-
-			for ( ; index < length; index++ ) {
-				prop = props[ index ];
-				Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
-				Animation.tweeners[ prop ].unshift( callback );
-			}
-		},
-
-		prefilters: [ defaultPrefilter ],
-
-		prefilter: function( callback, prepend ) {
-			if ( prepend ) {
-				Animation.prefilters.unshift( callback );
-			} else {
-				Animation.prefilters.push( callback );
-			}
-		}
-	} );
-
-	jQuery.speed = function( speed, easing, fn ) {
-		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
-			complete: fn || !fn && easing ||
-				isFunction( speed ) && speed,
-			duration: speed,
-			easing: fn && easing || easing && !isFunction( easing ) && easing
-		};
-
-		// Go to the end state if fx are off
-		if ( jQuery.fx.off ) {
-			opt.duration = 0;
-
-		} else {
-			if ( typeof opt.duration !== "number" ) {
-				if ( opt.duration in jQuery.fx.speeds ) {
-					opt.duration = jQuery.fx.speeds[ opt.duration ];
-
-				} else {
-					opt.duration = jQuery.fx.speeds._default;
-				}
-			}
-		}
-
-		// Normalize opt.queue - true/undefined/null -> "fx"
-		if ( opt.queue == null || opt.queue === true ) {
-			opt.queue = "fx";
-		}
-
-		// Queueing
-		opt.old = opt.complete;
-
-		opt.complete = function() {
-			if ( isFunction( opt.old ) ) {
-				opt.old.call( this );
-			}
-
-			if ( opt.queue ) {
-				jQuery.dequeue( this, opt.queue );
-			}
-		};
-
-		return opt;
-	};
-
-	jQuery.fn.extend( {
-		fadeTo: function( speed, to, easing, callback ) {
-
-			// Show any hidden elements after setting opacity to 0
-			return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
-
-				// Animate to the value specified
-				.end().animate( { opacity: to }, speed, easing, callback );
-		},
-		animate: function( prop, speed, easing, callback ) {
-			var empty = jQuery.isEmptyObject( prop ),
-				optall = jQuery.speed( speed, easing, callback ),
-				doAnimation = function() {
-
-					// Operate on a copy of prop so per-property easing won't be lost
-					var anim = Animation( this, jQuery.extend( {}, prop ), optall );
-
-					// Empty animations, or finishing resolves immediately
-					if ( empty || dataPriv.get( this, "finish" ) ) {
-						anim.stop( true );
-					}
-				};
-				doAnimation.finish = doAnimation;
-
-			return empty || optall.queue === false ?
-				this.each( doAnimation ) :
-				this.queue( optall.queue, doAnimation );
-		},
-		stop: function( type, clearQueue, gotoEnd ) {
-			var stopQueue = function( hooks ) {
-				var stop = hooks.stop;
-				delete hooks.stop;
-				stop( gotoEnd );
-			};
-
-			if ( typeof type !== "string" ) {
-				gotoEnd = clearQueue;
-				clearQueue = type;
-				type = undefined;
-			}
-			if ( clearQueue && type !== false ) {
-				this.queue( type || "fx", [] );
-			}
-
-			return this.each( function() {
-				var dequeue = true,
-					index = type != null && type + "queueHooks",
-					timers = jQuery.timers,
-					data = dataPriv.get( this );
-
-				if ( index ) {
-					if ( data[ index ] && data[ index ].stop ) {
-						stopQueue( data[ index ] );
-					}
-				} else {
-					for ( index in data ) {
-						if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
-							stopQueue( data[ index ] );
-						}
-					}
-				}
-
-				for ( index = timers.length; index--; ) {
-					if ( timers[ index ].elem === this &&
-						( type == null || timers[ index ].queue === type ) ) {
-
-						timers[ index ].anim.stop( gotoEnd );
-						dequeue = false;
-						timers.splice( index, 1 );
-					}
-				}
-
-				// Start the next in the queue if the last step wasn't forced.
-				// Timers currently will call their complete callbacks, which
-				// will dequeue but only if they were gotoEnd.
-				if ( dequeue || !gotoEnd ) {
-					jQuery.dequeue( this, type );
-				}
-			} );
-		},
-		finish: function( type ) {
-			if ( type !== false ) {
-				type = type || "fx";
-			}
-			return this.each( function() {
-				var index,
-					data = dataPriv.get( this ),
-					queue = data[ type + "queue" ],
-					hooks = data[ type + "queueHooks" ],
-					timers = jQuery.timers,
-					length = queue ? queue.length : 0;
-
-				// Enable finishing flag on private data
-				data.finish = true;
-
-				// Empty the queue first
-				jQuery.queue( this, type, [] );
-
-				if ( hooks && hooks.stop ) {
-					hooks.stop.call( this, true );
-				}
-
-				// Look for any active animations, and finish them
-				for ( index = timers.length; index--; ) {
-					if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
-						timers[ index ].anim.stop( true );
-						timers.splice( index, 1 );
-					}
-				}
-
-				// Look for any animations in the old queue and finish them
-				for ( index = 0; index < length; index++ ) {
-					if ( queue[ index ] && queue[ index ].finish ) {
-						queue[ index ].finish.call( this );
-					}
-				}
-
-				// Turn off finishing flag
-				delete data.finish;
-			} );
-		}
-	} );
-
-	jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
-		var cssFn = jQuery.fn[ name ];
-		jQuery.fn[ name ] = function( speed, easing, callback ) {
-			return speed == null || typeof speed === "boolean" ?
-				cssFn.apply( this, arguments ) :
-				this.animate( genFx( name, true ), speed, easing, callback );
-		};
-	} );
-
-	// Generate shortcuts for custom animations
-	jQuery.each( {
-		slideDown: genFx( "show" ),
-		slideUp: genFx( "hide" ),
-		slideToggle: genFx( "toggle" ),
-		fadeIn: { opacity: "show" },
-		fadeOut: { opacity: "hide" },
-		fadeToggle: { opacity: "toggle" }
-	}, function( name, props ) {
-		jQuery.fn[ name ] = function( speed, easing, callback ) {
-			return this.animate( props, speed, easing, callback );
-		};
-	} );
-
-	jQuery.timers = [];
-	jQuery.fx.tick = function() {
-		var timer,
-			i = 0,
-			timers = jQuery.timers;
-
-		fxNow = Date.now();
-
-		for ( ; i < timers.length; i++ ) {
-			timer = timers[ i ];
-
-			// Run the timer and safely remove it when done (allowing for external removal)
-			if ( !timer() && timers[ i ] === timer ) {
-				timers.splice( i--, 1 );
-			}
-		}
-
-		if ( !timers.length ) {
-			jQuery.fx.stop();
-		}
-		fxNow = undefined;
-	};
-
-	jQuery.fx.timer = function( timer ) {
-		jQuery.timers.push( timer );
-		jQuery.fx.start();
-	};
-
-	jQuery.fx.interval = 13;
-	jQuery.fx.start = function() {
-		if ( inProgress ) {
-			return;
-		}
-
-		inProgress = true;
-		schedule();
-	};
-
-	jQuery.fx.stop = function() {
-		inProgress = null;
-	};
-
-	jQuery.fx.speeds = {
-		slow: 600,
-		fast: 200,
-
-		// Default speed
-		_default: 400
-	};
-
-
-	// Based off of the plugin by Clint Helfers, with permission.
-	// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
-	jQuery.fn.delay = function( time, type ) {
-		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
-		type = type || "fx";
-
-		return this.queue( type, function( next, hooks ) {
-			var timeout = window.setTimeout( next, time );
-			hooks.stop = function() {
-				window.clearTimeout( timeout );
-			};
-		} );
-	};
-
-
-	( function() {
-		var input = document.createElement( "input" ),
-			select = document.createElement( "select" ),
-			opt = select.appendChild( document.createElement( "option" ) );
-
-		input.type = "checkbox";
-
-		// Support: Android <=4.3 only
-		// Default value for a checkbox should be "on"
-		support.checkOn = input.value !== "";
-
-		// Support: IE <=11 only
-		// Must access selectedIndex to make default options select
-		support.optSelected = opt.selected;
-
-		// Support: IE <=11 only
-		// An input loses its value after becoming a radio
-		input = document.createElement( "input" );
-		input.value = "t";
-		input.type = "radio";
-		support.radioValue = input.value === "t";
-	} )();
-
-
-	var boolHook,
-		attrHandle = jQuery.expr.attrHandle;
-
-	jQuery.fn.extend( {
-		attr: function( name, value ) {
-			return access( this, jQuery.attr, name, value, arguments.length > 1 );
-		},
-
-		removeAttr: function( name ) {
-			return this.each( function() {
-				jQuery.removeAttr( this, name );
-			} );
-		}
-	} );
-
-	jQuery.extend( {
-		attr: function( elem, name, value ) {
-			var ret, hooks,
-				nType = elem.nodeType;
-
-			// Don't get/set attributes on text, comment and attribute nodes
-			if ( nType === 3 || nType === 8 || nType === 2 ) {
-				return;
-			}
-
-			// Fallback to prop when attributes are not supported
-			if ( typeof elem.getAttribute === "undefined" ) {
-				return jQuery.prop( elem, name, value );
-			}
-
-			// Attribute hooks are determined by the lowercase version
-			// Grab necessary hook if one is defined
-			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
-				hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
-					( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
-			}
-
-			if ( value !== undefined ) {
-				if ( value === null ) {
-					jQuery.removeAttr( elem, name );
-					return;
-				}
-
-				if ( hooks && "set" in hooks &&
-					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
-					return ret;
-				}
-
-				elem.setAttribute( name, value + "" );
-				return value;
-			}
-
-			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
-				return ret;
-			}
-
-			ret = jQuery.find.attr( elem, name );
-
-			// Non-existent attributes return null, we normalize to undefined
-			return ret == null ? undefined : ret;
-		},
-
-		attrHooks: {
-			type: {
-				set: function( elem, value ) {
-					if ( !support.radioValue && value === "radio" &&
-						nodeName( elem, "input" ) ) {
-						var val = elem.value;
-						elem.setAttribute( "type", value );
-						if ( val ) {
-							elem.value = val;
-						}
-						return value;
-					}
-				}
-			}
-		},
-
-		removeAttr: function( elem, value ) {
-			var name,
-				i = 0,
-
-				// Attribute names can contain non-HTML whitespace characters
-				// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
-				attrNames = value && value.match( rnothtmlwhite );
-
-			if ( attrNames && elem.nodeType === 1 ) {
-				while ( ( name = attrNames[ i++ ] ) ) {
-					elem.removeAttribute( name );
-				}
-			}
-		}
-	} );
-
-	// Hooks for boolean attributes
-	boolHook = {
-		set: function( elem, value, name ) {
-			if ( value === false ) {
-
-				// Remove boolean attributes when set to false
-				jQuery.removeAttr( elem, name );
-			} else {
-				elem.setAttribute( name, name );
-			}
-			return name;
-		}
-	};
-
-	jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
-		var getter = attrHandle[ name ] || jQuery.find.attr;
-
-		attrHandle[ name ] = function( elem, name, isXML ) {
-			var ret, handle,
-				lowercaseName = name.toLowerCase();
-
-			if ( !isXML ) {
-
-				// Avoid an infinite loop by temporarily removing this function from the getter
-				handle = attrHandle[ lowercaseName ];
-				attrHandle[ lowercaseName ] = ret;
-				ret = getter( elem, name, isXML ) != null ?
-					lowercaseName :
-					null;
-				attrHandle[ lowercaseName ] = handle;
-			}
-			return ret;
-		};
-	} );
-
-
-
-
-	var rfocusable = /^(?:input|select|textarea|button)$/i,
-		rclickable = /^(?:a|area)$/i;
-
-	jQuery.fn.extend( {
-		prop: function( name, value ) {
-			return access( this, jQuery.prop, name, value, arguments.length > 1 );
-		},
-
-		removeProp: function( name ) {
-			return this.each( function() {
-				delete this[ jQuery.propFix[ name ] || name ];
-			} );
-		}
-	} );
-
-	jQuery.extend( {
-		prop: function( elem, name, value ) {
-			var ret, hooks,
-				nType = elem.nodeType;
-
-			// Don't get/set properties on text, comment and attribute nodes
-			if ( nType === 3 || nType === 8 || nType === 2 ) {
-				return;
-			}
-
-			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
-
-				// Fix name and attach hooks
-				name = jQuery.propFix[ name ] || name;
-				hooks = jQuery.propHooks[ name ];
-			}
-
-			if ( value !== undefined ) {
-				if ( hooks && "set" in hooks &&
-					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
-					return ret;
-				}
-
-				return ( elem[ name ] = value );
-			}
-
-			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
-				return ret;
-			}
-
-			return elem[ name ];
-		},
-
-		propHooks: {
-			tabIndex: {
-				get: function( elem ) {
-
-					// Support: IE <=9 - 11 only
-					// elem.tabIndex doesn't always return the
-					// correct value when it hasn't been explicitly set
-					// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
-					// Use proper attribute retrieval(#12072)
-					var tabindex = jQuery.find.attr( elem, "tabindex" );
-
-					if ( tabindex ) {
-						return parseInt( tabindex, 10 );
-					}
-
-					if (
-						rfocusable.test( elem.nodeName ) ||
-						rclickable.test( elem.nodeName ) &&
-						elem.href
-					) {
-						return 0;
-					}
-
-					return -1;
-				}
-			}
-		},
-
-		propFix: {
-			"for": "htmlFor",
-			"class": "className"
-		}
-	} );
-
-	// Support: IE <=11 only
-	// Accessing the selectedIndex property
-	// forces the browser to respect setting selected
-	// on the option
-	// The getter ensures a default option is selected
-	// when in an optgroup
-	// eslint rule "no-unused-expressions" is disabled for this code
-	// since it considers such accessions noop
-	if ( !support.optSelected ) {
-		jQuery.propHooks.selected = {
-			get: function( elem ) {
-
-				/* eslint no-unused-expressions: "off" */
-
-				var parent = elem.parentNode;
-				if ( parent && parent.parentNode ) {
-					parent.parentNode.selectedIndex;
-				}
-				return null;
-			},
-			set: function( elem ) {
-
-				/* eslint no-unused-expressions: "off" */
-
-				var parent = elem.parentNode;
-				if ( parent ) {
-					parent.selectedIndex;
-
-					if ( parent.parentNode ) {
-						parent.parentNode.selectedIndex;
-					}
-				}
-			}
-		};
-	}
-
-	jQuery.each( [
-		"tabIndex",
-		"readOnly",
-		"maxLength",
-		"cellSpacing",
-		"cellPadding",
-		"rowSpan",
-		"colSpan",
-		"useMap",
-		"frameBorder",
-		"contentEditable"
-	], function() {
-		jQuery.propFix[ this.toLowerCase() ] = this;
-	} );
-
-
-
-
-		// Strip and collapse whitespace according to HTML spec
-		// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
-		function stripAndCollapse( value ) {
-			var tokens = value.match( rnothtmlwhite ) || [];
-			return tokens.join( " " );
-		}
-
-
-	function getClass( elem ) {
-		return elem.getAttribute && elem.getAttribute( "class" ) || "";
-	}
-
-	function classesToArray( value ) {
-		if ( Array.isArray( value ) ) {
-			return value;
-		}
-		if ( typeof value === "string" ) {
-			return value.match( rnothtmlwhite ) || [];
-		}
-		return [];
-	}
-
-	jQuery.fn.extend( {
-		addClass: function( value ) {
-			var classes, elem, cur, curValue, clazz, j, finalValue,
-				i = 0;
-
-			if ( isFunction( value ) ) {
-				return this.each( function( j ) {
-					jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
-				} );
-			}
-
-			classes = classesToArray( value );
-
-			if ( classes.length ) {
-				while ( ( elem = this[ i++ ] ) ) {
-					curValue = getClass( elem );
-					cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
-
-					if ( cur ) {
-						j = 0;
-						while ( ( clazz = classes[ j++ ] ) ) {
-							if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
-								cur += clazz + " ";
-							}
-						}
-
-						// Only assign if different to avoid unneeded rendering.
-						finalValue = stripAndCollapse( cur );
-						if ( curValue !== finalValue ) {
-							elem.setAttribute( "class", finalValue );
-						}
-					}
-				}
-			}
-
-			return this;
-		},
-
-		removeClass: function( value ) {
-			var classes, elem, cur, curValue, clazz, j, finalValue,
-				i = 0;
-
-			if ( isFunction( value ) ) {
-				return this.each( function( j ) {
-					jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
-				} );
-			}
-
-			if ( !arguments.length ) {
-				return this.attr( "class", "" );
-			}
-
-			classes = classesToArray( value );
-
-			if ( classes.length ) {
-				while ( ( elem = this[ i++ ] ) ) {
-					curValue = getClass( elem );
-
-					// This expression is here for better compressibility (see addClass)
-					cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
-
-					if ( cur ) {
-						j = 0;
-						while ( ( clazz = classes[ j++ ] ) ) {
-
-							// Remove *all* instances
-							while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
-								cur = cur.replace( " " + clazz + " ", " " );
-							}
-						}
-
-						// Only assign if different to avoid unneeded rendering.
-						finalValue = stripAndCollapse( cur );
-						if ( curValue !== finalValue ) {
-							elem.setAttribute( "class", finalValue );
-						}
-					}
-				}
-			}
-
-			return this;
-		},
-
-		toggleClass: function( value, stateVal ) {
-			var type = typeof value,
-				isValidValue = type === "string" || Array.isArray( value );
-
-			if ( typeof stateVal === "boolean" && isValidValue ) {
-				return stateVal ? this.addClass( value ) : this.removeClass( value );
-			}
-
-			if ( isFunction( value ) ) {
-				return this.each( function( i ) {
-					jQuery( this ).toggleClass(
-						value.call( this, i, getClass( this ), stateVal ),
-						stateVal
-					);
-				} );
-			}
-
-			return this.each( function() {
-				var className, i, self, classNames;
-
-				if ( isValidValue ) {
-
-					// Toggle individual class names
-					i = 0;
-					self = jQuery( this );
-					classNames = classesToArray( value );
-
-					while ( ( className = classNames[ i++ ] ) ) {
-
-						// Check each className given, space separated list
-						if ( self.hasClass( className ) ) {
-							self.removeClass( className );
-						} else {
-							self.addClass( className );
-						}
-					}
-
-				// Toggle whole class name
-				} else if ( value === undefined || type === "boolean" ) {
-					className = getClass( this );
-					if ( className ) {
-
-						// Store className if set
-						dataPriv.set( this, "__className__", className );
-					}
-
-					// If the element has a class name or if we're passed `false`,
-					// then remove the whole classname (if there was one, the above saved it).
-					// Otherwise bring back whatever was previously saved (if anything),
-					// falling back to the empty string if nothing was stored.
-					if ( this.setAttribute ) {
-						this.setAttribute( "class",
-							className || value === false ?
-							"" :
-							dataPriv.get( this, "__className__" ) || ""
-						);
-					}
-				}
-			} );
-		},
-
-		hasClass: function( selector ) {
-			var className, elem,
-				i = 0;
-
-			className = " " + selector + " ";
-			while ( ( elem = this[ i++ ] ) ) {
-				if ( elem.nodeType === 1 &&
-					( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
-						return true;
-				}
-			}
-
-			return false;
-		}
-	} );
-
-
-
-
-	var rreturn = /\r/g;
-
-	jQuery.fn.extend( {
-		val: function( value ) {
-			var hooks, ret, valueIsFunction,
-				elem = this[ 0 ];
-
-			if ( !arguments.length ) {
-				if ( elem ) {
-					hooks = jQuery.valHooks[ elem.type ] ||
-						jQuery.valHooks[ elem.nodeName.toLowerCase() ];
-
-					if ( hooks &&
-						"get" in hooks &&
-						( ret = hooks.get( elem, "value" ) ) !== undefined
-					) {
-						return ret;
-					}
-
-					ret = elem.value;
-
-					// Handle most common string cases
-					if ( typeof ret === "string" ) {
-						return ret.replace( rreturn, "" );
-					}
-
-					// Handle cases where value is null/undef or number
-					return ret == null ? "" : ret;
-				}
-
-				return;
-			}
-
-			valueIsFunction = isFunction( value );
-
-			return this.each( function( i ) {
-				var val;
-
-				if ( this.nodeType !== 1 ) {
-					return;
-				}
-
-				if ( valueIsFunction ) {
-					val = value.call( this, i, jQuery( this ).val() );
-				} else {
-					val = value;
-				}
-
-				// Treat null/undefined as ""; convert numbers to string
-				if ( val == null ) {
-					val = "";
-
-				} else if ( typeof val === "number" ) {
-					val += "";
-
-				} else if ( Array.isArray( val ) ) {
-					val = jQuery.map( val, function( value ) {
-						return value == null ? "" : value + "";
-					} );
-				}
-
-				hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
-
-				// If set returns undefined, fall back to normal setting
-				if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
-					this.value = val;
-				}
-			} );
-		}
-	} );
-
-	jQuery.extend( {
-		valHooks: {
-			option: {
-				get: function( elem ) {
-
-					var val = jQuery.find.attr( elem, "value" );
-					return val != null ?
-						val :
-
-						// Support: IE <=10 - 11 only
-						// option.text throws exceptions (#14686, #14858)
-						// Strip and collapse whitespace
-						// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
-						stripAndCollapse( jQuery.text( elem ) );
-				}
-			},
-			select: {
-				get: function( elem ) {
-					var value, option, i,
-						options = elem.options,
-						index = elem.selectedIndex,
-						one = elem.type === "select-one",
-						values = one ? null : [],
-						max = one ? index + 1 : options.length;
-
-					if ( index < 0 ) {
-						i = max;
-
-					} else {
-						i = one ? index : 0;
-					}
-
-					// Loop through all the selected options
-					for ( ; i < max; i++ ) {
-						option = options[ i ];
-
-						// Support: IE <=9 only
-						// IE8-9 doesn't update selected after form reset (#2551)
-						if ( ( option.selected || i === index ) &&
-
-								// Don't return options that are disabled or in a disabled optgroup
-								!option.disabled &&
-								( !option.parentNode.disabled ||
-									!nodeName( option.parentNode, "optgroup" ) ) ) {
-
-							// Get the specific value for the option
-							value = jQuery( option ).val();
-
-							// We don't need an array for one selects
-							if ( one ) {
-								return value;
-							}
-
-							// Multi-Selects return an array
-							values.push( value );
-						}
-					}
-
-					return values;
-				},
-
-				set: function( elem, value ) {
-					var optionSet, option,
-						options = elem.options,
-						values = jQuery.makeArray( value ),
-						i = options.length;
-
-					while ( i-- ) {
-						option = options[ i ];
-
-						/* eslint-disable no-cond-assign */
-
-						if ( option.selected =
-							jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
-						) {
-							optionSet = true;
-						}
-
-						/* eslint-enable no-cond-assign */
-					}
-
-					// Force browsers to behave consistently when non-matching value is set
-					if ( !optionSet ) {
-						elem.selectedIndex = -1;
-					}
-					return values;
-				}
-			}
-		}
-	} );
-
-	// Radios and checkboxes getter/setter
-	jQuery.each( [ "radio", "checkbox" ], function() {
-		jQuery.valHooks[ this ] = {
-			set: function( elem, value ) {
-				if ( Array.isArray( value ) ) {
-					return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
-				}
-			}
-		};
-		if ( !support.checkOn ) {
-			jQuery.valHooks[ this ].get = function( elem ) {
-				return elem.getAttribute( "value" ) === null ? "on" : elem.value;
-			};
-		}
-	} );
-
-
-
-
-	// Return jQuery for attributes-only inclusion
-
-
-	support.focusin = "onfocusin" in window;
-
-
-	var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
-		stopPropagationCallback = function( e ) {
-			e.stopPropagation();
-		};
-
-	jQuery.extend( jQuery.event, {
-
-		trigger: function( event, data, elem, onlyHandlers ) {
-
-			var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
-				eventPath = [ elem || document ],
-				type = hasOwn.call( event, "type" ) ? event.type : event,
-				namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
-
-			cur = lastElement = tmp = elem = elem || document;
-
-			// Don't do events on text and comment nodes
-			if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
-				return;
-			}
-
-			// focus/blur morphs to focusin/out; ensure we're not firing them right now
-			if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
-				return;
-			}
-
-			if ( type.indexOf( "." ) > -1 ) {
-
-				// Namespaced trigger; create a regexp to match event type in handle()
-				namespaces = type.split( "." );
-				type = namespaces.shift();
-				namespaces.sort();
-			}
-			ontype = type.indexOf( ":" ) < 0 && "on" + type;
-
-			// Caller can pass in a jQuery.Event object, Object, or just an event type string
-			event = event[ jQuery.expando ] ?
-				event :
-				new jQuery.Event( type, typeof event === "object" && event );
-
-			// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
-			event.isTrigger = onlyHandlers ? 2 : 3;
-			event.namespace = namespaces.join( "." );
-			event.rnamespace = event.namespace ?
-				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
-				null;
-
-			// Clean up the event in case it is being reused
-			event.result = undefined;
-			if ( !event.target ) {
-				event.target = elem;
-			}
-
-			// Clone any incoming data and prepend the event, creating the handler arg list
-			data = data == null ?
-				[ event ] :
-				jQuery.makeArray( data, [ event ] );
-
-			// Allow special events to draw outside the lines
-			special = jQuery.event.special[ type ] || {};
-			if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
-				return;
-			}
-
-			// Determine event propagation path in advance, per W3C events spec (#9951)
-			// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
-			if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
-
-				bubbleType = special.delegateType || type;
-				if ( !rfocusMorph.test( bubbleType + type ) ) {
-					cur = cur.parentNode;
-				}
-				for ( ; cur; cur = cur.parentNode ) {
-					eventPath.push( cur );
-					tmp = cur;
-				}
-
-				// Only add window if we got to document (e.g., not plain obj or detached DOM)
-				if ( tmp === ( elem.ownerDocument || document ) ) {
-					eventPath.push( tmp.defaultView || tmp.parentWindow || window );
-				}
-			}
-
-			// Fire handlers on the event path
-			i = 0;
-			while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
-				lastElement = cur;
-				event.type = i > 1 ?
-					bubbleType :
-					special.bindType || type;
-
-				// jQuery handler
-				handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
-					dataPriv.get( cur, "handle" );
-				if ( handle ) {
-					handle.apply( cur, data );
-				}
-
-				// Native handler
-				handle = ontype && cur[ ontype ];
-				if ( handle && handle.apply && acceptData( cur ) ) {
-					event.result = handle.apply( cur, data );
-					if ( event.result === false ) {
-						event.preventDefault();
-					}
-				}
-			}
-			event.type = type;
-
-			// If nobody prevented the default action, do it now
-			if ( !onlyHandlers && !event.isDefaultPrevented() ) {
-
-				if ( ( !special._default ||
-					special._default.apply( eventPath.pop(), data ) === false ) &&
-					acceptData( elem ) ) {
-
-					// Call a native DOM method on the target with the same name as the event.
-					// Don't do default actions on window, that's where global variables be (#6170)
-					if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
-
-						// Don't re-trigger an onFOO event when we call its FOO() method
-						tmp = elem[ ontype ];
-
-						if ( tmp ) {
-							elem[ ontype ] = null;
-						}
-
-						// Prevent re-triggering of the same event, since we already bubbled it above
-						jQuery.event.triggered = type;
-
-						if ( event.isPropagationStopped() ) {
-							lastElement.addEventListener( type, stopPropagationCallback );
-						}
-
-						elem[ type ]();
-
-						if ( event.isPropagationStopped() ) {
-							lastElement.removeEventListener( type, stopPropagationCallback );
-						}
-
-						jQuery.event.triggered = undefined;
-
-						if ( tmp ) {
-							elem[ ontype ] = tmp;
-						}
-					}
-				}
-			}
-
-			return event.result;
-		},
-
-		// Piggyback on a donor event to simulate a different one
-		// Used only for `focus(in | out)` events
-		simulate: function( type, elem, event ) {
-			var e = jQuery.extend(
-				new jQuery.Event(),
-				event,
-				{
-					type: type,
-					isSimulated: true
-				}
-			);
-
-			jQuery.event.trigger( e, null, elem );
-		}
-
-	} );
-
-	jQuery.fn.extend( {
-
-		trigger: function( type, data ) {
-			return this.each( function() {
-				jQuery.event.trigger( type, data, this );
-			} );
-		},
-		triggerHandler: function( type, data ) {
-			var elem = this[ 0 ];
-			if ( elem ) {
-				return jQuery.event.trigger( type, data, elem, true );
-			}
-		}
-	} );
-
-
-	// Support: Firefox <=44
-	// Firefox doesn't have focus(in | out) events
-	// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
-	//
-	// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
-	// focus(in | out) events fire after focus & blur events,
-	// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
-	// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
-	if ( !support.focusin ) {
-		jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
-
-			// Attach a single capturing handler on the document while someone wants focusin/focusout
-			var handler = function( event ) {
-				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
-			};
-
-			jQuery.event.special[ fix ] = {
-				setup: function() {
-					var doc = this.ownerDocument || this,
-						attaches = dataPriv.access( doc, fix );
-
-					if ( !attaches ) {
-						doc.addEventListener( orig, handler, true );
-					}
-					dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
-				},
-				teardown: function() {
-					var doc = this.ownerDocument || this,
-						attaches = dataPriv.access( doc, fix ) - 1;
-
-					if ( !attaches ) {
-						doc.removeEventListener( orig, handler, true );
-						dataPriv.remove( doc, fix );
-
-					} else {
-						dataPriv.access( doc, fix, attaches );
-					}
-				}
-			};
-		} );
-	}
-	var location = window.location;
-
-	var nonce = Date.now();
-
-	var rquery = ( /\?/ );
-
-
-
-	// Cross-browser xml parsing
-	jQuery.parseXML = function( data ) {
-		var xml;
-		if ( !data || typeof data !== "string" ) {
-			return null;
-		}
-
-		// Support: IE 9 - 11 only
-		// IE throws on parseFromString with invalid input.
-		try {
-			xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
-		} catch ( e ) {
-			xml = undefined;
-		}
-
-		if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
-			jQuery.error( "Invalid XML: " + data );
-		}
-		return xml;
-	};
-
-
-	var
-		rbracket = /\[\]$/,
-		rCRLF = /\r?\n/g,
-		rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
-		rsubmittable = /^(?:input|select|textarea|keygen)/i;
-
-	function buildParams( prefix, obj, traditional, add ) {
-		var name;
-
-		if ( Array.isArray( obj ) ) {
-
-			// Serialize array item.
-			jQuery.each( obj, function( i, v ) {
-				if ( traditional || rbracket.test( prefix ) ) {
-
-					// Treat each array item as a scalar.
-					add( prefix, v );
-
-				} else {
-
-					// Item is non-scalar (array or object), encode its numeric index.
-					buildParams(
-						prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
-						v,
-						traditional,
-						add
-					);
-				}
-			} );
-
-		} else if ( !traditional && toType( obj ) === "object" ) {
-
-			// Serialize object item.
-			for ( name in obj ) {
-				buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
-			}
-
-		} else {
-
-			// Serialize scalar item.
-			add( prefix, obj );
-		}
-	}
-
-	// Serialize an array of form elements or a set of
-	// key/values into a query string
-	jQuery.param = function( a, traditional ) {
-		var prefix,
-			s = [],
-			add = function( key, valueOrFunction ) {
-
-				// If value is a function, invoke it and use its return value
-				var value = isFunction( valueOrFunction ) ?
-					valueOrFunction() :
-					valueOrFunction;
-
-				s[ s.length ] = encodeURIComponent( key ) + "=" +
-					encodeURIComponent( value == null ? "" : value );
-			};
-
-		// If an array was passed in, assume that it is an array of form elements.
-		if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
-
-			// Serialize the form elements
-			jQuery.each( a, function() {
-				add( this.name, this.value );
-			} );
-
-		} else {
-
-			// If traditional, encode the "old" way (the way 1.3.2 or older
-			// did it), otherwise encode params recursively.
-			for ( prefix in a ) {
-				buildParams( prefix, a[ prefix ], traditional, add );
-			}
-		}
-
-		// Return the resulting serialization
-		return s.join( "&" );
-	};
-
-	jQuery.fn.extend( {
-		serialize: function() {
-			return jQuery.param( this.serializeArray() );
-		},
-		serializeArray: function() {
-			return this.map( function() {
-
-				// Can add propHook for "elements" to filter or add form elements
-				var elements = jQuery.prop( this, "elements" );
-				return elements ? jQuery.makeArray( elements ) : this;
-			} )
-			.filter( function() {
-				var type = this.type;
-
-				// Use .is( ":disabled" ) so that fieldset[disabled] works
-				return this.name && !jQuery( this ).is( ":disabled" ) &&
-					rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
-					( this.checked || !rcheckableType.test( type ) );
-			} )
-			.map( function( i, elem ) {
-				var val = jQuery( this ).val();
-
-				if ( val == null ) {
-					return null;
-				}
-
-				if ( Array.isArray( val ) ) {
-					return jQuery.map( val, function( val ) {
-						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-					} );
-				}
-
-				return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-			} ).get();
-		}
-	} );
-
-
-	var
-		r20 = /%20/g,
-		rhash = /#.*$/,
-		rantiCache = /([?&])_=[^&]*/,
-		rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
-
-		// #7653, #8125, #8152: local protocol detection
-		rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
-		rnoContent = /^(?:GET|HEAD)$/,
-		rprotocol = /^\/\//,
-
-		/* Prefilters
-		 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
-		 * 2) These are called:
-		 *    - BEFORE asking for a transport
-		 *    - AFTER param serialization (s.data is a string if s.processData is true)
-		 * 3) key is the dataType
-		 * 4) the catchall symbol "*" can be used
-		 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
-		 */
-		prefilters = {},
-
-		/* Transports bindings
-		 * 1) key is the dataType
-		 * 2) the catchall symbol "*" can be used
-		 * 3) selection will start with transport dataType and THEN go to "*" if needed
-		 */
-		transports = {},
-
-		// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
-		allTypes = "*/".concat( "*" ),
-
-		// Anchor tag for parsing the document origin
-		originAnchor = document.createElement( "a" );
-		originAnchor.href = location.href;
-
-	// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
-	function addToPrefiltersOrTransports( structure ) {
-
-		// dataTypeExpression is optional and defaults to "*"
-		return function( dataTypeExpression, func ) {
-
-			if ( typeof dataTypeExpression !== "string" ) {
-				func = dataTypeExpression;
-				dataTypeExpression = "*";
-			}
-
-			var dataType,
-				i = 0,
-				dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
-
-			if ( isFunction( func ) ) {
-
-				// For each dataType in the dataTypeExpression
-				while ( ( dataType = dataTypes[ i++ ] ) ) {
-
-					// Prepend if requested
-					if ( dataType[ 0 ] === "+" ) {
-						dataType = dataType.slice( 1 ) || "*";
-						( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
-
-					// Otherwise append
-					} else {
-						( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
-					}
-				}
-			}
-		};
-	}
-
-	// Base inspection function for prefilters and transports
-	function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
-
-		var inspected = {},
-			seekingTransport = ( structure === transports );
-
-		function inspect( dataType ) {
-			var selected;
-			inspected[ dataType ] = true;
-			jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
-				var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
-				if ( typeof dataTypeOrTransport === "string" &&
-					!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
-
-					options.dataTypes.unshift( dataTypeOrTransport );
-					inspect( dataTypeOrTransport );
-					return false;
-				} else if ( seekingTransport ) {
-					return !( selected = dataTypeOrTransport );
-				}
-			} );
-			return selected;
-		}
-
-		return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
-	}
-
-	// A special extend for ajax options
-	// that takes "flat" options (not to be deep extended)
-	// Fixes #9887
-	function ajaxExtend( target, src ) {
-		var key, deep,
-			flatOptions = jQuery.ajaxSettings.flatOptions || {};
-
-		for ( key in src ) {
-			if ( src[ key ] !== undefined ) {
-				( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
-			}
-		}
-		if ( deep ) {
-			jQuery.extend( true, target, deep );
-		}
-
-		return target;
-	}
-
-	/* Handles responses to an ajax request:
-	 * - finds the right dataType (mediates between content-type and expected dataType)
-	 * - returns the corresponding response
-	 */
-	function ajaxHandleResponses( s, jqXHR, responses ) {
-
-		var ct, type, finalDataType, firstDataType,
-			contents = s.contents,
-			dataTypes = s.dataTypes;
-
-		// Remove auto dataType and get content-type in the process
-		while ( dataTypes[ 0 ] === "*" ) {
-			dataTypes.shift();
-			if ( ct === undefined ) {
-				ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
-			}
-		}
-
-		// Check if we're dealing with a known content-type
-		if ( ct ) {
-			for ( type in contents ) {
-				if ( contents[ type ] && contents[ type ].test( ct ) ) {
-					dataTypes.unshift( type );
-					break;
-				}
-			}
-		}
-
-		// Check to see if we have a response for the expected dataType
-		if ( dataTypes[ 0 ] in responses ) {
-			finalDataType = dataTypes[ 0 ];
-		} else {
-
-			// Try convertible dataTypes
-			for ( type in responses ) {
-				if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
-					finalDataType = type;
-					break;
-				}
-				if ( !firstDataType ) {
-					firstDataType = type;
-				}
-			}
-
-			// Or just use first one
-			finalDataType = finalDataType || firstDataType;
-		}
-
-		// If we found a dataType
-		// We add the dataType to the list if needed
-		// and return the corresponding response
-		if ( finalDataType ) {
-			if ( finalDataType !== dataTypes[ 0 ] ) {
-				dataTypes.unshift( finalDataType );
-			}
-			return responses[ finalDataType ];
-		}
-	}
-
-	/* Chain conversions given the request and the original response
-	 * Also sets the responseXXX fields on the jqXHR instance
-	 */
-	function ajaxConvert( s, response, jqXHR, isSuccess ) {
-		var conv2, current, conv, tmp, prev,
-			converters = {},
-
-			// Work with a copy of dataTypes in case we need to modify it for conversion
-			dataTypes = s.dataTypes.slice();
-
-		// Create converters map with lowercased keys
-		if ( dataTypes[ 1 ] ) {
-			for ( conv in s.converters ) {
-				converters[ conv.toLowerCase() ] = s.converters[ conv ];
-			}
-		}
-
-		current = dataTypes.shift();
-
-		// Convert to each sequential dataType
-		while ( current ) {
-
-			if ( s.responseFields[ current ] ) {
-				jqXHR[ s.responseFields[ current ] ] = response;
-			}
-
-			// Apply the dataFilter if provided
-			if ( !prev && isSuccess && s.dataFilter ) {
-				response = s.dataFilter( response, s.dataType );
-			}
-
-			prev = current;
-			current = dataTypes.shift();
-
-			if ( current ) {
-
-				// There's only work to do if current dataType is non-auto
-				if ( current === "*" ) {
-
-					current = prev;
-
-				// Convert response if prev dataType is non-auto and differs from current
-				} else if ( prev !== "*" && prev !== current ) {
-
-					// Seek a direct converter
-					conv = converters[ prev + " " + current ] || converters[ "* " + current ];
-
-					// If none found, seek a pair
-					if ( !conv ) {
-						for ( conv2 in converters ) {
-
-							// If conv2 outputs current
-							tmp = conv2.split( " " );
-							if ( tmp[ 1 ] === current ) {
-
-								// If prev can be converted to accepted input
-								conv = converters[ prev + " " + tmp[ 0 ] ] ||
-									converters[ "* " + tmp[ 0 ] ];
-								if ( conv ) {
-
-									// Condense equivalence converters
-									if ( conv === true ) {
-										conv = converters[ conv2 ];
-
-									// Otherwise, insert the intermediate dataType
-									} else if ( converters[ conv2 ] !== true ) {
-										current = tmp[ 0 ];
-										dataTypes.unshift( tmp[ 1 ] );
-									}
-									break;
-								}
-							}
-						}
-					}
-
-					// Apply converter (if not an equivalence)
-					if ( conv !== true ) {
-
-						// Unless errors are allowed to bubble, catch and return them
-						if ( conv && s.throws ) {
-							response = conv( response );
-						} else {
-							try {
-								response = conv( response );
-							} catch ( e ) {
-								return {
-									state: "parsererror",
-									error: conv ? e : "No conversion from " + prev + " to " + current
-								};
-							}
-						}
-					}
-				}
-			}
-		}
-
-		return { state: "success", data: response };
-	}
-
-	jQuery.extend( {
-
-		// Counter for holding the number of active queries
-		active: 0,
-
-		// Last-Modified header cache for next request
-		lastModified: {},
-		etag: {},
-
-		ajaxSettings: {
-			url: location.href,
-			type: "GET",
-			isLocal: rlocalProtocol.test( location.protocol ),
-			global: true,
-			processData: true,
-			async: true,
-			contentType: "application/x-www-form-urlencoded; charset=UTF-8",
-
-			/*
-			timeout: 0,
-			data: null,
-			dataType: null,
-			username: null,
-			password: null,
-			cache: null,
-			throws: false,
-			traditional: false,
-			headers: {},
-			*/
-
-			accepts: {
-				"*": allTypes,
-				text: "text/plain",
-				html: "text/html",
-				xml: "application/xml, text/xml",
-				json: "application/json, text/javascript"
-			},
-
-			contents: {
-				xml: /\bxml\b/,
-				html: /\bhtml/,
-				json: /\bjson\b/
-			},
-
-			responseFields: {
-				xml: "responseXML",
-				text: "responseText",
-				json: "responseJSON"
-			},
-
-			// Data converters
-			// Keys separate source (or catchall "*") and destination types with a single space
-			converters: {
-
-				// Convert anything to text
-				"* text": String,
-
-				// Text to html (true = no transformation)
-				"text html": true,
-
-				// Evaluate text as a json expression
-				"text json": JSON.parse,
-
-				// Parse text as xml
-				"text xml": jQuery.parseXML
-			},
-
-			// For options that shouldn't be deep extended:
-			// you can add your own custom options here if
-			// and when you create one that shouldn't be
-			// deep extended (see ajaxExtend)
-			flatOptions: {
-				url: true,
-				context: true
-			}
-		},
-
-		// Creates a full fledged settings object into target
-		// with both ajaxSettings and settings fields.
-		// If target is omitted, writes into ajaxSettings.
-		ajaxSetup: function( target, settings ) {
-			return settings ?
-
-				// Building a settings object
-				ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
-
-				// Extending ajaxSettings
-				ajaxExtend( jQuery.ajaxSettings, target );
-		},
-
-		ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
-		ajaxTransport: addToPrefiltersOrTransports( transports ),
-
-		// Main method
-		ajax: function( url, options ) {
-
-			// If url is an object, simulate pre-1.5 signature
-			if ( typeof url === "object" ) {
-				options = url;
-				url = undefined;
-			}
-
-			// Force options to be an object
-			options = options || {};
-
-			var transport,
-
-				// URL without anti-cache param
-				cacheURL,
-
-				// Response headers
-				responseHeadersString,
-				responseHeaders,
-
-				// timeout handle
-				timeoutTimer,
-
-				// Url cleanup var
-				urlAnchor,
-
-				// Request state (becomes false upon send and true upon completion)
-				completed,
-
-				// To know if global events are to be dispatched
-				fireGlobals,
-
-				// Loop variable
-				i,
-
-				// uncached part of the url
-				uncached,
-
-				// Create the final options object
-				s = jQuery.ajaxSetup( {}, options ),
-
-				// Callbacks context
-				callbackContext = s.context || s,
-
-				// Context for global events is callbackContext if it is a DOM node or jQuery collection
-				globalEventContext = s.context &&
-					( callbackContext.nodeType || callbackContext.jquery ) ?
-						jQuery( callbackContext ) :
-						jQuery.event,
-
-				// Deferreds
-				deferred = jQuery.Deferred(),
-				completeDeferred = jQuery.Callbacks( "once memory" ),
-
-				// Status-dependent callbacks
-				statusCode = s.statusCode || {},
-
-				// Headers (they are sent all at once)
-				requestHeaders = {},
-				requestHeadersNames = {},
-
-				// Default abort message
-				strAbort = "canceled",
-
-				// Fake xhr
-				jqXHR = {
-					readyState: 0,
-
-					// Builds headers hashtable if needed
-					getResponseHeader: function( key ) {
-						var match;
-						if ( completed ) {
-							if ( !responseHeaders ) {
-								responseHeaders = {};
-								while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
-									responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
-								}
-							}
-							match = responseHeaders[ key.toLowerCase() ];
-						}
-						return match == null ? null : match;
-					},
-
-					// Raw string
-					getAllResponseHeaders: function() {
-						return completed ? responseHeadersString : null;
-					},
-
-					// Caches the header
-					setRequestHeader: function( name, value ) {
-						if ( completed == null ) {
-							name = requestHeadersNames[ name.toLowerCase() ] =
-								requestHeadersNames[ name.toLowerCase() ] || name;
-							requestHeaders[ name ] = value;
-						}
-						return this;
-					},
-
-					// Overrides response content-type header
-					overrideMimeType: function( type ) {
-						if ( completed == null ) {
-							s.mimeType = type;
-						}
-						return this;
-					},
-
-					// Status-dependent callbacks
-					statusCode: function( map ) {
-						var code;
-						if ( map ) {
-							if ( completed ) {
-
-								// Execute the appropriate callbacks
-								jqXHR.always( map[ jqXHR.status ] );
-							} else {
-
-								// Lazy-add the new callbacks in a way that preserves old ones
-								for ( code in map ) {
-									statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
-								}
-							}
-						}
-						return this;
-					},
-
-					// Cancel the request
-					abort: function( statusText ) {
-						var finalText = statusText || strAbort;
-						if ( transport ) {
-							transport.abort( finalText );
-						}
-						done( 0, finalText );
-						return this;
-					}
-				};
-
-			// Attach deferreds
-			deferred.promise( jqXHR );
-
-			// Add protocol if not provided (prefilters might expect it)
-			// Handle falsy url in the settings object (#10093: consistency with old signature)
-			// We also use the url parameter if available
-			s.url = ( ( url || s.url || location.href ) + "" )
-				.replace( rprotocol, location.protocol + "//" );
-
-			// Alias method option to type as per ticket #12004
-			s.type = options.method || options.type || s.method || s.type;
-
-			// Extract dataTypes list
-			s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
-
-			// A cross-domain request is in order when the origin doesn't match the current origin.
-			if ( s.crossDomain == null ) {
-				urlAnchor = document.createElement( "a" );
-
-				// Support: IE <=8 - 11, Edge 12 - 15
-				// IE throws exception on accessing the href property if url is malformed,
-				// e.g. http://example.com:80x/
-				try {
-					urlAnchor.href = s.url;
-
-					// Support: IE <=8 - 11 only
-					// Anchor's host property isn't correctly set when s.url is relative
-					urlAnchor.href = urlAnchor.href;
-					s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
-						urlAnchor.protocol + "//" + urlAnchor.host;
-				} catch ( e ) {
-
-					// If there is an error parsing the URL, assume it is crossDomain,
-					// it can be rejected by the transport if it is invalid
-					s.crossDomain = true;
-				}
-			}
-
-			// Convert data if not already a string
-			if ( s.data && s.processData && typeof s.data !== "string" ) {
-				s.data = jQuery.param( s.data, s.traditional );
-			}
-
-			// Apply prefilters
-			inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
-
-			// If request was aborted inside a prefilter, stop there
-			if ( completed ) {
-				return jqXHR;
-			}
-
-			// We can fire global events as of now if asked to
-			// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
-			fireGlobals = jQuery.event && s.global;
-
-			// Watch for a new set of requests
-			if ( fireGlobals && jQuery.active++ === 0 ) {
-				jQuery.event.trigger( "ajaxStart" );
-			}
-
-			// Uppercase the type
-			s.type = s.type.toUpperCase();
-
-			// Determine if request has content
-			s.hasContent = !rnoContent.test( s.type );
-
-			// Save the URL in case we're toying with the If-Modified-Since
-			// and/or If-None-Match header later on
-			// Remove hash to simplify url manipulation
-			cacheURL = s.url.replace( rhash, "" );
-
-			// More options handling for requests with no content
-			if ( !s.hasContent ) {
-
-				// Remember the hash so we can put it back
-				uncached = s.url.slice( cacheURL.length );
-
-				// If data is available and should be processed, append data to url
-				if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
-					cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
-
-					// #9682: remove data so that it's not used in an eventual retry
-					delete s.data;
-				}
-
-				// Add or update anti-cache param if needed
-				if ( s.cache === false ) {
-					cacheURL = cacheURL.replace( rantiCache, "$1" );
-					uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
-				}
-
-				// Put hash and anti-cache on the URL that will be requested (gh-1732)
-				s.url = cacheURL + uncached;
-
-			// Change '%20' to '+' if this is encoded form body content (gh-2658)
-			} else if ( s.data && s.processData &&
-				( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
-				s.data = s.data.replace( r20, "+" );
-			}
-
-			// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
-			if ( s.ifModified ) {
-				if ( jQuery.lastModified[ cacheURL ] ) {
-					jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
-				}
-				if ( jQuery.etag[ cacheURL ] ) {
-					jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
-				}
-			}
-
-			// Set the correct header, if data is being sent
-			if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
-				jqXHR.setRequestHeader( "Content-Type", s.contentType );
-			}
-
-			// Set the Accepts header for the server, depending on the dataType
-			jqXHR.setRequestHeader(
-				"Accept",
-				s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
-					s.accepts[ s.dataTypes[ 0 ] ] +
-						( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
-					s.accepts[ "*" ]
-			);
-
-			// Check for headers option
-			for ( i in s.headers ) {
-				jqXHR.setRequestHeader( i, s.headers[ i ] );
-			}
-
-			// Allow custom headers/mimetypes and early abort
-			if ( s.beforeSend &&
-				( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
-
-				// Abort if not done already and return
-				return jqXHR.abort();
-			}
-
-			// Aborting is no longer a cancellation
-			strAbort = "abort";
-
-			// Install callbacks on deferreds
-			completeDeferred.add( s.complete );
-			jqXHR.done( s.success );
-			jqXHR.fail( s.error );
-
-			// Get transport
-			transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
-
-			// If no transport, we auto-abort
-			if ( !transport ) {
-				done( -1, "No Transport" );
-			} else {
-				jqXHR.readyState = 1;
-
-				// Send global event
-				if ( fireGlobals ) {
-					globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
-				}
-
-				// If request was aborted inside ajaxSend, stop there
-				if ( completed ) {
-					return jqXHR;
-				}
-
-				// Timeout
-				if ( s.async && s.timeout > 0 ) {
-					timeoutTimer = window.setTimeout( function() {
-						jqXHR.abort( "timeout" );
-					}, s.timeout );
-				}
-
-				try {
-					completed = false;
-					transport.send( requestHeaders, done );
-				} catch ( e ) {
-
-					// Rethrow post-completion exceptions
-					if ( completed ) {
-						throw e;
-					}
-
-					// Propagate others as results
-					done( -1, e );
-				}
-			}
-
-			// Callback for when everything is done
-			function done( status, nativeStatusText, responses, headers ) {
-				var isSuccess, success, error, response, modified,
-					statusText = nativeStatusText;
-
-				// Ignore repeat invocations
-				if ( completed ) {
-					return;
-				}
-
-				completed = true;
-
-				// Clear timeout if it exists
-				if ( timeoutTimer ) {
-					window.clearTimeout( timeoutTimer );
-				}
-
-				// Dereference transport for early garbage collection
-				// (no matter how long the jqXHR object will be used)
-				transport = undefined;
-
-				// Cache response headers
-				responseHeadersString = headers || "";
-
-				// Set readyState
-				jqXHR.readyState = status > 0 ? 4 : 0;
-
-				// Determine if successful
-				isSuccess = status >= 200 && status < 300 || status === 304;
-
-				// Get response data
-				if ( responses ) {
-					response = ajaxHandleResponses( s, jqXHR, responses );
-				}
-
-				// Convert no matter what (that way responseXXX fields are always set)
-				response = ajaxConvert( s, response, jqXHR, isSuccess );
-
-				// If successful, handle type chaining
-				if ( isSuccess ) {
-
-					// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
-					if ( s.ifModified ) {
-						modified = jqXHR.getResponseHeader( "Last-Modified" );
-						if ( modified ) {
-							jQuery.lastModified[ cacheURL ] = modified;
-						}
-						modified = jqXHR.getResponseHeader( "etag" );
-						if ( modified ) {
-							jQuery.etag[ cacheURL ] = modified;
-						}
-					}
-
-					// if no content
-					if ( status === 204 || s.type === "HEAD" ) {
-						statusText = "nocontent";
-
-					// if not modified
-					} else if ( status === 304 ) {
-						statusText = "notmodified";
-
-					// If we have data, let's convert it
-					} else {
-						statusText = response.state;
-						success = response.data;
-						error = response.error;
-						isSuccess = !error;
-					}
-				} else {
-
-					// Extract error from statusText and normalize for non-aborts
-					error = statusText;
-					if ( status || !statusText ) {
-						statusText = "error";
-						if ( status < 0 ) {
-							status = 0;
-						}
-					}
-				}
-
-				// Set data for the fake xhr object
-				jqXHR.status = status;
-				jqXHR.statusText = ( nativeStatusText || statusText ) + "";
-
-				// Success/Error
-				if ( isSuccess ) {
-					deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
-				} else {
-					deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
-				}
-
-				// Status-dependent callbacks
-				jqXHR.statusCode( statusCode );
-				statusCode = undefined;
-
-				if ( fireGlobals ) {
-					globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
-						[ jqXHR, s, isSuccess ? success : error ] );
-				}
-
-				// Complete
-				completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
-
-				if ( fireGlobals ) {
-					globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
-
-					// Handle the global AJAX counter
-					if ( !( --jQuery.active ) ) {
-						jQuery.event.trigger( "ajaxStop" );
-					}
-				}
-			}
-
-			return jqXHR;
-		},
-
-		getJSON: function( url, data, callback ) {
-			return jQuery.get( url, data, callback, "json" );
-		},
-
-		getScript: function( url, callback ) {
-			return jQuery.get( url, undefined, callback, "script" );
-		}
-	} );
-
-	jQuery.each( [ "get", "post" ], function( i, method ) {
-		jQuery[ method ] = function( url, data, callback, type ) {
-
-			// Shift arguments if data argument was omitted
-			if ( isFunction( data ) ) {
-				type = type || callback;
-				callback = data;
-				data = undefined;
-			}
-
-			// The url can be an options object (which then must have .url)
-			return jQuery.ajax( jQuery.extend( {
-				url: url,
-				type: method,
-				dataType: type,
-				data: data,
-				success: callback
-			}, jQuery.isPlainObject( url ) && url ) );
-		};
-	} );
-
-
-	jQuery._evalUrl = function( url ) {
-		return jQuery.ajax( {
-			url: url,
-
-			// Make this explicit, since user can override this through ajaxSetup (#11264)
-			type: "GET",
-			dataType: "script",
-			cache: true,
-			async: false,
-			global: false,
-			"throws": true
-		} );
-	};
-
-
-	jQuery.fn.extend( {
-		wrapAll: function( html ) {
-			var wrap;
-
-			if ( this[ 0 ] ) {
-				if ( isFunction( html ) ) {
-					html = html.call( this[ 0 ] );
-				}
-
-				// The elements to wrap the target around
-				wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
-
-				if ( this[ 0 ].parentNode ) {
-					wrap.insertBefore( this[ 0 ] );
-				}
-
-				wrap.map( function() {
-					var elem = this;
-
-					while ( elem.firstElementChild ) {
-						elem = elem.firstElementChild;
-					}
-
-					return elem;
-				} ).append( this );
-			}
-
-			return this;
-		},
-
-		wrapInner: function( html ) {
-			if ( isFunction( html ) ) {
-				return this.each( function( i ) {
-					jQuery( this ).wrapInner( html.call( this, i ) );
-				} );
-			}
-
-			return this.each( function() {
-				var self = jQuery( this ),
-					contents = self.contents();
-
-				if ( contents.length ) {
-					contents.wrapAll( html );
-
-				} else {
-					self.append( html );
-				}
-			} );
-		},
-
-		wrap: function( html ) {
-			var htmlIsFunction = isFunction( html );
-
-			return this.each( function( i ) {
-				jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
-			} );
-		},
-
-		unwrap: function( selector ) {
-			this.parent( selector ).not( "body" ).each( function() {
-				jQuery( this ).replaceWith( this.childNodes );
-			} );
-			return this;
-		}
-	} );
-
-
-	jQuery.expr.pseudos.hidden = function( elem ) {
-		return !jQuery.expr.pseudos.visible( elem );
-	};
-	jQuery.expr.pseudos.visible = function( elem ) {
-		return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
-	};
-
-
-
-
-	jQuery.ajaxSettings.xhr = function() {
-		try {
-			return new window.XMLHttpRequest();
-		} catch ( e ) {}
-	};
-
-	var xhrSuccessStatus = {
-
-			// File protocol always yields status code 0, assume 200
-			0: 200,
-
-			// Support: IE <=9 only
-			// #1450: sometimes IE returns 1223 when it should be 204
-			1223: 204
-		},
-		xhrSupported = jQuery.ajaxSettings.xhr();
-
-	support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
-	support.ajax = xhrSupported = !!xhrSupported;
-
-	jQuery.ajaxTransport( function( options ) {
-		var callback, errorCallback;
-
-		// Cross domain only allowed if supported through XMLHttpRequest
-		if ( support.cors || xhrSupported && !options.crossDomain ) {
-			return {
-				send: function( headers, complete ) {
-					var i,
-						xhr = options.xhr();
-
-					xhr.open(
-						options.type,
-						options.url,
-						options.async,
-						options.username,
-						options.password
-					);
-
-					// Apply custom fields if provided
-					if ( options.xhrFields ) {
-						for ( i in options.xhrFields ) {
-							xhr[ i ] = options.xhrFields[ i ];
-						}
-					}
-
-					// Override mime type if needed
-					if ( options.mimeType && xhr.overrideMimeType ) {
-						xhr.overrideMimeType( options.mimeType );
-					}
-
-					// X-Requested-With header
-					// For cross-domain requests, seeing as conditions for a preflight are
-					// akin to a jigsaw puzzle, we simply never set it to be sure.
-					// (it can always be set on a per-request basis or even using ajaxSetup)
-					// For same-domain requests, won't change header if already provided.
-					if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
-						headers[ "X-Requested-With" ] = "XMLHttpRequest";
-					}
-
-					// Set headers
-					for ( i in headers ) {
-						xhr.setRequestHeader( i, headers[ i ] );
-					}
-
-					// Callback
-					callback = function( type ) {
-						return function() {
-							if ( callback ) {
-								callback = errorCallback = xhr.onload =
-									xhr.onerror = xhr.onabort = xhr.ontimeout =
-										xhr.onreadystatechange = null;
-
-								if ( type === "abort" ) {
-									xhr.abort();
-								} else if ( type === "error" ) {
-
-									// Support: IE <=9 only
-									// On a manual native abort, IE9 throws
-									// errors on any property access that is not readyState
-									if ( typeof xhr.status !== "number" ) {
-										complete( 0, "error" );
-									} else {
-										complete(
-
-											// File: protocol always yields status 0; see #8605, #14207
-											xhr.status,
-											xhr.statusText
-										);
-									}
-								} else {
-									complete(
-										xhrSuccessStatus[ xhr.status ] || xhr.status,
-										xhr.statusText,
-
-										// Support: IE <=9 only
-										// IE9 has no XHR2 but throws on binary (trac-11426)
-										// For XHR2 non-text, let the caller handle it (gh-2498)
-										( xhr.responseType || "text" ) !== "text"  ||
-										typeof xhr.responseText !== "string" ?
-											{ binary: xhr.response } :
-											{ text: xhr.responseText },
-										xhr.getAllResponseHeaders()
-									);
-								}
-							}
-						};
-					};
-
-					// Listen to events
-					xhr.onload = callback();
-					errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
-
-					// Support: IE 9 only
-					// Use onreadystatechange to replace onabort
-					// to handle uncaught aborts
-					if ( xhr.onabort !== undefined ) {
-						xhr.onabort = errorCallback;
-					} else {
-						xhr.onreadystatechange = function() {
-
-							// Check readyState before timeout as it changes
-							if ( xhr.readyState === 4 ) {
-
-								// Allow onerror to be called first,
-								// but that will not handle a native abort
-								// Also, save errorCallback to a variable
-								// as xhr.onerror cannot be accessed
-								window.setTimeout( function() {
-									if ( callback ) {
-										errorCallback();
-									}
-								} );
-							}
-						};
-					}
-
-					// Create the abort callback
-					callback = callback( "abort" );
-
-					try {
-
-						// Do send the request (this may raise an exception)
-						xhr.send( options.hasContent && options.data || null );
-					} catch ( e ) {
-
-						// #14683: Only rethrow if this hasn't been notified as an error yet
-						if ( callback ) {
-							throw e;
-						}
-					}
-				},
-
-				abort: function() {
-					if ( callback ) {
-						callback();
-					}
-				}
-			};
-		}
-	} );
-
-
-
-
-	// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
-	jQuery.ajaxPrefilter( function( s ) {
-		if ( s.crossDomain ) {
-			s.contents.script = false;
-		}
-	} );
-
-	// Install script dataType
-	jQuery.ajaxSetup( {
-		accepts: {
-			script: "text/javascript, application/javascript, " +
-				"application/ecmascript, application/x-ecmascript"
-		},
-		contents: {
-			script: /\b(?:java|ecma)script\b/
-		},
-		converters: {
-			"text script": function( text ) {
-				jQuery.globalEval( text );
-				return text;
-			}
-		}
-	} );
-
-	// Handle cache's special case and crossDomain
-	jQuery.ajaxPrefilter( "script", function( s ) {
-		if ( s.cache === undefined ) {
-			s.cache = false;
-		}
-		if ( s.crossDomain ) {
-			s.type = "GET";
-		}
-	} );
-
-	// Bind script tag hack transport
-	jQuery.ajaxTransport( "script", function( s ) {
-
-		// This transport only deals with cross domain requests
-		if ( s.crossDomain ) {
-			var script, callback;
-			return {
-				send: function( _, complete ) {
-					script = jQuery( "<script>" ).prop( {
-						charset: s.scriptCharset,
-						src: s.url
-					} ).on(
-						"load error",
-						callback = function( evt ) {
-							script.remove();
-							callback = null;
-							if ( evt ) {
-								complete( evt.type === "error" ? 404 : 200, evt.type );
-							}
-						}
-					);
-
-					// Use native DOM manipulation to avoid our domManip AJAX trickery
-					document.head.appendChild( script[ 0 ] );
-				},
-				abort: function() {
-					if ( callback ) {
-						callback();
-					}
-				}
-			};
-		}
-	} );
-
-
-
-
-	var oldCallbacks = [],
-		rjsonp = /(=)\?(?=&|$)|\?\?/;
-
-	// Default jsonp settings
-	jQuery.ajaxSetup( {
-		jsonp: "callback",
-		jsonpCallback: function() {
-			var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
-			this[ callback ] = true;
-			return callback;
-		}
-	} );
-
-	// Detect, normalize options and install callbacks for jsonp requests
-	jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
-
-		var callbackName, overwritten, responseContainer,
-			jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
-				"url" :
-				typeof s.data === "string" &&
-					( s.contentType || "" )
-						.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
-					rjsonp.test( s.data ) && "data"
-			);
-
-		// Handle iff the expected data type is "jsonp" or we have a parameter to set
-		if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
-
-			// Get callback name, remembering preexisting value associated with it
-			callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
-				s.jsonpCallback() :
-				s.jsonpCallback;
-
-			// Insert callback into url or form data
-			if ( jsonProp ) {
-				s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
-			} else if ( s.jsonp !== false ) {
-				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
-			}
-
-			// Use data converter to retrieve json after script execution
-			s.converters[ "script json" ] = function() {
-				if ( !responseContainer ) {
-					jQuery.error( callbackName + " was not called" );
-				}
-				return responseContainer[ 0 ];
-			};
-
-			// Force json dataType
-			s.dataTypes[ 0 ] = "json";
-
-			// Install callback
-			overwritten = window[ callbackName ];
-			window[ callbackName ] = function() {
-				responseContainer = arguments;
-			};
-
-			// Clean-up function (fires after converters)
-			jqXHR.always( function() {
-
-				// If previous value didn't exist - remove it
-				if ( overwritten === undefined ) {
-					jQuery( window ).removeProp( callbackName );
-
-				// Otherwise restore preexisting value
-				} else {
-					window[ callbackName ] = overwritten;
-				}
-
-				// Save back as free
-				if ( s[ callbackName ] ) {
-
-					// Make sure that re-using the options doesn't screw things around
-					s.jsonpCallback = originalSettings.jsonpCallback;
-
-					// Save the callback name for future use
-					oldCallbacks.push( callbackName );
-				}
-
-				// Call if it was a function and we have a response
-				if ( responseContainer && isFunction( overwritten ) ) {
-					overwritten( responseContainer[ 0 ] );
-				}
-
-				responseContainer = overwritten = undefined;
-			} );
-
-			// Delegate to script
-			return "script";
-		}
-	} );
-
-
-
-
-	// Support: Safari 8 only
-	// In Safari 8 documents created via document.implementation.createHTMLDocument
-	// collapse sibling forms: the second one becomes a child of the first one.
-	// Because of that, this security measure has to be disabled in Safari 8.
-	// https://bugs.webkit.org/show_bug.cgi?id=137337
-	support.createHTMLDocument = ( function() {
-		var body = document.implementation.createHTMLDocument( "" ).body;
-		body.innerHTML = "<form></form><form></form>";
-		return body.childNodes.length === 2;
-	} )();
-
-
-	// Argument "data" should be string of html
-	// context (optional): If specified, the fragment will be created in this context,
-	// defaults to document
-	// keepScripts (optional): If true, will include scripts passed in the html string
-	jQuery.parseHTML = function( data, context, keepScripts ) {
-		if ( typeof data !== "string" ) {
-			return [];
-		}
-		if ( typeof context === "boolean" ) {
-			keepScripts = context;
-			context = false;
-		}
-
-		var base, parsed, scripts;
-
-		if ( !context ) {
-
-			// Stop scripts or inline event handlers from being executed immediately
-			// by using document.implementation
-			if ( support.createHTMLDocument ) {
-				context = document.implementation.createHTMLDocument( "" );
-
-				// Set the base href for the created document
-				// so any parsed elements with URLs
-				// are based on the document's URL (gh-2965)
-				base = context.createElement( "base" );
-				base.href = document.location.href;
-				context.head.appendChild( base );
-			} else {
-				context = document;
-			}
-		}
-
-		parsed = rsingleTag.exec( data );
-		scripts = !keepScripts && [];
-
-		// Single tag
-		if ( parsed ) {
-			return [ context.createElement( parsed[ 1 ] ) ];
-		}
-
-		parsed = buildFragment( [ data ], context, scripts );
-
-		if ( scripts && scripts.length ) {
-			jQuery( scripts ).remove();
-		}
-
-		return jQuery.merge( [], parsed.childNodes );
-	};
-
-
-	/**
-	 * Load a url into a page
-	 */
-	jQuery.fn.load = function( url, params, callback ) {
-		var selector, type, response,
-			self = this,
-			off = url.indexOf( " " );
-
-		if ( off > -1 ) {
-			selector = stripAndCollapse( url.slice( off ) );
-			url = url.slice( 0, off );
-		}
-
-		// If it's a function
-		if ( isFunction( params ) ) {
-
-			// We assume that it's the callback
-			callback = params;
-			params = undefined;
-
-		// Otherwise, build a param string
-		} else if ( params && typeof params === "object" ) {
-			type = "POST";
-		}
-
-		// If we have elements to modify, make the request
-		if ( self.length > 0 ) {
-			jQuery.ajax( {
-				url: url,
-
-				// If "type" variable is undefined, then "GET" method will be used.
-				// Make value of this field explicit since
-				// user can override it through ajaxSetup method
-				type: type || "GET",
-				dataType: "html",
-				data: params
-			} ).done( function( responseText ) {
-
-				// Save response for use in complete callback
-				response = arguments;
-
-				self.html( selector ?
-
-					// If a selector was specified, locate the right elements in a dummy div
-					// Exclude scripts to avoid IE 'Permission Denied' errors
-					jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
-
-					// Otherwise use the full result
-					responseText );
-
-			// If the request succeeds, this function gets "data", "status", "jqXHR"
-			// but they are ignored because response was set above.
-			// If it fails, this function gets "jqXHR", "status", "error"
-			} ).always( callback && function( jqXHR, status ) {
-				self.each( function() {
-					callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
-				} );
-			} );
-		}
-
-		return this;
-	};
-
-
-
-
-	// Attach a bunch of functions for handling common AJAX events
-	jQuery.each( [
-		"ajaxStart",
-		"ajaxStop",
-		"ajaxComplete",
-		"ajaxError",
-		"ajaxSuccess",
-		"ajaxSend"
-	], function( i, type ) {
-		jQuery.fn[ type ] = function( fn ) {
-			return this.on( type, fn );
-		};
-	} );
-
-
-
-
-	jQuery.expr.pseudos.animated = function( elem ) {
-		return jQuery.grep( jQuery.timers, function( fn ) {
-			return elem === fn.elem;
-		} ).length;
-	};
-
-
-
-
-	jQuery.offset = {
-		setOffset: function( elem, options, i ) {
-			var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
-				position = jQuery.css( elem, "position" ),
-				curElem = jQuery( elem ),
-				props = {};
-
-			// Set position first, in-case top/left are set even on static elem
-			if ( position === "static" ) {
-				elem.style.position = "relative";
-			}
-
-			curOffset = curElem.offset();
-			curCSSTop = jQuery.css( elem, "top" );
-			curCSSLeft = jQuery.css( elem, "left" );
-			calculatePosition = ( position === "absolute" || position === "fixed" ) &&
-				( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
-
-			// Need to be able to calculate position if either
-			// top or left is auto and position is either absolute or fixed
-			if ( calculatePosition ) {
-				curPosition = curElem.position();
-				curTop = curPosition.top;
-				curLeft = curPosition.left;
-
-			} else {
-				curTop = parseFloat( curCSSTop ) || 0;
-				curLeft = parseFloat( curCSSLeft ) || 0;
-			}
-
-			if ( isFunction( options ) ) {
-
-				// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
-				options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
-			}
-
-			if ( options.top != null ) {
-				props.top = ( options.top - curOffset.top ) + curTop;
-			}
-			if ( options.left != null ) {
-				props.left = ( options.left - curOffset.left ) + curLeft;
-			}
-
-			if ( "using" in options ) {
-				options.using.call( elem, props );
-
-			} else {
-				curElem.css( props );
-			}
-		}
-	};
-
-	jQuery.fn.extend( {
-
-		// offset() relates an element's border box to the document origin
-		offset: function( options ) {
-
-			// Preserve chaining for setter
-			if ( arguments.length ) {
-				return options === undefined ?
-					this :
-					this.each( function( i ) {
-						jQuery.offset.setOffset( this, options, i );
-					} );
-			}
-
-			var rect, win,
-				elem = this[ 0 ];
-
-			if ( !elem ) {
-				return;
-			}
-
-			// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
-			// Support: IE <=11 only
-			// Running getBoundingClientRect on a
-			// disconnected node in IE throws an error
-			if ( !elem.getClientRects().length ) {
-				return { top: 0, left: 0 };
-			}
-
-			// Get document-relative position by adding viewport scroll to viewport-relative gBCR
-			rect = elem.getBoundingClientRect();
-			win = elem.ownerDocument.defaultView;
-			return {
-				top: rect.top + win.pageYOffset,
-				left: rect.left + win.pageXOffset
-			};
-		},
-
-		// position() relates an element's margin box to its offset parent's padding box
-		// This corresponds to the behavior of CSS absolute positioning
-		position: function() {
-			if ( !this[ 0 ] ) {
-				return;
-			}
-
-			var offsetParent, offset, doc,
-				elem = this[ 0 ],
-				parentOffset = { top: 0, left: 0 };
-
-			// position:fixed elements are offset from the viewport, which itself always has zero offset
-			if ( jQuery.css( elem, "position" ) === "fixed" ) {
-
-				// Assume position:fixed implies availability of getBoundingClientRect
-				offset = elem.getBoundingClientRect();
-
-			} else {
-				offset = this.offset();
-
-				// Account for the *real* offset parent, which can be the document or its root element
-				// when a statically positioned element is identified
-				doc = elem.ownerDocument;
-				offsetParent = elem.offsetParent || doc.documentElement;
-				while ( offsetParent &&
-					( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
-					jQuery.css( offsetParent, "position" ) === "static" ) {
-
-					offsetParent = offsetParent.parentNode;
-				}
-				if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
-
-					// Incorporate borders into its offset, since they are outside its content origin
-					parentOffset = jQuery( offsetParent ).offset();
-					parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
-					parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
-				}
-			}
-
-			// Subtract parent offsets and element margins
-			return {
-				top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
-				left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
-			};
-		},
-
-		// This method will return documentElement in the following cases:
-		// 1) For the element inside the iframe without offsetParent, this method will return
-		//    documentElement of the parent window
-		// 2) For the hidden or detached element
-		// 3) For body or html element, i.e. in case of the html node - it will return itself
-		//
-		// but those exceptions were never presented as a real life use-cases
-		// and might be considered as more preferable results.
-		//
-		// This logic, however, is not guaranteed and can change at any point in the future
-		offsetParent: function() {
-			return this.map( function() {
-				var offsetParent = this.offsetParent;
-
-				while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
-					offsetParent = offsetParent.offsetParent;
-				}
-
-				return offsetParent || documentElement;
-			} );
-		}
-	} );
-
-	// Create scrollLeft and scrollTop methods
-	jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
-		var top = "pageYOffset" === prop;
-
-		jQuery.fn[ method ] = function( val ) {
-			return access( this, function( elem, method, val ) {
-
-				// Coalesce documents and windows
-				var win;
-				if ( isWindow( elem ) ) {
-					win = elem;
-				} else if ( elem.nodeType === 9 ) {
-					win = elem.defaultView;
-				}
-
-				if ( val === undefined ) {
-					return win ? win[ prop ] : elem[ method ];
-				}
-
-				if ( win ) {
-					win.scrollTo(
-						!top ? val : win.pageXOffset,
-						top ? val : win.pageYOffset
-					);
-
-				} else {
-					elem[ method ] = val;
-				}
-			}, method, val, arguments.length );
-		};
-	} );
-
-	// Support: Safari <=7 - 9.1, Chrome <=37 - 49
-	// Add the top/left cssHooks using jQuery.fn.position
-	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
-	// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
-	// getComputedStyle returns percent when specified for top/left/bottom/right;
-	// rather than make the css module depend on the offset module, just check for it here
-	jQuery.each( [ "top", "left" ], function( i, prop ) {
-		jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
-			function( elem, computed ) {
-				if ( computed ) {
-					computed = curCSS( elem, prop );
-
-					// If curCSS returns percentage, fallback to offset
-					return rnumnonpx.test( computed ) ?
-						jQuery( elem ).position()[ prop ] + "px" :
-						computed;
-				}
-			}
-		);
-	} );
-
-
-	// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
-	jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
-		jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
-			function( defaultExtra, funcName ) {
-
-			// Margin is only for outerHeight, outerWidth
-			jQuery.fn[ funcName ] = function( margin, value ) {
-				var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
-					extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
-
-				return access( this, function( elem, type, value ) {
-					var doc;
-
-					if ( isWindow( elem ) ) {
-
-						// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
-						return funcName.indexOf( "outer" ) === 0 ?
-							elem[ "inner" + name ] :
-							elem.document.documentElement[ "client" + name ];
-					}
-
-					// Get document width or height
-					if ( elem.nodeType === 9 ) {
-						doc = elem.documentElement;
-
-						// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
-						// whichever is greatest
-						return Math.max(
-							elem.body[ "scroll" + name ], doc[ "scroll" + name ],
-							elem.body[ "offset" + name ], doc[ "offset" + name ],
-							doc[ "client" + name ]
-						);
-					}
-
-					return value === undefined ?
-
-						// Get width or height on the element, requesting but not forcing parseFloat
-						jQuery.css( elem, type, extra ) :
-
-						// Set width or height on the element
-						jQuery.style( elem, type, value, extra );
-				}, type, chainable ? margin : undefined, chainable );
-			};
-		} );
-	} );
-
-
-	jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
-		"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
-		"change select submit keydown keypress keyup contextmenu" ).split( " " ),
-		function( i, name ) {
-
-		// Handle event binding
-		jQuery.fn[ name ] = function( data, fn ) {
-			return arguments.length > 0 ?
-				this.on( name, null, data, fn ) :
-				this.trigger( name );
-		};
-	} );
-
-	jQuery.fn.extend( {
-		hover: function( fnOver, fnOut ) {
-			return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
-		}
-	} );
-
-
-
-
-	jQuery.fn.extend( {
-
-		bind: function( types, data, fn ) {
-			return this.on( types, null, data, fn );
-		},
-		unbind: function( types, fn ) {
-			return this.off( types, null, fn );
-		},
-
-		delegate: function( selector, types, data, fn ) {
-			return this.on( types, selector, data, fn );
-		},
-		undelegate: function( selector, types, fn ) {
-
-			// ( namespace ) or ( selector, types [, fn] )
-			return arguments.length === 1 ?
-				this.off( selector, "**" ) :
-				this.off( types, selector || "**", fn );
-		}
-	} );
-
-	// Bind a function to a context, optionally partially applying any
-	// arguments.
-	// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
-	// However, it is not slated for removal any time soon
-	jQuery.proxy = function( fn, context ) {
-		var tmp, args, proxy;
-
-		if ( typeof context === "string" ) {
-			tmp = fn[ context ];
-			context = fn;
-			fn = tmp;
-		}
-
-		// Quick check to determine if target is callable, in the spec
-		// this throws a TypeError, but we will just return undefined.
-		if ( !isFunction( fn ) ) {
-			return undefined;
-		}
-
-		// Simulated bind
-		args = slice.call( arguments, 2 );
-		proxy = function() {
-			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
-		};
-
-		// Set the guid of unique handler to the same of original handler, so it can be removed
-		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
-
-		return proxy;
-	};
-
-	jQuery.holdReady = function( hold ) {
-		if ( hold ) {
-			jQuery.readyWait++;
-		} else {
-			jQuery.ready( true );
-		}
-	};
-	jQuery.isArray = Array.isArray;
-	jQuery.parseJSON = JSON.parse;
-	jQuery.nodeName = nodeName;
-	jQuery.isFunction = isFunction;
-	jQuery.isWindow = isWindow;
-	jQuery.camelCase = camelCase;
-	jQuery.type = toType;
-
-	jQuery.now = Date.now;
-
-	jQuery.isNumeric = function( obj ) {
-
-		// As of jQuery 3.0, isNumeric is limited to
-		// strings and numbers (primitives or objects)
-		// that can be coerced to finite numbers (gh-2662)
-		var type = jQuery.type( obj );
-		return ( type === "number" || type === "string" ) &&
-
-			// parseFloat NaNs numeric-cast false positives ("")
-			// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
-			// subtraction forces infinities to NaN
-			!isNaN( obj - parseFloat( obj ) );
-	};
-
-
-
-
-	// Register as a named AMD module, since jQuery can be concatenated with other
-	// files that may use define, but not via a proper concatenation script that
-	// understands anonymous AMD modules. A named AMD is safest and most robust
-	// way to register. Lowercase jquery is used because AMD module names are
-	// derived from file names, and jQuery is normally delivered in a lowercase
-	// file name. Do this after creating the global so that if an AMD module wants
-	// to call noConflict to hide this version of jQuery, it will work.
-
-	// Note that for maximum portability, libraries that are not jQuery should
-	// declare themselves as anonymous modules, and avoid setting a global if an
-	// AMD loader is present. jQuery is a special case. For more information, see
-	// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
-
-	if ( true ) {
-		!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
-			return jQuery;
-		}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-	}
-
-
-
-
-	var
-
-		// Map over jQuery in case of overwrite
-		_jQuery = window.jQuery,
-
-		// Map over the $ in case of overwrite
-		_$ = window.$;
-
-	jQuery.noConflict = function( deep ) {
-		if ( window.$ === jQuery ) {
-			window.$ = _$;
-		}
-
-		if ( deep && window.jQuery === jQuery ) {
-			window.jQuery = _jQuery;
-		}
-
-		return jQuery;
-	};
-
-	// Expose jQuery and $ identifiers, even in AMD
-	// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
-	// and CommonJS for browser emulators (#13566)
-	if ( !noGlobal ) {
-		window.jQuery = window.$ = jQuery;
-	}
-
-
-
-
-	return jQuery;
-	} );
-
-
-/***/ }),
-/* 2 */
-/***/ (function(module, exports) {
-
-	// Add a polyfill for window.requestAnimationFrame.
-	if (!window.requestAnimationFrame) {
-	  var _animationFrameFunc = [];
-	  if (!window.performance || !window.performance.now) {
-	    window.performance = {now: function () { return new Date().getTime(); }};
-	  }
-	  window.requestAnimationFrame = function (func) {
-	    'use strict';
-
-	    if (!_animationFrameFunc.length) {
-	      var time = window.performance.now();
-	      window.setTimeout(function () {
-	        var funcs = _animationFrameFunc;
-	        _animationFrameFunc = [];
-	        var curtime = window.performance.now();
-	        for (var i = 0; i < funcs.length; i += 1) {
-	          funcs[i].call(window, curtime);
-	        }
-	      }, 15 - (parseInt(time, 10) % 15));
-	    }
-	    _animationFrameFunc.push(func);
-	  };
-	}
-
-	// Add a polyfill for Math.log2
-	if (!Math.log2) {
-	  Math.log2 = function () {
-	    return Math.log.apply(Math, arguments) / Math.LN2;
-	  };
-	}
-
-	// Add a polyfill for Math.sinh
-	Math.sinh = Math.sinh || function (x) {
-	  var y = Math.exp(x);
-	  return (y - 1 / y) / 2;
-	};
-
-
-/***/ }),
-/* 3 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	// style-loader: Adds some css to the DOM by adding a <style> tag
-
-	// load the styles
-	var content = __webpack_require__(4);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(6)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/stylus-loader/index.js!./main.styl", function() {
-				var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/stylus-loader/index.js!./main.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
-
-/***/ }),
-/* 4 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	exports = module.exports = __webpack_require__(5)();
-	// imports
-
-
-	// module
-	exports.push([module.id, ".geojs-map{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.geojs-map .geo-attribution{position:absolute;right:0;bottom:0;padding-right:5px;cursor:auto;font:11px/1.5 Helvetica Neue,Arial,Helvetica,sans-serif;z-index:1001;background:hsla(0,0%,100%,.7);clear:both;display:block;pointer-events:auto}.geojs-map .geo-attribution .geo-attribution-layer{padding-left:5px}.geojs-map .canvas-canvas{display:block;-webkit-transform-origin:0 0;transform-origin:0 0}.geojs-map .webgl-canvas{display:block}.geojs-map .geojs-layer{position:absolute;width:100%;height:100%;pointer-events:none}.geojs-map .geojs-layer.active>*{pointer-events:auto}.geojs-map .geo-tile-layer{-webkit-transform-origin:0 0;transform-origin:0 0;line-height:0;font-size:0}.geojs-map.annotation-input{cursor:crosshair}.geojs-map.highlight-focus:after{content:\"\";display:block;position:absolute;box-sizing:border-box;left:0;top:0;right:0;bottom:0;border:3px solid Highlight;opacity:1;-ms-filter:none;filter:none;transition:opacity 0s;visibility:hidden}.geojs-map.highlight-focus:focus:after{visibility:visible;transition:opacity 2.5s ease-in;opacity:0;-ms-filter:\"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";filter:alpha(opacity=0)}.geo-tile-container{position:absolute}.geo-tile-container.crop{overflow:hidden}", ""]);
-
-	// exports
-
-
-/***/ }),
-/* 5 */
-/***/ (function(module, exports) {
-
-	/*
-		MIT License http://www.opensource.org/licenses/mit-license.php
-		Author Tobias Koppers @sokra
-	*/
-	// css base code, injected by the css-loader
-	module.exports = function() {
-		var list = [];
-
-		// return the list of modules as css string
-		list.toString = function toString() {
-			var result = [];
-			for(var i = 0; i < this.length; i++) {
-				var item = this[i];
-				if(item[2]) {
-					result.push("@media " + item[2] + "{" + item[1] + "}");
-				} else {
-					result.push(item[1]);
-				}
-			}
-			return result.join("");
-		};
-
-		// import a list of modules into the list
-		list.i = function(modules, mediaQuery) {
-			if(typeof modules === "string")
-				modules = [[null, modules, ""]];
-			var alreadyImportedModules = {};
-			for(var i = 0; i < this.length; i++) {
-				var id = this[i][0];
-				if(typeof id === "number")
-					alreadyImportedModules[id] = true;
-			}
-			for(i = 0; i < modules.length; i++) {
-				var item = modules[i];
-				// skip already imported module
-				// this implementation is not 100% perfect for weird media query combinations
-				//  when a module is imported multiple times with different media queries.
-				//  I hope this will never occur (Hey this way we have smaller bundles)
-				if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
-					if(mediaQuery && !item[2]) {
-						item[2] = mediaQuery;
-					} else if(mediaQuery) {
-						item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
-					}
-					list.push(item);
-				}
-			}
-		};
-		return list;
-	};
-
-
-/***/ }),
-/* 6 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/*
-		MIT License http://www.opensource.org/licenses/mit-license.php
-		Author Tobias Koppers @sokra
-	*/
-	var stylesInDom = {},
-		memoize = function(fn) {
-			var memo;
-			return function () {
-				if (typeof memo === "undefined") memo = fn.apply(this, arguments);
-				return memo;
-			};
-		},
-		isOldIE = memoize(function() {
-			return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
-		}),
-		getHeadElement = memoize(function () {
-			return document.head || document.getElementsByTagName("head")[0];
-		}),
-		singletonElement = null,
-		singletonCounter = 0,
-		styleElementsInsertedAtTop = [];
-
-	module.exports = function(list, options) {
-		if(false) {
-			if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
-		}
-
-		options = options || {};
-		// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
-		// tags it will allow on a page
-		if (typeof options.singleton === "undefined") options.singleton = isOldIE();
-
-		// By default, add <style> tags to the bottom of <head>.
-		if (typeof options.insertAt === "undefined") options.insertAt = "bottom";
-
-		var styles = listToStyles(list);
-		addStylesToDom(styles, options);
-
-		return function update(newList) {
-			var mayRemove = [];
-			for(var i = 0; i < styles.length; i++) {
-				var item = styles[i];
-				var domStyle = stylesInDom[item.id];
-				domStyle.refs--;
-				mayRemove.push(domStyle);
-			}
-			if(newList) {
-				var newStyles = listToStyles(newList);
-				addStylesToDom(newStyles, options);
-			}
-			for(var i = 0; i < mayRemove.length; i++) {
-				var domStyle = mayRemove[i];
-				if(domStyle.refs === 0) {
-					for(var j = 0; j < domStyle.parts.length; j++)
-						domStyle.parts[j]();
-					delete stylesInDom[domStyle.id];
-				}
-			}
-		};
-	}
-
-	function addStylesToDom(styles, options) {
-		for(var i = 0; i < styles.length; i++) {
-			var item = styles[i];
-			var domStyle = stylesInDom[item.id];
-			if(domStyle) {
-				domStyle.refs++;
-				for(var j = 0; j < domStyle.parts.length; j++) {
-					domStyle.parts[j](item.parts[j]);
-				}
-				for(; j < item.parts.length; j++) {
-					domStyle.parts.push(addStyle(item.parts[j], options));
-				}
-			} else {
-				var parts = [];
-				for(var j = 0; j < item.parts.length; j++) {
-					parts.push(addStyle(item.parts[j], options));
-				}
-				stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
-			}
-		}
-	}
-
-	function listToStyles(list) {
-		var styles = [];
-		var newStyles = {};
-		for(var i = 0; i < list.length; i++) {
-			var item = list[i];
-			var id = item[0];
-			var css = item[1];
-			var media = item[2];
-			var sourceMap = item[3];
-			var part = {css: css, media: media, sourceMap: sourceMap};
-			if(!newStyles[id])
-				styles.push(newStyles[id] = {id: id, parts: [part]});
-			else
-				newStyles[id].parts.push(part);
-		}
-		return styles;
-	}
-
-	function insertStyleElement(options, styleElement) {
-		var head = getHeadElement();
-		var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];
-		if (options.insertAt === "top") {
-			if(!lastStyleElementInsertedAtTop) {
-				head.insertBefore(styleElement, head.firstChild);
-			} else if(lastStyleElementInsertedAtTop.nextSibling) {
-				head.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
-			} else {
-				head.appendChild(styleElement);
-			}
-			styleElementsInsertedAtTop.push(styleElement);
-		} else if (options.insertAt === "bottom") {
-			head.appendChild(styleElement);
-		} else {
-			throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
-		}
-	}
-
-	function removeStyleElement(styleElement) {
-		styleElement.parentNode.removeChild(styleElement);
-		var idx = styleElementsInsertedAtTop.indexOf(styleElement);
-		if(idx >= 0) {
-			styleElementsInsertedAtTop.splice(idx, 1);
-		}
-	}
-
-	function createStyleElement(options) {
-		var styleElement = document.createElement("style");
-		styleElement.type = "text/css";
-		insertStyleElement(options, styleElement);
-		return styleElement;
-	}
-
-	function createLinkElement(options) {
-		var linkElement = document.createElement("link");
-		linkElement.rel = "stylesheet";
-		insertStyleElement(options, linkElement);
-		return linkElement;
-	}
-
-	function addStyle(obj, options) {
-		var styleElement, update, remove;
-
-		if (options.singleton) {
-			var styleIndex = singletonCounter++;
-			styleElement = singletonElement || (singletonElement = createStyleElement(options));
-			update = applyToSingletonTag.bind(null, styleElement, styleIndex, false);
-			remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true);
-		} else if(obj.sourceMap &&
-			typeof URL === "function" &&
-			typeof URL.createObjectURL === "function" &&
-			typeof URL.revokeObjectURL === "function" &&
-			typeof Blob === "function" &&
-			typeof btoa === "function") {
-			styleElement = createLinkElement(options);
-			update = updateLink.bind(null, styleElement);
-			remove = function() {
-				removeStyleElement(styleElement);
-				if(styleElement.href)
-					URL.revokeObjectURL(styleElement.href);
-			};
-		} else {
-			styleElement = createStyleElement(options);
-			update = applyToTag.bind(null, styleElement);
-			remove = function() {
-				removeStyleElement(styleElement);
-			};
-		}
-
-		update(obj);
-
-		return function updateStyle(newObj) {
-			if(newObj) {
-				if(newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap)
-					return;
-				update(obj = newObj);
-			} else {
-				remove();
-			}
-		};
-	}
-
-	var replaceText = (function () {
-		var textStore = [];
-
-		return function (index, replacement) {
-			textStore[index] = replacement;
-			return textStore.filter(Boolean).join('\n');
-		};
-	})();
-
-	function applyToSingletonTag(styleElement, index, remove, obj) {
-		var css = remove ? "" : obj.css;
-
-		if (styleElement.styleSheet) {
-			styleElement.styleSheet.cssText = replaceText(index, css);
-		} else {
-			var cssNode = document.createTextNode(css);
-			var childNodes = styleElement.childNodes;
-			if (childNodes[index]) styleElement.removeChild(childNodes[index]);
-			if (childNodes.length) {
-				styleElement.insertBefore(cssNode, childNodes[index]);
-			} else {
-				styleElement.appendChild(cssNode);
-			}
-		}
-	}
-
-	function applyToTag(styleElement, obj) {
-		var css = obj.css;
-		var media = obj.media;
-
-		if(media) {
-			styleElement.setAttribute("media", media)
-		}
-
-		if(styleElement.styleSheet) {
-			styleElement.styleSheet.cssText = css;
-		} else {
-			while(styleElement.firstChild) {
-				styleElement.removeChild(styleElement.firstChild);
-			}
-			styleElement.appendChild(document.createTextNode(css));
-		}
-	}
-
-	function updateLink(linkElement, obj) {
-		var css = obj.css;
-		var sourceMap = obj.sourceMap;
-
-		if(sourceMap) {
-			// http://stackoverflow.com/a/26603875
-			css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";
-		}
-
-		var blob = new Blob([css], { type: "text/css" });
-
-		var oldSrc = linkElement.href;
-
-		linkElement.href = URL.createObjectURL(blob);
-
-		if(oldSrc)
-			URL.revokeObjectURL(oldSrc);
-	}
-
-
-/***/ }),
-/* 7 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var geo_event = __webpack_require__(9);
-	var geo_action = __webpack_require__(10);
-	var transform = __webpack_require__(11);
-	var util = __webpack_require__(83);
-	var registerAnnotation = __webpack_require__(201).registerAnnotation;
-	var lineFeature = __webpack_require__(206);
-	var pointFeature = __webpack_require__(212);
-	var polygonFeature = __webpack_require__(217);
-	var textFeature = __webpack_require__(218);
-
-	var annotationId = 0;
-
-	var annotationState = {
-	  create: 'create',
-	  done: 'done',
-	  highlight: 'highlight',
-	  edit: 'edit'
-	};
-
-	var annotationActionOwner = 'annotationAction';
-
-	var defaultEditHandleStyle = {
-	  fill: true,
-	  fillColor: function (d) {
-	    return d.selected ? {r: 0, g: 1, b: 1} : {r: 0.3, g: 0.3, b: 0.3};
-	  },
-	  fillOpacity: function (d) {
-	    return d.selected ? 0.5 : 0.25;
-	  },
-	  radius: function (d) {
-	    return d.type === 'edge' || d.type === 'rotate' ? 8 : 10;
-	  },
-	  scaled: false,
-	  stroke: true,
-	  strokeColor: {r: 0, g: 0, b: 1},
-	  strokeOpacity: 1,
-	  strokeWidth: function (d) {
-	    return d.type === 'edge' || d.type === 'rotate' ? 2 : 3;
-	  },
-	  rotateHandleOffset: 24, // should be roughly twice radius + strokeWidth
-	  rotateHandleRotation: -Math.PI / 4,
-	  resizeHandleOffset: 48, // should be roughly twice radius + strokeWidth + rotateHandleOffset
-	  resizeHandleRotation: -Math.PI / 4,
-	  // handles may be a function to dynamically generate the results
-	  handles: {  // if `false`, the handle won't be created for editing
-	    vertex: true,
-	    edge: true,
-	    center: true,
-	    rotate: true,
-	    resize: true
-	  }
-	};
-	var editHandleFeatureLevel = 3;
-
-	/**
-	 * Base annotation class.
-	 *
-	 * @class
-	 * @alias geo.annotation
-	 * @param {string} type The type of annotation.  These should be registered
-	 *    with utils.registerAnnotation and can be listed with same function.
-	 * @param {object?} [args] Individual annotations have additional options.
-	 * @param {string} [args.name] A name for the annotation.  This defaults to
-	 *    the type with a unique ID suffixed to it.
-	 * @param {geo.annotationLayer} [arg.layer] A reference to the controlling
-	 *    layer.  This is used for coordinate transforms.
-	 * @param {string} [args.state] Initial annotation state.  One of the
-	 *    `geo.annotation.state` values.
-	 * @param {boolean|string[]} [args.showLabel=true] `true` to show the
-	 *    annotation label on annotations in done or edit states.  Alternately, a
-	 *    list of states in which to show the label.  Falsy to not show the label.
-	 * @returns {geo.annotation}
-	 */
-	var annotation = function (type, args) {
-	  'use strict';
-	  if (!(this instanceof annotation)) {
-	    return new annotation(type, args);
-	  }
-
-	  var m_this = this,
-	      m_options = $.extend({}, {showLabel: true}, args || {}),
-	      m_id = m_options.annotationId;
-	  delete m_options.annotationId;
-	  if (m_id === undefined || (m_options.layer && m_options.layer.annotationById(m_id))) {
-	    annotationId += 1;
-	    if (m_id !== undefined) {
-	      console.warn('Annotation id ' + m_id + ' is in use; using ' + annotationId + ' instead.');
-	    }
-	    m_id = annotationId;
-	  } else {
-	    if (m_id > annotationId) {
-	      annotationId = m_id;
-	    }
-	  }
-	  var m_name = m_options.name || (
-	        type.charAt(0).toUpperCase() + type.substr(1) + ' ' + m_id),
-	      m_label = m_options.label || null,
-	      m_description = m_options.description || undefined,
-	      m_type = type,
-	      m_layer = m_options.layer,
-	      /* one of annotationState.* */
-	      m_state = m_options.state || annotationState.done;
-	  delete m_options.state;
-	  delete m_options.layer;
-	  delete m_options.name;
-	  delete m_options.label;
-	  delete m_options.description;
-
-	  /**
-	   * Clean up any resources that the annotation is using.
-	   */
-	  this._exit = function () {
-	  };
-
-	  /**
-	   * Get a unique annotation id.
-	   *
-	   * @returns {number} The annotation id.
-	   */
-	  this.id = function () {
-	    return m_id;
-	  };
-
-	  /**
-	   * Get or set the name of this annotation.
-	   *
-	   * @param {string|undefined} arg If `undefined`, return the name, otherwise
-	   *    change it.  When setting the name, the value is trimmed of
-	   *    whitespace.  The name will not be changed to an empty string.
-	   * @returns {this|string} The current name or this annotation.
-	   */
-	  this.name = function (arg) {
-	    if (arg === undefined) {
-	      return m_name;
-	    }
-	    if (arg !== null && ('' + arg).trim()) {
-	      arg = ('' + arg).trim();
-	      if (arg !== m_name) {
-	        m_name = arg;
-	        m_this.modified();
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the label of this annotation.
-	   *
-	   * @param {string|null|undefined} arg If `undefined`, return the label,
-	   *    otherwise change it.  `null` to clear the label.
-	   * @param {boolean} noFallback If not truthy and the label is `null`, return
-	   *    the name, otherwise return the actual value for label.
-	   * @returns {this|string} The current label or this annotation.
-	   */
-	  this.label = function (arg, noFallback) {
-	    if (arg === undefined) {
-	      return m_label === null && !noFallback ? m_name : m_label;
-	    }
-	    if (arg !== m_label) {
-	      m_label = arg;
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Return the coordinate associated with the label.
-	   *
-	   * @returns {geo.geoPosition|undefined} The map gcs position for the label,
-	   *    or `undefined` if no such position exists.
-	   */
-	  this._labelPosition = function () {
-	    return util.centerFromPerimeter(m_this._coordinates());
-	  };
-
-	  /**
-	   * Return the coordinate associated with the rotation handle for the
-	   * annotation.
-	   *
-	   * @param {number} [offset] An additional offset from cetner to apply to the
-	   *    handle.
-	   * @param {number} [rotation] An additional rotation to apply to the handle.
-	   * @returns {geo.geoPosition|undefined} The map gcs position for the handle,
-	   *    or `undefined` if no such position exists.
-	   */
-	  this._rotateHandlePosition = function (offset, rotation) {
-	    var map = m_this.layer().map(),
-	        coor = m_this._coordinates(),
-	        center = util.centerFromPerimeter(m_this._coordinates()),
-	        dispCenter = center ? map.gcsToDisplay(center, null) : undefined,
-	        i, pos, maxr2 = 0, r;
-	    if (!center) {
-	      return;
-	    }
-	    offset = offset || 0;
-	    rotation = rotation || 0;
-	    for (i = 0; i < coor.length; i += 1) {
-	      pos = map.gcsToDisplay(coor[i], null);
-	      maxr2 = Math.max(maxr2, Math.pow(pos.x - dispCenter.x, 2) + Math.pow(pos.y - dispCenter.y, 2));
-	    }
-	    r = Math.sqrt(maxr2) + offset;
-	    pos = map.displayToGcs({
-	      x: dispCenter.x + r * Math.cos(rotation),
-	      y: dispCenter.y - r * Math.sin(rotation)}, null);
-	    return pos;
-	  };
-
-	  /**
-	   * If the label should be shown, get a record of the label that can be used
-	   * in a `geo.textFeature`.
-	   *
-	   * @returns {geo.annotationLayer.labelRecord|undefined} A label record, or
-	   *    `undefined` if it should not be shown.
-	   */
-	  this.labelRecord = function () {
-	    var show = m_this.options('showLabel');
-	    if (!show) {
-	      return;
-	    }
-	    var state = m_this.state();
-	    if ((show === true && state === annotationState.create) ||
-	        (show !== true && show.indexOf(state) < 0)) {
-	      return;
-	    }
-	    var style = m_this.labelStyle();
-	    var labelRecord = {
-	      text: m_this.label(),
-	      position: m_this._labelPosition()
-	    };
-	    if (!labelRecord.position) {
-	      return;
-	    }
-	    if (style) {
-	      labelRecord.style = style;
-	    }
-	    return labelRecord;
-	  };
-
-	  /**
-	   * Get or set the description of this annotation.
-	   *
-	   * @param {string|undefined} arg If `undefined`, return the description,
-	   *    otherwise change it.
-	   * @returns {this|string} The current description or this annotation.
-	   */
-	  this.description = function (arg) {
-	    if (arg === undefined) {
-	      return m_description;
-	    }
-	    if (arg !== m_description) {
-	      m_description = arg;
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the annotation layer associated with this annotation.
-	   *
-	   * @param {geo.annotationLayer|undefined} arg if undefined, return the layer,
-	   *    otherwise change it.
-	   * @returns {this|geo.annotationLayer} the current layer or this annotation.
-	   */
-	  this.layer = function (arg) {
-	    if (arg === undefined) {
-	      return m_layer;
-	    }
-	    m_layer = arg;
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the state of this annotation.
-	   *
-	   * @param {string|undefined} arg If `undefined`, return the state,
-	   *    otherwise change it.  This should be one of the
-	   *    `geo.annotation.state` values.
-	   * @returns {this|string} The current state or this annotation.
-	   */
-	  this.state = function (arg) {
-	    if (arg === undefined) {
-	      return m_state;
-	    }
-	    if (m_state !== arg) {
-	      m_state = arg;
-	      if (m_this.layer()) {
-	        m_this.layer().geoTrigger(geo_event.annotation.state, {
-	          annotation: this
-	        });
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Return actions needed for the specified state of this annotation.
-	   *
-	   * @param {string} [state] The state to return actions for.  Defaults to
-	   *    the current state.
-	   * @returns {geo.actionRecord[]} A list of actions.
-	   */
-	  this.actions = function (state) {
-	    if (!state) {
-	      state = m_this.state();
-	    }
-	    switch (state) {
-	      case annotationState.edit:
-	        return [{
-	          action: geo_action.annotation_edit_handle,
-	          name: 'annotation edit',
-	          owner: annotationActionOwner,
-	          input: 'left'
-	        }, {
-	          action: geo_action.annotation_edit_handle,
-	          name: 'annotation edit',
-	          owner: annotationActionOwner,
-	          input: 'pan'
-	        }];
-	      default:
-	        return [];
-	    }
-	  };
-
-	  /**
-	   * Process any non-edit actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if the
-	   *    annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.processAction = function () {
-	    return undefined;
-	  };
-
-	  /**
-	   * Process any edit actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean} `true` to update the annotation, falsy to not update
-	   *    anything.
-	   */
-	  this.processEditAction = function (evt) {
-	    if (!evt || !m_this._editHandle || !m_this._editHandle.handle) {
-	      return;
-	    }
-	    switch (m_this._editHandle.handle.type) {
-	      case 'vertex':
-	        return m_this._processEditActionVertex(evt);
-	      case 'edge':
-	        return m_this._processEditActionEdge(evt);
-	      case 'center':
-	        return m_this._processEditActionCenter(evt);
-	      case 'rotate':
-	        return m_this._processEditActionRotate(evt);
-	      case 'resize':
-	        return m_this._processEditActionResize(evt);
-	    }
-	  };
-
-	  /**
-	   * When an edit handle is selected or deselected (for instance, by moving the
-	   * mouse on or off of it), mark if it is selected and record the current
-	   * coordinates.
-	   *
-	   * @param {object} handle The data for the edit handle.
-	   * @param {boolean} enable True to enable the handle, false to disable.
-	   * @returns {this}
-	   */
-	  this.selectEditHandle = function (handle, enable) {
-	    if (enable && m_this._editHandle && m_this._editHandle.handle &&
-	        m_this._editHandle.handle.selected) {
-	      m_this._editHandle.handle.selected = false;
-	    }
-	    handle.selected = enable;
-	    var amountRotated = (m_this._editHandle || {}).amountRotated || 0;
-	    m_this._editHandle = {
-	      handle: handle,
-	      startCoordinates: m_this._coordinates().slice(),
-	      center: util.centerFromPerimeter(m_this._coordinates()),
-	      rotatePosition: m_this._rotateHandlePosition(
-	        handle.style.rotateHandleOffset, handle.style.rotateHandleRotation + amountRotated),
-	      startAmountRotated: amountRotated,
-	      amountRotated: amountRotated,
-	      resizePosition: m_this._rotateHandlePosition(
-	        handle.style.resizeHandleOffset, handle.style.resizeHandleRotation)
-	    };
-	    return m_this;
-	  };
-
-	  /**
-	   * Set or get options.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the options object.
-	   *    If a string, either set or return the option of that name.  If an
-	   *    object, update the options with the object's values.
-	   * @param {object} [arg2] If `arg1` is a string and this is defined, set
-	   *    the option to this value.
-	   * @returns {object|this} If options are set, return the annotation,
-	   *    otherwise return the requested option or the set of options.
-	   */
-	  this.options = function (arg1, arg2) {
-	    if (arg1 === undefined) {
-	      return m_options;
-	    }
-	    if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return m_options[arg1];
-	    }
-	    if (arg2 === undefined) {
-	      m_options = $.extend(true, m_options, arg1);
-	      /* For style objects, re-extend them without recursion.  This allows
-	       * setting colors without an opacity field, for instance. */
-	      ['style', 'createStyle', 'editStyle', 'editHandleStyle', 'labelStyle',
-	        'highlightStyle'
-	      ].forEach(function (key) {
-	        if (arg1[key] !== undefined) {
-	          $.extend(m_options[key], arg1[key]);
-	        }
-	      });
-	    } else {
-	      m_options[arg1] = arg2;
-	    }
-	    if (m_options.coordinates) {
-	      var coor = m_options.coordinates;
-	      delete m_options.coordinates;
-	      m_this._coordinates(coor);
-	    }
-	    if (m_options.name !== undefined) {
-	      var name = m_options.name;
-	      delete m_options.name;
-	      m_this.name(name);
-	    }
-	    if (m_options.label !== undefined) {
-	      var label = m_options.label;
-	      delete m_options.label;
-	      m_this.label(label);
-	    }
-	    if (m_options.description !== undefined) {
-	      var description = m_options.description;
-	      delete m_options.description;
-	      m_this.description(description);
-	    }
-	    m_this.modified();
-	    return this;
-	  };
-
-	  /**
-	   * Set or get style.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the current style
-	   *    object.  If a string and `arg2` is undefined, return the style
-	   *    associated with the specified key.  If a string and `arg2` is defined,
-	   *    set the named style to the specified value.  Otherwise, extend the
-	   *    current style with the values in the specified object.
-	   * @param {*} [arg2] If `arg1` is a string, the new value for that style.
-	   * @param {string} [styleType='style'] The name of the style type, such as
-	   *    `createStyle', `editStyle`, `editHandleStyle`, `labelStyle`, or
-	   *    `highlightStyle`.
-	   * @returns {object|this} Either the entire style object, the value of a
-	   *    specific style, or the current class instance.
-	   */
-	  this.style = function (arg1, arg2, styleType) {
-	    styleType = styleType || 'style';
-	    if (arg1 === undefined) {
-	      return m_options[styleType];
-	    }
-	    if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return (m_options[styleType] || {})[arg1];
-	    }
-	    if (m_options[styleType] === undefined) {
-	      m_options[styleType] = {};
-	    }
-	    if (arg2 === undefined) {
-	      m_options[styleType] = $.extend(true, m_options[styleType], arg1);
-	    } else {
-	      m_options[styleType][arg1] = arg2;
-	    }
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  ['createStyle', 'editStyle', 'editHandleStyle', 'labelStyle', 'highlightStyle'
-	  ].forEach(function (styleType) {
-	    /**
-	     * Set or get a specific style type.
-	     *
-	     * @param {string|object} [arg1] If `undefined`, return the current style
-	     *    object.  If a string and `arg2` is undefined, return the style
-	     *    associated with the specified key.  If a string and `arg2` is defined,
-	     *    set the named style to the specified value.  Otherwise, extend the
-	     *    current style with the values in the specified object.
-	     * @param {*} [arg2] If `arg1` is a string, the new value for that style.
-	     * @returns {object|this} Either the entire style object, the value of a
-	     *    specific style, or the current class instance.
-	     */
-	    m_this[styleType] = function (arg1, arg2) {
-	      return m_this.style(arg1, arg2, styleType);
-	    };
-	  });
-
-	  /**
-	   * Return the style dictionary for a particular state.
-	   * @param {string} [state] The state to return styles for.  Defaults to the
-	   *    current state.
-	   * @returns {object} The style object for the state.  If there is no such
-	   *    style defined, the default style is used.
-	   */
-	  this.styleForState = function (state) {
-	    state = state || m_this.state();
-	    /* for some states, fall back to the general style if they don't specify a
-	     * value explicitly. */
-	    if (state === annotationState.edit || state === annotationState.highlight) {
-	      return $.extend({}, m_options.style, m_options[state + 'Style']);
-	    }
-	    if (state === annotationState.create) {
-	      return $.extend({}, m_options.style, m_options.editStyle,
-	                      m_options[state + 'Style']);
-	    }
-	    return m_options[state + 'Style'] || m_options.style || {};
-	  };
-
-	  /**
-	   * Get the type of this annotation.
-	   *
-	   * @returns {string} The annotation type.
-	   */
-	  this.type = function () {
-	    return m_type;
-	  };
-
-	  /**
-	   * Get a list of renderable features for this annotation.  The list index is
-	   * functionally a z-index for the feature.  Each entry is a dictionary with
-	   * the key as the feature name (such as `line`, `quad`, or `polygon`), and
-	   * the value a dictionary of values to pass to the feature constructor, such
-	   * as `style` and `coordinates`.
-	   *
-	   * @returns {array} An array of features.
-	   */
-	  this.features = function () {
-	    return [];
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation.  If the event is processed,
-	   * evt.handled should be set to `true` to prevent further processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClick = function (evt) {
-	    return undefined;
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation when in edit mode.  If the event
-	   * is processed, evt.handled should be set to `true` to prevent further
-	   * processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClickEdit = function (evt) {
-	    return undefined;
-	  };
-
-	  /**
-	   * Handle a mouse move on this annotation.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   * @returns {boolean} Truthy to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.mouseMove = function (evt) {
-	    return undefined;
-	  };
-
-	  /**
-	   * Get or set coordinates associated with this annotation in the map gcs
-	   * coordinate system.
-	   *
-	   * @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
-	   *  to set.
-	   * @returns {geo.geoPosition[]} The current array of coordinates.
-	   */
-	  this._coordinates = function (coordinates) {
-	    return [];
-	  };
-
-	  /**
-	   * Get coordinates associated with this annotation.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.geoPosition[]} An array of coordinates.
-	   */
-	  this.coordinates = function (gcs) {
-	    var coord = m_this._coordinates() || [];
-	    if (m_this.layer()) {
-	      var map = m_this.layer().map();
-	      gcs = (gcs === null ? map.gcs() : (
-	             gcs === undefined ? map.ingcs() : gcs));
-	      if (gcs !== map.gcs()) {
-	        coord = transform.transformCoordinates(map.gcs(), gcs, coord);
-	      }
-	    }
-	    return coord;
-	  };
-
-	  /**
-	   * Mark this annotation as modified.  This just marks the parent layer as
-	   * modified.
-	   *
-	   * @returns {this} The annotation.
-	   */
-	  this.modified = function () {
-	    if (m_this.layer()) {
-	      m_this.layer().modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Draw this annotation.  This just updates and draws the parent layer.
-	   *
-	   * @returns {this} The annotation.
-	   */
-	  this.draw = function () {
-	    if (m_this.layer()) {
-	      m_this.layer()._update();
-	      m_this.layer().draw();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Return a list of styles that should be preserved in a geojson
-	   * representation of the annotation.
-	   *
-	   * @returns {string[]} A list of style names to store.
-	   */
-	  this._geojsonStyles = function () {
-	    return [
-	      'closed', 'fill', 'fillColor', 'fillOpacity', 'lineCap', 'lineJoin',
-	      'radius', 'stroke', 'strokeColor', 'strokeOffset', 'strokeOpacity',
-	      'strokeWidth'];
-	  };
-
-	  /**
-	   * Return the coordinates to be stored in a geojson geometry object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {array} An array of flattened coordinates in the interface gcs
-	   *    coordinate system.  `undefined` if this annotation is incomplete.
-	   */
-	  this._geojsonCoordinates = function (gcs) {
-	    return [];
-	  };
-
-	  /**
-	   * Return the geometry type that is used to store this annotation in geojson.
-	   *
-	   * @returns {string} A geojson geometry type.
-	   */
-	  this._geojsonGeometryType = function () {
-	    return '';
-	  };
-
-	  /**
-	   * Return the annotation as a geojson object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @param {boolean} [includeCrs] If truthy, include the coordinate system.
-	   * @returns {object} The annotation as a geojson object, or `undefined` if it
-	   *    should not be represented (for instance, while it is being created).
-	   */
-	  this.geojson = function (gcs, includeCrs) {
-	    var coor = m_this._geojsonCoordinates(gcs),
-	        geotype = m_this._geojsonGeometryType(),
-	        styles = m_this._geojsonStyles(),
-	        objStyle = m_this.options('style') || {},
-	        objLabelStyle = m_this.labelStyle() || {},
-	        i, key, value;
-	    if (!coor || !coor.length || !geotype) {
-	      return;
-	    }
-	    var obj = {
-	      type: 'Feature',
-	      geometry: {
-	        type: geotype,
-	        coordinates: coor
-	      },
-	      properties: {
-	        annotationType: m_type,
-	        name: m_this.name(),
-	        annotationId: m_this.id()
-	      }
-	    };
-	    if (m_label) {
-	      obj.properties.label = m_label;
-	    }
-	    if (m_description) {
-	      obj.properties.description = m_description;
-	    }
-	    if (m_this.options('showLabel') === false) {
-	      obj.properties.showLabel = m_this.options('showLabel');
-	    }
-	    for (i = 0; i < styles.length; i += 1) {
-	      key = styles[i];
-	      value = util.ensureFunction(objStyle[key])();
-	      if (value !== undefined) {
-	        if (key.toLowerCase().match(/color$/)) {
-	          value = util.convertColorToHex(value, 'needed');
-	        }
-	        obj.properties[key] = value;
-	      }
-	    }
-	    for (i = 0; i < textFeature.usedStyles.length; i += 1) {
-	      key = textFeature.usedStyles[i];
-	      value = util.ensureFunction(objLabelStyle[key])();
-	      if (value !== undefined) {
-	        if (key.toLowerCase().match(/color$/)) {
-	          value = util.convertColorToHex(value, 'needed');
-	        }
-	        obj.properties['label' + key.charAt(0).toUpperCase() + key.slice(1)] = value;
-	      }
-	    }
-	    if (includeCrs) {
-	      var map = m_this.layer().map();
-	      gcs = (gcs === null ? map.gcs() : (
-	             gcs === undefined ? map.ingcs() : gcs));
-	      obj.crs = {
-	        type: 'name',
-	        properties: {
-	          type: 'proj4',
-	          name: gcs
-	        }
-	      };
-	    }
-	    return obj;
-	  };
-
-	  /**
-	   * Add edit handles to the feature list.
-	   *
-	   * @param {array} features The array of features to modify.
-	   * @param {geo.geoPosition[]} vertices An array of vertices in map gcs
-	   *    coordinates.
-	   * @param {object} [opts] If specified, the keys are the types of the
-	   *    handles.  This matches the `editHandleStyle.handle` object.  Any type
-	   *    that is set to `false` in either `opts` or `editHandleStyle.handle`
-	   *    will prevent those handles from being created.
-	   * @param {boolean} [isOpen=false] If true, no edge handle will be created
-	   *    between the last and first vertices.
-	   */
-	  this._addEditHandles = function (features, vertices, opts, isOpen) {
-	    var editPoints,
-	        style = $.extend({}, defaultEditHandleStyle, m_this.editHandleStyle()),
-	        handles = util.ensureFunction(style.handles)() || {},
-	        selected = (m_this._editHandle && m_this._editHandle.handle &&
-	                    m_this._editHandle.handle.selected ?
-	                    m_this._editHandle.handle : undefined);
-	    /* opts specify which handles are allowed.  They must be allowed by the
-	     * original opts object and by the editHandleStyle.handle object. */
-	    opts = $.extend({}, opts);
-	    Object.keys(handles).forEach(function (key) {
-	      if (handles[key] === false) {
-	        opts[key] = false;
-	      }
-	    });
-	    if (!features[editHandleFeatureLevel]) {
-	      features[editHandleFeatureLevel] = {point: []};
-	    }
-	    editPoints = features[editHandleFeatureLevel].point;
-	    vertices.forEach(function (pt, idx) {
-	      if (opts.vertex !== false) {
-	        editPoints.push($.extend({}, pt, {type: 'vertex', index: idx, style: style, editHandle: true}));
-	      }
-	      if (opts.edge !== false && idx !== vertices.length - 1 && (pt.x !== vertices[idx + 1].x || pt.y !== vertices[idx + 1].y)) {
-	        editPoints.push($.extend({
-	          x: (pt.x + vertices[idx + 1].x) / 2,
-	          y: (pt.y + vertices[idx + 1].y) / 2
-	        }, {type: 'edge', index: idx, style: style, editHandle: true}));
-	      }
-	      if (opts.edge !== false && !isOpen && idx === vertices.length - 1 && (pt.x !== vertices[0].x || pt.y !== vertices[0].y)) {
-	        editPoints.push($.extend({
-	          x: (pt.x + vertices[0].x) / 2,
-	          y: (pt.y + vertices[0].y) / 2
-	        }, {type: 'edge', index: idx, style: style, editHandle: true}));
-	      }
-	    });
-	    if (opts.center !== false) {
-	      editPoints.push($.extend({}, util.centerFromPerimeter(m_this._coordinates()), {type: 'center', style: style, editHandle: true}));
-	    }
-	    if (opts.rotate !== false) {
-	      editPoints.push($.extend(m_this._rotateHandlePosition(
-	        style.rotateHandleOffset,
-	        style.rotateHandleRotation + (selected && selected.type === 'rotate' ? m_this._editHandle.amountRotated : 0)
-	      ), {type: 'rotate', style: style, editHandle: true}));
-	      if (m_this._editHandle && (!selected || selected.type !== 'rotate')) {
-	        m_this._editHandle.amountRotated = 0;
-	      }
-	    }
-	    if (opts.resize !== false) {
-	      editPoints.push($.extend(m_this._rotateHandlePosition(
-	        style.resizeHandleOffset,
-	        style.resizeHandleRotation
-	      ), {type: 'resize', style: style, editHandle: true}));
-	    }
-	    if (selected) {
-	      editPoints.forEach(function (pt) {
-	        if (pt.type === selected.type && pt.index === selected.index) {
-	          pt.selected = true;
-	        }
-	      });
-	    }
-	  };
-
-	  /**
-	   * Process the edit center action for a general annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this._processEditActionCenter = function (evt) {
-	    var start = m_this._editHandle.startCoordinates,
-	        delta = {
-	          x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
-	          y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
-	        },
-	        curPts = m_this._coordinates();
-	    var pts = start.map(function (elem) {
-	      return {x: elem.x + delta.x, y: elem.y + delta.y};
-	    });
-	    if (pts[0].x !== curPts[0].x || pts[0].y !== curPts[0].y) {
-	      m_this._coordinates(pts);
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  /**
-	   * Process the edit rotate action for a general annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this._processEditActionRotate = function (evt) {
-	    var handle = m_this._editHandle,
-	        start = handle.startCoordinates,
-	        delta = {
-	          x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
-	          y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
-	        },
-	        ang1 = Math.atan2(
-	            handle.rotatePosition.y - handle.center.y,
-	            handle.rotatePosition.x - handle.center.x),
-	        ang2 = Math.atan2(
-	            handle.rotatePosition.y + delta.y - handle.center.y,
-	            handle.rotatePosition.x + delta.x - handle.center.x),
-	        ang = ang2 - ang1,
-	        curPts = m_this._coordinates();
-	    var pts = start.map(function (elem) {
-	      var delta = {x: elem.x - handle.center.x, y: elem.y - handle.center.y};
-	      return {
-	        x: delta.x * Math.cos(ang) - delta.y * Math.sin(ang) + handle.center.x,
-	        y: delta.x * Math.sin(ang) + delta.y * Math.cos(ang) + handle.center.y
-	      };
-	    });
-	    if (pts[0].x !== curPts[0].x || pts[0].y !== curPts[0].y) {
-	      m_this._coordinates(pts);
-	      handle.amountRotated = handle.startAmountRotated + ang;
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  /**
-	   * Process the edit resize action for a general annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this._processEditActionResize = function (evt) {
-	    var handle = m_this._editHandle,
-	        start = handle.startCoordinates,
-	        delta = {
-	          x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
-	          y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
-	        },
-	        map = m_this.layer().map(),
-	        p0 = map.gcsToDisplay(handle.center, null),
-	        p1 = map.gcsToDisplay(handle.resizePosition, null),
-	        p2 = map.gcsToDisplay({
-	          x: handle.resizePosition.x + delta.x,
-	          y: handle.resizePosition.y + delta.y
-	        }, null),
-	        d01 = Math.pow(Math.pow(p1.y - p0.y, 2) +
-	                       Math.pow(p1.x - p0.x, 2), 0.5) -
-	              handle.handle.style.resizeHandleOffset,
-	        d02 = Math.pow(Math.pow(p2.y - p0.y, 2) +
-	                       Math.pow(p2.x - p0.x, 2), 0.5) -
-	              handle.handle.style.resizeHandleOffset,
-	        curPts = m_this._coordinates();
-	    if (d02 && d01) {
-	      var scale = d02 / d01;
-	      var pts = start.map(function (elem) {
-	        return {
-	          x: (elem.x - handle.center.x) * scale + handle.center.x,
-	          y: (elem.y - handle.center.y) * scale + handle.center.y
-	        };
-	      });
-	      if (pts[0].x !== curPts[0].x || pts[0].y !== curPts[0].y) {
-	        m_this._coordinates(pts);
-	        return true;
-	      }
-	    }
-	    return false;
-	  };
-
-	  /**
-	   * Process the edit edge action for a general annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this._processEditActionEdge = function (evt) {
-	    var handle = m_this._editHandle,
-	        index = handle.handle.index,
-	        curPts = m_this._coordinates();
-	    curPts.splice(index + 1, 0, {x: handle.handle.x, y: handle.handle.y});
-	    handle.handle.type = 'vertex';
-	    handle.handle.index += 1;
-	    handle.startCoordinates = curPts.slice();
-	    m_this.modified();
-	    return true;
-	  };
-
-	  /**
-	   * Process the edit vertex action for a general annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @param {boolean} [canClose] if True, this annotation has a closed style
-	   *    that indicates if the first and last vertices are joined.  If falsy, is
-	   *    allowed to be changed to true.
-	   * @returns {boolean|string} `true` to update the annotation, `false` to
-	   *    prevent closure, any other falsy to not update anything.
-	   */
-	  this._processEditActionVertex = function (evt, canClose) {
-	    var handle = m_this._editHandle,
-	        index = handle.handle.index,
-	        start = handle.startCoordinates,
-	        curPts = m_this._coordinates(),
-	        origLen = curPts.length,
-	        origPt = curPts[index],
-	        delta = {
-	          x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
-	          y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
-	        },
-	        layer = m_this.layer(),
-	        aPP = layer.options('adjacentPointProximity'),
-	        near, atEnd;
-
-	    curPts[index] = {
-	      x: start[index].x + delta.x,
-	      y: start[index].y + delta.y
-	    };
-	    if (layer.displayDistance(curPts[index], null, start[index], null) <= aPP) {
-	      /* if we haven't moved at least aPP from where the vertex started, don't
-	       * allow it to be merged into another vertex.  This prevents small scale
-	       * edits from collapsing immediately. */
-	    } else if (layer.displayDistance(
-	        curPts[index], null,
-	        curPts[(index + 1) % curPts.length], null) <= aPP) {
-	      near = (index + 1) % curPts.length;
-	    } else if (layer.displayDistance(
-	        curPts[index], null,
-	        curPts[(index + curPts.length - 1) % curPts.length], null) <= aPP) {
-	      near = (index + curPts.length - 1) % curPts.length;
-	    }
-	    atEnd = ((near === 0 && index === curPts.length - 1) ||
-	             (near === curPts.length - 1 && index === 0));
-	    if (canClose === false && atEnd) {
-	      near = undefined;
-	    }
-	    if (near !== undefined && curPts.length > (canClose || m_this.options('style').closed ? 3 : 2)) {
-	      curPts[index] = {x: curPts[near].x, y: curPts[near].y};
-	      if (evt.event === geo_event.actionup) {
-	        if (canClose && atEnd) {
-	          m_this.options('style').closed = true;
-	        }
-	        curPts.splice(index, 1);
-	      }
-	    }
-	    if (curPts.length === origLen &&
-	        curPts[index].x === origPt.x && curPts[index].y === origPt.y) {
-	      return false;
-	    }
-	    m_this._coordinates(curPts);
-	    return true;
-	  };
-	};
-
-	/**
-	 * Rectangle annotation class.
-	 *
-	 * Rectangles are always rendered as polygons.  This could be changed -- if no
-	 * stroke is specified, the quad feature would be sufficient and work on more
-	 * renderers.
-	 *
-	 * @class
-	 * @alias geo.rectangleAnnotation
-	 * @extends geo.annotation
-	 *
-	 * @param {object?} [args] Options for the annotation.
-	 * @param {string} [args.name] A name for the annotation.  This defaults to
-	 *    the type with a unique ID suffixed to it.
-	 * @param {string} [args.state] initial annotation state.  One of the
-	 *    annotation.state values.
-	 * @param {boolean|string[]} [args.showLabel=true] `true` to show the
-	 *    annotation label on annotations in done or edit states.  Alternately, a
-	 *    list of states in which to show the label.  Falsy to not show the label.
-	 * @param {geo.geoPosition[]} [args.corners] A list of four corners in map
-	 *    gcs coordinates.  These must be in order around the perimeter of the
-	 *    rectangle (in either direction).
-	 * @param {geo.geoPosition[]} [args.coordinates] An alternate name for
-	 *    `args.corners`.
-	 * @param {object} [args.style] The style to apply to a finished rectangle.
-	 *    This uses styles for polygons, including `fill`, `fillColor`,
-	 *    `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
-	 *    `strokeOpacity`.
-	 * @param {object} [args.editStyle] The style to apply to a rectangle in edit
-	 *    mode.  This uses styles for polygons and lines, including `fill`,
-	 *    `fillColor`, `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
-	 *    `strokeOpacity`.
-	 */
-	var rectangleAnnotation = function (args) {
-	  'use strict';
-	  if (!(this instanceof rectangleAnnotation)) {
-	    return new rectangleAnnotation(args);
-	  }
-
-	  args = $.extend(true, {}, {
-	    style: {
-	      fill: true,
-	      fillColor: {r: 0, g: 1, b: 0},
-	      fillOpacity: 0.25,
-	      polygon: function (d) { return d.polygon; },
-	      stroke: true,
-	      strokeColor: {r: 0, g: 0, b: 0},
-	      strokeOpacity: 1,
-	      strokeWidth: 3,
-	      uniformPolygon: true
-	    },
-	    highlightStyle: {
-	      fillColor: {r: 0, g: 1, b: 1},
-	      fillOpacity: 0.5,
-	      strokeWidth: 5
-	    },
-	    createStyle: {
-	      fillColor: {r: 0.3, g: 0.3, b: 0.3},
-	      fillOpacity: 0.25,
-	      strokeColor: {r: 0, g: 0, b: 1}
-	    }
-	  }, args || {});
-	  args.corners = args.corners || args.coordinates || [];
-	  delete args.coordinates;
-	  annotation.call(this, 'rectangle', args);
-
-	  var m_this = this,
-	      s_actions = this.actions,
-	      s_processEditAction = this.processEditAction;
-
-	  /**
-	   * Return actions needed for the specified state of this annotation.
-	   *
-	   * @param {string} [state] The state to return actions for.  Defaults to
-	   *    the current state.
-	   * @returns {geo.actionRecord[]} A list of actions.
-	   */
-	  this.actions = function (state) {
-	    if (!state) {
-	      state = m_this.state();
-	    }
-	    switch (state) {
-	      case annotationState.create:
-	        return [{
-	          action: geo_action.annotation_rectangle,
-	          name: 'rectangle create',
-	          owner: annotationActionOwner,
-	          input: 'left',
-	          modifiers: {shift: false, ctrl: false},
-	          selectionRectangle: true
-	        }];
-	      default:
-	        return s_actions.apply(m_this, arguments);
-	    }
-	  };
-
-	  /**
-	   * Process any actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if the
-	   *    annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.processAction = function (evt) {
-	    var layer = m_this.layer();
-	    if (m_this.state() !== annotationState.create || !layer ||
-	        evt.event !== geo_event.actionselection ||
-	        evt.state.action !== geo_action.annotation_rectangle) {
-	      return;
-	    }
-	    var map = layer.map(),
-	        corners = [
-	          /* Keep in map gcs, not interface gcs to avoid wrapping issues */
-	          map.displayToGcs({x: evt.lowerLeft.x, y: evt.lowerLeft.y}, null),
-	          map.displayToGcs({x: evt.lowerLeft.x, y: evt.upperRight.y}, null),
-	          map.displayToGcs({x: evt.upperRight.x, y: evt.upperRight.y}, null),
-	          map.displayToGcs({x: evt.upperRight.x, y: evt.lowerLeft.y}, null)
-	        ];
-	    /* Don't keep rectangles that have nearly zero area in display pixels */
-	    if (layer.displayDistance(corners[0], null, corners[1], null) *
-	        layer.displayDistance(corners[0], null, corners[3], null) < 0.01) {
-	      return 'remove';
-	    }
-	    m_this.options('corners', corners);
-	    m_this.state(annotationState.done);
-	    return 'done';
-	  };
-
-	  /**
-	   * Get a list of renderable features for this annotation.
-	   *
-	   * @returns {array} An array of features.
-	   */
-	  this.features = function () {
-	    var opt = m_this.options(),
-	        state = m_this.state(),
-	        features;
-	    switch (state) {
-	      case annotationState.create:
-	        features = [];
-	        if (opt.corners && opt.corners.length >= 4) {
-	          features = [{
-	            polygon: {
-	              polygon: opt.corners,
-	              style: m_this.styleForState(state)
-	            }
-	          }];
-	        }
-	        break;
-	      default:
-	        features = [{
-	          polygon: {
-	            polygon: opt.corners,
-	            style: m_this.styleForState(state)
-	          }
-	        }];
-	        if (state === annotationState.edit) {
-	          m_this._addEditHandles(features, opt.corners);
-	        }
-	        break;
-	    }
-	    return features;
-	  };
-
-	  /**
-	   * Get coordinates associated with this annotation in the map gcs coordinate
-	   * system.
-	   *
-	   * @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
-	   *  to set.
-	   * @returns {geo.geoPosition[]} The current array of coordinates.
-	   */
-	  this._coordinates = function (coordinates) {
-	    if (coordinates && coordinates.length >= 4) {
-	      m_this.options('corners', coordinates.slice(0, 4));
-	      /* Should we ensure that the four points form a rectangle in the current
-	       * projection, though this might not be rectangular in another gcs? */
-	    }
-	    return m_this.options('corners');
-	  };
-
-	  /**
-	   * Return the coordinates to be stored in a geojson geometry object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {array} An array of flattened coordinates in the interface gcs
-	   *    coordinate system.  `undefined` if this annotation is incomplete.
-	   */
-	  this._geojsonCoordinates = function (gcs) {
-	    var src = m_this.coordinates(gcs);
-	    if (!src || m_this.state() === annotationState.create || src.length < 4) {
-	      return;
-	    }
-	    var coor = [];
-	    for (var i = 0; i < 4; i += 1) {
-	      coor.push([src[i].x, src[i].y]);
-	    }
-	    coor.push([src[0].x, src[0].y]);
-	    return [coor];
-	  };
-
-	  /**
-	   * Return the geometry type that is used to store this annotation in geojson.
-	   *
-	   * @returns {string} A geojson geometry type.
-	   */
-	  this._geojsonGeometryType = function () {
-	    return 'Polygon';
-	  };
-
-	  /**
-	   * Return a list of styles that should be preserved in a geojson
-	   * representation of the annotation.
-	   *
-	   * @returns {string[]} A list of style names to store.
-	   */
-	  this._geojsonStyles = function () {
-	    return [
-	      'fill', 'fillColor', 'fillOpacity', 'lineCap', 'lineJoin', 'stroke',
-	      'strokeColor', 'strokeOffset', 'strokeOpacity', 'strokeWidth'];
-	  };
-
-	  /**
-	   * Set three corners based on an initial corner and a mouse event.
-	   *
-	   * @param {geo.geoPosition} corners An array of four corners to update.
-	   * @param {geo.event} evt The mouse move event.
-	   */
-	  this._setCornersFromMouse = function (corners, evt) {
-	    var map = m_this.layer().map(),
-	        c0 = map.gcsToDisplay({x: corners[0].x, y: corners[0].y}, null),
-	        c2 = map.gcsToDisplay(evt.mapgcs, null),
-	        c1 = {x: c2.x, y: c0.y},
-	        c3 = {x: c0.x, y: c2.y};
-	    corners[2] = $.extend({}, evt.mapgcs);
-	    corners[1] = map.displayToGcs(c1, null);
-	    corners[3] = map.displayToGcs(c3, null);
-	  };
-
-	  /**
-	   * Handle a mouse move on this annotation.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   * @returns {boolean} Truthy to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.mouseMove = function (evt) {
-	    if (m_this.state() !== annotationState.create) {
-	      return;
-	    }
-	    var corners = m_this.options('corners');
-	    if (corners.length) {
-	      m_this._setCornersFromMouse(corners, evt);
-	      return true;
-	    }
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation.  If the event is processed,
-	   * evt.handled should be set to `true` to prevent further processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClick = function (evt) {
-	    var layer = m_this.layer();
-	    if (m_this.state() !== annotationState.create || !layer) {
-	      return;
-	    }
-	    if (!evt.buttonsDown.left && !evt.buttonsDown.right) {
-	      return;
-	    }
-	    var corners = m_this.options('corners');
-	    if (evt.buttonsDown.right && !corners.length) {
-	      return;
-	    }
-	    evt.handled = true;
-	    if (corners.length) {
-	      m_this._setCornersFromMouse(corners, evt);
-	      /* Don't keep rectangles that have nearly zero area in display pixels */
-	      if (layer.displayDistance(corners[0], null, corners[1], null) *
-	          layer.displayDistance(corners[0], null, corners[3], null) < 0.01) {
-	        return 'remove';
-	      }
-	      m_this.state(annotationState.done);
-	      return 'done';
-	    }
-	    if (evt.buttonsDown.left) {
-	      corners.push($.extend({}, evt.mapgcs));
-	      corners.push($.extend({}, evt.mapgcs));
-	      corners.push($.extend({}, evt.mapgcs));
-	      corners.push($.extend({}, evt.mapgcs));
-	      return true;
-	    }
-	  };
-
-	  /**
-	   * Process any edit actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.processEditAction = function (evt) {
-	    var start = m_this._editHandle.startCoordinates,
-	        delta = {
-	          x: evt.mouse.mapgcs.x - evt.state.origin.mapgcs.x,
-	          y: evt.mouse.mapgcs.y - evt.state.origin.mapgcs.y
-	        },
-	        type = m_this._editHandle.handle.type,
-	        index = m_this._editHandle.handle.index,
-	        ang = [
-	          Math.atan2(start[1].y - start[0].y, start[1].x - start[0].x),
-	          Math.atan2(start[2].y - start[1].y, start[2].x - start[1].x),
-	          Math.atan2(start[3].y - start[2].y, start[3].x - start[2].x),
-	          Math.atan2(start[0].y - start[3].y, start[0].x - start[3].x)
-	        ],
-	        corners, delta1, delta2, ang1, ang2;
-	    // an angle can be zero because it is horizontal or undefined.  If opposite
-	    // angles are both zero, this is a degenerate rectangle (a line or a point)
-	    if (!ang[0] && !ang[1] && !ang[2] && !ang[3]) {
-	      ang[1] = Math.PI / 2;
-	      ang[2] = Math.PI;
-	      ang[3] = -Math.PI / 2;
-	    }
-	    if (!ang[0] && !ang[2]) {
-	      ang[0] = ang[1] - Math.PI / 2;
-	      ang[2] = ang[1] + Math.PI / 2;
-	    }
-	    if (!ang[1] && !ang[3]) {
-	      ang[1] = ang[2] - Math.PI / 2;
-	      ang[3] = ang[2] + Math.PI / 2;
-	    }
-	    switch (type) {
-	      case 'vertex':
-	        corners = start.map(function (elem) {
-	          return {x: elem.x, y: elem.y};
-	        });
-	        ang1 = ang[(index + 1) % 4];
-	        delta1 = {
-	          x: (delta.x * Math.cos(ang1) + delta.y * Math.sin(ang1)) * Math.cos(ang1),
-	          y: (delta.y * Math.sin(ang1) + delta.x * Math.cos(ang1)) * Math.sin(ang1)
-	        };
-	        ang2 = ang[index];
-	        delta2 = {
-	          x: (delta.x * Math.cos(ang2) + delta.y * Math.sin(ang2)) * Math.cos(ang2),
-	          y: (delta.y * Math.sin(ang2) + delta.x * Math.cos(ang2)) * Math.sin(ang2)
-	        };
-	        corners[index].x += delta.x;
-	        corners[index].y += delta.y;
-	        corners[(index + 1) % 4].x += delta1.x;
-	        corners[(index + 1) % 4].y += delta1.y;
-	        corners[(index + 3) % 4].x += delta2.x;
-	        corners[(index + 3) % 4].y += delta2.y;
-	        m_this.options('corners', corners);
-	        return true;
-	      case 'edge':
-	        corners = start.map(function (elem) {
-	          return {x: elem.x, y: elem.y};
-	        });
-	        ang1 = ang[(index + 1) % 4];
-	        delta = {
-	          x: (delta.x * Math.cos(ang1) + delta.y * Math.sin(ang1)) * Math.cos(ang1),
-	          y: (delta.y * Math.sin(ang1) + delta.x * Math.cos(ang1)) * Math.sin(ang1)
-	        };
-	        corners[index].x += delta.x;
-	        corners[index].y += delta.y;
-	        corners[(index + 1) % 4].x += delta.x;
-	        corners[(index + 1) % 4].y += delta.y;
-	        m_this.options('corners', corners);
-	        return true;
-	    }
-	    return s_processEditAction.apply(m_this, arguments);
-	  };
-	};
-	inherit(rectangleAnnotation, annotation);
-
-	var rectangleRequiredFeatures = {};
-	rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
-	registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
-
-	/**
-	 * Polygon annotation class
-	 *
-	 * When complete, polygons are rendered as polygons.  During creation they are
-	 * rendered as lines and polygons.
-	 *
-	 * @class
-	 * @alias geo.polygonAnnotation
-	 * @extends geo.annotation
-	 *
-	 * @param {object?} [args] Options for the annotation.
-	 * @param {string} [args.name] A name for the annotation.  This defaults to
-	 *    the type with a unique ID suffixed to it.
-	 * @param {string} [args.state] initial annotation state.  One of the
-	 *    annotation.state values.
-	 * @param {boolean|string[]} [args.showLabel=true] `true` to show the
-	 *    annotation label on annotations in done or edit states.  Alternately, a
-	 *    list of states in which to show the label.  Falsy to not show the label.
-	 * @param {geo.geoPosition[]} [args.vertices] A list of vertices in map gcs
-	 *    coordinates.  These must be in order around the perimeter of the
-	 *    polygon (in either direction).
-	 * @param {geo.geoPosition[]} [args.coordinates] An alternate name for
-	 *    `args.vertices`.
-	 * @param {object} [args.style] The style to apply to a finished polygon.
-	 *    This uses styles for polygons, including `fill`, `fillColor`,
-	 *    `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
-	 *    `strokeOpacity`.
-	 * @param {object} [args.editStyle] The style to apply to a polygon in edit
-	 *    mode.  This uses styles for polygons and lines, including `fill`,
-	 *    `fillColor`, `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
-	 *    `strokeOpacity`.
-	 */
-	var polygonAnnotation = function (args) {
-	  'use strict';
-	  if (!(this instanceof polygonAnnotation)) {
-	    return new polygonAnnotation(args);
-	  }
-
-	  args = $.extend(true, {}, {
-	    style: {
-	      fill: true,
-	      fillColor: {r: 0, g: 1, b: 0},
-	      fillOpacity: 0.25,
-	      polygon: function (d) { return d.polygon; },
-	      stroke: true,
-	      strokeColor: {r: 0, g: 0, b: 0},
-	      strokeOpacity: 1,
-	      strokeWidth: 3,
-	      uniformPolygon: true
-	    },
-	    highlightStyle: {
-	      fillColor: {r: 0, g: 1, b: 1},
-	      fillOpacity: 0.5,
-	      strokeWidth: 5
-	    },
-	    createStyle: {
-	      closed: false,
-	      fillColor: {r: 0.3, g: 0.3, b: 0.3},
-	      fillOpacity: 0.25,
-	      line: function (d) {
-	        /* Return an array that has the same number of items as we have
-	         * vertices. */
-	        return Array.apply(null, Array(m_this.options('vertices').length)).map(
-	            function () { return d; });
-	      },
-	      position: function (d, i) {
-	        return m_this.options('vertices')[i];
-	      },
-	      stroke: false,
-	      strokeColor: {r: 0, g: 0, b: 1}
-	    }
-	  }, args || {});
-	  args.vertices = args.vertices || args.coordinates || [];
-	  delete args.coordinates;
-	  annotation.call(this, 'polygon', args);
-
-	  var m_this = this;
-
-	  /**
-	   * Get a list of renderable features for this annotation.  When the polygon
-	   * is done, this is just a single polygon.  During creation this can be a
-	   * polygon and line at z-levels 1 and 2.
-	   *
-	   * @returns {array} An array of features.
-	   */
-	  this.features = function () {
-	    var opt = m_this.options(),
-	        state = m_this.state(),
-	        style = m_this.styleForState(state),
-	        features;
-	    switch (state) {
-	      case annotationState.create:
-	        features = [];
-	        if (opt.vertices && opt.vertices.length >= 3) {
-	          features[1] = {
-	            polygon: {
-	              polygon: opt.vertices,
-	              style: style
-	            }
-	          };
-	        }
-	        if (opt.vertices && opt.vertices.length >= 2) {
-	          features[2] = {
-	            line: {
-	              line: opt.vertices,
-	              style: style
-	            }
-	          };
-	        }
-	        break;
-	      default:
-	        features = [{
-	          polygon: {
-	            polygon: opt.vertices,
-	            style: style
-	          }
-	        }];
-	        if (state === annotationState.edit) {
-	          m_this._addEditHandles(features, opt.vertices);
-	        }
-	        break;
-	    }
-	    return features;
-	  };
-
-	  /**
-	   * Get coordinates associated with this annotation in the map gcs coordinate
-	   * system.
-	   *
-	   * @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
-	   *  to set.
-	   * @returns {geo.geoPosition[]} The current array of coordinates.
-	   */
-	  this._coordinates = function (coordinates) {
-	    if (coordinates) {
-	      m_this.options('vertices', coordinates);
-	    }
-	    return m_this.options('vertices');
-	  };
-
-	  /**
-	   * Handle a mouse move on this annotation.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   * @returns {boolean} Truthy to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.mouseMove = function (evt) {
-	    if (m_this.state() !== annotationState.create) {
-	      return;
-	    }
-	    var vertices = m_this.options('vertices');
-	    if (vertices.length) {
-	      vertices[vertices.length - 1] = evt.mapgcs;
-	      return true;
-	    }
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation.  If the event is processed,
-	   * evt.handled should be set to `true` to prevent further processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClick = function (evt) {
-	    var layer = m_this.layer();
-	    if (m_this.state() !== annotationState.create || !layer) {
-	      return;
-	    }
-	    var end = !!evt.buttonsDown.right, skip;
-	    if (!evt.buttonsDown.left && !evt.buttonsDown.right) {
-	      return;
-	    }
-	    var vertices = m_this.options('vertices');
-	    if (evt.buttonsDown.right && !vertices.length) {
-	      return;
-	    }
-	    evt.handled = true;
-	    if (evt.buttonsDown.left) {
-	      if (vertices.length) {
-	        if (vertices.length >= 2 && layer.displayDistance(
-	            vertices[vertices.length - 2], null, evt.map, 'display') <=
-	            layer.options('adjacentPointProximity')) {
-	          skip = true;
-	          if (m_this._lastClick &&
-	              evt.time - m_this._lastClick < layer.options('dblClickTime')) {
-	            end = true;
-	          }
-	        } else if (vertices.length >= 2 && layer.displayDistance(
-	            vertices[0], null, evt.map, 'display') <=
-	            layer.options('finalPointProximity')) {
-	          end = true;
-	        } else {
-	          vertices[vertices.length - 1] = evt.mapgcs;
-	        }
-	      } else {
-	        vertices.push(evt.mapgcs);
-	      }
-	      if (!end && !skip) {
-	        vertices.push(evt.mapgcs);
-	      }
-	      m_this._lastClick = evt.time;
-	    }
-	    if (end) {
-	      if (vertices.length < 4) {
-	        return 'remove';
-	      }
-	      vertices.pop();
-	      m_this.state(annotationState.done);
-	      return 'done';
-	    }
-	    return (end || !skip);
-	  };
-
-	  /**
-	   * Return the coordinates to be stored in a geojson geometry object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {array} An array of flattened coordinates in the interface gcs
-	   *    coordinate system.  `undefined` if this annotation is incomplete.
-	   */
-	  this._geojsonCoordinates = function (gcs) {
-	    var src = m_this.coordinates(gcs);
-	    if (!src || src.length < 3 || m_this.state() === annotationState.create) {
-	      return;
-	    }
-	    var coor = [];
-	    for (var i = 0; i < src.length; i += 1) {
-	      coor.push([src[i].x, src[i].y]);
-	    }
-	    coor.push([src[0].x, src[0].y]);
-	    return [coor];
-	  };
-
-	  /**
-	   * Return the geometry type that is used to store this annotation in geojson.
-	   *
-	   * @returns {string} A geojson geometry type.
-	   */
-	  this._geojsonGeometryType = function () {
-	    return 'Polygon';
-	  };
-
-	  /**
-	   * Return a list of styles that should be preserved in a geojson
-	   * representation of the annotation.
-	   *
-	   * @returns {string[]} A list of style names to store.
-	   */
-	  this._geojsonStyles = function () {
-	    return [
-	      'fill', 'fillColor', 'fillOpacity', 'lineCap', 'lineJoin', 'stroke',
-	      'strokeColor', 'strokeOffset', 'strokeOpacity', 'strokeWidth'];
-	  };
-	};
-	inherit(polygonAnnotation, annotation);
-
-	var polygonRequiredFeatures = {};
-	polygonRequiredFeatures[polygonFeature.capabilities.feature] = true;
-	polygonRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
-	registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);
-
-	/**
-	 * Line annotation class.
-	 *
-	 * @class
-	 * @alias geo.lineAnnotation
-	 * @extends geo.annotation
-	 *
-	 * @param {object?} [args] Options for the annotation.
-	 * @param {string} [args.name] A name for the annotation.  This defaults to
-	 *    the type with a unique ID suffixed to it.
-	 * @param {string} [args.state] initial annotation state.  One of the
-	 *    annotation.state values.
-	 * @param {boolean|string[]} [args.showLabel=true] `true` to show the
-	 *    annotation label on annotations in done or edit states.  Alternately, a
-	 *    list of states in which to show the label.  Falsy to not show the label.
-	 * @param {geo.geoPosition[]} [args.vertices] A list of vertices in map gcs
-	 *    coordinates.
-	 * @param {geo.geoPosition[]} [args.coordinates] An alternate name for
-	 *    `args.corners`.
-	 * @param {object} [args.style] The style to apply to a finished line.
-	 *    This uses styles for lines, including `strokeWidth`, `strokeColor`,
-	 *    `strokeOpacity`, `strokeOffset`, `closed`, `lineCap`, and `lineJoin`.
-	 * @param {object} [args.editStyle] The style to apply to a line in edit
-	 *    mode.  This uses styles for lines, including `strokeWidth`,
-	 *    `strokeColor`, `strokeOpacity`, `strokeOffset`, `closed`, `lineCap`,
-	 *    and `lineJoin`.
-	 */
-	var lineAnnotation = function (args) {
-	  'use strict';
-	  if (!(this instanceof lineAnnotation)) {
-	    return new lineAnnotation(args);
-	  }
-
-	  args = $.extend(true, {}, {
-	    style: {
-	      line: function (d) {
-	        /* Return an array that has the same number of items as we have
-	         * vertices. */
-	        return Array.apply(null, Array(m_this.options('vertices').length)).map(
-	            function () { return d; });
-	      },
-	      position: function (d, i) {
-	        return m_this.options('vertices')[i];
-	      },
-	      strokeColor: {r: 0, g: 0, b: 0},
-	      strokeOpacity: 1,
-	      strokeWidth: 3,
-	      closed: false,
-	      lineCap: 'butt',
-	      lineJoin: 'miter'
-	    },
-	    highlightStyle: {
-	      strokeWidth: 5
-	    },
-	    createStyle: {
-	      line: function (d) {
-	        /* Return an array that has the same number of items as we have
-	         * vertices. */
-	        return Array.apply(null, Array(m_this.options('vertices').length)).map(
-	            function () { return d; });
-	      },
-	      position: function (d, i) {
-	        return m_this.options('vertices')[i];
-	      },
-	      strokeColor: {r: 0, g: 0, b: 1},
-	      strokeOpacity: 1,
-	      strokeWidth: 3,
-	      closed: false,
-	      lineCap: 'butt',
-	      lineJoin: 'miter'
-	    }
-	  }, args || {});
-	  args.vertices = args.vertices || args.coordinates || [];
-	  delete args.coordinates;
-	  annotation.call(this, 'line', args);
-
-	  var m_this = this,
-	      s_actions = this.actions,
-	      s_processEditAction = this.processEditAction;
-
-	  /**
-	   * Get a list of renderable features for this annotation.
-	   *
-	   * @returns {array} An array of features.
-	   */
-	  this.features = function () {
-	    var opt = m_this.options(),
-	        state = m_this.state(),
-	        features;
-	    switch (state) {
-	      case annotationState.create:
-	        features = [{
-	          line: {
-	            line: opt.vertices,
-	            style: m_this.styleForState(state)
-	          }
-	        }];
-	        break;
-	      default:
-	        features = [{
-	          line: {
-	            line: opt.vertices,
-	            style: m_this.styleForState(state)
-	          }
-	        }];
-	        if (state === annotationState.edit) {
-	          m_this._addEditHandles(features, opt.vertices, undefined, !m_this.style('closed'));
-	        }
-	        break;
-	    }
-	    return features;
-	  };
-
-	  /**
-	   * Get coordinates associated with this annotation in the map gcs coordinate
-	   * system.
-	   *
-	   * @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
-	   *  to set.
-	   * @returns {geo.geoPosition[]} The current array of coordinates.
-	   */
-	  this._coordinates = function (coordinates) {
-	    if (coordinates) {
-	      m_this.options('vertices', coordinates);
-	    }
-	    return m_this.options('vertices');
-	  };
-
-	  /**
-	   * Handle a mouse move on this annotation.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   * @returns {boolean} Truthy to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.mouseMove = function (evt) {
-	    if (m_this.state() !== annotationState.create) {
-	      return;
-	    }
-	    var vertices = m_this.options('vertices');
-	    if (vertices.length) {
-	      vertices[vertices.length - 1] = evt.mapgcs;
-	      return true;
-	    }
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation.  If the event is processed,
-	   * evt.handled should be set to `true` to prevent further processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClick = function (evt) {
-	    var layer = m_this.layer();
-	    if (m_this.state() !== annotationState.create || !layer) {
-	      return;
-	    }
-	    var end = !!evt.buttonsDown.right, skip;
-	    if (!evt.buttonsDown.left && !evt.buttonsDown.right) {
-	      return;
-	    }
-	    var vertices = m_this.options('vertices');
-	    if (evt.buttonsDown.right && !vertices.length) {
-	      return;
-	    }
-	    evt.handled = true;
-	    if (evt.buttonsDown.left) {
-	      if (vertices.length) {
-	        if (vertices.length >= 2 && layer.displayDistance(
-	            vertices[vertices.length - 2], null, evt.map, 'display') <=
-	            layer.options('adjacentPointProximity')) {
-	          skip = true;
-	          if (m_this._lastClick &&
-	              evt.time - m_this._lastClick < layer.options('dblClickTime')) {
-	            end = true;
-	          }
-	        } else if (vertices.length >= 2 && layer.displayDistance(
-	            vertices[0], null, evt.map, 'display') <=
-	            layer.options('finalPointProximity')) {
-	          end = 'close';
-	        } else {
-	          vertices[vertices.length - 1] = evt.mapgcs;
-	        }
-	      } else {
-	        vertices.push(evt.mapgcs);
-	      }
-	      if (!end && !skip) {
-	        vertices.push(evt.mapgcs);
-	      }
-	      m_this._lastClick = evt.time;
-	      m_this._lastClickVertexCount = vertices.length;
-	    }
-	    if (end) {
-	      if (vertices.length < 3) {
-	        return 'remove';
-	      }
-	      vertices.pop();
-	      m_this.options('style').closed = end === 'close';
-	      m_this.state(annotationState.done);
-	      return 'done';
-	    }
-	    return (end || !skip);
-	  };
-
-	  /**
-	   * Return actions needed for the specified state of this annotation.
-	   *
-	   * @param {string} [state] The state to return actions for.  Defaults to
-	   *    the current state.
-	   * @returns {geo.actionRecord[]} A list of actions.
-	   */
-	  this.actions = function (state) {
-	    if (!state) {
-	      state = m_this.state();
-	    }
-	    switch (state) {
-	      case annotationState.create:
-	        return [{
-	          action: geo_action.annotation_line,
-	          name: 'line create',
-	          owner: annotationActionOwner,
-	          input: 'left',
-	          modifiers: {shift: false, ctrl: false}
-	        }, {
-	          action: geo_action.annotation_line,
-	          name: 'line create',
-	          owner: annotationActionOwner,
-	          input: 'pan'
-	        }];
-	      default:
-	        return s_actions.apply(m_this, arguments);
-	    }
-	  };
-
-	  /**
-	   * Process any actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if the
-	   *    annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.processAction = function (evt) {
-	    var layer = m_this.layer();
-	    if (m_this.state() !== annotationState.create || !layer ||
-	        evt.state.action !== geo_action.annotation_line) {
-	      return;
-	    }
-	    var cpp = layer.options('continuousPointProximity');
-	    var cpc = layer.options('continuousPointColinearity');
-	    if (cpp || cpp === 0) {
-	      var vertices = m_this.options('vertices');
-	      if (!vertices.length) {
-	        vertices.push(evt.mouse.mapgcs);
-	        vertices.push(evt.mouse.mapgcs);
-	        return true;
-	      }
-	      var dist = layer.displayDistance(vertices[vertices.length - 2], null, evt.mouse.map, 'display');
-	      if (dist && dist > cpp) {
-	        // combine nearly colinear points
-	        if (vertices.length >= (m_this._lastClickVertexCount || 1) + 3) {
-	          var d01 = layer.displayDistance(vertices[vertices.length - 3], null, vertices[vertices.length - 2], null),
-	              d12 = dist,
-	              d02 = layer.displayDistance(vertices[vertices.length - 3], null, evt.mouse.map, 'display');
-	          if (d01 && d02) {
-	            var costheta = (d02 * d02 - d01 * d01 - d12 * d12) / (2 * d01 * d12);
-	            if (costheta > Math.cos(cpc)) {
-	              vertices.pop();
-	            }
-	          }
-	        }
-	        vertices[vertices.length - 1] = evt.mouse.mapgcs;
-	        vertices.push(evt.mouse.mapgcs);
-	        return true;
-	      }
-	    }
-	  };
-
-	  /**
-	   * Return the coordinates to be stored in a geojson geometry object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {array} An array of flattened coordinates in the interface gcs
-	   *    coordinate system.  `undefined` if this annotation is incomplete.
-	   */
-	  this._geojsonCoordinates = function (gcs) {
-	    var src = m_this.coordinates(gcs);
-	    if (!src || src.length < 2 || m_this.state() === annotationState.create) {
-	      return;
-	    }
-	    var coor = [];
-	    for (var i = 0; i < src.length; i += 1) {
-	      coor.push([src[i].x, src[i].y]);
-	    }
-	    return coor;
-	  };
-
-	  /**
-	   * Return the geometry type that is used to store this annotation in geojson.
-	   *
-	   * @returns {string} A geojson geometry type.
-	   */
-	  this._geojsonGeometryType = function () {
-	    return 'LineString';
-	  };
-
-	  /**
-	   * Return a list of styles that should be preserved in a geojson
-	   * representation of the annotation.
-	   *
-	   * @returns {string[]} A list of style names to store.
-	   */
-	  this._geojsonStyles = function () {
-	    return [
-	      'closed', 'lineCap', 'lineJoin', 'strokeColor', 'strokeOffset',
-	      'strokeOpacity', 'strokeWidth'];
-	  };
-
-	  /**
-	   * Process any edit actions for this annotation.
-	   *
-	   * @param {geo.event} evt The action event.
-	   * @returns {boolean|string} `true` to update the annotation, falsy to not
-	   *    update anything.
-	   */
-	  this.processEditAction = function (evt) {
-	    switch (m_this._editHandle.handle.type) {
-	      case 'vertex':
-	        return m_this._processEditActionVertex(evt, true);
-	    }
-	    return s_processEditAction.apply(m_this, arguments);
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation when in edit mode.  If the event
-	   * is processed, evt.handled should be set to `true` to prevent further
-	   * processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClickEdit = function (evt) {
-	    // if we get a left double click on an edge on a closed line, break the
-	    // line at that edge
-	    var layer = m_this.layer(),
-	        handle = m_this._editHandle,
-	        split;
-	    // ensure we are in edit mode and this is a left click
-	    if (m_this.state() !== annotationState.edit || !layer || !evt.buttonsDown.left) {
-	      return;
-	    }
-	    // ensure this is an edge on a closed line
-	    if (!handle || !handle.handle.selected || handle.handle.type !== 'edge' || !m_this.options('style').closed) {
-	      return;
-	    }
-	    evt.handled = true;
-	    if (m_this._lastClick && evt.time - m_this._lastClick < layer.options('dblClickTime')) {
-	      split = true;
-	    }
-	    m_this._lastClick = evt.time;
-	    if (split) {
-	      var index = handle.handle.index,
-	          curPts = m_this._coordinates(),
-	          pts = curPts.slice(index + 1).concat(curPts.slice(0, index + 1));
-	      m_this._coordinates(pts);
-	      m_this.options('style').closed = false;
-	      handle.handle.index = undefined;
-	      return true;
-	    }
-	  };
-
-	};
-	inherit(lineAnnotation, annotation);
-
-	var lineRequiredFeatures = {};
-	lineRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
-	registerAnnotation('line', lineAnnotation, lineRequiredFeatures);
-
-	/**
-	 * Point annotation class.
-	 *
-	 * @class
-	 * @alias geo.poinyAnnotation
-	 * @extends geo.annotation
-	 *
-	 * @param {object?} [args] Options for the annotation.
-	 * @param {string} [args.name] A name for the annotation.  This defaults to
-	 *    the type with a unique ID suffixed to it.
-	 * @param {string} [args.state] initial annotation state.  One of the
-	 *    annotation.state values.
-	 * @param {boolean|string[]} [args.showLabel=true] `true` to show the
-	 *    annotation label on annotations in done or edit states.  Alternately, a
-	 *    list of states in which to show the label.  Falsy to not show the label.
-	 * @param {geo.geoPosition} [args.position] A coordinate in map gcs
-	 *    coordinates.
-	 * @param {geo.geoPosition[]} [args.coordinates] An array with one coordinate
-	 *  to use in place of `args.position`.
-	 * @param {object} [args.style] The style to apply to a finished point.
-	 *    This uses styles for points, including `radius`, `fill`, `fillColor`,
-	 *    `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, `strokeOpacity`,
-	 *    and `scaled`.  If `scaled` is `false`, the point is not scaled with
-	 *    zoom level.  If it is `true`, the radius is based on the zoom level at
-	 *    first instantiation.  Otherwise, if it is a number, the radius is used
-	 *    at that zoom level.
-	 * @param {object} [args.editStyle] The style to apply to a point in edit
-	 *    mode.
-	 */
-	var pointAnnotation = function (args) {
-	  'use strict';
-	  if (!(this instanceof pointAnnotation)) {
-	    return new pointAnnotation(args);
-	  }
-
-	  args = $.extend(true, {}, {
-	    style: {
-	      fill: true,
-	      fillColor: {r: 0, g: 1, b: 0},
-	      fillOpacity: 0.25,
-	      radius: 10,
-	      scaled: false,
-	      stroke: true,
-	      strokeColor: {r: 0, g: 0, b: 0},
-	      strokeOpacity: 1,
-	      strokeWidth: 3
-	    },
-	    createStyle: {
-	      fillColor: {r: 0.3, g: 0.3, b: 0.3},
-	      fillOpacity: 0.25,
-	      strokeColor: {r: 0, g: 0, b: 1}
-	    },
-	    highlightStyle: {
-	      fillColor: {r: 0, g: 1, b: 1},
-	      fillOpacity: 0.5,
-	      strokeWidth: 5
-	    }
-	  }, args || {});
-	  args.position = args.position || (args.coordinates ? args.coordinates[0] : undefined);
-	  delete args.coordinates;
-	  annotation.call(this, 'point', args);
-
-	  var m_this = this;
-
-	  /**
-	   * Get a list of renderable features for this annotation.
-	   *
-	   * @returns {array} An array of features.
-	   */
-	  this.features = function () {
-	    var opt = m_this.options(),
-	        state = m_this.state(),
-	        features, style, scaleOnZoom;
-	    switch (state) {
-	      case annotationState.create:
-	        features = [];
-	        break;
-	      default:
-	        style = m_this.styleForState(state);
-	        if (opt.style.scaled || opt.style.scaled === 0) {
-	          if (opt.style.scaled === true) {
-	            opt.style.scaled = m_this.layer().map().zoom();
-	          }
-	          style = $.extend({}, style, {
-	            radius: function () {
-	              var radius = opt.style.radius,
-	                  zoom = m_this.layer().map().zoom();
-	              if (util.isFunction(radius)) {
-	                radius = radius.apply(m_this, arguments);
-	              }
-	              radius *= Math.pow(2, zoom - opt.style.scaled);
-	              return radius;
-	            }
-	          });
-	          scaleOnZoom = true;
-	        }
-	        features = [{
-	          point: {
-	            x: opt.position.x,
-	            y: opt.position.y,
-	            style: style,
-	            scaleOnZoom: scaleOnZoom
-	          }
-	        }];
-	        if (state === annotationState.edit) {
-	          m_this._addEditHandles(
-	            features, [opt.position],
-	            {edge: false, center: false, resize: false, rotate: false});
-	        }
-	        break;
-	    }
-	    return features;
-	  };
-
-	  /**
-	   * Get coordinates associated with this annotation in the map gcs coordinate
-	   * system.
-	   *
-	   * @param {geo.geoPosition[]} [coordinates] An optional array of coordinates
-	   *  to set.
-	   * @returns {geo.geoPosition[]} The current array of coordinates.
-	   */
-	  this._coordinates = function (coordinates) {
-	    if (coordinates && coordinates.length >= 1) {
-	      m_this.options('position', coordinates[0]);
-	    }
-	    if (m_this.state() === annotationState.create) {
-	      return [];
-	    }
-	    return [m_this.options('position')];
-	  };
-
-	  /**
-	   * Handle a mouse click on this annotation.  If the event is processed,
-	   * evt.handled should be set to `true` to prevent further processing.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   * @returns {boolean|string} `true` to update the annotation, `'done'` if
-	   *    the annotation was completed (changed from create to done state),
-	   *    `'remove'` if the annotation should be removed, falsy to not update
-	   *    anything.
-	   */
-	  this.mouseClick = function (evt) {
-	    if (m_this.state() !== annotationState.create) {
-	      return;
-	    }
-	    if (!evt.buttonsDown.left) {
-	      return;
-	    }
-	    evt.handled = true;
-	    m_this.options('position', evt.mapgcs);
-	    m_this.state(annotationState.done);
-	    return 'done';
-	  };
-
-	  /**
-	   * Return a list of styles that should be preserved in a geojson
-	   * representation of the annotation.
-	   *
-	   * @returns {string[]} A list of style names to store.
-	   */
-	  this._geojsonStyles = function () {
-	    return [
-	      'fill', 'fillColor', 'fillOpacity', 'radius', 'scaled', 'stroke',
-	      'strokeColor', 'strokeOpacity', 'strokeWidth'];
-	  };
-
-	  /**
-	   * Return the coordinates to be stored in a geojson geometry object.
-	   *
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {array} An array of flattened coordinates in the interface gcs
-	   *    coordinate system.  `undefined` if this annotation is incomplete.
-	   */
-	  this._geojsonCoordinates = function (gcs) {
-	    var src = m_this.coordinates(gcs);
-	    if (!src || m_this.state() === annotationState.create || src.length < 1 || src[0] === undefined) {
-	      return;
-	    }
-	    return [src[0].x, src[0].y];
-	  };
-
-	  /**
-	   * Return the geometry type that is used to store this annotation in geojson.
-	   *
-	   * @returns {string} A geojson geometry type.
-	   */
-	  this._geojsonGeometryType = function () {
-	    return 'Point';
-	  };
-	};
-	inherit(pointAnnotation, annotation);
-
-	var pointRequiredFeatures = {};
-	pointRequiredFeatures[pointFeature.capabilities.feature] = true;
-	registerAnnotation('point', pointAnnotation, pointRequiredFeatures);
-
-	module.exports = {
-	  state: annotationState,
-	  actionOwner: annotationActionOwner,
-	  annotation: annotation,
-	  lineAnnotation: lineAnnotation,
-	  pointAnnotation: pointAnnotation,
-	  polygonAnnotation: polygonAnnotation,
-	  rectangleAnnotation: rectangleAnnotation,
-	  _editHandleFeatureLevel: editHandleFeatureLevel
-	};
-
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports) {
-
-	function newfunc() {
-	  return function () {};
-	}
-
-	/**
-	 * Convenient function to define JS inheritance.
-	 *
-	 * @param {object} C Child class instance.
-	 * @param {object} P Parent class instance.
-	 */
-	module.exports = function (C, P) {
-	  var F = newfunc();
-	  F.prototype = P.prototype;
-	  C.prototype = new F();
-	  C.prototype.constructor = C;
-	};
-
-
-/***/ }),
-/* 9 */
-/***/ (function(module, exports) {
-
-	/**
-	 * Common object containing all event types that are provided by the GeoJS
-	 * API.  Each property contained here is a valid target for event handling
-	 * via {@link geo.object#geoOn}.  The event object provided to handlers is
-	 * different for each event type.  Each handler is generally called with the
-	 * `this` context being the class that caused the event.<br>
-	 * <br>
-	 * The following properties are common to all event objects:
-	 *
-	 * @namespace
-	 * @alias geo.event
-	 * @type {object}
-	 * @property {string} event The event type that was triggered.
-	 * @property {object} geo A universal event object for controlling propagation.
-	 *
-	 * @example
-	 * map.geoOn(geo.event.layerAdd, function (event) {
-	 *   // event is an object with type: {@link geo.event.layerAdd}
-	 * });
-	 *
-	 */
-	var geo_event = {};
-
-	/*
-	 * Event types
-	 */
-
-	/**
-	 * Triggered when a layer is added to the map.
-	 *
-	 * @event geo.event.layerAdd
-	 * @type {object}
-	 * @property {geo.map} target The current map.
-	 * @property {geo.layer} layer The new layer that was added.
-	 */
-	geo_event.layerAdd = 'geo_layerAdd';
-
-	/**
-	 * Triggered when a layer is removed from the map.
-	 *
-	 * @event geo.event.layerRemove
-	 * @type {object}
-	 * @property {geo.map} target The current map.
-	 * @property {geo.layer} layer The old layer that was removed.
-	 */
-	geo_event.layerRemove = 'geo_layerRemove';
-
-	/**
-	 * Triggered when the map's zoom level is changed.
-	 *
-	 * @event geo.event.zoom
-	 * @type {object}
-	 * @property {number} zoomLevel New zoom level.
-	 * @property {geo.screenPosition} screenPosition The screen position of the
-	 *      mouse pointer.
-	 */
-	geo_event.zoom = 'geo_zoom';
-
-	/**
-	 * Triggered when the map is rotated around the current map center (pointing
-	 * downward so that positive angles are clockwise rotations).
-	 *
-	 * @event geo.event.rotate
-	 * @type {object}
-	 * @property {number} rotation The angle of the rotation in radians.  This is
-	 *      the map's complete rotation, not a delta.
-	 * @property {geo.screenPosition} screenPosition The screen position of the
-	 *      mouse pointer.
-	 */
-	geo_event.rotate = 'geo_rotate';
-
-	/**
-	 * Triggered when the map is panned either by user interaction or map
-	 * transition.
-	 *
-	 * @event geo.event.pan
-	 * @type {object}
-	 * @property {object} screenDelta The number of pixels of the pan.
-	 * @property {number} screenDelta.x Horizontal pan distance in pixels.
-	 * @property {number} screenDelta.y Vertical pan distance in pixels.
-	 */
-	geo_event.pan = 'geo_pan';
-
-	/**
-	 * Triggered when the map's canvas is resized.
-	 *
-	 * @event geo.event.resize
-	 * @type {object}
-	 * @property {geo.map} target The map that was resized.
-	 * @property {number} width The new width in pixels.
-	 * @property {number} height The new height in pixels.
-	 */
-	geo_event.resize = 'geo_resize';
-
-	/**
-	 * Triggered on every call to {@link geo.map#draw} before the map is rendered.
-	 *
-	 * @event geo.event.draw
-	 * @type {object}
-	 * @property {geo.map} target The current map.
-	 */
-	geo_event.draw = 'geo_draw';
-
-	/**
-	 * Triggered on every call to {@link geo.map#draw} after the map is rendered.
-	 *
-	 * @event geo.event.drawEnd
-	 * @type {object}
-	 * @property {geo.map} target The current map.
-	 */
-	geo_event.drawEnd = 'geo_drawEnd';
-
-	/**
-	 * Triggered on every `mousemove` over the map's DOM element unless a click
-	 * might occur.  The event object extends {@link geo.mouseState}.
-	 *
-	 * @event geo.event.mousemove
-	 */
-	geo_event.mousemove = 'geo_mousemove';
-
-	/**
-	 * Triggered on `mouseup` events that happen soon enough and close enough to a
-	 * `mousedown` event.  The event object extends {@link geo.mouseState}.
-	 *
-	 * @event geo.event.mouseclick
-	 * @property {geo.mouseButtons} buttonsDown The buttons that were down at the
-	 *      start of the click action.
-	 */
-	geo_event.mouseclick = 'geo_mouseclick';
-
-	/**
-	 * Triggered on every `mousemove` during a brushing selection.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.brush
-	 */
-	geo_event.brush = 'geo_brush';
-
-	/**
-	 * Triggered after a brush selection ends.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.brushend
-	 */
-	geo_event.brushend = 'geo_brushend';
-
-	/**
-	 * Triggered when a brush selection starts.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.brushstart
-	 */
-	geo_event.brushstart = 'geo_brushstart';
-
-	/**
-	 * Triggered when brushing results in a selection.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.select
-	 */
-	geo_event.select = 'geo_select';
-
-	/**
-	 * Triggered when brushing results in a zoom selection.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.zoomselect
-	 */
-	geo_event.zoomselect = 'geo_zoomselect';
-
-	/**
-	 * Triggered when brushing results in a zoom-out selection.
-	 * The event object extends {@link geo.brushSelection}.
-	 *
-	 * @event geo.event.unzoomselect
-	 */
-
-	geo_event.unzoomselect = 'geo_unzoomselect';
-
-	/**
-	 * Triggered when an action is initiated with mouse down.
-	 *
-	 * @event geo.event.actiondown
-	 * @property {geo.actionState} state The action state.
-	 * @property {geo.mouseState} mouse The mouse state.
-	 * @property {jQuery.Event} event The triggering jQuery event.
-	 */
-	geo_event.actiondown = 'geo_actiondown';
-
-	/**
-	 * Triggered when an action is being processed during mouse movement.
-	 *
-	 * @event geo.event.actionmove
-	 * @property {geo.actionState} state The action state.
-	 * @property {geo.mouseState} mouse The mouse state.
-	 * @property {jQuery.Event} event The triggering event.
-	 */
-	geo_event.actionmove = 'geo_actionmove';
-
-	/**
-	 * Triggered when an action is ended with a mouse up.
-	 *
-	 * @event geo.event.actionup
-	 * @property {geo.actionState} state The action state.
-	 * @property {geo.mouseState} mouse The mouse state.
-	 * @property {jQuery.Event} event The triggering event.
-	 */
-	geo_event.actionup = 'geo_actionup';
-
-	/**
-	 * Triggered when an action results in a selection.
-	 *
-	 * @event geo.event.actionselection
-	 * @property {geo.actionState} state The action state.
-	 * @property {geo.mouseState} mouse The mouse state.
-	 * @property {jQuery.Event} event The triggering event.
-	 * @property {geo.screenPosition} lowerLeft Lower left of selection in screen
-	 *      coordinates.
-	 * @property {geo.screenPosition} upperRight Upper right of selection in screen
-	 *      coordinates.
-	 */
-	geo_event.actionselection = 'geo_actionselection';
-
-	/**
-	 * Triggered when an action is triggered with a mouse wheel event.
-	 *
-	 * @event geo.event.actionwheel
-	 * @property {geo.actionState} state The action state.
-	 * @property {geo.mouseState} mouse The mouse state.
-	 * @property {jQuery.Event} event The triggering event.
-	 */
-	geo_event.actionwheel = 'geo_actionwheel';
-
-	/**
-	 * Triggered when an action is triggered via the keyboard.
-	 *
-	 * @event geo.event.keyaction
-	 * @property {object} move The movement that would happen if the action is
-	 *      passed through.
-	 * @property {number} [move.zoomDelta] A change in the zoom level.
-	 * @property {number} [move.zoom] A new zoom level.
-	 * @property {number} [move.rotationDelta] A change in the rotation in radians.
-	 * @property {number} [move.rotation] A new absolute rotation in radians.
-	 * @property {number} [move.panX] A horizontal shift in display pixels.
-	 * @property {number} [move.panY] A vertical shift in display pixels.
-	 * @property {boolean} [move.cancel] Set to `true` to cancel the entire
-	 *      movement.
-	 * @property {string} action Action based on key
-	 * @property {number} factor Factor based on metakeys [0-2].  0 means a small
-	 *      movement is preferred, 1 a medium movement, and 2 a large movement.
-	 * @property {jQuery.Event} event The triggering event
-	 */
-	geo_event.keyaction = 'geo_keyaction';
-
-	/**
-	 * Triggered before a map navigation animation begins.  Set
-	 * `event.geo.cancelAnimation` to cancel the animation of the navigation.  This
-	 * will cause the map to navigate to the target location immediately.  Set
-	 * `event.geo.cancelNavigation` to cancel the navigation completely.  The
-	 * transition options can be modified in place.
-	 *
-	 * @event geo.event.transitionstart
-	 * @type {object}
-	 * @property {geo.geoPosition} center The target center.
-	 * @property {number} zoom The target zoom level.
-	 * @property {number} duration The duration of the transition in milliseconds.
-	 * @property {function} ease The easing function.
-	 */
-	geo_event.transitionstart = 'geo_transitionstart';
-
-	/**
-	 * Triggered after a map navigation animation ends.
-	 *
-	 * @event geo.event.transitionend
-	 * @type {object}
-	 * @property {geo.geoPosition} center The target center.
-	 * @property {number} zoom The target zoom level.
-	 * @property {number} duration The duration of the transition in milliseconds.
-	 * @property {function} ease The easing function.
-	 */
-	geo_event.transitionend = 'geo_transitionend';
-
-	/**
-	 * Triggered if a map navigation animation is canceled.
-	 *
-	 * @event geo.event.transitioncancel
-	 * @type {object}
-	 * @property {geo.geoPosition} center The target center.
-	 * @property {number} zoom The target zoom level.
-	 * @property {number} duration The duration of the transition in milliseconds.
-	 * @property {function} ease The easing function.
-	 */
-	geo_event.transitioncancel = 'geo_transitioncancel';
-
-	/**
-	 * Triggered when the parallel projection mode is changes.
-	 *
-	 * @event geo.event.parallelprojection
-	 * @type {object}
-	 * @property {boolean} paralellProjection `true` if parallel projection is
-	 *      turned on.
-	 */
-	geo_event.parallelprojection = 'geo_parallelprojection';
-
-	/**
-	 * This event object provides mouse/keyboard events that can be handled
-	 * by the features.  This provides a similar interface as core events,
-	 * but with different names so the events don't interfere.  Subclasses
-	 * can override this to provide custom events.
-	 *
-	 * These events will only be triggered on features which were instantiated
-	 * with the option 'selectionAPI'.
-	 * @namespace geo.event.feature
-	 */
-	geo_event.feature = {
-	  /**
-	   * The event is the feature version of {@link geo.event.mousemove}.
-	   * @event geo.event.feature.mousemove
-	   */
-	  mousemove:  'geo_feature_mousemove',
-	  /**
-	   * The event is the feature version of {@link geo.event.mouseover}.
-	   * @event geo.event.feature.mouseover
-	   */
-	  mouseover:  'geo_feature_mouseover',
-	  /**
-	   * The event contains the `feature`, the `mouse` record, the `previous`
-	   * record of data elements that were under the mouse, and `over`, the new
-	   * record of data elements that are unrder the mouse.
-	   * @event geo.event.feature.mouseover_order
-	  */
-	  mouseover_order: 'geo_feature_mouseover_order',
-	  /**
-	   * The event is the feature version of {@link geo.event.mouseout}.
-	   * @event geo.event.feature.mouseout
-	   */
-	  mouseout:   'geo_feature_mouseout',
-	  /**
-	   * The event is the feature version of {@link geo.event.mouseon}.
-	   * @event geo.event.feature.mouseon
-	   */
-	  mouseon:    'geo_feature_mouseon',
-	  /**
-	   * The event is the feature version of {@link geo.event.mouseoff}.
-	   * @event geo.event.feature.mouseoff
-	   */
-	  mouseoff:   'geo_feature_mouseoff',
-	  /**
-	   * The event is the feature version of {@link geo.event.mouseclick}.
-	   * @event geo.event.feature.mouseclick
-	   */
-	  mouseclick: 'geo_feature_mouseclick',
-	  /**
-	   * The event contains the `feature`, the `mouse` record, and `over`, the
-	   * record of data elements that are unrder the mouse.
-	   * @event geo.event.feature.mouseclick_order
-	  */
-	  mouseclick_order: 'geo_feature_mouseclick_order',
-	  /**
-	   * The event is the feature version of {@link geo.event.brushend}.
-	   * @event geo.event.feature.brushend
-	   */
-	  brushend:   'geo_feature_brushend',
-	  /**
-	   * The event is the feature version of {@link geo.event.brush}.
-	   * @event geo.event.feature.brush
-	   */
-	  brush:      'geo_feature_brush'
-	};
-
-	/**
-	 * These events are triggered by the pixelmap feature.
-	 * @namespace geo.event.pixelmap
-	 */
-	geo_event.pixelmap = {
-	  /**
-	   * Report that an image associated with a pixel map has been prepared and
-	   * rendered once.
-	   *
-	   * @event geo.event.pixelmap.prepared
-	   * @type {object}
-	   * @property {geo.pixelmapFeature} pixelmap The pixelmap object that was
-	   *    prepared.
-	   */
-	  prepared: 'geo_pixelmap_prepared'
-	};
-
-	/**
-	 * These events are triggered by the map screenshot feature.
-	 * @namespace geo.event.screenshot
-	 */
-	geo_event.screenshot = {
-	  /**
-	   * Triggered when a screenshot has been completed.
-	   *
-	   * @event geo.event.screenshot.ready
-	   * @property {HTMLCanvasElement} canvas The canvas used to take the
-	   *    screenshot.
-	   * @property {string|HTMLCanvasElement} screenshot The screenshot as a
-	   *    dataURL string or the canvas, depending on the screenshot request.
-	   */
-	  ready: 'geo_screenshot_ready'
-	};
-
-	/**
-	 * These events are triggered by the camera when it's internal state is
-	 * mutated.
-	 * @namespace geo.event.camera
-	 */
-	geo_event.camera = {};
-
-	/**
-	 * Triggered after a general view matrix change (any change in the visible
-	 * bounds).  This is equivalent to the union of pan and zoom.
-	 *
-	 * @event geo.event.camera.view
-	 * @type {object}
-	 * @property {geo.camera} camera The camera instance.
-	 */
-	geo_event.camera.view = 'geo_camera_view';
-
-	/**
-	 * Triggered after a projection change.
-	 *
-	 * @event geo.event.camera.projection
-	 * @property {geo.camera} camera The camera instance.
-	 * @property {string} type The projection type, either `'perspective'` or
-	 *      `'parallel'`.
-	 */
-	geo_event.camera.projection = 'geo_camera_projection';
-
-	/**
-	 * Triggered after a viewport change.
-	 *
-	 * @event geo.event.camera.viewport
-	 * @property {geo.camera} camera The camera instance.
-	 * @property {geo.screenSize} viewport The new viewport size.
-	 */
-	geo_event.camera.viewport = 'geo_camera_viewport';
-
-	/**
-	 * These events are triggered by the annotation layer.
-	 * @namespace geo.event.annotation
-	 */
-	geo_event.annotation = {};
-
-	/**
-	 * Triggered when an annotation has been added.
-	 *
-	 * @event geo.event.annotation.add
-	 * @type {object}
-	 * @property {geo.annotation} annotation The annotation that was added.
-	 */
-	geo_event.annotation.add = 'geo_annotation_add';
-
-	/**
-	 * Triggered when an annotation is about to be added.
-	 *
-	 * @event geo.event.annotation.add_before
-	 * @type {object}
-	 * @property {geo.annotation} annotation The annotation that will be added.
-	 */
-	geo_event.annotation.add_before = 'geo_annotation_add_before';
-
-	/**
-	 * Triggered when an annotation has been altered.  This is currently only
-	 * triggered when updating existing annotations via the geojson function.
-	 *
-	 * @event geo.event.annotation.update
-	 * @type {object}
-	 * @property {geo.annotation} annotation The annotation that was altered.
-	 */
-	geo_event.annotation.update = 'geo_annotation_update';
-
-	/**
-	 * Triggered when an annotation has been removed.
-	 *
-	 * @event geo.event.annotation.remove
-	 * @type {object}
-	 * @property {geo.annotation} annotation The annotation that was removed.
-	 */
-	geo_event.annotation.remove = 'geo_annotation_remove';
-
-	/**
-	 * Triggered when an annotation's state changes.
-	 *
-	 * @event geo.event.annotation.state
-	 * @type {object}
-	 * @property {geo.annotation} annotation The annotation that changed.
-	 */
-	geo_event.annotation.state = 'geo_annotation_state';
-
-	/**
-	 * Triggered when the annotation mode is changed.
-	 *
-	 * @event geo.event.annotation.mode
-	 * @type {object}
-	 * @property {string?} mode The new annotation mode.  This is one of the values
-	 *      from `geo.annotation.annotationState`.
-	 * @property {string?} oldMode The annotation mode before this change.  This is
-	 *      one of the values from `geo.annotation.annotationState`.
-	 */
-	geo_event.annotation.mode = 'geo_annotation_mode';
-
-	module.exports = geo_event;
-
-
-/***/ }),
-/* 10 */
-/***/ (function(module, exports) {
-
-	/**
-	 * Common object containing all action types that are provided by the GeoJS
-	 * API.
-	 */
-	var geo_action = {
-	  momentum: 'geo_action_momentum',
-	  pan: 'geo_action_pan',
-	  rotate: 'geo_action_rotate',
-	  select: 'geo_action_select',
-	  unzoomselect: 'geo_action_unzoomselect',
-	  zoom: 'geo_action_zoom',
-	  zoomrotate: 'geo_action_zoom_rotate',
-	  zoomselect: 'geo_action_zoomselect',
-
-	  // annotation actions
-	  annotation_line: 'geo_annotation_line',
-	  annotation_polygon: 'geo_annotation_polygon',
-	  annotation_rectangle: 'geo_annotation_rectangle',
-	  annotation_edit_handle: 'geo_annotation_edit_handle'
-	};
-
-	module.exports = geo_action;
-
-
-/***/ }),
-/* 11 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var proj4 = __webpack_require__(12);
-	var util = __webpack_require__(83);
-
-	/**
-	 * This purpose of this class is to provide a generic interface for computing
-	 * coordinate transformationss.  The interface is taken from the proj4js,
-	 * which also provides the geospatial projection implementation.  The
-	 * interface is intentionally simple to allow for custom, non-geospatial use
-	 * cases. For further details, see http://proj4js.org/
-	 *
-	 * The default transforms lat/long coordinates into web mercator
-	 * for use with standard tile sets.
-	 *
-	 * This class is intended to be extended in the future to support 2.5 and 3
-	 * dimensional transformations.  The forward/inverse methods take optional
-	 * z values that are ignored in current mapping context, but will in the
-	 * future perform more general 3D transformations.
-	 *
-	 * @class geo.transform
-	 * @param {object} options Constructor options
-	 * @param {string} options.source A proj4 string for the source projection
-	 * @param {string} options.target A proj4 string for the target projection
-	 * @returns {geo.transform}
-	 */
-
-	var transformCache = {};
-	/* Up to maxTransformCacheSize squared might be cached.  When the maximum cache
-	 * size is reached, the cache is completely emptied.  Since we probably won't
-	 * be rapidly switching between a large number of transforms, this is adequate
-	 * simple behavior. */
-	var maxTransformCacheSize = 10;
-
-	var transform = function (options) {
-	  'use strict';
-	  if (!(this instanceof transform)) {
-	    options = options || {};
-	    if (!(options.source in transformCache)) {
-	      if (Object.size(transformCache) >= maxTransformCacheSize) {
-	        transformCache = {};
-	      }
-	      transformCache[options.source] = {};
-	    }
-	    if (!(options.target in transformCache[options.source])) {
-	      if (Object.size(transformCache[options.source]) >= maxTransformCacheSize) {
-	        transformCache[options.source] = {};
-	      }
-	      transformCache[options.source][options.target] = new transform(options);
-	    }
-	    return transformCache[options.source][options.target];
-	  }
-
-	  var m_this = this,
-	      m_proj,   // The raw proj4js object
-	      m_source, // The source projection
-	      m_target; // The target projection
-
-	  /**
-	   * Generate the internal proj4 object.
-	   * @private
-	   */
-	  function generate_proj4() {
-	    m_proj = new proj4(
-	      m_this.source(),
-	      m_this.target()
-	    );
-	  }
-
-	  /**
-	   * Get/Set the source projection.
-	   *
-	   * @param {string} [arg] The new source projection.  If `undefined`, return
-	   *    the current source projection.
-	   * @returns {string|this} The current source projection if it was queried,
-	   *    otherwise the current transform object.
-	   */
-	  this.source = function (arg) {
-	    if (arg === undefined) {
-	      return m_source || 'EPSG:4326';
-	    }
-	    m_source = arg;
-	    generate_proj4();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set the target projection.
-	   *
-	   * @param {string} [arg] The new target projection.  If `undefined`, return
-	   *    the current target projection.
-	   * @returns {string|this} The current target projection if it was queried,
-	   *    otherwise the current transform object.
-	   */
-	  this.target = function (arg) {
-	    if (arg === undefined) {
-	      return m_target || 'EPSG:3857';
-	    }
-	    m_target = arg;
-	    generate_proj4();
-	    return m_this;
-	  };
-
-	  /**
-	   * Perform a forward transformation (source -> target).
-	   * @protected
-	   *
-	   * @param {geo.geoPosition} point The point in source coordinates.
-	   * @returns {geo.geoPosition} A point object in the target coordinates.
-	   */
-	  this._forward = function (point) {
-	    var pt = m_proj.forward(point);
-	    pt.z = point.z || 0;
-	    return pt;
-	  };
-
-	  /**
-	   * Perform an inverse transformation (target -> source).
-	   * @protected
-	   *
-	   * @param {geo.geoPosition} point The point in target coordinates.
-	   * @returns {geo.geoPosition} A point object in the source coordinates.
-	   */
-	  this._inverse = function (point) {
-	    var pt = m_proj.inverse(point);
-	    pt.z = point.z || 0;
-	    return pt;
-	  };
-
-	  /**
-	   * Perform a forward transformation (source -> target) in place.
-	   * @protected
-	   *
-	   * @param {geo.geoPosition|geo.geoPosition[]} point The point coordinates
-	   *    or array of points in source coordinates.
-	   * @returns {geo.geoPosition|geo.geoPosition[]} A point object or array in
-	   *    the target coordinates.
-	   */
-	  this.forward = function (point) {
-	    if (Array.isArray(point)) {
-	      return point.map(m_this._forward);
-	    }
-	    return m_this._forward(point);
-	  };
-
-	  /**
-	   * Perform an inverse transformation (target -> source) in place.
-	   * @protected
-	   *
-	   * @param {geo.geoPosition|geo.geoPosition[]} point The point coordinates
-	   *    or array of points in target coordinates.
-	   * @returns {geo.geoPosition|geo.geoPosition[]} A point object or array in
-	   *    the source coordinates.
-	   */
-	  this.inverse = function (point) {
-	    if (Array.isArray(point)) {
-	      return point.map(m_this._inverse);
-	    }
-	    return m_this._inverse(point);
-	  };
-
-	  // Set defaults given by the constructor
-	  options = options || {};
-	  try {
-	    this.source(options.source);
-	  } catch (err) {
-	    console.error('Can\'t use transform source: ' + options.source);
-	    this.source('EPSG:4326');
-	  }
-	  try {
-	    this.target(options.target);
-	  } catch (err) {
-	    console.error('Can\'t use transform target: ' + options.target);
-	    this.target('EPSG:3857');
-	  }
-
-	  return this;
-	};
-
-	/**
-	 * Contains a reference to `proj4.defs`.  The functions serves two
-	 * purposes.
-	 *
-	 *   1. It is a key value mapping of all loaded projection definitions
-	 *   2. It is a function that will add additional definitions.
-	 *
-	 * See:
-	 *   http://proj4js.org/
-	 */
-	transform.defs = proj4.defs;
-
-	/**
-	 * Look up a projection definition from epsg.io.
-	 * For the moment, we only handle `EPSG` codes.
-	 *
-	 * @param {string} projection A projection alias (e.g. EPSG:4326)
-	 * @returns {promise} Resolves with the proj4 definition
-	 */
-	transform.lookup = function (projection) {
-	  var $ = __webpack_require__(1);
-	  var code, defer = $.Deferred(), parts;
-
-	  if (proj4.defs.hasOwnProperty(projection)) {
-	    return defer.resolve(proj4.defs[projection]);
-	  }
-
-	  parts = projection.split(':');
-	  if (parts.length !== 2 || parts[0].toUpperCase() !== 'EPSG') {
-	    return defer.reject('Invalid projection code').promise();
-	  }
-	  code = parts[1];
-
-	  return $.ajax({
-	    url: 'http://epsg.io/?q=' + code + '&format=json'
-	  }).done(function (data) {
-	    var result = (data.results || [])[0];
-	    if (!result || !result.proj4) {
-	      return defer.reject(data).promise();
-	    }
-
-	    proj4.defs(projection, result.proj4);
-	    return $.when(proj4.defs[projection]);
-	  });
-	};
-
-	/**
-	 * Transform an array of coordinates from one projection into another.  The
-	 * transformation may occur in place (modifying the input coordinate array),
-	 * depending on the input format.  The coordinates can be an object with x,
-	 * y, and (optionally z) or an array of 2 or 3 values, or an array of either
-	 * of those, or a single flat array with 2 or 3 components per coordinate.
-	 * Arrays are always modified in place.  Individual point objects are not
-	 * altered; new point objects are returned unless no transform is needed.
-	 *
-	 * @param {string} srcPrj The source projection.
-	 * @param {string} tgtPrj The destination projection.
-	 * @param {geoPosition|geoPosition[]|number[]} coordinates An array of
-	 *      coordinate objects.  These may be in object or array form, or a flat
-	 *      array.
-	 * @param {number} numberOfComponents For flat arrays, either 2 or 3.
-	 * @returns {geoPosition|geoPosition[]|number[]} The transformed coordinates.
-	 */
-	transform.transformCoordinates = function (
-	        srcPrj, tgtPrj, coordinates, numberOfComponents) {
-	  'use strict';
-
-	  if (srcPrj === tgtPrj) {
-	    return coordinates;
-	  }
-
-	  var trans = transform({source: srcPrj, target: tgtPrj}), output;
-	  if (util.isObject(coordinates) && 'x' in coordinates && 'y' in coordinates) {
-	    output = trans.forward({x: coordinates.x, y: coordinates.y, z: coordinates.z || 0});
-	    if ('z' in coordinates) {
-	      return output;
-	    }
-	    return {x: output.x, y: output.y};
-	  }
-	  if (Array.isArray(coordinates) && coordinates.length === 1 &&
-	      util.isObject(coordinates[0]) && 'x' in coordinates[0] &&
-	      'y' in coordinates[0]) {
-	    output = trans.forward({x: coordinates[0].x, y: coordinates[0].y, z: coordinates[0].z || 0});
-	    if ('z' in coordinates[0]) {
-	      return [output];
-	    }
-	    return [{x: output.x, y: output.y}];
-	  }
-	  return transform.transformCoordinatesArray(trans, coordinates, numberOfComponents);
-	};
-
-	/**
-	 * Transform an array of coordinates from one projection into another.  The
-	 * transformation may occur in place (modifying the input coordinate array),
-	 * depending on the input format.  The coordinates can be an array of 2 or 3
-	 * values, or an array of either of those, or a single flat array with 2 or 3
-	 * components per coordinate.  The array is modified in place.
-	 *
-	 * @param {transform} trans The transformation object.
-	 * @param {geoPosition[]|number[]} coordinates An array of coordinate
-	 *      objects or a flat array.
-	 * @param {number} numberOfComponents For flat arrays, either 2 or 3.
-	 * @returns {geoPosition[]|number[]} The transformed coordinates
-	 */
-	transform.transformCoordinatesArray = function (trans, coordinates, numberOfComponents) {
-	  var i, count, offset, xAcc, yAcc, zAcc, writer, output, projPoint;
-
-	  // Default Z accessor
-	  zAcc = function () {
-	    return 0.0;
-	  };
-
-	  // Helper methods
-	  function handleArrayCoordinates() {
-	    if (Array.isArray(coordinates[0])) {
-	      if (coordinates[0].length === 2) {
-	        xAcc = function (index) {
-	          return coordinates[index][0];
-	        };
-	        yAcc = function (index) {
-	          return coordinates[index][1];
-	        };
-	        writer = function (index, x, y) {
-	          output[index] = [x, y];
-	        };
-	      } else if (coordinates[0].length === 3) {
-	        xAcc = function (index) {
-	          return coordinates[index][0];
-	        };
-	        yAcc = function (index) {
-	          return coordinates[index][1];
-	        };
-	        zAcc = function (index) {
-	          return coordinates[index][2];
-	        };
-	        writer = function (index, x, y, z) {
-	          output[index] = [x, y, z];
-	        };
-	      } else {
-	        throw new Error('Invalid coordinates. Requires two or three components per array');
-	      }
-	    } else {
-	      if (coordinates.length === 2) {
-	        offset = 2;
-
-	        xAcc = function (index) {
-	          return coordinates[index * offset];
-	        };
-	        yAcc = function (index) {
-	          return coordinates[index * offset + 1];
-	        };
-	        writer = function (index, x, y) {
-	          output[index] = x;
-	          output[index + 1] = y;
-	        };
-	      } else if (coordinates.length === 3) {
-	        offset = 3;
-
-	        xAcc = function (index) {
-	          return coordinates[index * offset];
-	        };
-	        yAcc = function (index) {
-	          return coordinates[index * offset + 1];
-	        };
-	        zAcc = function (index) {
-	          return coordinates[index * offset + 2];
-	        };
-	        writer = function (index, x, y, z) {
-	          output[index] = x;
-	          output[index + 1] = y;
-	          output[index + 2] = z;
-	        };
-	      } else if (numberOfComponents) {
-	        if (numberOfComponents === 2 || numberOfComponents === 3) {
-	          offset = numberOfComponents;
-
-	          xAcc = function (index) {
-	            return coordinates[index];
-	          };
-	          yAcc = function (index) {
-	            return coordinates[index + 1];
-	          };
-	          if (numberOfComponents === 2) {
-	            writer = function (index, x, y) {
-	              output[index] = x;
-	              output[index + 1] = y;
-	            };
-	          } else {
-	            zAcc = function (index) {
-	              return coordinates[index + 2];
-	            };
-	            writer = function (index, x, y, z) {
-	              output[index] = x;
-	              output[index + 1] = y;
-	              output[index + 2] = z;
-	            };
-	          }
-	        } else {
-	          throw new Error('Number of components should be two or three');
-	        }
-	      } else {
-	        throw new Error('Invalid coordinates');
-	      }
-	    }
-	  }
-
-	  // Helper methods
-	  function handleObjectCoordinates() {
-	    if (coordinates[0] &&
-	        'x' in coordinates[0] &&
-	        'y' in coordinates[0]) {
-	      xAcc = function (index) {
-	        return coordinates[index].x;
-	      };
-	      yAcc = function (index) {
-	        return coordinates[index].y;
-	      };
-
-	      if ('z' in coordinates[0]) {
-	        zAcc = function (index) {
-	          return coordinates[index].z;
-	        };
-	        writer = function (index, x, y, z) {
-	          output[i] = {x: x, y: y, z: z};
-	        };
-	      } else {
-	        writer = function (index, x, y) {
-	          output[index] = {x: x, y: y};
-	        };
-	      }
-	    } else {
-	      throw new Error('Invalid coordinates');
-	    }
-	  }
-
-	  if (Array.isArray(coordinates)) {
-	    output = [];
-	    output.length = coordinates.length;
-	    count = coordinates.length;
-
-	    if (!coordinates.length) {
-	      return output;
-	    }
-	    if (Array.isArray(coordinates[0]) || util.isObject(coordinates[0])) {
-	      offset = 1;
-
-	      if (Array.isArray(coordinates[0])) {
-	        handleArrayCoordinates();
-	      } else if (util.isObject(coordinates[0])) {
-	        handleObjectCoordinates();
-	      }
-	    } else {
-	      handleArrayCoordinates();
-	    }
-	  } else {
-	    throw new Error('Coordinates are not valid');
-	  }
-
-	  for (i = 0; i < count; i += offset) {
-	    projPoint = trans.forward({x: xAcc(i), y: yAcc(i), z: zAcc(i)});
-	    writer(i, projPoint.x, projPoint.y, projPoint.z);
-	  }
-	  return output;
-	};
-
-	/**
-	 * Apply an affine transformation consisting of a translation then a scaling
-	 * to the given coordinate array.  Note, the transformation occurs in place
-	 * so the input coordinate object are mutated.
-	 *
-	 * @param {object} def
-	 * @param {geo.geoPosition} def.origin The transformed origin
-	 * @param {object} def.scale The transformed scale factor.  This is an object
-	 *  with `x`, `y`, and `z` parameters.
-	 * @param {geo.geoPosition[]} coords An array of coordinate objects.
-	 * @returns {geo.geoPosition[]} The transformed coordinates.
-	 */
-	transform.affineForward = function (def, coords) {
-	  'use strict';
-	  var i, origin = def.origin, scale = def.scale || {x: 1, y: 1, z: 1};
-	  for (i = 0; i < coords.length; i += 1) {
-	    coords[i].x = (coords[i].x - origin.x) * scale.x;
-	    coords[i].y = (coords[i].y - origin.y) * scale.y;
-	    coords[i].z = ((coords[i].z || 0) - (origin.z || 0)) * scale.z;
-	  }
-	  return coords;
-	};
-
-	/**
-	 * Apply an inverse affine transformation which is the inverse to {@link
-	 * geo.transform.affineForward}.  Note, the transformation occurs in place so
-	 * the input coordinate object are mutated.
-	 *
-	 * @param {object} def
-	 * @param {geo.geoPosition} def.origin The transformed origin
-	 * @param {object} def.scale The transformed scale factor.  This is an object
-	 *  with `x`, `y`, and `z` parameters.
-	 * @param {geo.geoPosition[]} coords An array of coordinate objects.
-	 * @returns {geo.geoPosition[]} The transformed coordinates.
-	 */
-	transform.affineInverse = function (def, coords) {
-	  'use strict';
-	  var i, origin = def.origin, scale = def.scale || {x: 1, y: 1, z: 1};
-	  for (i = 0; i < coords.length; i += 1) {
-	    coords[i].x = coords[i].x / scale.x + origin.x;
-	    coords[i].y = coords[i].y / scale.y + origin.y;
-	    coords[i].z = (coords[i].z || 0) / scale.z + (origin.z || 0);
-	  }
-	  return coords;
-	};
-
-	/**
-	 * Compute the distance on the surface on a sphere.  The sphere is the major
-	 * radius of a specified ellipsoid.  Altitude is ignored.
-	 *
-	 * @param {geo.geoPosition} pt1 The first point.
-	 * @param {geo.geoPosition} pt2 The second point.
-	 * @param {string|geo.transform} [gcs] `undefined` to use the same gcs as the
-	 *    ellipsoid, otherwise the gcs of the points.
-	 * @param {string|geo.transform} [baseGcs='EPSG:4326'] the gcs of the
-	 *    ellipsoid.
-	 * @param {object} [ellipsoid=proj4.WGS84] An object with at least `a` and one
-	 *    of `b`, `f`, or `rf` (1 / `f`) -- this works with  proj4 ellipsoid
-	 *    definitions.
-	 * @param {number} [maxIterations=100] Maximum number of iterations to use
-	 *    to test convergence.
-	 * @returns {number} The distance in meters (or whatever units the ellipsoid
-	 *    was specified in.
-	 */
-	transform.sphericalDistance = function (pt1, pt2, gcs, baseGcs, ellipsoid) {
-	  baseGcs = baseGcs || 'EPSG:4326';
-	  ellipsoid = ellipsoid || proj4.WGS84;
-	  gcs = gcs || baseGcs;
-	  if (gcs !== baseGcs) {
-	    var pts = transform.transformCoordinates(gcs, baseGcs, [pt1, pt2]);
-	    pt1 = pts[0];
-	    pt2 = pts[1];
-	  }
-	  // baseGcs must be in degrees or this will be wrong
-	  var phi1 = pt1.y * Math.PI / 180,
-	      phi2 = pt2.y * Math.PI / 180,
-	      lambda = (pt2.x - pt1.x) * Math.PI / 180,
-	      sinphi1 = Math.sin(phi1), cosphi1 = Math.cos(phi1),
-	      sinphi2 = Math.sin(phi2), cosphi2 = Math.cos(phi2);
-	  var sigma = Math.atan2(
-	    Math.pow(
-	      Math.pow(cosphi2 * Math.sin(lambda), 2) +
-	      Math.pow(cosphi1 * sinphi2 - sinphi1 * cosphi2 * Math.cos(lambda), 2), 0.5),
-	    sinphi1 * sinphi2 + cosphi1 * cosphi2 * Math.cos(lambda)
-	  );
-	  return ellipsoid.a * sigma;
-	};
-
-	/**
-	 * Compute the Vincenty distance on the surface on an ellipsoid.  Altitude is
-	 * ignored.
-	 *
-	 * @param {geo.geoPosition} pt1 The first point.
-	 * @param {geo.geoPosition} pt2 The second point.
-	 * @param {string|geo.transform} [gcs] `undefined` to use the same gcs as the
-	 *    ellipsoid, otherwise the gcs of the points.
-	 * @param {string|geo.transform} [baseGcs='EPSG:4326'] the gcs of the
-	 *    ellipsoid.
-	 * @param {object} [ellipsoid=proj4.WGS84] An object with at least `a` and one
-	 *    of `b`, `f`, or `rf` (1 / `f`) -- this works with  proj4 ellipsoid
-	 *    definitions.
-	 * @param {number} [maxIterations=100] Maximum number of iterations to use
-	 *    to test convergence.
-	 * @returns {object} An object with `distance` in meters (or whatever units the
-	 *    ellipsoid was specified in), `alpha1` and `alpha2`, the azimuths at the
-	 *    two points in radians.  The result may be `undefined` if the formula
-	 *    fails to converge, which can happen near antipodal points.
-	 */
-	transform.vincentyDistance = function (pt1, pt2, gcs, baseGcs, ellipsoid, maxIterations) {
-	  baseGcs = baseGcs || 'EPSG:4326';
-	  ellipsoid = ellipsoid || proj4.WGS84;
-	  maxIterations = maxIterations || 100;
-	  gcs = gcs || baseGcs;
-	  if (gcs !== baseGcs) {
-	    var pts = transform.transformCoordinates(gcs, baseGcs, [pt1, pt2]);
-	    pt1 = pts[0];
-	    pt2 = pts[1];
-	  }
-	  var a = ellipsoid.a,
-	      b = ellipsoid.b || ellipsoid.a * (1.0 - (ellipsoid.f || 1.0 / ellipsoid.rf)),
-	      f = ellipsoid.f || (ellipsoid.rf ? 1.0 / ellipsoid.rf : 1.0 - b / a),
-	      // baseGcs must be in degrees or this will be wrong
-	      phi1 = pt1.y * Math.PI / 180,
-	      phi2 = pt2.y * Math.PI / 180,
-	      L = (((pt2.x - pt1.x) % 360 + 360) % 360) * Math.PI / 180,
-	      U1 = Math.atan((1 - f) * Math.tan(phi1)),  // reduced latitude
-	      U2 = Math.atan((1 - f) * Math.tan(phi2)),
-	      sinU1 = Math.sin(U1), cosU1 = Math.cos(U1),
-	      sinU2 = Math.sin(U2), cosU2 = Math.cos(U2),
-	      lambda = L, lastLambda = L + Math.PI * 2,
-	      sinSigma, cosSigma, sigma, sinAlpha, cos2alpha, cos2sigmasubm, C,
-	      u2, A, B, deltaSigma, iter;
-	  if (phi1 === phi2 && !L) {
-	    return {
-	      distance: 0,
-	      alpha1: 0,
-	      alpha2: 0
-	    };
-	  }
-	  for (iter = maxIterations; iter > 0 && Math.abs(lambda - lastLambda) > 1e-12; iter -= 1) {
-	    sinSigma = Math.pow(
-	        Math.pow(cosU2 * Math.sin(lambda), 2) +
-	        Math.pow(cosU1 * sinU2 - sinU1 * cosU2 * Math.cos(lambda), 2), 0.5);
-	    cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * Math.cos(lambda);
-	    sigma = Math.atan2(sinSigma, cosSigma);
-	    sinAlpha = cosU1 * cosU2 * Math.sin(lambda) / sinSigma;
-	    cos2alpha = 1 - Math.pow(sinAlpha, 2);
-	    // cos2alpha is zero only when phi1 and phi2 are nearly zero.  In this
-	    // case, sinU1 and sinU2 are nearly zero and the the second term can be
-	    // dropped
-	    cos2sigmasubm = cosSigma - (cos2alpha ? 2 * sinU1 * sinU2 / cos2alpha : 0);
-	    C = f / 16 * cos2alpha * (4 + f * (4 - 3 * cos2alpha));
-	    lastLambda = lambda;
-	    lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (
-	        cos2sigmasubm + C * cosSigma * (-1 + 2 * Math.pow(cos2sigmasubm, 2))));
-	  }
-	  if (!iter) { // failure to converge
-	    return;
-	  }
-	  u2 = cos2alpha * (a * a - b * b) / (b * b);
-	  A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)));
-	  B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)));
-	  deltaSigma = B * sinSigma * (cos2sigmasubm + B / 4 * (
-	    cosSigma * (-1 + 2 * Math.pow(cos2sigmasubm, 2)) -
-	    B / 6 * cos2sigmasubm * (-3 + 4 * sinSigma * sinSigma) *
-	    (-3 + 4 * Math.pow(cos2sigmasubm, 2))));
-	  return {
-	    distance: b * A * (sigma - deltaSigma),
-	    alpha1: Math.atan2(cosU2 * Math.sin(lambda), cosU1 * sinU2 - sinU1 * cosU2 * Math.cos(lambda)),
-	    alpha2: Math.atan2(cosU1 * Math.sin(lambda), -sinU1 * cosU2 + cosU1 * sinU2 * Math.cos(lambda))
-	  };
-	};
-
-	module.exports = transform;
-
-
-/***/ }),
-/* 12 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var proj4 = __webpack_require__(13);
-	proj4.defaultDatum = 'WGS84'; //default datum
-	proj4.Proj = __webpack_require__(14);
-	proj4.WGS84 = new proj4.Proj('WGS84');
-	proj4.Point = __webpack_require__(40);
-	proj4.toPoint = __webpack_require__(39);
-	proj4.defs = __webpack_require__(16);
-	proj4.transform = __webpack_require__(35);
-	proj4.mgrs = __webpack_require__(41);
-	proj4.version = __webpack_require__(42);
-	__webpack_require__(43)(proj4);
-	module.exports = proj4;
-
-
-/***/ }),
-/* 13 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var proj = __webpack_require__(14);
-	var transform = __webpack_require__(35);
-	var wgs84 = proj('WGS84');
-
-	function transformer(from, to, coords) {
-	  var transformedArray;
-	  if (Array.isArray(coords)) {
-	    transformedArray = transform(from, to, coords);
-	    if (coords.length === 3) {
-	      return [transformedArray.x, transformedArray.y, transformedArray.z];
-	    }
-	    else {
-	      return [transformedArray.x, transformedArray.y];
-	    }
-	  }
-	  else {
-	    return transform(from, to, coords);
-	  }
-	}
-
-	function checkProj(item) {
-	  if (item instanceof proj) {
-	    return item;
-	  }
-	  if (item.oProj) {
-	    return item.oProj;
-	  }
-	  return proj(item);
-	}
-	function proj4(fromProj, toProj, coord) {
-	  fromProj = checkProj(fromProj);
-	  var single = false;
-	  var obj;
-	  if (typeof toProj === 'undefined') {
-	    toProj = fromProj;
-	    fromProj = wgs84;
-	    single = true;
-	  }
-	  else if (typeof toProj.x !== 'undefined' || Array.isArray(toProj)) {
-	    coord = toProj;
-	    toProj = fromProj;
-	    fromProj = wgs84;
-	    single = true;
-	  }
-	  toProj = checkProj(toProj);
-	  if (coord) {
-	    return transformer(fromProj, toProj, coord);
-	  }
-	  else {
-	    obj = {
-	      forward: function(coords) {
-	        return transformer(fromProj, toProj, coords);
-	      },
-	      inverse: function(coords) {
-	        return transformer(toProj, fromProj, coords);
-	      }
-	    };
-	    if (single) {
-	      obj.oProj = toProj;
-	    }
-	    return obj;
-	  }
-	}
-	module.exports = proj4;
-
-/***/ }),
-/* 14 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var parseCode = __webpack_require__(15);
-	var extend = __webpack_require__(22);
-	var projections = __webpack_require__(23);
-	var deriveConstants = __webpack_require__(31);
-	var Datum = __webpack_require__(33);
-	var datum = __webpack_require__(34);
-
-
-	function Projection(srsCode,callback) {
-	  if (!(this instanceof Projection)) {
-	    return new Projection(srsCode);
-	  }
-	  callback = callback || function(error){
-	    if(error){
-	      throw error;
-	    }
-	  };
-	  var json = parseCode(srsCode);
-	  if(typeof json !== 'object'){
-	    callback(srsCode);
-	    return;
-	  }
-	  var ourProj = Projection.projections.get(json.projName);
-	  if(!ourProj){
-	    callback(srsCode);
-	    return;
-	  }
-	  if (json.datumCode && json.datumCode !== 'none') {
-	    var datumDef = Datum[json.datumCode];
-	    if (datumDef) {
-	      json.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
-	      json.ellps = datumDef.ellipse;
-	      json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode;
-	    }
-	  }
-	  json.k0 = json.k0 || 1.0;
-	  json.axis = json.axis || 'enu';
-
-	  var sphere = deriveConstants.sphere(json.a, json.b, json.rf, json.ellps, json.sphere);
-	  var ecc = deriveConstants.eccentricity(sphere.a, sphere.b, sphere.rf, json.R_A);
-	  var datumObj = json.datum || datum(json.datumCode, json.datum_params, sphere.a, sphere.b, ecc.es, ecc.ep2);
-
-	  extend(this, json); // transfer everything over from the projection because we don't know what we'll need
-	  extend(this, ourProj); // transfer all the methods from the projection
-
-	  // copy the 4 things over we calulated in deriveConstants.sphere
-	  this.a = sphere.a;
-	  this.b = sphere.b;
-	  this.rf = sphere.rf;
-	  this.sphere = sphere.sphere;
-
-	  // copy the 3 things we calculated in deriveConstants.eccentricity
-	  this.es = ecc.es;
-	  this.e = ecc.e;
-	  this.ep2 = ecc.ep2;
-
-	  // add in the datum object
-	  this.datum = datumObj;
-
-	  // init the projection
-	  this.init();
-
-	  // legecy callback from back in the day when it went to spatialreference.org
-	  callback(null, this);
-
-	}
-	Projection.projections = projections;
-	Projection.projections.start();
-	module.exports = Projection;
-
-
-/***/ }),
-/* 15 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var defs = __webpack_require__(16);
-	var wkt = __webpack_require__(21);
-	var projStr = __webpack_require__(18);
-	function testObj(code){
-	  return typeof code === 'string';
-	}
-	function testDef(code){
-	  return code in defs;
-	}
-	var codeWords = ['GEOGCS','GEOCCS','PROJCS','LOCAL_CS'];
-
-	function testWKT(code){
-	  return codeWords.some(function (word) {
-	    return code.indexOf(word) > -1;
-	  });
-	}
-	function testProj(code){
-	  return code[0] === '+';
-	}
-	function parse(code){
-	  if (testObj(code)) {
-	    //check to see if this is a WKT string
-	    if (testDef(code)) {
-	      return defs[code];
-	    }
-	    if (testWKT(code)) {
-	      return wkt(code);
-	    }
-	    if (testProj(code)) {
-	      return projStr(code);
-	    }
-	  }else{
-	    return code;
-	  }
-	}
-
-	module.exports = parse;
-
-
-/***/ }),
-/* 16 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var globals = __webpack_require__(17);
-	var parseProj = __webpack_require__(18);
-	var wkt = __webpack_require__(21);
-
-	function defs(name) {
-	  /*global console*/
-	  var that = this;
-	  if (arguments.length === 2) {
-	    var def = arguments[1];
-	    if (typeof def === 'string') {
-	      if (def.charAt(0) === '+') {
-	        defs[name] = parseProj(arguments[1]);
-	      }
-	      else {
-	        defs[name] = wkt(arguments[1]);
-	      }
-	    } else {
-	      defs[name] = def;
-	    }
-	  }
-	  else if (arguments.length === 1) {
-	    if (Array.isArray(name)) {
-	      return name.map(function(v) {
-	        if (Array.isArray(v)) {
-	          defs.apply(that, v);
-	        }
-	        else {
-	          defs(v);
-	        }
-	      });
-	    }
-	    else if (typeof name === 'string') {
-	      if (name in defs) {
-	        return defs[name];
-	      }
-	    }
-	    else if ('EPSG' in name) {
-	      defs['EPSG:' + name.EPSG] = name;
-	    }
-	    else if ('ESRI' in name) {
-	      defs['ESRI:' + name.ESRI] = name;
-	    }
-	    else if ('IAU2000' in name) {
-	      defs['IAU2000:' + name.IAU2000] = name;
-	    }
-	    else {
-	      console.log(name);
-	    }
-	    return;
-	  }
-
-
-	}
-	globals(defs);
-	module.exports = defs;
-
-
-/***/ }),
-/* 17 */
-/***/ (function(module, exports) {
-
-	module.exports = function(defs) {
-	  defs('EPSG:4326', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees");
-	  defs('EPSG:4269', "+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees");
-	  defs('EPSG:3857', "+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs");
-
-	  defs.WGS84 = defs['EPSG:4326'];
-	  defs['EPSG:3785'] = defs['EPSG:3857']; // maintain backward compat, official code is 3857
-	  defs.GOOGLE = defs['EPSG:3857'];
-	  defs['EPSG:900913'] = defs['EPSG:3857'];
-	  defs['EPSG:102113'] = defs['EPSG:3857'];
-	};
-
-
-/***/ }),
-/* 18 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var D2R = 0.01745329251994329577;
-	var PrimeMeridian = __webpack_require__(19);
-	var units = __webpack_require__(20);
-
-	module.exports = function(defData) {
-	  var self = {};
-	  var paramObj = defData.split('+').map(function(v) {
-	    return v.trim();
-	  }).filter(function(a) {
-	    return a;
-	  }).reduce(function(p, a) {
-	    var split = a.split('=');
-	    split.push(true);
-	    p[split[0].toLowerCase()] = split[1];
-	    return p;
-	  }, {});
-	  var paramName, paramVal, paramOutname;
-	  var params = {
-	    proj: 'projName',
-	    datum: 'datumCode',
-	    rf: function(v) {
-	      self.rf = parseFloat(v);
-	    },
-	    lat_0: function(v) {
-	      self.lat0 = v * D2R;
-	    },
-	    lat_1: function(v) {
-	      self.lat1 = v * D2R;
-	    },
-	    lat_2: function(v) {
-	      self.lat2 = v * D2R;
-	    },
-	    lat_ts: function(v) {
-	      self.lat_ts = v * D2R;
-	    },
-	    lon_0: function(v) {
-	      self.long0 = v * D2R;
-	    },
-	    lon_1: function(v) {
-	      self.long1 = v * D2R;
-	    },
-	    lon_2: function(v) {
-	      self.long2 = v * D2R;
-	    },
-	    alpha: function(v) {
-	      self.alpha = parseFloat(v) * D2R;
-	    },
-	    lonc: function(v) {
-	      self.longc = v * D2R;
-	    },
-	    x_0: function(v) {
-	      self.x0 = parseFloat(v);
-	    },
-	    y_0: function(v) {
-	      self.y0 = parseFloat(v);
-	    },
-	    k_0: function(v) {
-	      self.k0 = parseFloat(v);
-	    },
-	    k: function(v) {
-	      self.k0 = parseFloat(v);
-	    },
-	    a: function(v) {
-	      self.a = parseFloat(v);
-	    },
-	    b: function(v) {
-	      self.b = parseFloat(v);
-	    },
-	    r_a: function() {
-	      self.R_A = true;
-	    },
-	    zone: function(v) {
-	      self.zone = parseInt(v, 10);
-	    },
-	    south: function() {
-	      self.utmSouth = true;
-	    },
-	    towgs84: function(v) {
-	      self.datum_params = v.split(",").map(function(a) {
-	        return parseFloat(a);
-	      });
-	    },
-	    to_meter: function(v) {
-	      self.to_meter = parseFloat(v);
-	    },
-	    units: function(v) {
-	      self.units = v;
-	      if (units[v]) {
-	        self.to_meter = units[v].to_meter;
-	      }
-	    },
-	    from_greenwich: function(v) {
-	      self.from_greenwich = v * D2R;
-	    },
-	    pm: function(v) {
-	      self.from_greenwich = (PrimeMeridian[v] ? PrimeMeridian[v] : parseFloat(v)) * D2R;
-	    },
-	    nadgrids: function(v) {
-	      if (v === '@null') {
-	        self.datumCode = 'none';
-	      }
-	      else {
-	        self.nadgrids = v;
-	      }
-	    },
-	    axis: function(v) {
-	      var legalAxis = "ewnsud";
-	      if (v.length === 3 && legalAxis.indexOf(v.substr(0, 1)) !== -1 && legalAxis.indexOf(v.substr(1, 1)) !== -1 && legalAxis.indexOf(v.substr(2, 1)) !== -1) {
-	        self.axis = v;
-	      }
-	    }
-	  };
-	  for (paramName in paramObj) {
-	    paramVal = paramObj[paramName];
-	    if (paramName in params) {
-	      paramOutname = params[paramName];
-	      if (typeof paramOutname === 'function') {
-	        paramOutname(paramVal);
-	      }
-	      else {
-	        self[paramOutname] = paramVal;
-	      }
-	    }
-	    else {
-	      self[paramName] = paramVal;
-	    }
-	  }
-	  if(typeof self.datumCode === 'string' && self.datumCode !== "WGS84"){
-	    self.datumCode = self.datumCode.toLowerCase();
-	  }
-	  return self;
-	};
-
-
-/***/ }),
-/* 19 */
-/***/ (function(module, exports) {
-
-	exports.greenwich = 0.0; //"0dE",
-	exports.lisbon = -9.131906111111; //"9d07'54.862\"W",
-	exports.paris = 2.337229166667; //"2d20'14.025\"E",
-	exports.bogota = -74.080916666667; //"74d04'51.3\"W",
-	exports.madrid = -3.687938888889; //"3d41'16.58\"W",
-	exports.rome = 12.452333333333; //"12d27'8.4\"E",
-	exports.bern = 7.439583333333; //"7d26'22.5\"E",
-	exports.jakarta = 106.807719444444; //"106d48'27.79\"E",
-	exports.ferro = -17.666666666667; //"17d40'W",
-	exports.brussels = 4.367975; //"4d22'4.71\"E",
-	exports.stockholm = 18.058277777778; //"18d3'29.8\"E",
-	exports.athens = 23.7163375; //"23d42'58.815\"E",
-	exports.oslo = 10.722916666667; //"10d43'22.5\"E"
-
-/***/ }),
-/* 20 */
-/***/ (function(module, exports) {
-
-	exports.ft = {to_meter: 0.3048};
-	exports['us-ft'] = {to_meter: 1200 / 3937};
-
-
-/***/ }),
-/* 21 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var D2R = 0.01745329251994329577;
-	var extend = __webpack_require__(22);
-
-	function mapit(obj, key, v) {
-	  obj[key] = v.map(function(aa) {
-	    var o = {};
-	    sExpr(aa, o);
-	    return o;
-	  }).reduce(function(a, b) {
-	    return extend(a, b);
-	  }, {});
-	}
-
-	function sExpr(v, obj) {
-	  var key;
-	  if (!Array.isArray(v)) {
-	    obj[v] = true;
-	    return;
-	  }
-	  else {
-	    key = v.shift();
-	    if (key === 'PARAMETER') {
-	      key = v.shift();
-	    }
-	    if (v.length === 1) {
-	      if (Array.isArray(v[0])) {
-	        obj[key] = {};
-	        sExpr(v[0], obj[key]);
-	      }
-	      else {
-	        obj[key] = v[0];
-	      }
-	    }
-	    else if (!v.length) {
-	      obj[key] = true;
-	    }
-	    else if (key === 'TOWGS84') {
-	      obj[key] = v;
-	    }
-	    else {
-	      obj[key] = {};
-	      if (['UNIT', 'PRIMEM', 'VERT_DATUM'].indexOf(key) > -1) {
-	        obj[key] = {
-	          name: v[0].toLowerCase(),
-	          convert: v[1]
-	        };
-	        if (v.length === 3) {
-	          obj[key].auth = v[2];
-	        }
-	      }
-	      else if (key === 'SPHEROID') {
-	        obj[key] = {
-	          name: v[0],
-	          a: v[1],
-	          rf: v[2]
-	        };
-	        if (v.length === 4) {
-	          obj[key].auth = v[3];
-	        }
-	      }
-	      else if (['GEOGCS', 'GEOCCS', 'DATUM', 'VERT_CS', 'COMPD_CS', 'LOCAL_CS', 'FITTED_CS', 'LOCAL_DATUM'].indexOf(key) > -1) {
-	        v[0] = ['name', v[0]];
-	        mapit(obj, key, v);
-	      }
-	      else if (v.every(function(aa) {
-	        return Array.isArray(aa);
-	      })) {
-	        mapit(obj, key, v);
-	      }
-	      else {
-	        sExpr(v, obj[key]);
-	      }
-	    }
-	  }
-	}
-
-	function rename(obj, params) {
-	  var outName = params[0];
-	  var inName = params[1];
-	  if (!(outName in obj) && (inName in obj)) {
-	    obj[outName] = obj[inName];
-	    if (params.length === 3) {
-	      obj[outName] = params[2](obj[outName]);
-	    }
-	  }
-	}
-
-	function d2r(input) {
-	  return input * D2R;
-	}
-
-	function cleanWKT(wkt) {
-	  if (wkt.type === 'GEOGCS') {
-	    wkt.projName = 'longlat';
-	  }
-	  else if (wkt.type === 'LOCAL_CS') {
-	    wkt.projName = 'identity';
-	    wkt.local = true;
-	  }
-	  else {
-	    if (typeof wkt.PROJECTION === "object") {
-	      wkt.projName = Object.keys(wkt.PROJECTION)[0];
-	    }
-	    else {
-	      wkt.projName = wkt.PROJECTION;
-	    }
-	  }
-	  if (wkt.UNIT) {
-	    wkt.units = wkt.UNIT.name.toLowerCase();
-	    if (wkt.units === 'metre') {
-	      wkt.units = 'meter';
-	    }
-	    if (wkt.UNIT.convert) {
-	      if (wkt.type === 'GEOGCS') {
-	        if (wkt.DATUM && wkt.DATUM.SPHEROID) {
-	          wkt.to_meter = parseFloat(wkt.UNIT.convert, 10)*wkt.DATUM.SPHEROID.a;
-	        }
-	      } else {
-	        wkt.to_meter = parseFloat(wkt.UNIT.convert, 10);
-	      }
-	    }
-	  }
-
-	  if (wkt.GEOGCS) {
-	    //if(wkt.GEOGCS.PRIMEM&&wkt.GEOGCS.PRIMEM.convert){
-	    //  wkt.from_greenwich=wkt.GEOGCS.PRIMEM.convert*D2R;
-	    //}
-	    if (wkt.GEOGCS.DATUM) {
-	      wkt.datumCode = wkt.GEOGCS.DATUM.name.toLowerCase();
-	    }
-	    else {
-	      wkt.datumCode = wkt.GEOGCS.name.toLowerCase();
-	    }
-	    if (wkt.datumCode.slice(0, 2) === 'd_') {
-	      wkt.datumCode = wkt.datumCode.slice(2);
-	    }
-	    if (wkt.datumCode === 'new_zealand_geodetic_datum_1949' || wkt.datumCode === 'new_zealand_1949') {
-	      wkt.datumCode = 'nzgd49';
-	    }
-	    if (wkt.datumCode === "wgs_1984") {
-	      if (wkt.PROJECTION === 'Mercator_Auxiliary_Sphere') {
-	        wkt.sphere = true;
-	      }
-	      wkt.datumCode = 'wgs84';
-	    }
-	    if (wkt.datumCode.slice(-6) === '_ferro') {
-	      wkt.datumCode = wkt.datumCode.slice(0, - 6);
-	    }
-	    if (wkt.datumCode.slice(-8) === '_jakarta') {
-	      wkt.datumCode = wkt.datumCode.slice(0, - 8);
-	    }
-	    if (~wkt.datumCode.indexOf('belge')) {
-	      wkt.datumCode = "rnb72";
-	    }
-	    if (wkt.GEOGCS.DATUM && wkt.GEOGCS.DATUM.SPHEROID) {
-	      wkt.ellps = wkt.GEOGCS.DATUM.SPHEROID.name.replace('_19', '').replace(/[Cc]larke\_18/, 'clrk');
-	      if (wkt.ellps.toLowerCase().slice(0, 13) === "international") {
-	        wkt.ellps = 'intl';
-	      }
-
-	      wkt.a = wkt.GEOGCS.DATUM.SPHEROID.a;
-	      wkt.rf = parseFloat(wkt.GEOGCS.DATUM.SPHEROID.rf, 10);
-	    }
-	    if (~wkt.datumCode.indexOf('osgb_1936')) {
-	      wkt.datumCode = "osgb36";
-	    }
-	  }
-	  if (wkt.b && !isFinite(wkt.b)) {
-	    wkt.b = wkt.a;
-	  }
-
-	  function toMeter(input) {
-	    var ratio = wkt.to_meter || 1;
-	    return parseFloat(input, 10) * ratio;
-	  }
-	  var renamer = function(a) {
-	    return rename(wkt, a);
-	  };
-	  var list = [
-	    ['standard_parallel_1', 'Standard_Parallel_1'],
-	    ['standard_parallel_2', 'Standard_Parallel_2'],
-	    ['false_easting', 'False_Easting'],
-	    ['false_northing', 'False_Northing'],
-	    ['central_meridian', 'Central_Meridian'],
-	    ['latitude_of_origin', 'Latitude_Of_Origin'],
-	    ['latitude_of_origin', 'Central_Parallel'],
-	    ['scale_factor', 'Scale_Factor'],
-	    ['k0', 'scale_factor'],
-	    ['latitude_of_center', 'Latitude_of_center'],
-	    ['lat0', 'latitude_of_center', d2r],
-	    ['longitude_of_center', 'Longitude_Of_Center'],
-	    ['longc', 'longitude_of_center', d2r],
-	    ['x0', 'false_easting', toMeter],
-	    ['y0', 'false_northing', toMeter],
-	    ['long0', 'central_meridian', d2r],
-	    ['lat0', 'latitude_of_origin', d2r],
-	    ['lat0', 'standard_parallel_1', d2r],
-	    ['lat1', 'standard_parallel_1', d2r],
-	    ['lat2', 'standard_parallel_2', d2r],
-	    ['alpha', 'azimuth', d2r],
-	    ['srsCode', 'name']
-	  ];
-	  list.forEach(renamer);
-	  if (!wkt.long0 && wkt.longc && (wkt.projName === 'Albers_Conic_Equal_Area' || wkt.projName === "Lambert_Azimuthal_Equal_Area")) {
-	    wkt.long0 = wkt.longc;
-	  }
-	  if (!wkt.lat_ts && wkt.lat1 && (wkt.projName === 'Stereographic_South_Pole' || wkt.projName === 'Polar Stereographic (variant B)')) {
-	    wkt.lat0 = d2r(wkt.lat1 > 0 ? 90 : -90);
-	    wkt.lat_ts = wkt.lat1;
-	  }
-	}
-	module.exports = function(wkt, self) {
-	  var lisp = JSON.parse(("," + wkt).replace(/\s*\,\s*([A-Z_0-9]+?)(\[)/g, ',["$1",').slice(1).replace(/\s*\,\s*([A-Z_0-9]+?)\]/g, ',"$1"]').replace(/,\["VERTCS".+/,''));
-	  var type = lisp.shift();
-	  var name = lisp.shift();
-	  lisp.unshift(['name', name]);
-	  lisp.unshift(['type', type]);
-	  lisp.unshift('output');
-	  var obj = {};
-	  sExpr(lisp, obj);
-	  cleanWKT(obj.output);
-	  return extend(self, obj.output);
-	};
-
-
-/***/ }),
-/* 22 */
-/***/ (function(module, exports) {
-
-	module.exports = function(destination, source) {
-	  destination = destination || {};
-	  var value, property;
-	  if (!source) {
-	    return destination;
-	  }
-	  for (property in source) {
-	    value = source[property];
-	    if (value !== undefined) {
-	      destination[property] = value;
-	    }
-	  }
-	  return destination;
-	};
-
-
-/***/ }),
-/* 23 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var projs = [
-	  __webpack_require__(24),
-	  __webpack_require__(30)
-	];
-	var names = {};
-	var projStore = [];
-
-	function add(proj, i) {
-	  var len = projStore.length;
-	  if (!proj.names) {
-	    console.log(i);
-	    return true;
-	  }
-	  projStore[len] = proj;
-	  proj.names.forEach(function(n) {
-	    names[n.toLowerCase()] = len;
-	  });
-	  return this;
-	}
-
-	exports.add = add;
-
-	exports.get = function(name) {
-	  if (!name) {
-	    return false;
-	  }
-	  var n = name.toLowerCase();
-	  if (typeof names[n] !== 'undefined' && projStore[names[n]]) {
-	    return projStore[names[n]];
-	  }
-	};
-	exports.start = function() {
-	  projs.forEach(add);
-	};
-
-
-/***/ }),
-/* 24 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var msfnz = __webpack_require__(25);
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	var R2D = 57.29577951308232088;
-	var adjust_lon = __webpack_require__(26);
-	var FORTPI = Math.PI/4;
-	var tsfnz = __webpack_require__(28);
-	var phi2z = __webpack_require__(29);
-	exports.init = function() {
-	  var con = this.b / this.a;
-	  this.es = 1 - con * con;
-	  if(!('x0' in this)){
-	    this.x0 = 0;
-	  }
-	  if(!('y0' in this)){
-	    this.y0 = 0;
-	  }
-	  this.e = Math.sqrt(this.es);
-	  if (this.lat_ts) {
-	    if (this.sphere) {
-	      this.k0 = Math.cos(this.lat_ts);
-	    }
-	    else {
-	      this.k0 = msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts));
-	    }
-	  }
-	  else {
-	    if (!this.k0) {
-	      if (this.k) {
-	        this.k0 = this.k;
-	      }
-	      else {
-	        this.k0 = 1;
-	      }
-	    }
-	  }
-	};
-
-	/* Mercator forward equations--mapping lat,long to x,y
-	  --------------------------------------------------*/
-
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  // convert to radians
-	  if (lat * R2D > 90 && lat * R2D < -90 && lon * R2D > 180 && lon * R2D < -180) {
-	    return null;
-	  }
-
-	  var x, y;
-	  if (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN) {
-	    return null;
-	  }
-	  else {
-	    if (this.sphere) {
-	      x = this.x0 + this.a * this.k0 * adjust_lon(lon - this.long0);
-	      y = this.y0 + this.a * this.k0 * Math.log(Math.tan(FORTPI + 0.5 * lat));
-	    }
-	    else {
-	      var sinphi = Math.sin(lat);
-	      var ts = tsfnz(this.e, lat, sinphi);
-	      x = this.x0 + this.a * this.k0 * adjust_lon(lon - this.long0);
-	      y = this.y0 - this.a * this.k0 * Math.log(ts);
-	    }
-	    p.x = x;
-	    p.y = y;
-	    return p;
-	  }
-	};
-
-
-	/* Mercator inverse equations--mapping x,y to lat/long
-	  --------------------------------------------------*/
-	exports.inverse = function(p) {
-
-	  var x = p.x - this.x0;
-	  var y = p.y - this.y0;
-	  var lon, lat;
-
-	  if (this.sphere) {
-	    lat = HALF_PI - 2 * Math.atan(Math.exp(-y / (this.a * this.k0)));
-	  }
-	  else {
-	    var ts = Math.exp(-y / (this.a * this.k0));
-	    lat = phi2z(this.e, ts);
-	    if (lat === -9999) {
-	      return null;
-	    }
-	  }
-	  lon = adjust_lon(this.long0 + x / (this.a * this.k0));
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-
-	exports.names = ["Mercator", "Popular Visualisation Pseudo Mercator", "Mercator_1SP", "Mercator_Auxiliary_Sphere", "merc"];
-
-
-/***/ }),
-/* 25 */
-/***/ (function(module, exports) {
-
-	module.exports = function(eccent, sinphi, cosphi) {
-	  var con = eccent * sinphi;
-	  return cosphi / (Math.sqrt(1 - con * con));
-	};
-
-/***/ }),
-/* 26 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var TWO_PI = Math.PI * 2;
-	// SPI is slightly greater than Math.PI, so values that exceed the -180..180
-	// degree range by a tiny amount don't get wrapped. This prevents points that
-	// have drifted from their original location along the 180th meridian (due to
-	// floating point error) from changing their sign.
-	var SPI = 3.14159265359;
-	var sign = __webpack_require__(27);
-
-	module.exports = function(x) {
-	  return (Math.abs(x) <= SPI) ? x : (x - (sign(x) * TWO_PI));
-	};
-
-/***/ }),
-/* 27 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  return x<0 ? -1 : 1;
-	};
-
-/***/ }),
-/* 28 */
-/***/ (function(module, exports) {
-
-	var HALF_PI = Math.PI/2;
-
-	module.exports = function(eccent, phi, sinphi) {
-	  var con = eccent * sinphi;
-	  var com = 0.5 * eccent;
-	  con = Math.pow(((1 - con) / (1 + con)), com);
-	  return (Math.tan(0.5 * (HALF_PI - phi)) / con);
-	};
-
-/***/ }),
-/* 29 */
-/***/ (function(module, exports) {
-
-	var HALF_PI = Math.PI/2;
-	module.exports = function(eccent, ts) {
-	  var eccnth = 0.5 * eccent;
-	  var con, dphi;
-	  var phi = HALF_PI - 2 * Math.atan(ts);
-	  for (var i = 0; i <= 15; i++) {
-	    con = eccent * Math.sin(phi);
-	    dphi = HALF_PI - 2 * Math.atan(ts * (Math.pow(((1 - con) / (1 + con)), eccnth))) - phi;
-	    phi += dphi;
-	    if (Math.abs(dphi) <= 0.0000000001) {
-	      return phi;
-	    }
-	  }
-	  //console.log("phi2z has NoConvergence");
-	  return -9999;
-	};
-
-/***/ }),
-/* 30 */
-/***/ (function(module, exports) {
-
-	exports.init = function() {
-	  //no-op for longlat
-	};
-
-	function identity(pt) {
-	  return pt;
-	}
-	exports.forward = identity;
-	exports.inverse = identity;
-	exports.names = ["longlat", "identity"];
-
-
-/***/ }),
-/* 31 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	// ellipoid pj_set_ell.c
-	var SIXTH = 0.1666666666666666667;
-	/* 1/6 */
-	var RA4 = 0.04722222222222222222;
-	/* 17/360 */
-	var RA6 = 0.02215608465608465608;
-	var EPSLN = 1.0e-10;
-	var Ellipsoid = __webpack_require__(32);
-
-	exports.eccentricity = function(a, b, rf, R_A) {
-	  var a2 = a * a; // used in geocentric
-	  var b2 = b * b; // used in geocentric
-	  var es = (a2 - b2) / a2; // e ^ 2
-	  var e = 0;
-	  if (R_A) {
-	    a *= 1 - es * (SIXTH + es * (RA4 + es * RA6));
-	    a2 = a * a;
-	    es = 0;
-	  } else {
-	    e = Math.sqrt(es); // eccentricity
-	  }
-	  var ep2 = (a2 - b2) / b2; // used in geocentric
-	  return {
-	    es: es,
-	    e: e,
-	    ep2: ep2
-	  };
-	};
-	exports.sphere = function (a, b, rf, ellps, sphere) {
-	  if (!a) { // do we have an ellipsoid?
-	    var ellipse = Ellipsoid[ellps];
-	    if (!ellipse) {
-	      ellipse = Ellipsoid.WGS84;
-	    }
-	    a = ellipse.a;
-	    b = ellipse.b;
-	    rf = ellipse.rf;
-	  }
-
-	  if (rf && !b) {
-	    b = (1.0 - 1.0 / rf) * a;
-	  }
-	  if (rf === 0 || Math.abs(a - b) < EPSLN) {
-	    sphere = true;
-	    b = a;
-	  }
-	  return {
-	    a: a,
-	    b: b,
-	    rf: rf,
-	    sphere: sphere
-	  };
-	};
-
-
-/***/ }),
-/* 32 */
-/***/ (function(module, exports) {
-
-	exports.MERIT = {
-	  a: 6378137.0,
-	  rf: 298.257,
-	  ellipseName: "MERIT 1983"
-	};
-	exports.SGS85 = {
-	  a: 6378136.0,
-	  rf: 298.257,
-	  ellipseName: "Soviet Geodetic System 85"
-	};
-	exports.GRS80 = {
-	  a: 6378137.0,
-	  rf: 298.257222101,
-	  ellipseName: "GRS 1980(IUGG, 1980)"
-	};
-	exports.IAU76 = {
-	  a: 6378140.0,
-	  rf: 298.257,
-	  ellipseName: "IAU 1976"
-	};
-	exports.airy = {
-	  a: 6377563.396,
-	  b: 6356256.910,
-	  ellipseName: "Airy 1830"
-	};
-	exports.APL4 = {
-	  a: 6378137,
-	  rf: 298.25,
-	  ellipseName: "Appl. Physics. 1965"
-	};
-	exports.NWL9D = {
-	  a: 6378145.0,
-	  rf: 298.25,
-	  ellipseName: "Naval Weapons Lab., 1965"
-	};
-	exports.mod_airy = {
-	  a: 6377340.189,
-	  b: 6356034.446,
-	  ellipseName: "Modified Airy"
-	};
-	exports.andrae = {
-	  a: 6377104.43,
-	  rf: 300.0,
-	  ellipseName: "Andrae 1876 (Den., Iclnd.)"
-	};
-	exports.aust_SA = {
-	  a: 6378160.0,
-	  rf: 298.25,
-	  ellipseName: "Australian Natl & S. Amer. 1969"
-	};
-	exports.GRS67 = {
-	  a: 6378160.0,
-	  rf: 298.2471674270,
-	  ellipseName: "GRS 67(IUGG 1967)"
-	};
-	exports.bessel = {
-	  a: 6377397.155,
-	  rf: 299.1528128,
-	  ellipseName: "Bessel 1841"
-	};
-	exports.bess_nam = {
-	  a: 6377483.865,
-	  rf: 299.1528128,
-	  ellipseName: "Bessel 1841 (Namibia)"
-	};
-	exports.clrk66 = {
-	  a: 6378206.4,
-	  b: 6356583.8,
-	  ellipseName: "Clarke 1866"
-	};
-	exports.clrk80 = {
-	  a: 6378249.145,
-	  rf: 293.4663,
-	  ellipseName: "Clarke 1880 mod."
-	};
-	exports.clrk58 = {
-	  a: 6378293.645208759,
-	  rf: 294.2606763692654,
-	  ellipseName: "Clarke 1858"
-	};
-	exports.CPM = {
-	  a: 6375738.7,
-	  rf: 334.29,
-	  ellipseName: "Comm. des Poids et Mesures 1799"
-	};
-	exports.delmbr = {
-	  a: 6376428.0,
-	  rf: 311.5,
-	  ellipseName: "Delambre 1810 (Belgium)"
-	};
-	exports.engelis = {
-	  a: 6378136.05,
-	  rf: 298.2566,
-	  ellipseName: "Engelis 1985"
-	};
-	exports.evrst30 = {
-	  a: 6377276.345,
-	  rf: 300.8017,
-	  ellipseName: "Everest 1830"
-	};
-	exports.evrst48 = {
-	  a: 6377304.063,
-	  rf: 300.8017,
-	  ellipseName: "Everest 1948"
-	};
-	exports.evrst56 = {
-	  a: 6377301.243,
-	  rf: 300.8017,
-	  ellipseName: "Everest 1956"
-	};
-	exports.evrst69 = {
-	  a: 6377295.664,
-	  rf: 300.8017,
-	  ellipseName: "Everest 1969"
-	};
-	exports.evrstSS = {
-	  a: 6377298.556,
-	  rf: 300.8017,
-	  ellipseName: "Everest (Sabah & Sarawak)"
-	};
-	exports.fschr60 = {
-	  a: 6378166.0,
-	  rf: 298.3,
-	  ellipseName: "Fischer (Mercury Datum) 1960"
-	};
-	exports.fschr60m = {
-	  a: 6378155.0,
-	  rf: 298.3,
-	  ellipseName: "Fischer 1960"
-	};
-	exports.fschr68 = {
-	  a: 6378150.0,
-	  rf: 298.3,
-	  ellipseName: "Fischer 1968"
-	};
-	exports.helmert = {
-	  a: 6378200.0,
-	  rf: 298.3,
-	  ellipseName: "Helmert 1906"
-	};
-	exports.hough = {
-	  a: 6378270.0,
-	  rf: 297.0,
-	  ellipseName: "Hough"
-	};
-	exports.intl = {
-	  a: 6378388.0,
-	  rf: 297.0,
-	  ellipseName: "International 1909 (Hayford)"
-	};
-	exports.kaula = {
-	  a: 6378163.0,
-	  rf: 298.24,
-	  ellipseName: "Kaula 1961"
-	};
-	exports.lerch = {
-	  a: 6378139.0,
-	  rf: 298.257,
-	  ellipseName: "Lerch 1979"
-	};
-	exports.mprts = {
-	  a: 6397300.0,
-	  rf: 191.0,
-	  ellipseName: "Maupertius 1738"
-	};
-	exports.new_intl = {
-	  a: 6378157.5,
-	  b: 6356772.2,
-	  ellipseName: "New International 1967"
-	};
-	exports.plessis = {
-	  a: 6376523.0,
-	  rf: 6355863.0,
-	  ellipseName: "Plessis 1817 (France)"
-	};
-	exports.krass = {
-	  a: 6378245.0,
-	  rf: 298.3,
-	  ellipseName: "Krassovsky, 1942"
-	};
-	exports.SEasia = {
-	  a: 6378155.0,
-	  b: 6356773.3205,
-	  ellipseName: "Southeast Asia"
-	};
-	exports.walbeck = {
-	  a: 6376896.0,
-	  b: 6355834.8467,
-	  ellipseName: "Walbeck"
-	};
-	exports.WGS60 = {
-	  a: 6378165.0,
-	  rf: 298.3,
-	  ellipseName: "WGS 60"
-	};
-	exports.WGS66 = {
-	  a: 6378145.0,
-	  rf: 298.25,
-	  ellipseName: "WGS 66"
-	};
-	exports.WGS7 = {
-	  a: 6378135.0,
-	  rf: 298.26,
-	  ellipseName: "WGS 72"
-	};
-	exports.WGS84 = {
-	  a: 6378137.0,
-	  rf: 298.257223563,
-	  ellipseName: "WGS 84"
-	};
-	exports.sphere = {
-	  a: 6370997.0,
-	  b: 6370997.0,
-	  ellipseName: "Normal Sphere (r=6370997)"
-	};
-
-/***/ }),
-/* 33 */
-/***/ (function(module, exports) {
-
-	exports.wgs84 = {
-	  towgs84: "0,0,0",
-	  ellipse: "WGS84",
-	  datumName: "WGS84"
-	};
-	exports.ch1903 = {
-	  towgs84: "674.374,15.056,405.346",
-	  ellipse: "bessel",
-	  datumName: "swiss"
-	};
-	exports.ggrs87 = {
-	  towgs84: "-199.87,74.79,246.62",
-	  ellipse: "GRS80",
-	  datumName: "Greek_Geodetic_Reference_System_1987"
-	};
-	exports.nad83 = {
-	  towgs84: "0,0,0",
-	  ellipse: "GRS80",
-	  datumName: "North_American_Datum_1983"
-	};
-	exports.nad27 = {
-	  nadgrids: "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat",
-	  ellipse: "clrk66",
-	  datumName: "North_American_Datum_1927"
-	};
-	exports.potsdam = {
-	  towgs84: "606.0,23.0,413.0",
-	  ellipse: "bessel",
-	  datumName: "Potsdam Rauenberg 1950 DHDN"
-	};
-	exports.carthage = {
-	  towgs84: "-263.0,6.0,431.0",
-	  ellipse: "clark80",
-	  datumName: "Carthage 1934 Tunisia"
-	};
-	exports.hermannskogel = {
-	  towgs84: "653.0,-212.0,449.0",
-	  ellipse: "bessel",
-	  datumName: "Hermannskogel"
-	};
-	exports.ire65 = {
-	  towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15",
-	  ellipse: "mod_airy",
-	  datumName: "Ireland 1965"
-	};
-	exports.rassadiran = {
-	  towgs84: "-133.63,-157.5,-158.62",
-	  ellipse: "intl",
-	  datumName: "Rassadiran"
-	};
-	exports.nzgd49 = {
-	  towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993",
-	  ellipse: "intl",
-	  datumName: "New Zealand Geodetic Datum 1949"
-	};
-	exports.osgb36 = {
-	  towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894",
-	  ellipse: "airy",
-	  datumName: "Airy 1830"
-	};
-	exports.s_jtsk = {
-	  towgs84: "589,76,480",
-	  ellipse: 'bessel',
-	  datumName: 'S-JTSK (Ferro)'
-	};
-	exports.beduaram = {
-	  towgs84: '-106,-87,188',
-	  ellipse: 'clrk80',
-	  datumName: 'Beduaram'
-	};
-	exports.gunung_segara = {
-	  towgs84: '-403,684,41',
-	  ellipse: 'bessel',
-	  datumName: 'Gunung Segara Jakarta'
-	};
-	exports.rnb72 = {
-	  towgs84: "106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1",
-	  ellipse: "intl",
-	  datumName: "Reseau National Belge 1972"
-	};
-
-/***/ }),
-/* 34 */
-/***/ (function(module, exports) {
-
-	var PJD_3PARAM = 1;
-	var PJD_7PARAM = 2;
-	var PJD_WGS84 = 4; // WGS84 or equivalent
-	var PJD_NODATUM = 5; // WGS84 or equivalent
-	var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
-
-	function datum(datumCode, datum_params, a, b, es, ep2) {
-	  var out = {};
-	  out.datum_type = PJD_WGS84; //default setting
-	  if (datumCode && datumCode === 'none') {
-	    out.datum_type = PJD_NODATUM;
-	  }
-
-	  if (datum_params) {
-	    out.datum_params = datum_params.map(parseFloat);
-	    if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) {
-	      out.datum_type = PJD_3PARAM;
-	    }
-	    if (out.datum_params.length > 3) {
-	      if (out.datum_params[3] !== 0 || out.datum_params[4] !== 0 || out.datum_params[5] !== 0 || out.datum_params[6] !== 0) {
-	        out.datum_type = PJD_7PARAM;
-	        out.datum_params[3] *= SEC_TO_RAD;
-	        out.datum_params[4] *= SEC_TO_RAD;
-	        out.datum_params[5] *= SEC_TO_RAD;
-	        out.datum_params[6] = (out.datum_params[6] / 1000000.0) + 1.0;
-	      }
-	    }
-	  }
-
-
-	  out.a = a; //datum object also uses these values
-	  out.b = b;
-	  out.es = es;
-	  out.ep2 = ep2;
-	  return out;
-	}
-
-	module.exports = datum;
-
-
-/***/ }),
-/* 35 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var D2R = 0.01745329251994329577;
-	var R2D = 57.29577951308232088;
-	var PJD_3PARAM = 1;
-	var PJD_7PARAM = 2;
-	var datum_transform = __webpack_require__(36);
-	var adjust_axis = __webpack_require__(38);
-	var proj = __webpack_require__(14);
-	var toPoint = __webpack_require__(39);
-	function checkNotWGS(source, dest) {
-	  return ((source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM) && dest.datumCode !== 'WGS84') || ((dest.datum.datum_type === PJD_3PARAM || dest.datum.datum_type === PJD_7PARAM) && source.datumCode !== 'WGS84');
-	}
-	module.exports = function transform(source, dest, point) {
-	  var wgs84;
-	  if (Array.isArray(point)) {
-	    point = toPoint(point);
-	  }
-
-	  // Workaround for datum shifts towgs84, if either source or destination projection is not wgs84
-	  if (source.datum && dest.datum && checkNotWGS(source, dest)) {
-	    wgs84 = new proj('WGS84');
-	    point = transform(source, wgs84, point);
-	    source = wgs84;
-	  }
-	  // DGR, 2010/11/12
-	  if (source.axis !== 'enu') {
-	    point = adjust_axis(source, false, point);
-	  }
-	  // Transform source points to long/lat, if they aren't already.
-	  if (source.projName === 'longlat') {
-	    point = {
-	      x: point.x * D2R,
-	      y: point.y * D2R
-	    };
-	  }
-	  else {
-	    if (source.to_meter) {
-	      point = {
-	        x: point.x * source.to_meter,
-	        y: point.y * source.to_meter
-	      };
-	    }
-	    point = source.inverse(point); // Convert Cartesian to longlat
-	  }
-	  // Adjust for the prime meridian if necessary
-	  if (source.from_greenwich) {
-	    point.x += source.from_greenwich;
-	  }
-
-	  // Convert datums if needed, and if possible.
-	  point = datum_transform(source.datum, dest.datum, point);
-
-	  // Adjust for the prime meridian if necessary
-	  if (dest.from_greenwich) {
-	    point = {
-	      x: point.x - dest.grom_greenwich,
-	      y: point.y
-	    };
-	  }
-
-	  if (dest.projName === 'longlat') {
-	    // convert radians to decimal degrees
-	    point = {
-	      x: point.x * R2D,
-	      y: point.y * R2D
-	    };
-	  } else { // else project
-	    point = dest.forward(point);
-	    if (dest.to_meter) {
-	      point = {
-	        x: point.x / dest.to_meter,
-	        y: point.y / dest.to_meter
-	      };
-	    }
-	  }
-
-	  // DGR, 2010/11/12
-	  if (dest.axis !== 'enu') {
-	    return adjust_axis(dest, true, point);
-	  }
-
-	  return point;
-	};
-
-
-/***/ }),
-/* 36 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var PJD_3PARAM = 1;
-	var PJD_7PARAM = 2;
-	var PJD_NODATUM = 5; // WGS84 or equivalent
-	var datum = __webpack_require__(37);
-	function checkParams(type) {
-	  return (type === PJD_3PARAM || type === PJD_7PARAM);
-	}
-	module.exports = function(source, dest, point) {
-	  // Short cut if the datums are identical.
-	  if (datum.compareDatums(source, dest)) {
-	    return point; // in this case, zero is sucess,
-	    // whereas cs_compare_datums returns 1 to indicate TRUE
-	    // confusing, should fix this
-	  }
-
-	  // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest
-	  if (source.datum_type === PJD_NODATUM || dest.datum_type === PJD_NODATUM) {
-	    return point;
-	  }
-
-	  // If this datum requires grid shifts, then apply it to geodetic coordinates.
-
-	  // Do we need to go through geocentric coordinates?
-	  if (source.es === dest.es && source.a === dest.a && !checkParams(source.datum_type) &&  !checkParams(dest.datum_type)) {
-	    return point;
-	  }
-
-	  // Convert to geocentric coordinates.
-	  point = datum.geodeticToGeocentric(point, source.es, source.a);
-	  // Convert between datums
-	  if (checkParams(source.datum_type)) {
-	    point = datum.geocentricToWgs84(point, source.datum_type, source.datum_params);
-	  }
-	  if (checkParams(dest.datum_type)) {
-	    point = datum.geocentricFromWgs84(point, dest.datum_type, dest.datum_params);
-	  }
-	  return datum.geocentricToGeodetic(point, dest.es, dest.a, dest.b);
-
-	};
-
-
-/***/ }),
-/* 37 */
-/***/ (function(module, exports) {
-
-	'use strict';
-	var PJD_3PARAM = 1;
-	var PJD_7PARAM = 2;
-	var HALF_PI = Math.PI/2;
-
-	exports.compareDatums = function(source, dest) {
-	  if (source.datum_type !== dest.datum_type) {
-	    return false; // false, datums are not equal
-	  } else if (source.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
-	    // the tolerence for es is to ensure that GRS80 and WGS84
-	    // are considered identical
-	    return false;
-	  } else if (source.datum_type === PJD_3PARAM) {
-	    return (this.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2]);
-	  } else if (source.datum_type === PJD_7PARAM) {
-	    return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2] && source.datum_params[3] === dest.datum_params[3] && source.datum_params[4] === dest.datum_params[4] && source.datum_params[5] === dest.datum_params[5] && source.datum_params[6] === dest.datum_params[6]);
-	  } else {
-	    return true; // datums are equal
-	  }
-	}; // cs_compare_datums()
-
-	/*
-	 * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
-	 * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
-	 * according to the current ellipsoid parameters.
-	 *
-	 *    Latitude  : Geodetic latitude in radians                     (input)
-	 *    Longitude : Geodetic longitude in radians                    (input)
-	 *    Height    : Geodetic height, in meters                       (input)
-	 *    X         : Calculated Geocentric X coordinate, in meters    (output)
-	 *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
-	 *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
-	 *
-	 */
-	exports.geodeticToGeocentric = function(p, es, a) {
-	  var Longitude = p.x;
-	  var Latitude = p.y;
-	  var Height = p.z ? p.z : 0; //Z value not always supplied
-
-	  var Rn; /*  Earth radius at location  */
-	  var Sin_Lat; /*  Math.sin(Latitude)  */
-	  var Sin2_Lat; /*  Square of Math.sin(Latitude)  */
-	  var Cos_Lat; /*  Math.cos(Latitude)  */
-
-	  /*
-	   ** Don't blow up if Latitude is just a little out of the value
-	   ** range as it may just be a rounding issue.  Also removed longitude
-	   ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
-	   */
-	  if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
-	    Latitude = -HALF_PI;
-	  } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
-	    Latitude = HALF_PI;
-	  } else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
-	    /* Latitude out of range */
-	    //..reportError('geocent:lat out of range:' + Latitude);
-	    return null;
-	  }
-
-	  if (Longitude > Math.PI) {
-	    Longitude -= (2 * Math.PI);
-	  }
-	  Sin_Lat = Math.sin(Latitude);
-	  Cos_Lat = Math.cos(Latitude);
-	  Sin2_Lat = Sin_Lat * Sin_Lat;
-	  Rn = a / (Math.sqrt(1.0e0 - es * Sin2_Lat));
-	  return {
-	    x: (Rn + Height) * Cos_Lat * Math.cos(Longitude),
-	    y: (Rn + Height) * Cos_Lat * Math.sin(Longitude),
-	    z: ((Rn * (1 - es)) + Height) * Sin_Lat
-	  };
-	}; // cs_geodetic_to_geocentric()
-
-
-	exports.geocentricToGeodetic = function(p, es, a, b) {
-	  /* local defintions and variables */
-	  /* end-criterium of loop, accuracy of sin(Latitude) */
-	  var genau = 1e-12;
-	  var genau2 = (genau * genau);
-	  var maxiter = 30;
-
-	  var P; /* distance between semi-minor axis and location */
-	  var RR; /* distance between center and location */
-	  var CT; /* sin of geocentric latitude */
-	  var ST; /* cos of geocentric latitude */
-	  var RX;
-	  var RK;
-	  var RN; /* Earth radius at location */
-	  var CPHI0; /* cos of start or old geodetic latitude in iterations */
-	  var SPHI0; /* sin of start or old geodetic latitude in iterations */
-	  var CPHI; /* cos of searched geodetic latitude */
-	  var SPHI; /* sin of searched geodetic latitude */
-	  var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
-	  var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
-
-	  var X = p.x;
-	  var Y = p.y;
-	  var Z = p.z ? p.z : 0.0; //Z value not always supplied
-	  var Longitude;
-	  var Latitude;
-	  var Height;
-
-	  P = Math.sqrt(X * X + Y * Y);
-	  RR = Math.sqrt(X * X + Y * Y + Z * Z);
-
-	  /*      special cases for latitude and longitude */
-	  if (P / a < genau) {
-
-	    /*  special case, if P=0. (X=0., Y=0.) */
-	    Longitude = 0.0;
-
-	    /*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
-	     *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
-	    if (RR / a < genau) {
-	      Latitude = HALF_PI;
-	      Height = -b;
-	      return {
-	        x: p.x,
-	        y: p.y,
-	        z: p.z
-	      };
-	    }
-	  } else {
-	    /*  ellipsoidal (geodetic) longitude
-	     *  interval: -PI < Longitude <= +PI */
-	    Longitude = Math.atan2(Y, X);
-	  }
-
-	  /* --------------------------------------------------------------
-	   * Following iterative algorithm was developped by
-	   * "Institut for Erdmessung", University of Hannover, July 1988.
-	   * Internet: www.ife.uni-hannover.de
-	   * Iterative computation of CPHI,SPHI and Height.
-	   * Iteration of CPHI and SPHI to 10**-12 radian resp.
-	   * 2*10**-7 arcsec.
-	   * --------------------------------------------------------------
-	   */
-	  CT = Z / RR;
-	  ST = P / RR;
-	  RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST);
-	  CPHI0 = ST * (1.0 - es) * RX;
-	  SPHI0 = CT * RX;
-	  iter = 0;
-
-	  /* loop to find sin(Latitude) resp. Latitude
-	   * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
-	  do {
-	    iter++;
-	    RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0);
-
-	    /*  ellipsoidal (geodetic) height */
-	    Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0);
-
-	    RK = es * RN / (RN + Height);
-	    RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
-	    CPHI = ST * (1.0 - RK) * RX;
-	    SPHI = CT * RX;
-	    SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
-	    CPHI0 = CPHI;
-	    SPHI0 = SPHI;
-	  }
-	  while (SDPHI * SDPHI > genau2 && iter < maxiter);
-
-	  /*      ellipsoidal (geodetic) latitude */
-	  Latitude = Math.atan(SPHI / Math.abs(CPHI));
-	  return {
-	    x: Longitude,
-	    y: Latitude,
-	    z: Height
-	  };
-	}; // cs_geocentric_to_geodetic()
-
-
-	/****************************************************************/
-	// pj_geocentic_to_wgs84( p )
-	//  p = point to transform in geocentric coordinates (x,y,z)
-
-
-	/** point object, nothing fancy, just allows values to be
-	    passed back and forth by reference rather than by value.
-	    Other point classes may be used as long as they have
-	    x and y properties, which will get modified in the transform method.
-	*/
-	exports.geocentricToWgs84 = function(p, datum_type, datum_params) {
-
-	  if (datum_type === PJD_3PARAM) {
-	    // if( x[io] === HUGE_VAL )
-	    //    continue;
-	    return {
-	      x: p.x + datum_params[0],
-	      y: p.y + datum_params[1],
-	      z: p.z + datum_params[2],
-	    };
-	  } else if (datum_type === PJD_7PARAM) {
-	    var Dx_BF = datum_params[0];
-	    var Dy_BF = datum_params[1];
-	    var Dz_BF = datum_params[2];
-	    var Rx_BF = datum_params[3];
-	    var Ry_BF = datum_params[4];
-	    var Rz_BF = datum_params[5];
-	    var M_BF = datum_params[6];
-	    // if( x[io] === HUGE_VAL )
-	    //    continue;
-	    return {
-	      x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF,
-	      y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF,
-	      z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF
-	    };
-	  }
-	}; // cs_geocentric_to_wgs84
-
-	/****************************************************************/
-	// pj_geocentic_from_wgs84()
-	//  coordinate system definition,
-	//  point to transform in geocentric coordinates (x,y,z)
-	exports.geocentricFromWgs84 = function(p, datum_type, datum_params) {
-
-	  if (datum_type === PJD_3PARAM) {
-	    //if( x[io] === HUGE_VAL )
-	    //    continue;
-	    return {
-	      x: p.x - datum_params[0],
-	      y: p.y - datum_params[1],
-	      z: p.z - datum_params[2],
-	    };
-
-	  } else if (datum_type === PJD_7PARAM) {
-	    var Dx_BF = datum_params[0];
-	    var Dy_BF = datum_params[1];
-	    var Dz_BF = datum_params[2];
-	    var Rx_BF = datum_params[3];
-	    var Ry_BF = datum_params[4];
-	    var Rz_BF = datum_params[5];
-	    var M_BF = datum_params[6];
-	    var x_tmp = (p.x - Dx_BF) / M_BF;
-	    var y_tmp = (p.y - Dy_BF) / M_BF;
-	    var z_tmp = (p.z - Dz_BF) / M_BF;
-	    //if( x[io] === HUGE_VAL )
-	    //    continue;
-
-	    return {
-	      x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp,
-	      y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp,
-	      z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp
-	    };
-	  } //cs_geocentric_from_wgs84()
-	};
-
-
-/***/ }),
-/* 38 */
-/***/ (function(module, exports) {
-
-	module.exports = function(crs, denorm, point) {
-	  var xin = point.x,
-	    yin = point.y,
-	    zin = point.z || 0.0;
-	  var v, t, i;
-	  var out = {};
-	  for (i = 0; i < 3; i++) {
-	    if (denorm && i === 2 && point.z === undefined) {
-	      continue;
-	    }
-	    if (i === 0) {
-	      v = xin;
-	      t = 'x';
-	    }
-	    else if (i === 1) {
-	      v = yin;
-	      t = 'y';
-	    }
-	    else {
-	      v = zin;
-	      t = 'z';
-	    }
-	    switch (crs.axis[i]) {
-	    case 'e':
-	      out[t] = v;
-	      break;
-	    case 'w':
-	      out[t] = -v;
-	      break;
-	    case 'n':
-	      out[t] = v;
-	      break;
-	    case 's':
-	      out[t] = -v;
-	      break;
-	    case 'u':
-	      if (point[t] !== undefined) {
-	        out.z = v;
-	      }
-	      break;
-	    case 'd':
-	      if (point[t] !== undefined) {
-	        out.z = -v;
-	      }
-	      break;
-	    default:
-	      //console.log("ERROR: unknow axis ("+crs.axis[i]+") - check definition of "+crs.projName);
-	      return null;
-	    }
-	  }
-	  return out;
-	};
-
-
-/***/ }),
-/* 39 */
-/***/ (function(module, exports) {
-
-	module.exports = function (array){
-	  var out = {
-	    x: array[0],
-	    y: array[1]
-	  };
-	  if (array.length>2) {
-	    out.z = array[2];
-	  }
-	  if (array.length>3) {
-	    out.m = array[3];
-	  }
-	  return out;
-	};
-
-/***/ }),
-/* 40 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var mgrs = __webpack_require__(41);
-
-	function Point(x, y, z) {
-	  if (!(this instanceof Point)) {
-	    return new Point(x, y, z);
-	  }
-	  if (Array.isArray(x)) {
-	    this.x = x[0];
-	    this.y = x[1];
-	    this.z = x[2] || 0.0;
-	  } else if(typeof x === 'object') {
-	    this.x = x.x;
-	    this.y = x.y;
-	    this.z = x.z || 0.0;
-	  } else if (typeof x === 'string' && typeof y === 'undefined') {
-	    var coords = x.split(',');
-	    this.x = parseFloat(coords[0], 10);
-	    this.y = parseFloat(coords[1], 10);
-	    this.z = parseFloat(coords[2], 10) || 0.0;
-	  } else {
-	    this.x = x;
-	    this.y = y;
-	    this.z = z || 0.0;
-	  }
-	  console.warn('proj4.Point will be removed in version 3, use proj4.toPoint');
-	}
-
-	Point.fromMGRS = function(mgrsStr) {
-	  return new Point(mgrs.toPoint(mgrsStr));
-	};
-	Point.prototype.toMGRS = function(accuracy) {
-	  return mgrs.forward([this.x, this.y], accuracy);
-	};
-	module.exports = Point;
-
-
-/***/ }),
-/* 41 */
-/***/ (function(module, exports) {
-
-	
-
-
-	/**
-	 * UTM zones are grouped, and assigned to one of a group of 6
-	 * sets.
-	 *
-	 * {int} @private
-	 */
-	var NUM_100K_SETS = 6;
-
-	/**
-	 * The column letters (for easting) of the lower left value, per
-	 * set.
-	 *
-	 * {string} @private
-	 */
-	var SET_ORIGIN_COLUMN_LETTERS = 'AJSAJS';
-
-	/**
-	 * The row letters (for northing) of the lower left value, per
-	 * set.
-	 *
-	 * {string} @private
-	 */
-	var SET_ORIGIN_ROW_LETTERS = 'AFAFAF';
-
-	var A = 65; // A
-	var I = 73; // I
-	var O = 79; // O
-	var V = 86; // V
-	var Z = 90; // Z
-
-	/**
-	 * Conversion of lat/lon to MGRS.
-	 *
-	 * @param {object} ll Object literal with lat and lon properties on a
-	 *     WGS84 ellipsoid.
-	 * @param {int} accuracy Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for
-	 *      100 m, 2 for 1000 m or 1 for 10000 m). Optional, default is 5.
-	 * @return {string} the MGRS string for the given location and accuracy.
-	 */
-	exports.forward = function(ll, accuracy) {
-	  accuracy = accuracy || 5; // default accuracy 1m
-	  return encode(LLtoUTM({
-	    lat: ll[1],
-	    lon: ll[0]
-	  }), accuracy);
-	};
-
-	/**
-	 * Conversion of MGRS to lat/lon.
-	 *
-	 * @param {string} mgrs MGRS string.
-	 * @return {array} An array with left (longitude), bottom (latitude), right
-	 *     (longitude) and top (latitude) values in WGS84, representing the
-	 *     bounding box for the provided MGRS reference.
-	 */
-	exports.inverse = function(mgrs) {
-	  var bbox = UTMtoLL(decode(mgrs.toUpperCase()));
-	  if (bbox.lat && bbox.lon) {
-	    return [bbox.lon, bbox.lat, bbox.lon, bbox.lat];
-	  }
-	  return [bbox.left, bbox.bottom, bbox.right, bbox.top];
-	};
-
-	exports.toPoint = function(mgrs) {
-	  var bbox = UTMtoLL(decode(mgrs.toUpperCase()));
-	  if (bbox.lat && bbox.lon) {
-	    return [bbox.lon, bbox.lat];
-	  }
-	  return [(bbox.left + bbox.right) / 2, (bbox.top + bbox.bottom) / 2];
-	};
-	/**
-	 * Conversion from degrees to radians.
-	 *
-	 * @private
-	 * @param {number} deg the angle in degrees.
-	 * @return {number} the angle in radians.
-	 */
-	function degToRad(deg) {
-	  return (deg * (Math.PI / 180.0));
-	}
-
-	/**
-	 * Conversion from radians to degrees.
-	 *
-	 * @private
-	 * @param {number} rad the angle in radians.
-	 * @return {number} the angle in degrees.
-	 */
-	function radToDeg(rad) {
-	  return (180.0 * (rad / Math.PI));
-	}
-
-	/**
-	 * Converts a set of Longitude and Latitude co-ordinates to UTM
-	 * using the WGS84 ellipsoid.
-	 *
-	 * @private
-	 * @param {object} ll Object literal with lat and lon properties
-	 *     representing the WGS84 coordinate to be converted.
-	 * @return {object} Object literal containing the UTM value with easting,
-	 *     northing, zoneNumber and zoneLetter properties, and an optional
-	 *     accuracy property in digits. Returns null if the conversion failed.
-	 */
-	function LLtoUTM(ll) {
-	  var Lat = ll.lat;
-	  var Long = ll.lon;
-	  var a = 6378137.0; //ellip.radius;
-	  var eccSquared = 0.00669438; //ellip.eccsq;
-	  var k0 = 0.9996;
-	  var LongOrigin;
-	  var eccPrimeSquared;
-	  var N, T, C, A, M;
-	  var LatRad = degToRad(Lat);
-	  var LongRad = degToRad(Long);
-	  var LongOriginRad;
-	  var ZoneNumber;
-	  // (int)
-	  ZoneNumber = Math.floor((Long + 180) / 6) + 1;
-
-	  //Make sure the longitude 180.00 is in Zone 60
-	  if (Long === 180) {
-	    ZoneNumber = 60;
-	  }
-
-	  // Special zone for Norway
-	  if (Lat >= 56.0 && Lat < 64.0 && Long >= 3.0 && Long < 12.0) {
-	    ZoneNumber = 32;
-	  }
-
-	  // Special zones for Svalbard
-	  if (Lat >= 72.0 && Lat < 84.0) {
-	    if (Long >= 0.0 && Long < 9.0) {
-	      ZoneNumber = 31;
-	    }
-	    else if (Long >= 9.0 && Long < 21.0) {
-	      ZoneNumber = 33;
-	    }
-	    else if (Long >= 21.0 && Long < 33.0) {
-	      ZoneNumber = 35;
-	    }
-	    else if (Long >= 33.0 && Long < 42.0) {
-	      ZoneNumber = 37;
-	    }
-	  }
-
-	  LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3; //+3 puts origin
-	  // in middle of
-	  // zone
-	  LongOriginRad = degToRad(LongOrigin);
-
-	  eccPrimeSquared = (eccSquared) / (1 - eccSquared);
-
-	  N = a / Math.sqrt(1 - eccSquared * Math.sin(LatRad) * Math.sin(LatRad));
-	  T = Math.tan(LatRad) * Math.tan(LatRad);
-	  C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad);
-	  A = Math.cos(LatRad) * (LongRad - LongOriginRad);
-
-	  M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(2 * LatRad) + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(4 * LatRad) - (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.sin(6 * LatRad));
-
-	  var UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6.0 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120.0) + 500000.0);
-
-	  var UTMNorthing = (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24.0 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720.0)));
-	  if (Lat < 0.0) {
-	    UTMNorthing += 10000000.0; //10000000 meter offset for
-	    // southern hemisphere
-	  }
-
-	  return {
-	    northing: Math.round(UTMNorthing),
-	    easting: Math.round(UTMEasting),
-	    zoneNumber: ZoneNumber,
-	    zoneLetter: getLetterDesignator(Lat)
-	  };
-	}
-
-	/**
-	 * Converts UTM coords to lat/long, using the WGS84 ellipsoid. This is a convenience
-	 * class where the Zone can be specified as a single string eg."60N" which
-	 * is then broken down into the ZoneNumber and ZoneLetter.
-	 *
-	 * @private
-	 * @param {object} utm An object literal with northing, easting, zoneNumber
-	 *     and zoneLetter properties. If an optional accuracy property is
-	 *     provided (in meters), a bounding box will be returned instead of
-	 *     latitude and longitude.
-	 * @return {object} An object literal containing either lat and lon values
-	 *     (if no accuracy was provided), or top, right, bottom and left values
-	 *     for the bounding box calculated according to the provided accuracy.
-	 *     Returns null if the conversion failed.
-	 */
-	function UTMtoLL(utm) {
-
-	  var UTMNorthing = utm.northing;
-	  var UTMEasting = utm.easting;
-	  var zoneLetter = utm.zoneLetter;
-	  var zoneNumber = utm.zoneNumber;
-	  // check the ZoneNummber is valid
-	  if (zoneNumber < 0 || zoneNumber > 60) {
-	    return null;
-	  }
-
-	  var k0 = 0.9996;
-	  var a = 6378137.0; //ellip.radius;
-	  var eccSquared = 0.00669438; //ellip.eccsq;
-	  var eccPrimeSquared;
-	  var e1 = (1 - Math.sqrt(1 - eccSquared)) / (1 + Math.sqrt(1 - eccSquared));
-	  var N1, T1, C1, R1, D, M;
-	  var LongOrigin;
-	  var mu, phi1Rad;
-
-	  // remove 500,000 meter offset for longitude
-	  var x = UTMEasting - 500000.0;
-	  var y = UTMNorthing;
-
-	  // We must know somehow if we are in the Northern or Southern
-	  // hemisphere, this is the only time we use the letter So even
-	  // if the Zone letter isn't exactly correct it should indicate
-	  // the hemisphere correctly
-	  if (zoneLetter < 'N') {
-	    y -= 10000000.0; // remove 10,000,000 meter offset used
-	    // for southern hemisphere
-	  }
-
-	  // There are 60 zones with zone 1 being at West -180 to -174
-	  LongOrigin = (zoneNumber - 1) * 6 - 180 + 3; // +3 puts origin
-	  // in middle of
-	  // zone
-
-	  eccPrimeSquared = (eccSquared) / (1 - eccSquared);
-
-	  M = y / k0;
-	  mu = M / (a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256));
-
-	  phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu);
-	  // double phi1 = ProjMath.radToDeg(phi1Rad);
-
-	  N1 = a / Math.sqrt(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
-	  T1 = Math.tan(phi1Rad) * Math.tan(phi1Rad);
-	  C1 = eccPrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
-	  R1 = a * (1 - eccSquared) / Math.pow(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5);
-	  D = x / (N1 * k0);
-
-	  var lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720);
-	  lat = radToDeg(lat);
-
-	  var lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad);
-	  lon = LongOrigin + radToDeg(lon);
-
-	  var result;
-	  if (utm.accuracy) {
-	    var topRight = UTMtoLL({
-	      northing: utm.northing + utm.accuracy,
-	      easting: utm.easting + utm.accuracy,
-	      zoneLetter: utm.zoneLetter,
-	      zoneNumber: utm.zoneNumber
-	    });
-	    result = {
-	      top: topRight.lat,
-	      right: topRight.lon,
-	      bottom: lat,
-	      left: lon
-	    };
-	  }
-	  else {
-	    result = {
-	      lat: lat,
-	      lon: lon
-	    };
-	  }
-	  return result;
-	}
-
-	/**
-	 * Calculates the MGRS letter designator for the given latitude.
-	 *
-	 * @private
-	 * @param {number} lat The latitude in WGS84 to get the letter designator
-	 *     for.
-	 * @return {char} The letter designator.
-	 */
-	function getLetterDesignator(lat) {
-	  //This is here as an error flag to show that the Latitude is
-	  //outside MGRS limits
-	  var LetterDesignator = 'Z';
-
-	  if ((84 >= lat) && (lat >= 72)) {
-	    LetterDesignator = 'X';
-	  }
-	  else if ((72 > lat) && (lat >= 64)) {
-	    LetterDesignator = 'W';
-	  }
-	  else if ((64 > lat) && (lat >= 56)) {
-	    LetterDesignator = 'V';
-	  }
-	  else if ((56 > lat) && (lat >= 48)) {
-	    LetterDesignator = 'U';
-	  }
-	  else if ((48 > lat) && (lat >= 40)) {
-	    LetterDesignator = 'T';
-	  }
-	  else if ((40 > lat) && (lat >= 32)) {
-	    LetterDesignator = 'S';
-	  }
-	  else if ((32 > lat) && (lat >= 24)) {
-	    LetterDesignator = 'R';
-	  }
-	  else if ((24 > lat) && (lat >= 16)) {
-	    LetterDesignator = 'Q';
-	  }
-	  else if ((16 > lat) && (lat >= 8)) {
-	    LetterDesignator = 'P';
-	  }
-	  else if ((8 > lat) && (lat >= 0)) {
-	    LetterDesignator = 'N';
-	  }
-	  else if ((0 > lat) && (lat >= -8)) {
-	    LetterDesignator = 'M';
-	  }
-	  else if ((-8 > lat) && (lat >= -16)) {
-	    LetterDesignator = 'L';
-	  }
-	  else if ((-16 > lat) && (lat >= -24)) {
-	    LetterDesignator = 'K';
-	  }
-	  else if ((-24 > lat) && (lat >= -32)) {
-	    LetterDesignator = 'J';
-	  }
-	  else if ((-32 > lat) && (lat >= -40)) {
-	    LetterDesignator = 'H';
-	  }
-	  else if ((-40 > lat) && (lat >= -48)) {
-	    LetterDesignator = 'G';
-	  }
-	  else if ((-48 > lat) && (lat >= -56)) {
-	    LetterDesignator = 'F';
-	  }
-	  else if ((-56 > lat) && (lat >= -64)) {
-	    LetterDesignator = 'E';
-	  }
-	  else if ((-64 > lat) && (lat >= -72)) {
-	    LetterDesignator = 'D';
-	  }
-	  else if ((-72 > lat) && (lat >= -80)) {
-	    LetterDesignator = 'C';
-	  }
-	  return LetterDesignator;
-	}
-
-	/**
-	 * Encodes a UTM location as MGRS string.
-	 *
-	 * @private
-	 * @param {object} utm An object literal with easting, northing,
-	 *     zoneLetter, zoneNumber
-	 * @param {number} accuracy Accuracy in digits (1-5).
-	 * @return {string} MGRS string for the given UTM location.
-	 */
-	function encode(utm, accuracy) {
-	  // prepend with leading zeroes
-	  var seasting = "00000" + utm.easting,
-	    snorthing = "00000" + utm.northing;
-
-	  return utm.zoneNumber + utm.zoneLetter + get100kID(utm.easting, utm.northing, utm.zoneNumber) + seasting.substr(seasting.length - 5, accuracy) + snorthing.substr(snorthing.length - 5, accuracy);
-	}
-
-	/**
-	 * Get the two letter 100k designator for a given UTM easting,
-	 * northing and zone number value.
-	 *
-	 * @private
-	 * @param {number} easting
-	 * @param {number} northing
-	 * @param {number} zoneNumber
-	 * @return the two letter 100k designator for the given UTM location.
-	 */
-	function get100kID(easting, northing, zoneNumber) {
-	  var setParm = get100kSetForZone(zoneNumber);
-	  var setColumn = Math.floor(easting / 100000);
-	  var setRow = Math.floor(northing / 100000) % 20;
-	  return getLetter100kID(setColumn, setRow, setParm);
-	}
-
-	/**
-	 * Given a UTM zone number, figure out the MGRS 100K set it is in.
-	 *
-	 * @private
-	 * @param {number} i An UTM zone number.
-	 * @return {number} the 100k set the UTM zone is in.
-	 */
-	function get100kSetForZone(i) {
-	  var setParm = i % NUM_100K_SETS;
-	  if (setParm === 0) {
-	    setParm = NUM_100K_SETS;
-	  }
-
-	  return setParm;
-	}
-
-	/**
-	 * Get the two-letter MGRS 100k designator given information
-	 * translated from the UTM northing, easting and zone number.
-	 *
-	 * @private
-	 * @param {number} column the column index as it relates to the MGRS
-	 *        100k set spreadsheet, created from the UTM easting.
-	 *        Values are 1-8.
-	 * @param {number} row the row index as it relates to the MGRS 100k set
-	 *        spreadsheet, created from the UTM northing value. Values
-	 *        are from 0-19.
-	 * @param {number} parm the set block, as it relates to the MGRS 100k set
-	 *        spreadsheet, created from the UTM zone. Values are from
-	 *        1-60.
-	 * @return two letter MGRS 100k code.
-	 */
-	function getLetter100kID(column, row, parm) {
-	  // colOrigin and rowOrigin are the letters at the origin of the set
-	  var index = parm - 1;
-	  var colOrigin = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(index);
-	  var rowOrigin = SET_ORIGIN_ROW_LETTERS.charCodeAt(index);
-
-	  // colInt and rowInt are the letters to build to return
-	  var colInt = colOrigin + column - 1;
-	  var rowInt = rowOrigin + row;
-	  var rollover = false;
-
-	  if (colInt > Z) {
-	    colInt = colInt - Z + A - 1;
-	    rollover = true;
-	  }
-
-	  if (colInt === I || (colOrigin < I && colInt > I) || ((colInt > I || colOrigin < I) && rollover)) {
-	    colInt++;
-	  }
-
-	  if (colInt === O || (colOrigin < O && colInt > O) || ((colInt > O || colOrigin < O) && rollover)) {
-	    colInt++;
-
-	    if (colInt === I) {
-	      colInt++;
-	    }
-	  }
-
-	  if (colInt > Z) {
-	    colInt = colInt - Z + A - 1;
-	  }
-
-	  if (rowInt > V) {
-	    rowInt = rowInt - V + A - 1;
-	    rollover = true;
-	  }
-	  else {
-	    rollover = false;
-	  }
-
-	  if (((rowInt === I) || ((rowOrigin < I) && (rowInt > I))) || (((rowInt > I) || (rowOrigin < I)) && rollover)) {
-	    rowInt++;
-	  }
-
-	  if (((rowInt === O) || ((rowOrigin < O) && (rowInt > O))) || (((rowInt > O) || (rowOrigin < O)) && rollover)) {
-	    rowInt++;
-
-	    if (rowInt === I) {
-	      rowInt++;
-	    }
-	  }
-
-	  if (rowInt > V) {
-	    rowInt = rowInt - V + A - 1;
-	  }
-
-	  var twoLetter = String.fromCharCode(colInt) + String.fromCharCode(rowInt);
-	  return twoLetter;
-	}
-
-	/**
-	 * Decode the UTM parameters from a MGRS string.
-	 *
-	 * @private
-	 * @param {string} mgrsString an UPPERCASE coordinate string is expected.
-	 * @return {object} An object literal with easting, northing, zoneLetter,
-	 *     zoneNumber and accuracy (in meters) properties.
-	 */
-	function decode(mgrsString) {
-
-	  if (mgrsString && mgrsString.length === 0) {
-	    throw ("MGRSPoint coverting from nothing");
-	  }
-
-	  var length = mgrsString.length;
-
-	  var hunK = null;
-	  var sb = "";
-	  var testChar;
-	  var i = 0;
-
-	  // get Zone number
-	  while (!(/[A-Z]/).test(testChar = mgrsString.charAt(i))) {
-	    if (i >= 2) {
-	      throw ("MGRSPoint bad conversion from: " + mgrsString);
-	    }
-	    sb += testChar;
-	    i++;
-	  }
-
-	  var zoneNumber = parseInt(sb, 10);
-
-	  if (i === 0 || i + 3 > length) {
-	    // A good MGRS string has to be 4-5 digits long,
-	    // ##AAA/#AAA at least.
-	    throw ("MGRSPoint bad conversion from: " + mgrsString);
-	  }
-
-	  var zoneLetter = mgrsString.charAt(i++);
-
-	  // Should we check the zone letter here? Why not.
-	  if (zoneLetter <= 'A' || zoneLetter === 'B' || zoneLetter === 'Y' || zoneLetter >= 'Z' || zoneLetter === 'I' || zoneLetter === 'O') {
-	    throw ("MGRSPoint zone letter " + zoneLetter + " not handled: " + mgrsString);
-	  }
-
-	  hunK = mgrsString.substring(i, i += 2);
-
-	  var set = get100kSetForZone(zoneNumber);
-
-	  var east100k = getEastingFromChar(hunK.charAt(0), set);
-	  var north100k = getNorthingFromChar(hunK.charAt(1), set);
-
-	  // We have a bug where the northing may be 2000000 too low.
-	  // How
-	  // do we know when to roll over?
-
-	  while (north100k < getMinNorthing(zoneLetter)) {
-	    north100k += 2000000;
-	  }
-
-	  // calculate the char index for easting/northing separator
-	  var remainder = length - i;
-
-	  if (remainder % 2 !== 0) {
-	    throw ("MGRSPoint has to have an even number \nof digits after the zone letter and two 100km letters - front \nhalf for easting meters, second half for \nnorthing meters" + mgrsString);
-	  }
-
-	  var sep = remainder / 2;
-
-	  var sepEasting = 0.0;
-	  var sepNorthing = 0.0;
-	  var accuracyBonus, sepEastingString, sepNorthingString, easting, northing;
-	  if (sep > 0) {
-	    accuracyBonus = 100000.0 / Math.pow(10, sep);
-	    sepEastingString = mgrsString.substring(i, i + sep);
-	    sepEasting = parseFloat(sepEastingString) * accuracyBonus;
-	    sepNorthingString = mgrsString.substring(i + sep);
-	    sepNorthing = parseFloat(sepNorthingString) * accuracyBonus;
-	  }
-
-	  easting = sepEasting + east100k;
-	  northing = sepNorthing + north100k;
-
-	  return {
-	    easting: easting,
-	    northing: northing,
-	    zoneLetter: zoneLetter,
-	    zoneNumber: zoneNumber,
-	    accuracy: accuracyBonus
-	  };
-	}
-
-	/**
-	 * Given the first letter from a two-letter MGRS 100k zone, and given the
-	 * MGRS table set for the zone number, figure out the easting value that
-	 * should be added to the other, secondary easting value.
-	 *
-	 * @private
-	 * @param {char} e The first letter from a two-letter MGRS 100´k zone.
-	 * @param {number} set The MGRS table set for the zone number.
-	 * @return {number} The easting value for the given letter and set.
-	 */
-	function getEastingFromChar(e, set) {
-	  // colOrigin is the letter at the origin of the set for the
-	  // column
-	  var curCol = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(set - 1);
-	  var eastingValue = 100000.0;
-	  var rewindMarker = false;
-
-	  while (curCol !== e.charCodeAt(0)) {
-	    curCol++;
-	    if (curCol === I) {
-	      curCol++;
-	    }
-	    if (curCol === O) {
-	      curCol++;
-	    }
-	    if (curCol > Z) {
-	      if (rewindMarker) {
-	        throw ("Bad character: " + e);
-	      }
-	      curCol = A;
-	      rewindMarker = true;
-	    }
-	    eastingValue += 100000.0;
-	  }
-
-	  return eastingValue;
-	}
-
-	/**
-	 * Given the second letter from a two-letter MGRS 100k zone, and given the
-	 * MGRS table set for the zone number, figure out the northing value that
-	 * should be added to the other, secondary northing value. You have to
-	 * remember that Northings are determined from the equator, and the vertical
-	 * cycle of letters mean a 2000000 additional northing meters. This happens
-	 * approx. every 18 degrees of latitude. This method does *NOT* count any
-	 * additional northings. You have to figure out how many 2000000 meters need
-	 * to be added for the zone letter of the MGRS coordinate.
-	 *
-	 * @private
-	 * @param {char} n Second letter of the MGRS 100k zone
-	 * @param {number} set The MGRS table set number, which is dependent on the
-	 *     UTM zone number.
-	 * @return {number} The northing value for the given letter and set.
-	 */
-	function getNorthingFromChar(n, set) {
-
-	  if (n > 'V') {
-	    throw ("MGRSPoint given invalid Northing " + n);
-	  }
-
-	  // rowOrigin is the letter at the origin of the set for the
-	  // column
-	  var curRow = SET_ORIGIN_ROW_LETTERS.charCodeAt(set - 1);
-	  var northingValue = 0.0;
-	  var rewindMarker = false;
-
-	  while (curRow !== n.charCodeAt(0)) {
-	    curRow++;
-	    if (curRow === I) {
-	      curRow++;
-	    }
-	    if (curRow === O) {
-	      curRow++;
-	    }
-	    // fixing a bug making whole application hang in this loop
-	    // when 'n' is a wrong character
-	    if (curRow > V) {
-	      if (rewindMarker) { // making sure that this loop ends
-	        throw ("Bad character: " + n);
-	      }
-	      curRow = A;
-	      rewindMarker = true;
-	    }
-	    northingValue += 100000.0;
-	  }
-
-	  return northingValue;
-	}
-
-	/**
-	 * The function getMinNorthing returns the minimum northing value of a MGRS
-	 * zone.
-	 *
-	 * Ported from Geotrans' c Lattitude_Band_Value structure table.
-	 *
-	 * @private
-	 * @param {char} zoneLetter The MGRS zone to get the min northing for.
-	 * @return {number}
-	 */
-	function getMinNorthing(zoneLetter) {
-	  var northing;
-	  switch (zoneLetter) {
-	  case 'C':
-	    northing = 1100000.0;
-	    break;
-	  case 'D':
-	    northing = 2000000.0;
-	    break;
-	  case 'E':
-	    northing = 2800000.0;
-	    break;
-	  case 'F':
-	    northing = 3700000.0;
-	    break;
-	  case 'G':
-	    northing = 4600000.0;
-	    break;
-	  case 'H':
-	    northing = 5500000.0;
-	    break;
-	  case 'J':
-	    northing = 6400000.0;
-	    break;
-	  case 'K':
-	    northing = 7300000.0;
-	    break;
-	  case 'L':
-	    northing = 8200000.0;
-	    break;
-	  case 'M':
-	    northing = 9100000.0;
-	    break;
-	  case 'N':
-	    northing = 0.0;
-	    break;
-	  case 'P':
-	    northing = 800000.0;
-	    break;
-	  case 'Q':
-	    northing = 1700000.0;
-	    break;
-	  case 'R':
-	    northing = 2600000.0;
-	    break;
-	  case 'S':
-	    northing = 3500000.0;
-	    break;
-	  case 'T':
-	    northing = 4400000.0;
-	    break;
-	  case 'U':
-	    northing = 5300000.0;
-	    break;
-	  case 'V':
-	    northing = 6200000.0;
-	    break;
-	  case 'W':
-	    northing = 7000000.0;
-	    break;
-	  case 'X':
-	    northing = 7900000.0;
-	    break;
-	  default:
-	    northing = -1.0;
-	  }
-	  if (northing >= 0.0) {
-	    return northing;
-	  }
-	  else {
-	    throw ("Invalid zone letter: " + zoneLetter);
-	  }
-
-	}
-
-
-/***/ }),
-/* 42 */
-/***/ (function(module, exports) {
-
-	module.exports = '2.3.16';
-
-
-/***/ }),
-/* 43 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var projs = [
-	  __webpack_require__(44),
-	  __webpack_require__(48),
-	  __webpack_require__(50),
-	  __webpack_require__(53),
-	  __webpack_require__(54),
-	  __webpack_require__(55),
-	  __webpack_require__(56),
-	  __webpack_require__(57),
-	  __webpack_require__(58),
-	  __webpack_require__(67),
-	  __webpack_require__(69),
-	  __webpack_require__(71),
-	  __webpack_require__(72),
-	  __webpack_require__(74),
-	  __webpack_require__(75),
-	  __webpack_require__(76),
-	  __webpack_require__(77),
-	  __webpack_require__(78),
-	  __webpack_require__(79),
-	  __webpack_require__(80),
-	  __webpack_require__(81),
-	  __webpack_require__(82)
-	];
-	module.exports = function(proj4){
-	  projs.forEach(function(proj){
-	    proj4.Proj.projections.add(proj);
-	  });
-	};
-
-/***/ }),
-/* 44 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	// Heavily based on this tmerc projection implementation
-	// https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/tmerc.js
-
-	var pj_enfn = __webpack_require__(45);
-	var pj_mlfn = __webpack_require__(46);
-	var pj_inv_mlfn = __webpack_require__(47);
-	var adjust_lon = __webpack_require__(26);
-	var HALF_PI = Math.PI / 2;
-	var EPSLN = 1.0e-10;
-	var sign = __webpack_require__(27);
-
-	exports.init = function() {
-	  this.x0 = this.x0 !== undefined ? this.x0 : 0;
-	  this.y0 = this.y0 !== undefined ? this.y0 : 0;
-	  this.long0 = this.long0 !== undefined ? this.long0 : 0;
-	  this.lat0 = this.lat0 !== undefined ? this.lat0 : 0;
-
-	  if (this.es) {
-	    this.en = pj_enfn(this.es);
-	    this.ml0 = pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
-	  }
-	};
-
-	/**
-	    Transverse Mercator Forward  - long/lat to x/y
-	    long/lat in radians
-	  */
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  var delta_lon = adjust_lon(lon - this.long0);
-	  var con;
-	  var x, y;
-	  var sin_phi = Math.sin(lat);
-	  var cos_phi = Math.cos(lat);
-
-	  if (!this.es) {
-	    var b = cos_phi * Math.sin(delta_lon);
-
-	    if ((Math.abs(Math.abs(b) - 1)) < EPSLN) {
-	      return (93);
-	    }
-	    else {
-	      x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b)) + this.x0;
-	      y = cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - Math.pow(b, 2));
-	      b = Math.abs(y);
-
-	      if (b >= 1) {
-	        if ((b - 1) > EPSLN) {
-	          return (93);
-	        }
-	        else {
-	          y = 0;
-	        }
-	      }
-	      else {
-	        y = Math.acos(y);
-	      }
-
-	      if (lat < 0) {
-	        y = -y;
-	      }
-
-	      y = this.a * this.k0 * (y - this.lat0) + this.y0;
-	    }
-	  }
-	  else {
-	    var al = cos_phi * delta_lon;
-	    var als = Math.pow(al, 2);
-	    var c = this.ep2 * Math.pow(cos_phi, 2);
-	    var cs = Math.pow(c, 2);
-	    var tq = Math.abs(cos_phi) > EPSLN ? Math.tan(lat) : 0;
-	    var t = Math.pow(tq, 2);
-	    var ts = Math.pow(t, 2);
-	    con = 1 - this.es * Math.pow(sin_phi, 2);
-	    al = al / Math.sqrt(con);
-	    var ml = pj_mlfn(lat, sin_phi, cos_phi, this.en);
-
-	    x = this.a * (this.k0 * al * (1 +
-	      als / 6 * (1 - t + c +
-	      als / 20 * (5 - 18 * t + ts + 14 * c - 58 * t * c +
-	      als / 42 * (61 + 179 * ts - ts * t - 479 * t))))) +
-	      this.x0;
-
-	    y = this.a * (this.k0 * (ml - this.ml0 +
-	      sin_phi * delta_lon * al / 2 * (1 +
-	      als / 12 * (5 - t + 9 * c + 4 * cs +
-	      als / 30 * (61 + ts - 58 * t + 270 * c - 330 * t * c +
-	      als / 56 * (1385 + 543 * ts - ts * t - 3111 * t)))))) +
-	      this.y0;
-	  }
-
-	  p.x = x;
-	  p.y = y;
-
-	  return p;
-	};
-
-	/**
-	    Transverse Mercator Inverse  -  x/y to long/lat
-	  */
-	exports.inverse = function(p) {
-	  var con, phi;
-	  var lat, lon;
-	  var x = (p.x - this.x0) * (1 / this.a);
-	  var y = (p.y - this.y0) * (1 / this.a);
-
-	  if (!this.es) {
-	    var f = Math.exp(x / this.k0);
-	    var g = 0.5 * (f - 1 / f);
-	    var temp = this.lat0 + y / this.k0;
-	    var h = Math.cos(temp);
-	    con = Math.sqrt((1 - Math.pow(h, 2)) / (1 + Math.pow(g, 2)));
-	    lat = Math.asin(con);
-
-	    if (y < 0) {
-	      lat = -lat;
-	    }
-
-	    if ((g === 0) && (h === 0)) {
-	      lon = 0;
-	    }
-	    else {
-	      lon = adjust_lon(Math.atan2(g, h) + this.long0);
-	    }
-	  }
-	  else { // ellipsoidal form
-	    con = this.ml0 + y / this.k0;
-	    phi = pj_inv_mlfn(con, this.es, this.en);
-
-	    if (Math.abs(phi) < HALF_PI) {
-	      var sin_phi = Math.sin(phi);
-	      var cos_phi = Math.cos(phi);
-	      var tan_phi = Math.abs(cos_phi) > EPSLN ? Math.tan(phi) : 0;
-	      var c = this.ep2 * Math.pow(cos_phi, 2);
-	      var cs = Math.pow(c, 2);
-	      var t = Math.pow(tan_phi, 2);
-	      var ts = Math.pow(t, 2);
-	      con = 1 - this.es * Math.pow(sin_phi, 2);
-	      var d = x * Math.sqrt(con) / this.k0;
-	      var ds = Math.pow(d, 2);
-	      con = con * tan_phi;
-
-	      lat = phi - (con * ds / (1 - this.es)) * 0.5 * (1 -
-	        ds / 12 * (5 + 3 * t - 9 * c * t + c - 4 * cs -
-	        ds / 30 * (61 + 90 * t - 252 * c * t + 45 * ts + 46 * c -
-	        ds / 56 * (1385 + 3633 * t + 4095 * ts + 1574 * ts * t))));
-
-	      lon = adjust_lon(this.long0 + (d * (1 -
-	        ds / 6 * (1 + 2 * t + c -
-	        ds / 20 * (5 + 28 * t + 24 * ts + 8 * c * t + 6 * c -
-	        ds / 42 * (61 + 662 * t + 1320 * ts + 720 * ts * t)))) / cos_phi));
-	    }
-	    else {
-	      lat = HALF_PI * sign(y);
-	      lon = 0;
-	    }
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-
-	  return p;
-	};
-
-	exports.names = ["Transverse_Mercator", "Transverse Mercator", "tmerc"];
-
-
-/***/ }),
-/* 45 */
-/***/ (function(module, exports) {
-
-	var C00 = 1;
-	var C02 = 0.25;
-	var C04 = 0.046875;
-	var C06 = 0.01953125;
-	var C08 = 0.01068115234375;
-	var C22 = 0.75;
-	var C44 = 0.46875;
-	var C46 = 0.01302083333333333333;
-	var C48 = 0.00712076822916666666;
-	var C66 = 0.36458333333333333333;
-	var C68 = 0.00569661458333333333;
-	var C88 = 0.3076171875;
-
-	module.exports = function(es) {
-	  var en = [];
-	  en[0] = C00 - es * (C02 + es * (C04 + es * (C06 + es * C08)));
-	  en[1] = es * (C22 - es * (C04 + es * (C06 + es * C08)));
-	  var t = es * es;
-	  en[2] = t * (C44 - es * (C46 + es * C48));
-	  t *= es;
-	  en[3] = t * (C66 - es * C68);
-	  en[4] = t * es * C88;
-	  return en;
-	};
-
-/***/ }),
-/* 46 */
-/***/ (function(module, exports) {
-
-	module.exports = function(phi, sphi, cphi, en) {
-	  cphi *= sphi;
-	  sphi *= sphi;
-	  return (en[0] * phi - cphi * (en[1] + sphi * (en[2] + sphi * (en[3] + sphi * en[4]))));
-	};
-
-/***/ }),
-/* 47 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var pj_mlfn = __webpack_require__(46);
-	var EPSLN = 1.0e-10;
-	var MAX_ITER = 20;
-	module.exports = function(arg, es, en) {
-	  var k = 1 / (1 - es);
-	  var phi = arg;
-	  for (var i = MAX_ITER; i; --i) { /* rarely goes over 2 iterations */
-	    var s = Math.sin(phi);
-	    var t = 1 - es * s * s;
-	    //t = this.pj_mlfn(phi, s, Math.cos(phi), en) - arg;
-	    //phi -= t * (t * Math.sqrt(t)) * k;
-	    t = (pj_mlfn(phi, s, Math.cos(phi), en) - arg) * (t * Math.sqrt(t)) * k;
-	    phi -= t;
-	    if (Math.abs(t) < EPSLN) {
-	      return phi;
-	    }
-	  }
-	  //..reportError("cass:pj_inv_mlfn: Convergence error");
-	  return phi;
-	};
-
-/***/ }),
-/* 48 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_zone = __webpack_require__(49);
-	var tmerc = __webpack_require__(44);
-
-	exports.dependsOn = 'tmerc';
-
-	exports.init = function() {
-	  var zone = adjust_zone(this.zone, this.long0);
-	  if (!zone) {
-	    return;
-	  }
-
-	  this.lat0 = 0;
-	  this.long0 = (zone + 0.5) * Math.PI / 30 - Math.PI;
-	  this.x0 = 500000;
-	  this.y0 = this.utmSouth ? 10000000 : 0;
-	  this.k0 = 0.9996;
-
-	  tmerc.init.apply(this);
-	  this.forward = tmerc.forward;
-	  this.inverse = tmerc.inverse;
-	};
-
-	exports.names = ["Universal Transverse Mercator System", "utm"];
-
-
-/***/ }),
-/* 49 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-
-	module.exports = function(zone, lon) {
-	  if (!zone) {
-	    zone = Math.floor((adjust_lon(lon) + Math.PI) * 30 / Math.PI);
-
-	    if (zone < 0) {
-	      return 0;
-	    }
-	    else if (zone >= 60) {
-	      return 59;
-	    }
-	  }
-	  else {
-	    if (zone > 0 && zone <= 60) {
-	      return zone - 1;
-	    }
-	  }
-	};
-
-
-/***/ }),
-/* 50 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var gauss = __webpack_require__(51);
-	var adjust_lon = __webpack_require__(26);
-	exports.init = function() {
-	  gauss.init.apply(this);
-	  if (!this.rc) {
-	    return;
-	  }
-	  this.sinc0 = Math.sin(this.phic0);
-	  this.cosc0 = Math.cos(this.phic0);
-	  this.R2 = 2 * this.rc;
-	  if (!this.title) {
-	    this.title = "Oblique Stereographic Alternative";
-	  }
-	};
-
-	exports.forward = function(p) {
-	  var sinc, cosc, cosl, k;
-	  p.x = adjust_lon(p.x - this.long0);
-	  gauss.forward.apply(this, [p]);
-	  sinc = Math.sin(p.y);
-	  cosc = Math.cos(p.y);
-	  cosl = Math.cos(p.x);
-	  k = this.k0 * this.R2 / (1 + this.sinc0 * sinc + this.cosc0 * cosc * cosl);
-	  p.x = k * cosc * Math.sin(p.x);
-	  p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl);
-	  p.x = this.a * p.x + this.x0;
-	  p.y = this.a * p.y + this.y0;
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var sinc, cosc, lon, lat, rho;
-	  p.x = (p.x - this.x0) / this.a;
-	  p.y = (p.y - this.y0) / this.a;
-
-	  p.x /= this.k0;
-	  p.y /= this.k0;
-	  if ((rho = Math.sqrt(p.x * p.x + p.y * p.y))) {
-	    var c = 2 * Math.atan2(rho, this.R2);
-	    sinc = Math.sin(c);
-	    cosc = Math.cos(c);
-	    lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho);
-	    lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc);
-	  }
-	  else {
-	    lat = this.phic0;
-	    lon = 0;
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-	  gauss.inverse.apply(this, [p]);
-	  p.x = adjust_lon(p.x + this.long0);
-	  return p;
-	};
-
-	exports.names = ["Stereographic_North_Pole", "Oblique_Stereographic", "Polar_Stereographic", "sterea","Oblique Stereographic Alternative"];
-
-
-/***/ }),
-/* 51 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var FORTPI = Math.PI/4;
-	var srat = __webpack_require__(52);
-	var HALF_PI = Math.PI/2;
-	var MAX_ITER = 20;
-	exports.init = function() {
-	  var sphi = Math.sin(this.lat0);
-	  var cphi = Math.cos(this.lat0);
-	  cphi *= cphi;
-	  this.rc = Math.sqrt(1 - this.es) / (1 - this.es * sphi * sphi);
-	  this.C = Math.sqrt(1 + this.es * cphi * cphi / (1 - this.es));
-	  this.phic0 = Math.asin(sphi / this.C);
-	  this.ratexp = 0.5 * this.C * this.e;
-	  this.K = Math.tan(0.5 * this.phic0 + FORTPI) / (Math.pow(Math.tan(0.5 * this.lat0 + FORTPI), this.C) * srat(this.e * sphi, this.ratexp));
-	};
-
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  p.y = 2 * Math.atan(this.K * Math.pow(Math.tan(0.5 * lat + FORTPI), this.C) * srat(this.e * Math.sin(lat), this.ratexp)) - HALF_PI;
-	  p.x = this.C * lon;
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var DEL_TOL = 1e-14;
-	  var lon = p.x / this.C;
-	  var lat = p.y;
-	  var num = Math.pow(Math.tan(0.5 * lat + FORTPI) / this.K, 1 / this.C);
-	  for (var i = MAX_ITER; i > 0; --i) {
-	    lat = 2 * Math.atan(num * srat(this.e * Math.sin(p.y), - 0.5 * this.e)) - HALF_PI;
-	    if (Math.abs(lat - p.y) < DEL_TOL) {
-	      break;
-	    }
-	    p.y = lat;
-	  }
-	  /* convergence failed */
-	  if (!i) {
-	    return null;
-	  }
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["gauss"];
-
-
-/***/ }),
-/* 52 */
-/***/ (function(module, exports) {
-
-	module.exports = function(esinp, exp) {
-	  return (Math.pow((1 - esinp) / (1 + esinp), exp));
-	};
-
-/***/ }),
-/* 53 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	var sign = __webpack_require__(27);
-	var msfnz = __webpack_require__(25);
-	var tsfnz = __webpack_require__(28);
-	var phi2z = __webpack_require__(29);
-	var adjust_lon = __webpack_require__(26);
-	exports.ssfn_ = function(phit, sinphi, eccen) {
-	  sinphi *= eccen;
-	  return (Math.tan(0.5 * (HALF_PI + phit)) * Math.pow((1 - sinphi) / (1 + sinphi), 0.5 * eccen));
-	};
-
-	exports.init = function() {
-	  this.coslat0 = Math.cos(this.lat0);
-	  this.sinlat0 = Math.sin(this.lat0);
-	  if (this.sphere) {
-	    if (this.k0 === 1 && !isNaN(this.lat_ts) && Math.abs(this.coslat0) <= EPSLN) {
-	      this.k0 = 0.5 * (1 + sign(this.lat0) * Math.sin(this.lat_ts));
-	    }
-	  }
-	  else {
-	    if (Math.abs(this.coslat0) <= EPSLN) {
-	      if (this.lat0 > 0) {
-	        //North pole
-	        //trace('stere:north pole');
-	        this.con = 1;
-	      }
-	      else {
-	        //South pole
-	        //trace('stere:south pole');
-	        this.con = -1;
-	      }
-	    }
-	    this.cons = Math.sqrt(Math.pow(1 + this.e, 1 + this.e) * Math.pow(1 - this.e, 1 - this.e));
-	    if (this.k0 === 1 && !isNaN(this.lat_ts) && Math.abs(this.coslat0) <= EPSLN) {
-	      this.k0 = 0.5 * this.cons * msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts)) / tsfnz(this.e, this.con * this.lat_ts, this.con * Math.sin(this.lat_ts));
-	    }
-	    this.ms1 = msfnz(this.e, this.sinlat0, this.coslat0);
-	    this.X0 = 2 * Math.atan(this.ssfn_(this.lat0, this.sinlat0, this.e)) - HALF_PI;
-	    this.cosX0 = Math.cos(this.X0);
-	    this.sinX0 = Math.sin(this.X0);
-	  }
-	};
-
-	// Stereographic forward equations--mapping lat,long to x,y
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var sinlat = Math.sin(lat);
-	  var coslat = Math.cos(lat);
-	  var A, X, sinX, cosX, ts, rh;
-	  var dlon = adjust_lon(lon - this.long0);
-
-	  if (Math.abs(Math.abs(lon - this.long0) - Math.PI) <= EPSLN && Math.abs(lat + this.lat0) <= EPSLN) {
-	    //case of the origine point
-	    //trace('stere:this is the origin point');
-	    p.x = NaN;
-	    p.y = NaN;
-	    return p;
-	  }
-	  if (this.sphere) {
-	    //trace('stere:sphere case');
-	    A = 2 * this.k0 / (1 + this.sinlat0 * sinlat + this.coslat0 * coslat * Math.cos(dlon));
-	    p.x = this.a * A * coslat * Math.sin(dlon) + this.x0;
-	    p.y = this.a * A * (this.coslat0 * sinlat - this.sinlat0 * coslat * Math.cos(dlon)) + this.y0;
-	    return p;
-	  }
-	  else {
-	    X = 2 * Math.atan(this.ssfn_(lat, sinlat, this.e)) - HALF_PI;
-	    cosX = Math.cos(X);
-	    sinX = Math.sin(X);
-	    if (Math.abs(this.coslat0) <= EPSLN) {
-	      ts = tsfnz(this.e, lat * this.con, this.con * sinlat);
-	      rh = 2 * this.a * this.k0 * ts / this.cons;
-	      p.x = this.x0 + rh * Math.sin(lon - this.long0);
-	      p.y = this.y0 - this.con * rh * Math.cos(lon - this.long0);
-	      //trace(p.toString());
-	      return p;
-	    }
-	    else if (Math.abs(this.sinlat0) < EPSLN) {
-	      //Eq
-	      //trace('stere:equateur');
-	      A = 2 * this.a * this.k0 / (1 + cosX * Math.cos(dlon));
-	      p.y = A * sinX;
-	    }
-	    else {
-	      //other case
-	      //trace('stere:normal case');
-	      A = 2 * this.a * this.k0 * this.ms1 / (this.cosX0 * (1 + this.sinX0 * sinX + this.cosX0 * cosX * Math.cos(dlon)));
-	      p.y = A * (this.cosX0 * sinX - this.sinX0 * cosX * Math.cos(dlon)) + this.y0;
-	    }
-	    p.x = A * cosX * Math.sin(dlon) + this.x0;
-	  }
-	  //trace(p.toString());
-	  return p;
-	};
-
-
-	//* Stereographic inverse equations--mapping x,y to lat/long
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  var lon, lat, ts, ce, Chi;
-	  var rh = Math.sqrt(p.x * p.x + p.y * p.y);
-	  if (this.sphere) {
-	    var c = 2 * Math.atan(rh / (0.5 * this.a * this.k0));
-	    lon = this.long0;
-	    lat = this.lat0;
-	    if (rh <= EPSLN) {
-	      p.x = lon;
-	      p.y = lat;
-	      return p;
-	    }
-	    lat = Math.asin(Math.cos(c) * this.sinlat0 + p.y * Math.sin(c) * this.coslat0 / rh);
-	    if (Math.abs(this.coslat0) < EPSLN) {
-	      if (this.lat0 > 0) {
-	        lon = adjust_lon(this.long0 + Math.atan2(p.x, - 1 * p.y));
-	      }
-	      else {
-	        lon = adjust_lon(this.long0 + Math.atan2(p.x, p.y));
-	      }
-	    }
-	    else {
-	      lon = adjust_lon(this.long0 + Math.atan2(p.x * Math.sin(c), rh * this.coslat0 * Math.cos(c) - p.y * this.sinlat0 * Math.sin(c)));
-	    }
-	    p.x = lon;
-	    p.y = lat;
-	    return p;
-	  }
-	  else {
-	    if (Math.abs(this.coslat0) <= EPSLN) {
-	      if (rh <= EPSLN) {
-	        lat = this.lat0;
-	        lon = this.long0;
-	        p.x = lon;
-	        p.y = lat;
-	        //trace(p.toString());
-	        return p;
-	      }
-	      p.x *= this.con;
-	      p.y *= this.con;
-	      ts = rh * this.cons / (2 * this.a * this.k0);
-	      lat = this.con * phi2z(this.e, ts);
-	      lon = this.con * adjust_lon(this.con * this.long0 + Math.atan2(p.x, - 1 * p.y));
-	    }
-	    else {
-	      ce = 2 * Math.atan(rh * this.cosX0 / (2 * this.a * this.k0 * this.ms1));
-	      lon = this.long0;
-	      if (rh <= EPSLN) {
-	        Chi = this.X0;
-	      }
-	      else {
-	        Chi = Math.asin(Math.cos(ce) * this.sinX0 + p.y * Math.sin(ce) * this.cosX0 / rh);
-	        lon = adjust_lon(this.long0 + Math.atan2(p.x * Math.sin(ce), rh * this.cosX0 * Math.cos(ce) - p.y * this.sinX0 * Math.sin(ce)));
-	      }
-	      lat = -1 * phi2z(this.e, Math.tan(0.5 * (HALF_PI + Chi)));
-	    }
-	  }
-	  p.x = lon;
-	  p.y = lat;
-
-	  //trace(p.toString());
-	  return p;
-
-	};
-	exports.names = ["stere", "Stereographic_South_Pole", "Polar Stereographic (variant B)"];
-
-
-/***/ }),
-/* 54 */
-/***/ (function(module, exports) {
-
-	/*
-	  references:
-	    Formules et constantes pour le Calcul pour la
-	    projection cylindrique conforme à axe oblique et pour la transformation entre
-	    des systèmes de référence.
-	    http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf
-	  */
-	exports.init = function() {
-	  var phy0 = this.lat0;
-	  this.lambda0 = this.long0;
-	  var sinPhy0 = Math.sin(phy0);
-	  var semiMajorAxis = this.a;
-	  var invF = this.rf;
-	  var flattening = 1 / invF;
-	  var e2 = 2 * flattening - Math.pow(flattening, 2);
-	  var e = this.e = Math.sqrt(e2);
-	  this.R = this.k0 * semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2));
-	  this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4));
-	  this.b0 = Math.asin(sinPhy0 / this.alpha);
-	  var k1 = Math.log(Math.tan(Math.PI / 4 + this.b0 / 2));
-	  var k2 = Math.log(Math.tan(Math.PI / 4 + phy0 / 2));
-	  var k3 = Math.log((1 + e * sinPhy0) / (1 - e * sinPhy0));
-	  this.K = k1 - this.alpha * k2 + this.alpha * e / 2 * k3;
-	};
-
-
-	exports.forward = function(p) {
-	  var Sa1 = Math.log(Math.tan(Math.PI / 4 - p.y / 2));
-	  var Sa2 = this.e / 2 * Math.log((1 + this.e * Math.sin(p.y)) / (1 - this.e * Math.sin(p.y)));
-	  var S = -this.alpha * (Sa1 + Sa2) + this.K;
-
-	  // spheric latitude
-	  var b = 2 * (Math.atan(Math.exp(S)) - Math.PI / 4);
-
-	  // spheric longitude
-	  var I = this.alpha * (p.x - this.lambda0);
-
-	  // psoeudo equatorial rotation
-	  var rotI = Math.atan(Math.sin(I) / (Math.sin(this.b0) * Math.tan(b) + Math.cos(this.b0) * Math.cos(I)));
-
-	  var rotB = Math.asin(Math.cos(this.b0) * Math.sin(b) - Math.sin(this.b0) * Math.cos(b) * Math.cos(I));
-
-	  p.y = this.R / 2 * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB))) + this.y0;
-	  p.x = this.R * rotI + this.x0;
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var Y = p.x - this.x0;
-	  var X = p.y - this.y0;
-
-	  var rotI = Y / this.R;
-	  var rotB = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4);
-
-	  var b = Math.asin(Math.cos(this.b0) * Math.sin(rotB) + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI));
-	  var I = Math.atan(Math.sin(rotI) / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0) * Math.tan(rotB)));
-
-	  var lambda = this.lambda0 + I / this.alpha;
-
-	  var S = 0;
-	  var phy = b;
-	  var prevPhy = -1000;
-	  var iteration = 0;
-	  while (Math.abs(phy - prevPhy) > 0.0000001) {
-	    if (++iteration > 20) {
-	      //...reportError("omercFwdInfinity");
-	      return;
-	    }
-	    //S = Math.log(Math.tan(Math.PI / 4 + phy / 2));
-	    S = 1 / this.alpha * (Math.log(Math.tan(Math.PI / 4 + b / 2)) - this.K) + this.e * Math.log(Math.tan(Math.PI / 4 + Math.asin(this.e * Math.sin(phy)) / 2));
-	    prevPhy = phy;
-	    phy = 2 * Math.atan(Math.exp(S)) - Math.PI / 2;
-	  }
-
-	  p.x = lambda;
-	  p.y = phy;
-	  return p;
-	};
-
-	exports.names = ["somerc"];
-
-
-/***/ }),
-/* 55 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var tsfnz = __webpack_require__(28);
-	var adjust_lon = __webpack_require__(26);
-	var phi2z = __webpack_require__(29);
-	var HALF_PI = Math.PI/2;
-	var FORTPI = Math.PI/4;
-	var EPSLN = 1.0e-10;
-
-	/* Initialize the Oblique Mercator  projection
-	    ------------------------------------------*/
-	exports.init = function() {
-	  this.no_off = this.no_off || false;
-	  this.no_rot = this.no_rot || false;
-
-	  if (isNaN(this.k0)) {
-	    this.k0 = 1;
-	  }
-	  var sinlat = Math.sin(this.lat0);
-	  var coslat = Math.cos(this.lat0);
-	  var con = this.e * sinlat;
-
-	  this.bl = Math.sqrt(1 + this.es / (1 - this.es) * Math.pow(coslat, 4));
-	  this.al = this.a * this.bl * this.k0 * Math.sqrt(1 - this.es) / (1 - con * con);
-	  var t0 = tsfnz(this.e, this.lat0, sinlat);
-	  var dl = this.bl / coslat * Math.sqrt((1 - this.es) / (1 - con * con));
-	  if (dl * dl < 1) {
-	    dl = 1;
-	  }
-	  var fl;
-	  var gl;
-	  if (!isNaN(this.longc)) {
-	    //Central point and azimuth method
-
-	    if (this.lat0 >= 0) {
-	      fl = dl + Math.sqrt(dl * dl - 1);
-	    }
-	    else {
-	      fl = dl - Math.sqrt(dl * dl - 1);
-	    }
-	    this.el = fl * Math.pow(t0, this.bl);
-	    gl = 0.5 * (fl - 1 / fl);
-	    this.gamma0 = Math.asin(Math.sin(this.alpha) / dl);
-	    this.long0 = this.longc - Math.asin(gl * Math.tan(this.gamma0)) / this.bl;
-
-	  }
-	  else {
-	    //2 points method
-	    var t1 = tsfnz(this.e, this.lat1, Math.sin(this.lat1));
-	    var t2 = tsfnz(this.e, this.lat2, Math.sin(this.lat2));
-	    if (this.lat0 >= 0) {
-	      this.el = (dl + Math.sqrt(dl * dl - 1)) * Math.pow(t0, this.bl);
-	    }
-	    else {
-	      this.el = (dl - Math.sqrt(dl * dl - 1)) * Math.pow(t0, this.bl);
-	    }
-	    var hl = Math.pow(t1, this.bl);
-	    var ll = Math.pow(t2, this.bl);
-	    fl = this.el / hl;
-	    gl = 0.5 * (fl - 1 / fl);
-	    var jl = (this.el * this.el - ll * hl) / (this.el * this.el + ll * hl);
-	    var pl = (ll - hl) / (ll + hl);
-	    var dlon12 = adjust_lon(this.long1 - this.long2);
-	    this.long0 = 0.5 * (this.long1 + this.long2) - Math.atan(jl * Math.tan(0.5 * this.bl * (dlon12)) / pl) / this.bl;
-	    this.long0 = adjust_lon(this.long0);
-	    var dlon10 = adjust_lon(this.long1 - this.long0);
-	    this.gamma0 = Math.atan(Math.sin(this.bl * (dlon10)) / gl);
-	    this.alpha = Math.asin(dl * Math.sin(this.gamma0));
-	  }
-
-	  if (this.no_off) {
-	    this.uc = 0;
-	  }
-	  else {
-	    if (this.lat0 >= 0) {
-	      this.uc = this.al / this.bl * Math.atan2(Math.sqrt(dl * dl - 1), Math.cos(this.alpha));
-	    }
-	    else {
-	      this.uc = -1 * this.al / this.bl * Math.atan2(Math.sqrt(dl * dl - 1), Math.cos(this.alpha));
-	    }
-	  }
-
-	};
-
-
-	/* Oblique Mercator forward equations--mapping lat,long to x,y
-	    ----------------------------------------------------------*/
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var dlon = adjust_lon(lon - this.long0);
-	  var us, vs;
-	  var con;
-	  if (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN) {
-	    if (lat > 0) {
-	      con = -1;
-	    }
-	    else {
-	      con = 1;
-	    }
-	    vs = this.al / this.bl * Math.log(Math.tan(FORTPI + con * this.gamma0 * 0.5));
-	    us = -1 * con * HALF_PI * this.al / this.bl;
-	  }
-	  else {
-	    var t = tsfnz(this.e, lat, Math.sin(lat));
-	    var ql = this.el / Math.pow(t, this.bl);
-	    var sl = 0.5 * (ql - 1 / ql);
-	    var tl = 0.5 * (ql + 1 / ql);
-	    var vl = Math.sin(this.bl * (dlon));
-	    var ul = (sl * Math.sin(this.gamma0) - vl * Math.cos(this.gamma0)) / tl;
-	    if (Math.abs(Math.abs(ul) - 1) <= EPSLN) {
-	      vs = Number.POSITIVE_INFINITY;
-	    }
-	    else {
-	      vs = 0.5 * this.al * Math.log((1 - ul) / (1 + ul)) / this.bl;
-	    }
-	    if (Math.abs(Math.cos(this.bl * (dlon))) <= EPSLN) {
-	      us = this.al * this.bl * (dlon);
-	    }
-	    else {
-	      us = this.al * Math.atan2(sl * Math.cos(this.gamma0) + vl * Math.sin(this.gamma0), Math.cos(this.bl * dlon)) / this.bl;
-	    }
-	  }
-
-	  if (this.no_rot) {
-	    p.x = this.x0 + us;
-	    p.y = this.y0 + vs;
-	  }
-	  else {
-
-	    us -= this.uc;
-	    p.x = this.x0 + vs * Math.cos(this.alpha) + us * Math.sin(this.alpha);
-	    p.y = this.y0 + us * Math.cos(this.alpha) - vs * Math.sin(this.alpha);
-	  }
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var us, vs;
-	  if (this.no_rot) {
-	    vs = p.y - this.y0;
-	    us = p.x - this.x0;
-	  }
-	  else {
-	    vs = (p.x - this.x0) * Math.cos(this.alpha) - (p.y - this.y0) * Math.sin(this.alpha);
-	    us = (p.y - this.y0) * Math.cos(this.alpha) + (p.x - this.x0) * Math.sin(this.alpha);
-	    us += this.uc;
-	  }
-	  var qp = Math.exp(-1 * this.bl * vs / this.al);
-	  var sp = 0.5 * (qp - 1 / qp);
-	  var tp = 0.5 * (qp + 1 / qp);
-	  var vp = Math.sin(this.bl * us / this.al);
-	  var up = (vp * Math.cos(this.gamma0) + sp * Math.sin(this.gamma0)) / tp;
-	  var ts = Math.pow(this.el / Math.sqrt((1 + up) / (1 - up)), 1 / this.bl);
-	  if (Math.abs(up - 1) < EPSLN) {
-	    p.x = this.long0;
-	    p.y = HALF_PI;
-	  }
-	  else if (Math.abs(up + 1) < EPSLN) {
-	    p.x = this.long0;
-	    p.y = -1 * HALF_PI;
-	  }
-	  else {
-	    p.y = phi2z(this.e, ts);
-	    p.x = adjust_lon(this.long0 - Math.atan2(sp * Math.cos(this.gamma0) - vp * Math.sin(this.gamma0), Math.cos(this.bl * us / this.al)) / this.bl);
-	  }
-	  return p;
-	};
-
-	exports.names = ["Hotine_Oblique_Mercator", "Hotine Oblique Mercator", "Hotine_Oblique_Mercator_Azimuth_Natural_Origin", "Hotine_Oblique_Mercator_Azimuth_Center", "omerc"];
-
-/***/ }),
-/* 56 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var EPSLN = 1.0e-10;
-	var msfnz = __webpack_require__(25);
-	var tsfnz = __webpack_require__(28);
-	var HALF_PI = Math.PI/2;
-	var sign = __webpack_require__(27);
-	var adjust_lon = __webpack_require__(26);
-	var phi2z = __webpack_require__(29);
-	exports.init = function() {
-
-	  // array of:  r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north
-	  //double c_lat;                   /* center latitude                      */
-	  //double c_lon;                   /* center longitude                     */
-	  //double lat1;                    /* first standard parallel              */
-	  //double lat2;                    /* second standard parallel             */
-	  //double r_maj;                   /* major axis                           */
-	  //double r_min;                   /* minor axis                           */
-	  //double false_east;              /* x offset in meters                   */
-	  //double false_north;             /* y offset in meters                   */
-
-	  if (!this.lat2) {
-	    this.lat2 = this.lat1;
-	  } //if lat2 is not defined
-	  if (!this.k0) {
-	    this.k0 = 1;
-	  }
-	  this.x0 = this.x0 || 0;
-	  this.y0 = this.y0 || 0;
-	  // Standard Parallels cannot be equal and on opposite sides of the equator
-	  if (Math.abs(this.lat1 + this.lat2) < EPSLN) {
-	    return;
-	  }
-
-	  var temp = this.b / this.a;
-	  this.e = Math.sqrt(1 - temp * temp);
-
-	  var sin1 = Math.sin(this.lat1);
-	  var cos1 = Math.cos(this.lat1);
-	  var ms1 = msfnz(this.e, sin1, cos1);
-	  var ts1 = tsfnz(this.e, this.lat1, sin1);
-
-	  var sin2 = Math.sin(this.lat2);
-	  var cos2 = Math.cos(this.lat2);
-	  var ms2 = msfnz(this.e, sin2, cos2);
-	  var ts2 = tsfnz(this.e, this.lat2, sin2);
-
-	  var ts0 = tsfnz(this.e, this.lat0, Math.sin(this.lat0));
-
-	  if (Math.abs(this.lat1 - this.lat2) > EPSLN) {
-	    this.ns = Math.log(ms1 / ms2) / Math.log(ts1 / ts2);
-	  }
-	  else {
-	    this.ns = sin1;
-	  }
-	  if (isNaN(this.ns)) {
-	    this.ns = sin1;
-	  }
-	  this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns));
-	  this.rh = this.a * this.f0 * Math.pow(ts0, this.ns);
-	  if (!this.title) {
-	    this.title = "Lambert Conformal Conic";
-	  }
-	};
-
-
-	// Lambert Conformal conic forward equations--mapping lat,long to x,y
-	// -----------------------------------------------------------------
-	exports.forward = function(p) {
-
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  // singular cases :
-	  if (Math.abs(2 * Math.abs(lat) - Math.PI) <= EPSLN) {
-	    lat = sign(lat) * (HALF_PI - 2 * EPSLN);
-	  }
-
-	  var con = Math.abs(Math.abs(lat) - HALF_PI);
-	  var ts, rh1;
-	  if (con > EPSLN) {
-	    ts = tsfnz(this.e, lat, Math.sin(lat));
-	    rh1 = this.a * this.f0 * Math.pow(ts, this.ns);
-	  }
-	  else {
-	    con = lat * this.ns;
-	    if (con <= 0) {
-	      return null;
-	    }
-	    rh1 = 0;
-	  }
-	  var theta = this.ns * adjust_lon(lon - this.long0);
-	  p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0;
-	  p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0;
-
-	  return p;
-	};
-
-	// Lambert Conformal Conic inverse equations--mapping x,y to lat/long
-	// -----------------------------------------------------------------
-	exports.inverse = function(p) {
-
-	  var rh1, con, ts;
-	  var lat, lon;
-	  var x = (p.x - this.x0) / this.k0;
-	  var y = (this.rh - (p.y - this.y0) / this.k0);
-	  if (this.ns > 0) {
-	    rh1 = Math.sqrt(x * x + y * y);
-	    con = 1;
-	  }
-	  else {
-	    rh1 = -Math.sqrt(x * x + y * y);
-	    con = -1;
-	  }
-	  var theta = 0;
-	  if (rh1 !== 0) {
-	    theta = Math.atan2((con * x), (con * y));
-	  }
-	  if ((rh1 !== 0) || (this.ns > 0)) {
-	    con = 1 / this.ns;
-	    ts = Math.pow((rh1 / (this.a * this.f0)), con);
-	    lat = phi2z(this.e, ts);
-	    if (lat === -9999) {
-	      return null;
-	    }
-	  }
-	  else {
-	    lat = -HALF_PI;
-	  }
-	  lon = adjust_lon(theta / this.ns + this.long0);
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-
-	exports.names = ["Lambert Tangential Conformal Conic Projection", "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_2SP", "lcc"];
-
-
-/***/ }),
-/* 57 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	exports.init = function() {
-	  this.a = 6377397.155;
-	  this.es = 0.006674372230614;
-	  this.e = Math.sqrt(this.es);
-	  if (!this.lat0) {
-	    this.lat0 = 0.863937979737193;
-	  }
-	  if (!this.long0) {
-	    this.long0 = 0.7417649320975901 - 0.308341501185665;
-	  }
-	  /* if scale not set default to 0.9999 */
-	  if (!this.k0) {
-	    this.k0 = 0.9999;
-	  }
-	  this.s45 = 0.785398163397448; /* 45 */
-	  this.s90 = 2 * this.s45;
-	  this.fi0 = this.lat0;
-	  this.e2 = this.es;
-	  this.e = Math.sqrt(this.e2);
-	  this.alfa = Math.sqrt(1 + (this.e2 * Math.pow(Math.cos(this.fi0), 4)) / (1 - this.e2));
-	  this.uq = 1.04216856380474;
-	  this.u0 = Math.asin(Math.sin(this.fi0) / this.alfa);
-	  this.g = Math.pow((1 + this.e * Math.sin(this.fi0)) / (1 - this.e * Math.sin(this.fi0)), this.alfa * this.e / 2);
-	  this.k = Math.tan(this.u0 / 2 + this.s45) / Math.pow(Math.tan(this.fi0 / 2 + this.s45), this.alfa) * this.g;
-	  this.k1 = this.k0;
-	  this.n0 = this.a * Math.sqrt(1 - this.e2) / (1 - this.e2 * Math.pow(Math.sin(this.fi0), 2));
-	  this.s0 = 1.37008346281555;
-	  this.n = Math.sin(this.s0);
-	  this.ro0 = this.k1 * this.n0 / Math.tan(this.s0);
-	  this.ad = this.s90 - this.uq;
-	};
-
-	/* ellipsoid */
-	/* calculate xy from lat/lon */
-	/* Constants, identical to inverse transform function */
-	exports.forward = function(p) {
-	  var gfi, u, deltav, s, d, eps, ro;
-	  var lon = p.x;
-	  var lat = p.y;
-	  var delta_lon = adjust_lon(lon - this.long0);
-	  /* Transformation */
-	  gfi = Math.pow(((1 + this.e * Math.sin(lat)) / (1 - this.e * Math.sin(lat))), (this.alfa * this.e / 2));
-	  u = 2 * (Math.atan(this.k * Math.pow(Math.tan(lat / 2 + this.s45), this.alfa) / gfi) - this.s45);
-	  deltav = -delta_lon * this.alfa;
-	  s = Math.asin(Math.cos(this.ad) * Math.sin(u) + Math.sin(this.ad) * Math.cos(u) * Math.cos(deltav));
-	  d = Math.asin(Math.cos(u) * Math.sin(deltav) / Math.cos(s));
-	  eps = this.n * d;
-	  ro = this.ro0 * Math.pow(Math.tan(this.s0 / 2 + this.s45), this.n) / Math.pow(Math.tan(s / 2 + this.s45), this.n);
-	  p.y = ro * Math.cos(eps) / 1;
-	  p.x = ro * Math.sin(eps) / 1;
-
-	  if (!this.czech) {
-	    p.y *= -1;
-	    p.x *= -1;
-	  }
-	  return (p);
-	};
-
-	/* calculate lat/lon from xy */
-	exports.inverse = function(p) {
-	  var u, deltav, s, d, eps, ro, fi1;
-	  var ok;
-
-	  /* Transformation */
-	  /* revert y, x*/
-	  var tmp = p.x;
-	  p.x = p.y;
-	  p.y = tmp;
-	  if (!this.czech) {
-	    p.y *= -1;
-	    p.x *= -1;
-	  }
-	  ro = Math.sqrt(p.x * p.x + p.y * p.y);
-	  eps = Math.atan2(p.y, p.x);
-	  d = eps / Math.sin(this.s0);
-	  s = 2 * (Math.atan(Math.pow(this.ro0 / ro, 1 / this.n) * Math.tan(this.s0 / 2 + this.s45)) - this.s45);
-	  u = Math.asin(Math.cos(this.ad) * Math.sin(s) - Math.sin(this.ad) * Math.cos(s) * Math.cos(d));
-	  deltav = Math.asin(Math.cos(s) * Math.sin(d) / Math.cos(u));
-	  p.x = this.long0 - deltav / this.alfa;
-	  fi1 = u;
-	  ok = 0;
-	  var iter = 0;
-	  do {
-	    p.y = 2 * (Math.atan(Math.pow(this.k, - 1 / this.alfa) * Math.pow(Math.tan(u / 2 + this.s45), 1 / this.alfa) * Math.pow((1 + this.e * Math.sin(fi1)) / (1 - this.e * Math.sin(fi1)), this.e / 2)) - this.s45);
-	    if (Math.abs(fi1 - p.y) < 0.0000000001) {
-	      ok = 1;
-	    }
-	    fi1 = p.y;
-	    iter += 1;
-	  } while (ok === 0 && iter < 15);
-	  if (iter >= 15) {
-	    return null;
-	  }
-
-	  return (p);
-	};
-	exports.names = ["Krovak", "krovak"];
-
-
-/***/ }),
-/* 58 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var mlfn = __webpack_require__(59);
-	var e0fn = __webpack_require__(60);
-	var e1fn = __webpack_require__(61);
-	var e2fn = __webpack_require__(62);
-	var e3fn = __webpack_require__(63);
-	var gN = __webpack_require__(64);
-	var adjust_lon = __webpack_require__(26);
-	var adjust_lat = __webpack_require__(65);
-	var imlfn = __webpack_require__(66);
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	exports.init = function() {
-	  if (!this.sphere) {
-	    this.e0 = e0fn(this.es);
-	    this.e1 = e1fn(this.es);
-	    this.e2 = e2fn(this.es);
-	    this.e3 = e3fn(this.es);
-	    this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
-	  }
-	};
-
-
-
-	/* Cassini forward equations--mapping lat,long to x,y
-	  -----------------------------------------------------------------------*/
-	exports.forward = function(p) {
-
-	  /* Forward equations
-	      -----------------*/
-	  var x, y;
-	  var lam = p.x;
-	  var phi = p.y;
-	  lam = adjust_lon(lam - this.long0);
-
-	  if (this.sphere) {
-	    x = this.a * Math.asin(Math.cos(phi) * Math.sin(lam));
-	    y = this.a * (Math.atan2(Math.tan(phi), Math.cos(lam)) - this.lat0);
-	  }
-	  else {
-	    //ellipsoid
-	    var sinphi = Math.sin(phi);
-	    var cosphi = Math.cos(phi);
-	    var nl = gN(this.a, this.e, sinphi);
-	    var tl = Math.tan(phi) * Math.tan(phi);
-	    var al = lam * Math.cos(phi);
-	    var asq = al * al;
-	    var cl = this.es * cosphi * cosphi / (1 - this.es);
-	    var ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi);
-
-	    x = nl * al * (1 - asq * tl * (1 / 6 - (8 - tl + 8 * cl) * asq / 120));
-	    y = ml - this.ml0 + nl * sinphi / cosphi * asq * (0.5 + (5 - tl + 6 * cl) * asq / 24);
-
-
-	  }
-
-	  p.x = x + this.x0;
-	  p.y = y + this.y0;
-	  return p;
-	};
-
-	/* Inverse equations
-	  -----------------*/
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  var x = p.x / this.a;
-	  var y = p.y / this.a;
-	  var phi, lam;
-
-	  if (this.sphere) {
-	    var dd = y + this.lat0;
-	    phi = Math.asin(Math.sin(dd) * Math.cos(x));
-	    lam = Math.atan2(Math.tan(x), Math.cos(dd));
-	  }
-	  else {
-	    /* ellipsoid */
-	    var ml1 = this.ml0 / this.a + y;
-	    var phi1 = imlfn(ml1, this.e0, this.e1, this.e2, this.e3);
-	    if (Math.abs(Math.abs(phi1) - HALF_PI) <= EPSLN) {
-	      p.x = this.long0;
-	      p.y = HALF_PI;
-	      if (y < 0) {
-	        p.y *= -1;
-	      }
-	      return p;
-	    }
-	    var nl1 = gN(this.a, this.e, Math.sin(phi1));
-
-	    var rl1 = nl1 * nl1 * nl1 / this.a / this.a * (1 - this.es);
-	    var tl1 = Math.pow(Math.tan(phi1), 2);
-	    var dl = x * this.a / nl1;
-	    var dsq = dl * dl;
-	    phi = phi1 - nl1 * Math.tan(phi1) / rl1 * dl * dl * (0.5 - (1 + 3 * tl1) * dl * dl / 24);
-	    lam = dl * (1 - dsq * (tl1 / 3 + (1 + 3 * tl1) * tl1 * dsq / 15)) / Math.cos(phi1);
-
-	  }
-
-	  p.x = adjust_lon(lam + this.long0);
-	  p.y = adjust_lat(phi);
-	  return p;
-
-	};
-	exports.names = ["Cassini", "Cassini_Soldner", "cass"];
-
-/***/ }),
-/* 59 */
-/***/ (function(module, exports) {
-
-	module.exports = function(e0, e1, e2, e3, phi) {
-	  return (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi));
-	};
-
-/***/ }),
-/* 60 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  return (1 - 0.25 * x * (1 + x / 16 * (3 + 1.25 * x)));
-	};
-
-/***/ }),
-/* 61 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  return (0.375 * x * (1 + 0.25 * x * (1 + 0.46875 * x)));
-	};
-
-/***/ }),
-/* 62 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  return (0.05859375 * x * x * (1 + 0.75 * x));
-	};
-
-/***/ }),
-/* 63 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  return (x * x * x * (35 / 3072));
-	};
-
-/***/ }),
-/* 64 */
-/***/ (function(module, exports) {
-
-	module.exports = function(a, e, sinphi) {
-	  var temp = e * sinphi;
-	  return a / Math.sqrt(1 - temp * temp);
-	};
-
-/***/ }),
-/* 65 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var HALF_PI = Math.PI/2;
-	var sign = __webpack_require__(27);
-
-	module.exports = function(x) {
-	  return (Math.abs(x) < HALF_PI) ? x : (x - (sign(x) * Math.PI));
-	};
-
-/***/ }),
-/* 66 */
-/***/ (function(module, exports) {
-
-	module.exports = function(ml, e0, e1, e2, e3) {
-	  var phi;
-	  var dphi;
-
-	  phi = ml / e0;
-	  for (var i = 0; i < 15; i++) {
-	    dphi = (ml - (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi))) / (e0 - 2 * e1 * Math.cos(2 * phi) + 4 * e2 * Math.cos(4 * phi) - 6 * e3 * Math.cos(6 * phi));
-	    phi += dphi;
-	    if (Math.abs(dphi) <= 0.0000000001) {
-	      return phi;
-	    }
-	  }
-
-	  //..reportError("IMLFN-CONV:Latitude failed to converge after 15 iterations");
-	  return NaN;
-	};
-
-/***/ }),
-/* 67 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var HALF_PI = Math.PI/2;
-	var FORTPI = Math.PI/4;
-	var EPSLN = 1.0e-10;
-	var qsfnz = __webpack_require__(68);
-	var adjust_lon = __webpack_require__(26);
-	/*
-	  reference
-	    "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
-	    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
-	  */
-
-	exports.S_POLE = 1;
-	exports.N_POLE = 2;
-	exports.EQUIT = 3;
-	exports.OBLIQ = 4;
-
-
-	/* Initialize the Lambert Azimuthal Equal Area projection
-	  ------------------------------------------------------*/
-	exports.init = function() {
-	  var t = Math.abs(this.lat0);
-	  if (Math.abs(t - HALF_PI) < EPSLN) {
-	    this.mode = this.lat0 < 0 ? this.S_POLE : this.N_POLE;
-	  }
-	  else if (Math.abs(t) < EPSLN) {
-	    this.mode = this.EQUIT;
-	  }
-	  else {
-	    this.mode = this.OBLIQ;
-	  }
-	  if (this.es > 0) {
-	    var sinphi;
-
-	    this.qp = qsfnz(this.e, 1);
-	    this.mmf = 0.5 / (1 - this.es);
-	    this.apa = this.authset(this.es);
-	    switch (this.mode) {
-	    case this.N_POLE:
-	      this.dd = 1;
-	      break;
-	    case this.S_POLE:
-	      this.dd = 1;
-	      break;
-	    case this.EQUIT:
-	      this.rq = Math.sqrt(0.5 * this.qp);
-	      this.dd = 1 / this.rq;
-	      this.xmf = 1;
-	      this.ymf = 0.5 * this.qp;
-	      break;
-	    case this.OBLIQ:
-	      this.rq = Math.sqrt(0.5 * this.qp);
-	      sinphi = Math.sin(this.lat0);
-	      this.sinb1 = qsfnz(this.e, sinphi) / this.qp;
-	      this.cosb1 = Math.sqrt(1 - this.sinb1 * this.sinb1);
-	      this.dd = Math.cos(this.lat0) / (Math.sqrt(1 - this.es * sinphi * sinphi) * this.rq * this.cosb1);
-	      this.ymf = (this.xmf = this.rq) / this.dd;
-	      this.xmf *= this.dd;
-	      break;
-	    }
-	  }
-	  else {
-	    if (this.mode === this.OBLIQ) {
-	      this.sinph0 = Math.sin(this.lat0);
-	      this.cosph0 = Math.cos(this.lat0);
-	    }
-	  }
-	};
-
-	/* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y
-	  -----------------------------------------------------------------------*/
-	exports.forward = function(p) {
-
-	  /* Forward equations
-	      -----------------*/
-	  var x, y, coslam, sinlam, sinphi, q, sinb, cosb, b, cosphi;
-	  var lam = p.x;
-	  var phi = p.y;
-
-	  lam = adjust_lon(lam - this.long0);
-
-	  if (this.sphere) {
-	    sinphi = Math.sin(phi);
-	    cosphi = Math.cos(phi);
-	    coslam = Math.cos(lam);
-	    if (this.mode === this.OBLIQ || this.mode === this.EQUIT) {
-	      y = (this.mode === this.EQUIT) ? 1 + cosphi * coslam : 1 + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
-	      if (y <= EPSLN) {
-	        return null;
-	      }
-	      y = Math.sqrt(2 / y);
-	      x = y * cosphi * Math.sin(lam);
-	      y *= (this.mode === this.EQUIT) ? sinphi : this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
-	    }
-	    else if (this.mode === this.N_POLE || this.mode === this.S_POLE) {
-	      if (this.mode === this.N_POLE) {
-	        coslam = -coslam;
-	      }
-	      if (Math.abs(phi + this.phi0) < EPSLN) {
-	        return null;
-	      }
-	      y = FORTPI - phi * 0.5;
-	      y = 2 * ((this.mode === this.S_POLE) ? Math.cos(y) : Math.sin(y));
-	      x = y * Math.sin(lam);
-	      y *= coslam;
-	    }
-	  }
-	  else {
-	    sinb = 0;
-	    cosb = 0;
-	    b = 0;
-	    coslam = Math.cos(lam);
-	    sinlam = Math.sin(lam);
-	    sinphi = Math.sin(phi);
-	    q = qsfnz(this.e, sinphi);
-	    if (this.mode === this.OBLIQ || this.mode === this.EQUIT) {
-	      sinb = q / this.qp;
-	      cosb = Math.sqrt(1 - sinb * sinb);
-	    }
-	    switch (this.mode) {
-	    case this.OBLIQ:
-	      b = 1 + this.sinb1 * sinb + this.cosb1 * cosb * coslam;
-	      break;
-	    case this.EQUIT:
-	      b = 1 + cosb * coslam;
-	      break;
-	    case this.N_POLE:
-	      b = HALF_PI + phi;
-	      q = this.qp - q;
-	      break;
-	    case this.S_POLE:
-	      b = phi - HALF_PI;
-	      q = this.qp + q;
-	      break;
-	    }
-	    if (Math.abs(b) < EPSLN) {
-	      return null;
-	    }
-	    switch (this.mode) {
-	    case this.OBLIQ:
-	    case this.EQUIT:
-	      b = Math.sqrt(2 / b);
-	      if (this.mode === this.OBLIQ) {
-	        y = this.ymf * b * (this.cosb1 * sinb - this.sinb1 * cosb * coslam);
-	      }
-	      else {
-	        y = (b = Math.sqrt(2 / (1 + cosb * coslam))) * sinb * this.ymf;
-	      }
-	      x = this.xmf * b * cosb * sinlam;
-	      break;
-	    case this.N_POLE:
-	    case this.S_POLE:
-	      if (q >= 0) {
-	        x = (b = Math.sqrt(q)) * sinlam;
-	        y = coslam * ((this.mode === this.S_POLE) ? b : -b);
-	      }
-	      else {
-	        x = y = 0;
-	      }
-	      break;
-	    }
-	  }
-
-	  p.x = this.a * x + this.x0;
-	  p.y = this.a * y + this.y0;
-	  return p;
-	};
-
-	/* Inverse equations
-	  -----------------*/
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  var x = p.x / this.a;
-	  var y = p.y / this.a;
-	  var lam, phi, cCe, sCe, q, rho, ab;
-
-	  if (this.sphere) {
-	    var cosz = 0,
-	      rh, sinz = 0;
-
-	    rh = Math.sqrt(x * x + y * y);
-	    phi = rh * 0.5;
-	    if (phi > 1) {
-	      return null;
-	    }
-	    phi = 2 * Math.asin(phi);
-	    if (this.mode === this.OBLIQ || this.mode === this.EQUIT) {
-	      sinz = Math.sin(phi);
-	      cosz = Math.cos(phi);
-	    }
-	    switch (this.mode) {
-	    case this.EQUIT:
-	      phi = (Math.abs(rh) <= EPSLN) ? 0 : Math.asin(y * sinz / rh);
-	      x *= sinz;
-	      y = cosz * rh;
-	      break;
-	    case this.OBLIQ:
-	      phi = (Math.abs(rh) <= EPSLN) ? this.phi0 : Math.asin(cosz * this.sinph0 + y * sinz * this.cosph0 / rh);
-	      x *= sinz * this.cosph0;
-	      y = (cosz - Math.sin(phi) * this.sinph0) * rh;
-	      break;
-	    case this.N_POLE:
-	      y = -y;
-	      phi = HALF_PI - phi;
-	      break;
-	    case this.S_POLE:
-	      phi -= HALF_PI;
-	      break;
-	    }
-	    lam = (y === 0 && (this.mode === this.EQUIT || this.mode === this.OBLIQ)) ? 0 : Math.atan2(x, y);
-	  }
-	  else {
-	    ab = 0;
-	    if (this.mode === this.OBLIQ || this.mode === this.EQUIT) {
-	      x /= this.dd;
-	      y *= this.dd;
-	      rho = Math.sqrt(x * x + y * y);
-	      if (rho < EPSLN) {
-	        p.x = 0;
-	        p.y = this.phi0;
-	        return p;
-	      }
-	      sCe = 2 * Math.asin(0.5 * rho / this.rq);
-	      cCe = Math.cos(sCe);
-	      x *= (sCe = Math.sin(sCe));
-	      if (this.mode === this.OBLIQ) {
-	        ab = cCe * this.sinb1 + y * sCe * this.cosb1 / rho;
-	        q = this.qp * ab;
-	        y = rho * this.cosb1 * cCe - y * this.sinb1 * sCe;
-	      }
-	      else {
-	        ab = y * sCe / rho;
-	        q = this.qp * ab;
-	        y = rho * cCe;
-	      }
-	    }
-	    else if (this.mode === this.N_POLE || this.mode === this.S_POLE) {
-	      if (this.mode === this.N_POLE) {
-	        y = -y;
-	      }
-	      q = (x * x + y * y);
-	      if (!q) {
-	        p.x = 0;
-	        p.y = this.phi0;
-	        return p;
-	      }
-	      ab = 1 - q / this.qp;
-	      if (this.mode === this.S_POLE) {
-	        ab = -ab;
-	      }
-	    }
-	    lam = Math.atan2(x, y);
-	    phi = this.authlat(Math.asin(ab), this.apa);
-	  }
-
-
-	  p.x = adjust_lon(this.long0 + lam);
-	  p.y = phi;
-	  return p;
-	};
-
-	/* determine latitude from authalic latitude */
-	exports.P00 = 0.33333333333333333333;
-	exports.P01 = 0.17222222222222222222;
-	exports.P02 = 0.10257936507936507936;
-	exports.P10 = 0.06388888888888888888;
-	exports.P11 = 0.06640211640211640211;
-	exports.P20 = 0.01641501294219154443;
-
-	exports.authset = function(es) {
-	  var t;
-	  var APA = [];
-	  APA[0] = es * this.P00;
-	  t = es * es;
-	  APA[0] += t * this.P01;
-	  APA[1] = t * this.P10;
-	  t *= es;
-	  APA[0] += t * this.P02;
-	  APA[1] += t * this.P11;
-	  APA[2] = t * this.P20;
-	  return APA;
-	};
-
-	exports.authlat = function(beta, APA) {
-	  var t = beta + beta;
-	  return (beta + APA[0] * Math.sin(t) + APA[1] * Math.sin(t + t) + APA[2] * Math.sin(t + t + t));
-	};
-	exports.names = ["Lambert Azimuthal Equal Area", "Lambert_Azimuthal_Equal_Area", "laea"];
-
-
-/***/ }),
-/* 68 */
-/***/ (function(module, exports) {
-
-	module.exports = function(eccent, sinphi) {
-	  var con;
-	  if (eccent > 1.0e-7) {
-	    con = eccent * sinphi;
-	    return ((1 - eccent * eccent) * (sinphi / (1 - con * con) - (0.5 / eccent) * Math.log((1 - con) / (1 + con))));
-	  }
-	  else {
-	    return (2 * sinphi);
-	  }
-	};
-
-/***/ }),
-/* 69 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var EPSLN = 1.0e-10;
-	var msfnz = __webpack_require__(25);
-	var qsfnz = __webpack_require__(68);
-	var adjust_lon = __webpack_require__(26);
-	var asinz = __webpack_require__(70);
-	exports.init = function() {
-
-	  if (Math.abs(this.lat1 + this.lat2) < EPSLN) {
-	    return;
-	  }
-	  this.temp = this.b / this.a;
-	  this.es = 1 - Math.pow(this.temp, 2);
-	  this.e3 = Math.sqrt(this.es);
-
-	  this.sin_po = Math.sin(this.lat1);
-	  this.cos_po = Math.cos(this.lat1);
-	  this.t1 = this.sin_po;
-	  this.con = this.sin_po;
-	  this.ms1 = msfnz(this.e3, this.sin_po, this.cos_po);
-	  this.qs1 = qsfnz(this.e3, this.sin_po, this.cos_po);
-
-	  this.sin_po = Math.sin(this.lat2);
-	  this.cos_po = Math.cos(this.lat2);
-	  this.t2 = this.sin_po;
-	  this.ms2 = msfnz(this.e3, this.sin_po, this.cos_po);
-	  this.qs2 = qsfnz(this.e3, this.sin_po, this.cos_po);
-
-	  this.sin_po = Math.sin(this.lat0);
-	  this.cos_po = Math.cos(this.lat0);
-	  this.t3 = this.sin_po;
-	  this.qs0 = qsfnz(this.e3, this.sin_po, this.cos_po);
-
-	  if (Math.abs(this.lat1 - this.lat2) > EPSLN) {
-	    this.ns0 = (this.ms1 * this.ms1 - this.ms2 * this.ms2) / (this.qs2 - this.qs1);
-	  }
-	  else {
-	    this.ns0 = this.con;
-	  }
-	  this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1;
-	  this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0) / this.ns0;
-	};
-
-	/* Albers Conical Equal Area forward equations--mapping lat,long to x,y
-	  -------------------------------------------------------------------*/
-	exports.forward = function(p) {
-
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  this.sin_phi = Math.sin(lat);
-	  this.cos_phi = Math.cos(lat);
-
-	  var qs = qsfnz(this.e3, this.sin_phi, this.cos_phi);
-	  var rh1 = this.a * Math.sqrt(this.c - this.ns0 * qs) / this.ns0;
-	  var theta = this.ns0 * adjust_lon(lon - this.long0);
-	  var x = rh1 * Math.sin(theta) + this.x0;
-	  var y = this.rh - rh1 * Math.cos(theta) + this.y0;
-
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-
-	exports.inverse = function(p) {
-	  var rh1, qs, con, theta, lon, lat;
-
-	  p.x -= this.x0;
-	  p.y = this.rh - p.y + this.y0;
-	  if (this.ns0 >= 0) {
-	    rh1 = Math.sqrt(p.x * p.x + p.y * p.y);
-	    con = 1;
-	  }
-	  else {
-	    rh1 = -Math.sqrt(p.x * p.x + p.y * p.y);
-	    con = -1;
-	  }
-	  theta = 0;
-	  if (rh1 !== 0) {
-	    theta = Math.atan2(con * p.x, con * p.y);
-	  }
-	  con = rh1 * this.ns0 / this.a;
-	  if (this.sphere) {
-	    lat = Math.asin((this.c - con * con) / (2 * this.ns0));
-	  }
-	  else {
-	    qs = (this.c - con * con) / this.ns0;
-	    lat = this.phi1z(this.e3, qs);
-	  }
-
-	  lon = adjust_lon(theta / this.ns0 + this.long0);
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-
-	/* Function to compute phi1, the latitude for the inverse of the
-	   Albers Conical Equal-Area projection.
-	-------------------------------------------*/
-	exports.phi1z = function(eccent, qs) {
-	  var sinphi, cosphi, con, com, dphi;
-	  var phi = asinz(0.5 * qs);
-	  if (eccent < EPSLN) {
-	    return phi;
-	  }
-
-	  var eccnts = eccent * eccent;
-	  for (var i = 1; i <= 25; i++) {
-	    sinphi = Math.sin(phi);
-	    cosphi = Math.cos(phi);
-	    con = eccent * sinphi;
-	    com = 1 - con * con;
-	    dphi = 0.5 * com * com / cosphi * (qs / (1 - eccnts) - sinphi / com + 0.5 / eccent * Math.log((1 - con) / (1 + con)));
-	    phi = phi + dphi;
-	    if (Math.abs(dphi) <= 1e-7) {
-	      return phi;
-	    }
-	  }
-	  return null;
-	};
-	exports.names = ["Albers_Conic_Equal_Area", "Albers", "aea"];
-
-
-/***/ }),
-/* 70 */
-/***/ (function(module, exports) {
-
-	module.exports = function(x) {
-	  if (Math.abs(x) > 1) {
-	    x = (x > 1) ? 1 : -1;
-	  }
-	  return Math.asin(x);
-	};
-
-/***/ }),
-/* 71 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var EPSLN = 1.0e-10;
-	var asinz = __webpack_require__(70);
-
-	/*
-	  reference:
-	    Wolfram Mathworld "Gnomonic Projection"
-	    http://mathworld.wolfram.com/GnomonicProjection.html
-	    Accessed: 12th November 2009
-	  */
-	exports.init = function() {
-
-	  /* Place parameters in static storage for common use
-	      -------------------------------------------------*/
-	  this.sin_p14 = Math.sin(this.lat0);
-	  this.cos_p14 = Math.cos(this.lat0);
-	  // Approximation for projecting points to the horizon (infinity)
-	  this.infinity_dist = 1000 * this.a;
-	  this.rc = 1;
-	};
-
-
-	/* Gnomonic forward equations--mapping lat,long to x,y
-	    ---------------------------------------------------*/
-	exports.forward = function(p) {
-	  var sinphi, cosphi; /* sin and cos value        */
-	  var dlon; /* delta longitude value      */
-	  var coslon; /* cos of longitude        */
-	  var ksp; /* scale factor          */
-	  var g;
-	  var x, y;
-	  var lon = p.x;
-	  var lat = p.y;
-	  /* Forward equations
-	      -----------------*/
-	  dlon = adjust_lon(lon - this.long0);
-
-	  sinphi = Math.sin(lat);
-	  cosphi = Math.cos(lat);
-
-	  coslon = Math.cos(dlon);
-	  g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
-	  ksp = 1;
-	  if ((g > 0) || (Math.abs(g) <= EPSLN)) {
-	    x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon) / g;
-	    y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon) / g;
-	  }
-	  else {
-
-	    // Point is in the opposing hemisphere and is unprojectable
-	    // We still need to return a reasonable point, so we project 
-	    // to infinity, on a bearing 
-	    // equivalent to the northern hemisphere equivalent
-	    // This is a reasonable approximation for short shapes and lines that 
-	    // straddle the horizon.
-
-	    x = this.x0 + this.infinity_dist * cosphi * Math.sin(dlon);
-	    y = this.y0 + this.infinity_dist * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
-
-	  }
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-
-	exports.inverse = function(p) {
-	  var rh; /* Rho */
-	  var sinc, cosc;
-	  var c;
-	  var lon, lat;
-
-	  /* Inverse equations
-	      -----------------*/
-	  p.x = (p.x - this.x0) / this.a;
-	  p.y = (p.y - this.y0) / this.a;
-
-	  p.x /= this.k0;
-	  p.y /= this.k0;
-
-	  if ((rh = Math.sqrt(p.x * p.x + p.y * p.y))) {
-	    c = Math.atan2(rh, this.rc);
-	    sinc = Math.sin(c);
-	    cosc = Math.cos(c);
-
-	    lat = asinz(cosc * this.sin_p14 + (p.y * sinc * this.cos_p14) / rh);
-	    lon = Math.atan2(p.x * sinc, rh * this.cos_p14 * cosc - p.y * this.sin_p14 * sinc);
-	    lon = adjust_lon(this.long0 + lon);
-	  }
-	  else {
-	    lat = this.phic0;
-	    lon = 0;
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["gnom"];
-
-
-/***/ }),
-/* 72 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var qsfnz = __webpack_require__(68);
-	var msfnz = __webpack_require__(25);
-	var iqsfnz = __webpack_require__(73);
-	/*
-	  reference:  
-	    "Cartographic Projection Procedures for the UNIX Environment-
-	    A User's Manual" by Gerald I. Evenden,
-	    USGS Open File Report 90-284and Release 4 Interim Reports (2003)
-	*/
-	exports.init = function() {
-	  //no-op
-	  if (!this.sphere) {
-	    this.k0 = msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts));
-	  }
-	};
-
-
-	/* Cylindrical Equal Area forward equations--mapping lat,long to x,y
-	    ------------------------------------------------------------*/
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var x, y;
-	  /* Forward equations
-	      -----------------*/
-	  var dlon = adjust_lon(lon - this.long0);
-	  if (this.sphere) {
-	    x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
-	    y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts);
-	  }
-	  else {
-	    var qs = qsfnz(this.e, Math.sin(lat));
-	    x = this.x0 + this.a * this.k0 * dlon;
-	    y = this.y0 + this.a * qs * 0.5 / this.k0;
-	  }
-
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	/* Cylindrical Equal Area inverse equations--mapping x,y to lat/long
-	    ------------------------------------------------------------*/
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  var lon, lat;
-
-	  if (this.sphere) {
-	    lon = adjust_lon(this.long0 + (p.x / this.a) / Math.cos(this.lat_ts));
-	    lat = Math.asin((p.y / this.a) * Math.cos(this.lat_ts));
-	  }
-	  else {
-	    lat = iqsfnz(this.e, 2 * p.y * this.k0 / this.a);
-	    lon = adjust_lon(this.long0 + p.x / (this.a * this.k0));
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["cea"];
-
-
-/***/ }),
-/* 73 */
-/***/ (function(module, exports) {
-
-	var HALF_PI = Math.PI/2;
-
-	module.exports = function(eccent, q) {
-	  var temp = 1 - (1 - eccent * eccent) / (2 * eccent) * Math.log((1 - eccent) / (1 + eccent));
-	  if (Math.abs(Math.abs(q) - temp) < 1.0E-6) {
-	    if (q < 0) {
-	      return (-1 * HALF_PI);
-	    }
-	    else {
-	      return HALF_PI;
-	    }
-	  }
-	  //var phi = 0.5* q/(1-eccent*eccent);
-	  var phi = Math.asin(0.5 * q);
-	  var dphi;
-	  var sin_phi;
-	  var cos_phi;
-	  var con;
-	  for (var i = 0; i < 30; i++) {
-	    sin_phi = Math.sin(phi);
-	    cos_phi = Math.cos(phi);
-	    con = eccent * sin_phi;
-	    dphi = Math.pow(1 - con * con, 2) / (2 * cos_phi) * (q / (1 - eccent * eccent) - sin_phi / (1 - con * con) + 0.5 / eccent * Math.log((1 - con) / (1 + con)));
-	    phi += dphi;
-	    if (Math.abs(dphi) <= 0.0000000001) {
-	      return phi;
-	    }
-	  }
-
-	  //console.log("IQSFN-CONV:Latitude failed to converge after 30 iterations");
-	  return NaN;
-	};
-
-/***/ }),
-/* 74 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var adjust_lat = __webpack_require__(65);
-	exports.init = function() {
-
-	  this.x0 = this.x0 || 0;
-	  this.y0 = this.y0 || 0;
-	  this.lat0 = this.lat0 || 0;
-	  this.long0 = this.long0 || 0;
-	  this.lat_ts = this.lat_ts || 0;
-	  this.title = this.title || "Equidistant Cylindrical (Plate Carre)";
-
-	  this.rc = Math.cos(this.lat_ts);
-	};
-
-
-	// forward equations--mapping lat,long to x,y
-	// -----------------------------------------------------------------
-	exports.forward = function(p) {
-
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  var dlon = adjust_lon(lon - this.long0);
-	  var dlat = adjust_lat(lat - this.lat0);
-	  p.x = this.x0 + (this.a * dlon * this.rc);
-	  p.y = this.y0 + (this.a * dlat);
-	  return p;
-	};
-
-	// inverse equations--mapping x,y to lat/long
-	// -----------------------------------------------------------------
-	exports.inverse = function(p) {
-
-	  var x = p.x;
-	  var y = p.y;
-
-	  p.x = adjust_lon(this.long0 + ((x - this.x0) / (this.a * this.rc)));
-	  p.y = adjust_lat(this.lat0 + ((y - this.y0) / (this.a)));
-	  return p;
-	};
-	exports.names = ["Equirectangular", "Equidistant_Cylindrical", "eqc"];
-
-
-/***/ }),
-/* 75 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var e0fn = __webpack_require__(60);
-	var e1fn = __webpack_require__(61);
-	var e2fn = __webpack_require__(62);
-	var e3fn = __webpack_require__(63);
-	var adjust_lon = __webpack_require__(26);
-	var adjust_lat = __webpack_require__(65);
-	var mlfn = __webpack_require__(59);
-	var EPSLN = 1.0e-10;
-	var gN = __webpack_require__(64);
-	var MAX_ITER = 20;
-	exports.init = function() {
-	  /* Place parameters in static storage for common use
-	      -------------------------------------------------*/
-	  this.temp = this.b / this.a;
-	  this.es = 1 - Math.pow(this.temp, 2); // devait etre dans tmerc.js mais n y est pas donc je commente sinon retour de valeurs nulles
-	  this.e = Math.sqrt(this.es);
-	  this.e0 = e0fn(this.es);
-	  this.e1 = e1fn(this.es);
-	  this.e2 = e2fn(this.es);
-	  this.e3 = e3fn(this.es);
-	  this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); //si que des zeros le calcul ne se fait pas
-	};
-
-
-	/* Polyconic forward equations--mapping lat,long to x,y
-	    ---------------------------------------------------*/
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var x, y, el;
-	  var dlon = adjust_lon(lon - this.long0);
-	  el = dlon * Math.sin(lat);
-	  if (this.sphere) {
-	    if (Math.abs(lat) <= EPSLN) {
-	      x = this.a * dlon;
-	      y = -1 * this.a * this.lat0;
-	    }
-	    else {
-	      x = this.a * Math.sin(el) / Math.tan(lat);
-	      y = this.a * (adjust_lat(lat - this.lat0) + (1 - Math.cos(el)) / Math.tan(lat));
-	    }
-	  }
-	  else {
-	    if (Math.abs(lat) <= EPSLN) {
-	      x = this.a * dlon;
-	      y = -1 * this.ml0;
-	    }
-	    else {
-	      var nl = gN(this.a, this.e, Math.sin(lat)) / Math.tan(lat);
-	      x = nl * Math.sin(el);
-	      y = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, lat) - this.ml0 + nl * (1 - Math.cos(el));
-	    }
-
-	  }
-	  p.x = x + this.x0;
-	  p.y = y + this.y0;
-	  return p;
-	};
-
-
-	/* Inverse equations
-	  -----------------*/
-	exports.inverse = function(p) {
-	  var lon, lat, x, y, i;
-	  var al, bl;
-	  var phi, dphi;
-	  x = p.x - this.x0;
-	  y = p.y - this.y0;
-
-	  if (this.sphere) {
-	    if (Math.abs(y + this.a * this.lat0) <= EPSLN) {
-	      lon = adjust_lon(x / this.a + this.long0);
-	      lat = 0;
-	    }
-	    else {
-	      al = this.lat0 + y / this.a;
-	      bl = x * x / this.a / this.a + al * al;
-	      phi = al;
-	      var tanphi;
-	      for (i = MAX_ITER; i; --i) {
-	        tanphi = Math.tan(phi);
-	        dphi = -1 * (al * (phi * tanphi + 1) - phi - 0.5 * (phi * phi + bl) * tanphi) / ((phi - al) / tanphi - 1);
-	        phi += dphi;
-	        if (Math.abs(dphi) <= EPSLN) {
-	          lat = phi;
-	          break;
-	        }
-	      }
-	      lon = adjust_lon(this.long0 + (Math.asin(x * Math.tan(phi) / this.a)) / Math.sin(lat));
-	    }
-	  }
-	  else {
-	    if (Math.abs(y + this.ml0) <= EPSLN) {
-	      lat = 0;
-	      lon = adjust_lon(this.long0 + x / this.a);
-	    }
-	    else {
-
-	      al = (this.ml0 + y) / this.a;
-	      bl = x * x / this.a / this.a + al * al;
-	      phi = al;
-	      var cl, mln, mlnp, ma;
-	      var con;
-	      for (i = MAX_ITER; i; --i) {
-	        con = this.e * Math.sin(phi);
-	        cl = Math.sqrt(1 - con * con) * Math.tan(phi);
-	        mln = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi);
-	        mlnp = this.e0 - 2 * this.e1 * Math.cos(2 * phi) + 4 * this.e2 * Math.cos(4 * phi) - 6 * this.e3 * Math.cos(6 * phi);
-	        ma = mln / this.a;
-	        dphi = (al * (cl * ma + 1) - ma - 0.5 * cl * (ma * ma + bl)) / (this.es * Math.sin(2 * phi) * (ma * ma + bl - 2 * al * ma) / (4 * cl) + (al - ma) * (cl * mlnp - 2 / Math.sin(2 * phi)) - mlnp);
-	        phi -= dphi;
-	        if (Math.abs(dphi) <= EPSLN) {
-	          lat = phi;
-	          break;
-	        }
-	      }
-
-	      //lat=phi4z(this.e,this.e0,this.e1,this.e2,this.e3,al,bl,0,0);
-	      cl = Math.sqrt(1 - this.es * Math.pow(Math.sin(lat), 2)) * Math.tan(lat);
-	      lon = adjust_lon(this.long0 + Math.asin(x * cl / this.a) / Math.sin(lat));
-	    }
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["Polyconic", "poly"];
-
-/***/ }),
-/* 76 */
-/***/ (function(module, exports) {
-
-	var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
-	/*
-	  reference
-	    Department of Land and Survey Technical Circular 1973/32
-	      http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf
-	    OSG Technical Report 4.1
-	      http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf
-	  */
-
-	/**
-	 * iterations: Number of iterations to refine inverse transform.
-	 *     0 -> km accuracy
-	 *     1 -> m accuracy -- suitable for most mapping applications
-	 *     2 -> mm accuracy
-	 */
-	exports.iterations = 1;
-
-	exports.init = function() {
-	  this.A = [];
-	  this.A[1] = 0.6399175073;
-	  this.A[2] = -0.1358797613;
-	  this.A[3] = 0.063294409;
-	  this.A[4] = -0.02526853;
-	  this.A[5] = 0.0117879;
-	  this.A[6] = -0.0055161;
-	  this.A[7] = 0.0026906;
-	  this.A[8] = -0.001333;
-	  this.A[9] = 0.00067;
-	  this.A[10] = -0.00034;
-
-	  this.B_re = [];
-	  this.B_im = [];
-	  this.B_re[1] = 0.7557853228;
-	  this.B_im[1] = 0;
-	  this.B_re[2] = 0.249204646;
-	  this.B_im[2] = 0.003371507;
-	  this.B_re[3] = -0.001541739;
-	  this.B_im[3] = 0.041058560;
-	  this.B_re[4] = -0.10162907;
-	  this.B_im[4] = 0.01727609;
-	  this.B_re[5] = -0.26623489;
-	  this.B_im[5] = -0.36249218;
-	  this.B_re[6] = -0.6870983;
-	  this.B_im[6] = -1.1651967;
-
-	  this.C_re = [];
-	  this.C_im = [];
-	  this.C_re[1] = 1.3231270439;
-	  this.C_im[1] = 0;
-	  this.C_re[2] = -0.577245789;
-	  this.C_im[2] = -0.007809598;
-	  this.C_re[3] = 0.508307513;
-	  this.C_im[3] = -0.112208952;
-	  this.C_re[4] = -0.15094762;
-	  this.C_im[4] = 0.18200602;
-	  this.C_re[5] = 1.01418179;
-	  this.C_im[5] = 1.64497696;
-	  this.C_re[6] = 1.9660549;
-	  this.C_im[6] = 2.5127645;
-
-	  this.D = [];
-	  this.D[1] = 1.5627014243;
-	  this.D[2] = 0.5185406398;
-	  this.D[3] = -0.03333098;
-	  this.D[4] = -0.1052906;
-	  this.D[5] = -0.0368594;
-	  this.D[6] = 0.007317;
-	  this.D[7] = 0.01220;
-	  this.D[8] = 0.00394;
-	  this.D[9] = -0.0013;
-	};
-
-	/**
-	    New Zealand Map Grid Forward  - long/lat to x/y
-	    long/lat in radians
-	  */
-	exports.forward = function(p) {
-	  var n;
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  var delta_lat = lat - this.lat0;
-	  var delta_lon = lon - this.long0;
-
-	  // 1. Calculate d_phi and d_psi    ...                          // and d_lambda
-	  // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians.
-	  var d_phi = delta_lat / SEC_TO_RAD * 1E-5;
-	  var d_lambda = delta_lon;
-	  var d_phi_n = 1; // d_phi^0
-
-	  var d_psi = 0;
-	  for (n = 1; n <= 10; n++) {
-	    d_phi_n = d_phi_n * d_phi;
-	    d_psi = d_psi + this.A[n] * d_phi_n;
-	  }
-
-	  // 2. Calculate theta
-	  var th_re = d_psi;
-	  var th_im = d_lambda;
-
-	  // 3. Calculate z
-	  var th_n_re = 1;
-	  var th_n_im = 0; // theta^0
-	  var th_n_re1;
-	  var th_n_im1;
-
-	  var z_re = 0;
-	  var z_im = 0;
-	  for (n = 1; n <= 6; n++) {
-	    th_n_re1 = th_n_re * th_re - th_n_im * th_im;
-	    th_n_im1 = th_n_im * th_re + th_n_re * th_im;
-	    th_n_re = th_n_re1;
-	    th_n_im = th_n_im1;
-	    z_re = z_re + this.B_re[n] * th_n_re - this.B_im[n] * th_n_im;
-	    z_im = z_im + this.B_im[n] * th_n_re + this.B_re[n] * th_n_im;
-	  }
-
-	  // 4. Calculate easting and northing
-	  p.x = (z_im * this.a) + this.x0;
-	  p.y = (z_re * this.a) + this.y0;
-
-	  return p;
-	};
-
-
-	/**
-	    New Zealand Map Grid Inverse  -  x/y to long/lat
-	  */
-	exports.inverse = function(p) {
-	  var n;
-	  var x = p.x;
-	  var y = p.y;
-
-	  var delta_x = x - this.x0;
-	  var delta_y = y - this.y0;
-
-	  // 1. Calculate z
-	  var z_re = delta_y / this.a;
-	  var z_im = delta_x / this.a;
-
-	  // 2a. Calculate theta - first approximation gives km accuracy
-	  var z_n_re = 1;
-	  var z_n_im = 0; // z^0
-	  var z_n_re1;
-	  var z_n_im1;
-
-	  var th_re = 0;
-	  var th_im = 0;
-	  for (n = 1; n <= 6; n++) {
-	    z_n_re1 = z_n_re * z_re - z_n_im * z_im;
-	    z_n_im1 = z_n_im * z_re + z_n_re * z_im;
-	    z_n_re = z_n_re1;
-	    z_n_im = z_n_im1;
-	    th_re = th_re + this.C_re[n] * z_n_re - this.C_im[n] * z_n_im;
-	    th_im = th_im + this.C_im[n] * z_n_re + this.C_re[n] * z_n_im;
-	  }
-
-	  // 2b. Iterate to refine the accuracy of the calculation
-	  //        0 iterations gives km accuracy
-	  //        1 iteration gives m accuracy -- good enough for most mapping applications
-	  //        2 iterations bives mm accuracy
-	  for (var i = 0; i < this.iterations; i++) {
-	    var th_n_re = th_re;
-	    var th_n_im = th_im;
-	    var th_n_re1;
-	    var th_n_im1;
-
-	    var num_re = z_re;
-	    var num_im = z_im;
-	    for (n = 2; n <= 6; n++) {
-	      th_n_re1 = th_n_re * th_re - th_n_im * th_im;
-	      th_n_im1 = th_n_im * th_re + th_n_re * th_im;
-	      th_n_re = th_n_re1;
-	      th_n_im = th_n_im1;
-	      num_re = num_re + (n - 1) * (this.B_re[n] * th_n_re - this.B_im[n] * th_n_im);
-	      num_im = num_im + (n - 1) * (this.B_im[n] * th_n_re + this.B_re[n] * th_n_im);
-	    }
-
-	    th_n_re = 1;
-	    th_n_im = 0;
-	    var den_re = this.B_re[1];
-	    var den_im = this.B_im[1];
-	    for (n = 2; n <= 6; n++) {
-	      th_n_re1 = th_n_re * th_re - th_n_im * th_im;
-	      th_n_im1 = th_n_im * th_re + th_n_re * th_im;
-	      th_n_re = th_n_re1;
-	      th_n_im = th_n_im1;
-	      den_re = den_re + n * (this.B_re[n] * th_n_re - this.B_im[n] * th_n_im);
-	      den_im = den_im + n * (this.B_im[n] * th_n_re + this.B_re[n] * th_n_im);
-	    }
-
-	    // Complex division
-	    var den2 = den_re * den_re + den_im * den_im;
-	    th_re = (num_re * den_re + num_im * den_im) / den2;
-	    th_im = (num_im * den_re - num_re * den_im) / den2;
-	  }
-
-	  // 3. Calculate d_phi              ...                                    // and d_lambda
-	  var d_psi = th_re;
-	  var d_lambda = th_im;
-	  var d_psi_n = 1; // d_psi^0
-
-	  var d_phi = 0;
-	  for (n = 1; n <= 9; n++) {
-	    d_psi_n = d_psi_n * d_psi;
-	    d_phi = d_phi + this.D[n] * d_psi_n;
-	  }
-
-	  // 4. Calculate latitude and longitude
-	  // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians.
-	  var lat = this.lat0 + (d_phi * SEC_TO_RAD * 1E5);
-	  var lon = this.long0 + d_lambda;
-
-	  p.x = lon;
-	  p.y = lat;
-
-	  return p;
-	};
-	exports.names = ["New_Zealand_Map_Grid", "nzmg"];
-
-/***/ }),
-/* 77 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	/*
-	  reference
-	    "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
-	    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
-	  */
-
-
-	/* Initialize the Miller Cylindrical projection
-	  -------------------------------------------*/
-	exports.init = function() {
-	  //no-op
-	};
-
-
-	/* Miller Cylindrical forward equations--mapping lat,long to x,y
-	    ------------------------------------------------------------*/
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  /* Forward equations
-	      -----------------*/
-	  var dlon = adjust_lon(lon - this.long0);
-	  var x = this.x0 + this.a * dlon;
-	  var y = this.y0 + this.a * Math.log(Math.tan((Math.PI / 4) + (lat / 2.5))) * 1.25;
-
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	/* Miller Cylindrical inverse equations--mapping x,y to lat/long
-	    ------------------------------------------------------------*/
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-
-	  var lon = adjust_lon(this.long0 + p.x / this.a);
-	  var lat = 2.5 * (Math.atan(Math.exp(0.8 * p.y / this.a)) - Math.PI / 4);
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["Miller_Cylindrical", "mill"];
-
-
-/***/ }),
-/* 78 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var adjust_lat = __webpack_require__(65);
-	var pj_enfn = __webpack_require__(45);
-	var MAX_ITER = 20;
-	var pj_mlfn = __webpack_require__(46);
-	var pj_inv_mlfn = __webpack_require__(47);
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	var asinz = __webpack_require__(70);
-	exports.init = function() {
-	  /* Place parameters in static storage for common use
-	    -------------------------------------------------*/
-
-
-	  if (!this.sphere) {
-	    this.en = pj_enfn(this.es);
-	  }
-	  else {
-	    this.n = 1;
-	    this.m = 0;
-	    this.es = 0;
-	    this.C_y = Math.sqrt((this.m + 1) / this.n);
-	    this.C_x = this.C_y / (this.m + 1);
-	  }
-
-	};
-
-	/* Sinusoidal forward equations--mapping lat,long to x,y
-	  -----------------------------------------------------*/
-	exports.forward = function(p) {
-	  var x, y;
-	  var lon = p.x;
-	  var lat = p.y;
-	  /* Forward equations
-	    -----------------*/
-	  lon = adjust_lon(lon - this.long0);
-
-	  if (this.sphere) {
-	    if (!this.m) {
-	      lat = this.n !== 1 ? Math.asin(this.n * Math.sin(lat)) : lat;
-	    }
-	    else {
-	      var k = this.n * Math.sin(lat);
-	      for (var i = MAX_ITER; i; --i) {
-	        var V = (this.m * lat + Math.sin(lat) - k) / (this.m + Math.cos(lat));
-	        lat -= V;
-	        if (Math.abs(V) < EPSLN) {
-	          break;
-	        }
-	      }
-	    }
-	    x = this.a * this.C_x * lon * (this.m + Math.cos(lat));
-	    y = this.a * this.C_y * lat;
-
-	  }
-	  else {
-
-	    var s = Math.sin(lat);
-	    var c = Math.cos(lat);
-	    y = this.a * pj_mlfn(lat, s, c, this.en);
-	    x = this.a * lon * c / Math.sqrt(1 - this.es * s * s);
-	  }
-
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var lat, temp, lon, s;
-
-	  p.x -= this.x0;
-	  lon = p.x / this.a;
-	  p.y -= this.y0;
-	  lat = p.y / this.a;
-
-	  if (this.sphere) {
-	    lat /= this.C_y;
-	    lon = lon / (this.C_x * (this.m + Math.cos(lat)));
-	    if (this.m) {
-	      lat = asinz((this.m * lat + Math.sin(lat)) / this.n);
-	    }
-	    else if (this.n !== 1) {
-	      lat = asinz(Math.sin(lat) / this.n);
-	    }
-	    lon = adjust_lon(lon + this.long0);
-	    lat = adjust_lat(lat);
-	  }
-	  else {
-	    lat = pj_inv_mlfn(p.y / this.a, this.es, this.en);
-	    s = Math.abs(lat);
-	    if (s < HALF_PI) {
-	      s = Math.sin(lat);
-	      temp = this.long0 + p.x * Math.sqrt(1 - this.es * s * s) / (this.a * Math.cos(lat));
-	      //temp = this.long0 + p.x / (this.a * Math.cos(lat));
-	      lon = adjust_lon(temp);
-	    }
-	    else if ((s - EPSLN) < HALF_PI) {
-	      lon = this.long0;
-	    }
-	  }
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["Sinusoidal", "sinu"];
-
-/***/ }),
-/* 79 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var EPSLN = 1.0e-10;
-	exports.init = function() {};
-
-	/* Mollweide forward equations--mapping lat,long to x,y
-	    ----------------------------------------------------*/
-	exports.forward = function(p) {
-
-	  /* Forward equations
-	      -----------------*/
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  var delta_lon = adjust_lon(lon - this.long0);
-	  var theta = lat;
-	  var con = Math.PI * Math.sin(lat);
-
-	  /* Iterate using the Newton-Raphson method to find theta
-	      -----------------------------------------------------*/
-	  for (var i = 0; true; i++) {
-	    var delta_theta = -(theta + Math.sin(theta) - con) / (1 + Math.cos(theta));
-	    theta += delta_theta;
-	    if (Math.abs(delta_theta) < EPSLN) {
-	      break;
-	    }
-	  }
-	  theta /= 2;
-
-	  /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting"
-	       this is done here because of precision problems with "cos(theta)"
-	       --------------------------------------------------------------------------*/
-	  if (Math.PI / 2 - Math.abs(lat) < EPSLN) {
-	    delta_lon = 0;
-	  }
-	  var x = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0;
-	  var y = 1.4142135623731 * this.a * Math.sin(theta) + this.y0;
-
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	exports.inverse = function(p) {
-	  var theta;
-	  var arg;
-
-	  /* Inverse equations
-	      -----------------*/
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  arg = p.y / (1.4142135623731 * this.a);
-
-	  /* Because of division by zero problems, 'arg' can not be 1.  Therefore
-	       a number very close to one is used instead.
-	       -------------------------------------------------------------------*/
-	  if (Math.abs(arg) > 0.999999999999) {
-	    arg = 0.999999999999;
-	  }
-	  theta = Math.asin(arg);
-	  var lon = adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta))));
-	  if (lon < (-Math.PI)) {
-	    lon = -Math.PI;
-	  }
-	  if (lon > Math.PI) {
-	    lon = Math.PI;
-	  }
-	  arg = (2 * theta + Math.sin(2 * theta)) / Math.PI;
-	  if (Math.abs(arg) > 1) {
-	    arg = 1;
-	  }
-	  var lat = Math.asin(arg);
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["Mollweide", "moll"];
-
-
-/***/ }),
-/* 80 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var e0fn = __webpack_require__(60);
-	var e1fn = __webpack_require__(61);
-	var e2fn = __webpack_require__(62);
-	var e3fn = __webpack_require__(63);
-	var msfnz = __webpack_require__(25);
-	var mlfn = __webpack_require__(59);
-	var adjust_lon = __webpack_require__(26);
-	var adjust_lat = __webpack_require__(65);
-	var imlfn = __webpack_require__(66);
-	var EPSLN = 1.0e-10;
-	exports.init = function() {
-
-	  /* Place parameters in static storage for common use
-	      -------------------------------------------------*/
-	  // Standard Parallels cannot be equal and on opposite sides of the equator
-	  if (Math.abs(this.lat1 + this.lat2) < EPSLN) {
-	    return;
-	  }
-	  this.lat2 = this.lat2 || this.lat1;
-	  this.temp = this.b / this.a;
-	  this.es = 1 - Math.pow(this.temp, 2);
-	  this.e = Math.sqrt(this.es);
-	  this.e0 = e0fn(this.es);
-	  this.e1 = e1fn(this.es);
-	  this.e2 = e2fn(this.es);
-	  this.e3 = e3fn(this.es);
-
-	  this.sinphi = Math.sin(this.lat1);
-	  this.cosphi = Math.cos(this.lat1);
-
-	  this.ms1 = msfnz(this.e, this.sinphi, this.cosphi);
-	  this.ml1 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat1);
-
-	  if (Math.abs(this.lat1 - this.lat2) < EPSLN) {
-	    this.ns = this.sinphi;
-	  }
-	  else {
-	    this.sinphi = Math.sin(this.lat2);
-	    this.cosphi = Math.cos(this.lat2);
-	    this.ms2 = msfnz(this.e, this.sinphi, this.cosphi);
-	    this.ml2 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2);
-	    this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1);
-	  }
-	  this.g = this.ml1 + this.ms1 / this.ns;
-	  this.ml0 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
-	  this.rh = this.a * (this.g - this.ml0);
-	};
-
-
-	/* Equidistant Conic forward equations--mapping lat,long to x,y
-	  -----------------------------------------------------------*/
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var rh1;
-
-	  /* Forward equations
-	      -----------------*/
-	  if (this.sphere) {
-	    rh1 = this.a * (this.g - lat);
-	  }
-	  else {
-	    var ml = mlfn(this.e0, this.e1, this.e2, this.e3, lat);
-	    rh1 = this.a * (this.g - ml);
-	  }
-	  var theta = this.ns * adjust_lon(lon - this.long0);
-	  var x = this.x0 + rh1 * Math.sin(theta);
-	  var y = this.y0 + this.rh - rh1 * Math.cos(theta);
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	/* Inverse equations
-	  -----------------*/
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y = this.rh - p.y + this.y0;
-	  var con, rh1, lat, lon;
-	  if (this.ns >= 0) {
-	    rh1 = Math.sqrt(p.x * p.x + p.y * p.y);
-	    con = 1;
-	  }
-	  else {
-	    rh1 = -Math.sqrt(p.x * p.x + p.y * p.y);
-	    con = -1;
-	  }
-	  var theta = 0;
-	  if (rh1 !== 0) {
-	    theta = Math.atan2(con * p.x, con * p.y);
-	  }
-
-	  if (this.sphere) {
-	    lon = adjust_lon(this.long0 + theta / this.ns);
-	    lat = adjust_lat(this.g - rh1 / this.a);
-	    p.x = lon;
-	    p.y = lat;
-	    return p;
-	  }
-	  else {
-	    var ml = this.g - rh1 / this.a;
-	    lat = imlfn(ml, this.e0, this.e1, this.e2, this.e3);
-	    lon = adjust_lon(this.long0 + theta / this.ns);
-	    p.x = lon;
-	    p.y = lat;
-	    return p;
-	  }
-
-	};
-	exports.names = ["Equidistant_Conic", "eqdc"];
-
-
-/***/ }),
-/* 81 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	var asinz = __webpack_require__(70);
-	/* Initialize the Van Der Grinten projection
-	  ----------------------------------------*/
-	exports.init = function() {
-	  //this.R = 6370997; //Radius of earth
-	  this.R = this.a;
-	};
-
-	exports.forward = function(p) {
-
-	  var lon = p.x;
-	  var lat = p.y;
-
-	  /* Forward equations
-	    -----------------*/
-	  var dlon = adjust_lon(lon - this.long0);
-	  var x, y;
-
-	  if (Math.abs(lat) <= EPSLN) {
-	    x = this.x0 + this.R * dlon;
-	    y = this.y0;
-	  }
-	  var theta = asinz(2 * Math.abs(lat / Math.PI));
-	  if ((Math.abs(dlon) <= EPSLN) || (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN)) {
-	    x = this.x0;
-	    if (lat >= 0) {
-	      y = this.y0 + Math.PI * this.R * Math.tan(0.5 * theta);
-	    }
-	    else {
-	      y = this.y0 + Math.PI * this.R * -Math.tan(0.5 * theta);
-	    }
-	    //  return(OK);
-	  }
-	  var al = 0.5 * Math.abs((Math.PI / dlon) - (dlon / Math.PI));
-	  var asq = al * al;
-	  var sinth = Math.sin(theta);
-	  var costh = Math.cos(theta);
-
-	  var g = costh / (sinth + costh - 1);
-	  var gsq = g * g;
-	  var m = g * (2 / sinth - 1);
-	  var msq = m * m;
-	  var con = Math.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq);
-	  if (dlon < 0) {
-	    con = -con;
-	  }
-	  x = this.x0 + con;
-	  //con = Math.abs(con / (Math.PI * this.R));
-	  var q = asq + g;
-	  con = Math.PI * this.R * (m * q - al * Math.sqrt((msq + asq) * (asq + 1) - q * q)) / (msq + asq);
-	  if (lat >= 0) {
-	    //y = this.y0 + Math.PI * this.R * Math.sqrt(1 - con * con - 2 * al * con);
-	    y = this.y0 + con;
-	  }
-	  else {
-	    //y = this.y0 - Math.PI * this.R * Math.sqrt(1 - con * con - 2 * al * con);
-	    y = this.y0 - con;
-	  }
-	  p.x = x;
-	  p.y = y;
-	  return p;
-	};
-
-	/* Van Der Grinten inverse equations--mapping x,y to lat/long
-	  ---------------------------------------------------------*/
-	exports.inverse = function(p) {
-	  var lon, lat;
-	  var xx, yy, xys, c1, c2, c3;
-	  var a1;
-	  var m1;
-	  var con;
-	  var th1;
-	  var d;
-
-	  /* inverse equations
-	    -----------------*/
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  con = Math.PI * this.R;
-	  xx = p.x / con;
-	  yy = p.y / con;
-	  xys = xx * xx + yy * yy;
-	  c1 = -Math.abs(yy) * (1 + xys);
-	  c2 = c1 - 2 * yy * yy + xx * xx;
-	  c3 = -2 * c1 + 1 + 2 * yy * yy + xys * xys;
-	  d = yy * yy / c3 + (2 * c2 * c2 * c2 / c3 / c3 / c3 - 9 * c1 * c2 / c3 / c3) / 27;
-	  a1 = (c1 - c2 * c2 / 3 / c3) / c3;
-	  m1 = 2 * Math.sqrt(-a1 / 3);
-	  con = ((3 * d) / a1) / m1;
-	  if (Math.abs(con) > 1) {
-	    if (con >= 0) {
-	      con = 1;
-	    }
-	    else {
-	      con = -1;
-	    }
-	  }
-	  th1 = Math.acos(con) / 3;
-	  if (p.y >= 0) {
-	    lat = (-m1 * Math.cos(th1 + Math.PI / 3) - c2 / 3 / c3) * Math.PI;
-	  }
-	  else {
-	    lat = -(-m1 * Math.cos(th1 + Math.PI / 3) - c2 / 3 / c3) * Math.PI;
-	  }
-
-	  if (Math.abs(xx) < EPSLN) {
-	    lon = this.long0;
-	  }
-	  else {
-	    lon = adjust_lon(this.long0 + Math.PI * (xys - 1 + Math.sqrt(1 + 2 * (xx * xx - yy * yy) + xys * xys)) / 2 / xx);
-	  }
-
-	  p.x = lon;
-	  p.y = lat;
-	  return p;
-	};
-	exports.names = ["Van_der_Grinten_I", "VanDerGrinten", "vandg"];
-
-/***/ }),
-/* 82 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var adjust_lon = __webpack_require__(26);
-	var HALF_PI = Math.PI/2;
-	var EPSLN = 1.0e-10;
-	var mlfn = __webpack_require__(59);
-	var e0fn = __webpack_require__(60);
-	var e1fn = __webpack_require__(61);
-	var e2fn = __webpack_require__(62);
-	var e3fn = __webpack_require__(63);
-	var gN = __webpack_require__(64);
-	var asinz = __webpack_require__(70);
-	var imlfn = __webpack_require__(66);
-	exports.init = function() {
-	  this.sin_p12 = Math.sin(this.lat0);
-	  this.cos_p12 = Math.cos(this.lat0);
-	};
-
-	exports.forward = function(p) {
-	  var lon = p.x;
-	  var lat = p.y;
-	  var sinphi = Math.sin(p.y);
-	  var cosphi = Math.cos(p.y);
-	  var dlon = adjust_lon(lon - this.long0);
-	  var e0, e1, e2, e3, Mlp, Ml, tanphi, Nl1, Nl, psi, Az, G, H, GH, Hs, c, kp, cos_c, s, s2, s3, s4, s5;
-	  if (this.sphere) {
-	    if (Math.abs(this.sin_p12 - 1) <= EPSLN) {
-	      //North Pole case
-	      p.x = this.x0 + this.a * (HALF_PI - lat) * Math.sin(dlon);
-	      p.y = this.y0 - this.a * (HALF_PI - lat) * Math.cos(dlon);
-	      return p;
-	    }
-	    else if (Math.abs(this.sin_p12 + 1) <= EPSLN) {
-	      //South Pole case
-	      p.x = this.x0 + this.a * (HALF_PI + lat) * Math.sin(dlon);
-	      p.y = this.y0 + this.a * (HALF_PI + lat) * Math.cos(dlon);
-	      return p;
-	    }
-	    else {
-	      //default case
-	      cos_c = this.sin_p12 * sinphi + this.cos_p12 * cosphi * Math.cos(dlon);
-	      c = Math.acos(cos_c);
-	      kp = c / Math.sin(c);
-	      p.x = this.x0 + this.a * kp * cosphi * Math.sin(dlon);
-	      p.y = this.y0 + this.a * kp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * Math.cos(dlon));
-	      return p;
-	    }
-	  }
-	  else {
-	    e0 = e0fn(this.es);
-	    e1 = e1fn(this.es);
-	    e2 = e2fn(this.es);
-	    e3 = e3fn(this.es);
-	    if (Math.abs(this.sin_p12 - 1) <= EPSLN) {
-	      //North Pole case
-	      Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI);
-	      Ml = this.a * mlfn(e0, e1, e2, e3, lat);
-	      p.x = this.x0 + (Mlp - Ml) * Math.sin(dlon);
-	      p.y = this.y0 - (Mlp - Ml) * Math.cos(dlon);
-	      return p;
-	    }
-	    else if (Math.abs(this.sin_p12 + 1) <= EPSLN) {
-	      //South Pole case
-	      Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI);
-	      Ml = this.a * mlfn(e0, e1, e2, e3, lat);
-	      p.x = this.x0 + (Mlp + Ml) * Math.sin(dlon);
-	      p.y = this.y0 + (Mlp + Ml) * Math.cos(dlon);
-	      return p;
-	    }
-	    else {
-	      //Default case
-	      tanphi = sinphi / cosphi;
-	      Nl1 = gN(this.a, this.e, this.sin_p12);
-	      Nl = gN(this.a, this.e, sinphi);
-	      psi = Math.atan((1 - this.es) * tanphi + this.es * Nl1 * this.sin_p12 / (Nl * cosphi));
-	      Az = Math.atan2(Math.sin(dlon), this.cos_p12 * Math.tan(psi) - this.sin_p12 * Math.cos(dlon));
-	      if (Az === 0) {
-	        s = Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi));
-	      }
-	      else if (Math.abs(Math.abs(Az) - Math.PI) <= EPSLN) {
-	        s = -Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi));
-	      }
-	      else {
-	        s = Math.asin(Math.sin(dlon) * Math.cos(psi) / Math.sin(Az));
-	      }
-	      G = this.e * this.sin_p12 / Math.sqrt(1 - this.es);
-	      H = this.e * this.cos_p12 * Math.cos(Az) / Math.sqrt(1 - this.es);
-	      GH = G * H;
-	      Hs = H * H;
-	      s2 = s * s;
-	      s3 = s2 * s;
-	      s4 = s3 * s;
-	      s5 = s4 * s;
-	      c = Nl1 * s * (1 - s2 * Hs * (1 - Hs) / 6 + s3 / 8 * GH * (1 - 2 * Hs) + s4 / 120 * (Hs * (4 - 7 * Hs) - 3 * G * G * (1 - 7 * Hs)) - s5 / 48 * GH);
-	      p.x = this.x0 + c * Math.sin(Az);
-	      p.y = this.y0 + c * Math.cos(Az);
-	      return p;
-	    }
-	  }
-
-
-	};
-
-	exports.inverse = function(p) {
-	  p.x -= this.x0;
-	  p.y -= this.y0;
-	  var rh, z, sinz, cosz, lon, lat, con, e0, e1, e2, e3, Mlp, M, N1, psi, Az, cosAz, tmp, A, B, D, Ee, F;
-	  if (this.sphere) {
-	    rh = Math.sqrt(p.x * p.x + p.y * p.y);
-	    if (rh > (2 * HALF_PI * this.a)) {
-	      return;
-	    }
-	    z = rh / this.a;
-
-	    sinz = Math.sin(z);
-	    cosz = Math.cos(z);
-
-	    lon = this.long0;
-	    if (Math.abs(rh) <= EPSLN) {
-	      lat = this.lat0;
-	    }
-	    else {
-	      lat = asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh);
-	      con = Math.abs(this.lat0) - HALF_PI;
-	      if (Math.abs(con) <= EPSLN) {
-	        if (this.lat0 >= 0) {
-	          lon = adjust_lon(this.long0 + Math.atan2(p.x, - p.y));
-	        }
-	        else {
-	          lon = adjust_lon(this.long0 - Math.atan2(-p.x, p.y));
-	        }
-	      }
-	      else {
-	        /*con = cosz - this.sin_p12 * Math.sin(lat);
-	        if ((Math.abs(con) < EPSLN) && (Math.abs(p.x) < EPSLN)) {
-	          //no-op, just keep the lon value as is
-	        } else {
-	          var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh));
-	          lon = adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh)));
-	        }*/
-	        lon = adjust_lon(this.long0 + Math.atan2(p.x * sinz, rh * this.cos_p12 * cosz - p.y * this.sin_p12 * sinz));
-	      }
-	    }
-
-	    p.x = lon;
-	    p.y = lat;
-	    return p;
-	  }
-	  else {
-	    e0 = e0fn(this.es);
-	    e1 = e1fn(this.es);
-	    e2 = e2fn(this.es);
-	    e3 = e3fn(this.es);
-	    if (Math.abs(this.sin_p12 - 1) <= EPSLN) {
-	      //North pole case
-	      Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI);
-	      rh = Math.sqrt(p.x * p.x + p.y * p.y);
-	      M = Mlp - rh;
-	      lat = imlfn(M / this.a, e0, e1, e2, e3);
-	      lon = adjust_lon(this.long0 + Math.atan2(p.x, - 1 * p.y));
-	      p.x = lon;
-	      p.y = lat;
-	      return p;
-	    }
-	    else if (Math.abs(this.sin_p12 + 1) <= EPSLN) {
-	      //South pole case
-	      Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI);
-	      rh = Math.sqrt(p.x * p.x + p.y * p.y);
-	      M = rh - Mlp;
-
-	      lat = imlfn(M / this.a, e0, e1, e2, e3);
-	      lon = adjust_lon(this.long0 + Math.atan2(p.x, p.y));
-	      p.x = lon;
-	      p.y = lat;
-	      return p;
-	    }
-	    else {
-	      //default case
-	      rh = Math.sqrt(p.x * p.x + p.y * p.y);
-	      Az = Math.atan2(p.x, p.y);
-	      N1 = gN(this.a, this.e, this.sin_p12);
-	      cosAz = Math.cos(Az);
-	      tmp = this.e * this.cos_p12 * cosAz;
-	      A = -tmp * tmp / (1 - this.es);
-	      B = 3 * this.es * (1 - A) * this.sin_p12 * this.cos_p12 * cosAz / (1 - this.es);
-	      D = rh / N1;
-	      Ee = D - A * (1 + A) * Math.pow(D, 3) / 6 - B * (1 + 3 * A) * Math.pow(D, 4) / 24;
-	      F = 1 - A * Ee * Ee / 2 - D * Ee * Ee * Ee / 6;
-	      psi = Math.asin(this.sin_p12 * Math.cos(Ee) + this.cos_p12 * Math.sin(Ee) * cosAz);
-	      lon = adjust_lon(this.long0 + Math.asin(Math.sin(Az) * Math.sin(Ee) / Math.cos(psi)));
-	      lat = Math.atan((1 - this.es * F * this.sin_p12 / Math.sin(psi)) * Math.tan(psi) / (1 - this.es));
-	      p.x = lon;
-	      p.y = lat;
-	      return p;
-	    }
-	  }
-
-	};
-	exports.names = ["Azimuthal_Equidistant", "aeqd"];
-
-
-/***/ }),
-/* 83 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var proj4 = __webpack_require__(12);
-
-	var throttle = __webpack_require__(84);
-	var mockVGL = __webpack_require__(85);
-	var DistanceGrid = __webpack_require__(204);
-	var ClusterGroup = __webpack_require__(205);
-
-	var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-
-	var svgForeignObject = '<svg xmlns="http://www.w3.org/2000/svg">' +
-	  '<foreignObject width="100%" height="100%">' +
-	  '</foreignObject>' +
-	  '</svg>';
-
-	var m_timingData = {},
-	    m_timingKeepRecent = 200,
-	    m_threshold = 15,
-	    m_originalRequestAnimationFrame,
-	    m_htmlToImageSupport;
-
-	/**
-	 * @typedef {object} geo.util.cssColorConversionRecord
-	 * @property {string} name The name of the color conversion.
-	 * @property {RegEx} regex A regex that, if it matches the color string, will
-	 *      cause the process function to be invoked.
-	 * @property {function} process A function that takes (`color`, `match`) with
-	 *      the original color string and the results of matching the regex using
-	 *      the regex's `exec` function.  It outputs a {@link geo.geoColorObject}
-	 *      color object or the original color string if there is still a parsing
-	 *      failure.
-	 */
-
-	/**
-	 * Takes a variable number of arguments and returns the first numeric value
-	 * it finds.
-	 *
-	 * @param {...*} var_args Any number of arguments.
-	 * @returns {number} The first numeric argument, or `undefined` if there are no
-	 *      numeric arguments.
-	 * @private
-	 */
-	function setNumeric() {
-	  var i;
-	  for (i = 0; i < arguments.length; i += 1) {
-	    if (isFinite(arguments[i])) {
-	      return arguments[i];
-	    }
-	  }
-	}
-
-	/**
-	 * Contains utility classes and methods used by geojs.
-	 * @namespace geo.util
-	 */
-	var util = module.exports = {
-	  DistanceGrid: DistanceGrid,
-	  ClusterGroup: ClusterGroup,
-
-	  /**
-	   * Check if a point is inside of a polygon.
-	   * Algorithm description:
-	   *   http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
-	   * The point and polygon must be in the same coordinate system.
-	   *
-	   * @param {geo.point2D} point The test point.
-	   * @param {geo.point2D[]} outer The outer boundary of the polygon.
-	   * @param {Array.<geo.point2D[]>} [inner] A list of inner boundaries
-	   *    (holes).
-	   * @param {object} [range] If specified, this is the extent of the outer
-	   *    polygon and is used for early detection.
-	   * @param {geo.point2D} range.min The minimum value of coordinates in
-	   *    the outer polygon.
-	   * @param {geo.point2D} range.max The maximum value of coordinates in
-	   *    the outer polygon.
-	   * @returns {boolean} `true` if the point is inside or on the border of the
-	   *    polygon.
-	   * @memberof geo.util
-	   */
-	  pointInPolygon: function (point, outer, inner, range) {
-	    var inside = false, n = outer.length, i, j;
-
-	    if (range && range.min && range.max) {
-	      if (point.x < range.min.x || point.y < range.min.y ||
-	          point.x > range.max.x || point.y > range.max.y) {
-	        return;
-	      }
-	    }
-
-	    if (n < 3) {
-	      // we need 3 coordinates for this to make sense
-	      return false;
-	    }
-
-	    for (i = 0, j = n - 1; i < n; j = i, i += 1) {
-	      if (((outer[i].y > point.y) !== (outer[j].y > point.y)) &&
-	          (point.x < (outer[j].x - outer[i].x) *
-	          (point.y - outer[i].y) / (outer[j].y - outer[i].y) + outer[i].x)) {
-	        inside = !inside;
-	      }
-	    }
-
-	    if (inner && inside) {
-	      (inner || []).forEach(function (hole) {
-	        inside = inside && !util.pointInPolygon(point, hole);
-	      });
-	    }
-
-	    return inside;
-	  },
-
-	  /**
-	   * Return a point in the basis of the triangle.  If the point is located on
-	   * a vertex of the triangle, it will be at `vert0`: (0, 0), `vert1`:
-	   * (1, 0), `vert2`: (0, 1).  If it is within the triangle, its coordinates
-	   * will be 0 <= x <= 1, 0 <= y <= 1, x + y <= 1.  The point and vertices
-	   * must be in the same coordinate system.
-	   *
-	   * @param {geo.point2D} point The point to convert.
-	   * @param {geo.point2D} vert0 Vertex 0 of the triangle.
-	   * @param {geo.point2D} vert1 Vertex 1 (x direction) of the triangle.
-	   * @param {geo.point2D} vert2 Vertex 2 (y direction) of the triangle.
-	   * @returns {geo.point2D} The point in the triangle basis, or `undefined`
-	   *    if the triangle is degenerate.
-	   * @memberof geo.util
-	   */
-	  pointTo2DTriangleBasis: function (point, vert0, vert1, vert2) {
-	    var a = vert1.x - vert0.x,
-	        b = vert2.x - vert0.x,
-	        c = vert1.y - vert0.y,
-	        d = vert2.y - vert0.y,
-	        x = point.x - vert0.x,
-	        y = point.y - vert0.y,
-	        det = a * d - b * c;
-	    if (det) {
-	      return {x: (x * d - y * b) / det, y: (x * -c + y * a) / det};
-	    }
-	  },
-
-	  /**
-	   * Check if an object an HTML Image element that is fully loaded.
-	   *
-	   * @param {object} img An object that might be an HTML Image element.
-	   * @param {boolean} [allowFailedImage] If `true`, an image element that has
-	   *     a source and has failed to load is also considered 'ready' in the
-	   *     sense that it isn't expected to change to a better state.
-	   * @returns {boolean} `true` if this is an image that is ready.
-	   * @memberof geo.util
-	   */
-	  isReadyImage: function (img, allowFailedImage) {
-	    if (img instanceof Image && img.complete && img.src) {
-	      if ((img.naturalWidth && img.naturalHeight) || allowFailedImage) {
-	        return true;
-	      }
-	    }
-	    return false;
-	  },
-
-	  /**
-	   * Check if an object an HTMLVideoElement element that is loaded.
-	   *
-	   * @param {object} vid An object that might be an HTMLVideoElement.
-	   * @param {boolean} [allowFailedVideo] If `true`, an viedo element that has
-	   *     a source and has failed to load is also considered 'ready' in the
-	   *     sense that it isn't expected to change to a better state.
-	   * @returns {boolean} `true` if this is a video that is ready.
-	   * @memberof geo.util
-	   */
-	  isReadyVideo: function (vid, allowFailedVideo) {
-	    if (vid instanceof HTMLVideoElement && vid.src &&
-	        vid.HAVE_CURRENT_DATA !== undefined) {
-	      if ((vid.videoWidth && vid.videoHeight && vid.readyState >= vid.HAVE_CURRENT_DATA) ||
-	          (allowFailedVideo && vid.error)) {
-	        return true;
-	      }
-	    }
-	    return false;
-	  },
-
-	  /**
-	   * Test if an object is a function.
-	   *
-	   * @param {object} f An object that might be a function.
-	   * @returns {boolean} `true` if the object is a function.
-	   * @memberof geo.util
-	   */
-	  isFunction: function (f) {
-	    return typeof f === 'function';
-	  },
-
-	  /**
-	   * Return a function.  If the supplied object is a function, return it.
-	   * Otherwise, return a function that returns the argument.
-	   *
-	   * @param {object} f An object that might be a function.
-	   * @returns {function} A function.  Either `f` or a function that returns
-	   *    `f`.
-	   * @memberof geo.util
-	   */
-	  ensureFunction: function (f) {
-	    if (util.isFunction(f)) {
-	      return f;
-	    } else {
-	      return function () { return f; };
-	    }
-	  },
-
-	  /**
-	   * Check if a value coerces to a number that is finite, not a NaN, and not
-	   * `null`, `false`, or the empty string.
-	   *
-	   * @param {object} val The value to check.
-	   * @returns {boolean} True if `val` is a non-null, non-false, finite number.
-	   */
-	  isNonNullFinite: function (val) {
-	    return isFinite(val) && val !== null && val !== false && val !== '';
-	  },
-
-	  /**
-	   * Return a random string of length n || 8.  The string consists of
-	   * mixed-case ASCII alphanumerics.
-	   *
-	   * @param {number} [n=8] The length of the string to return.
-	   * @returns {string} A string of random characters.
-	   * @memberof geo.util
-	   */
-	  randomString: function (n) {
-	    var s, i, r;
-	    n = n || 8;
-	    s = '';
-	    for (i = 0; i < n; i += 1) {
-	      r = Math.floor(Math.random() * chars.length);
-	      s += chars.substring(r, r + 1);
-	    }
-	    return s;
-	  },
-
-	  /**
-	   * Convert a color to a standard rgb object.  Allowed inputs:
-	   *   - rgb object with optional 'a' (alpha) value.
-	   *   - css color name
-	   *   - #rrggbb, #rrggbbaa, #rgb, #rgba hexadecimal colors
-	   *   - rgb(), rgba(), hsl(), and hsla() css colors
-	   *   - transparent
-	   * The output object always contains r, g, b on a scale of [0-1].  If an
-	   * alpha value is specified, the output will also contain an 'a' value on a
-	   * scale of [0-1].  Objects already in rgb format are not checked to make
-	   * sure that all parameters are in the range of [0-1], but string inputs
-	   * are so validated.
-	   *
-	   * @param {geo.geoColor} color Any valid color input.
-	   * @returns {geo.geoColorObject} An rgb color object, possibly with an 'a'
-	   *    value.  If the input cannot be converted to a valid color, the input
-	   *    value is returned.
-	   * @memberof geo.util
-	   */
-	  convertColor: function (color) {
-	    if (color === undefined || color === null || (color.r !== undefined &&
-	        color.g !== undefined && color.b !== undefined)) {
-	      return color;
-	    }
-	    var opacity;
-	    if (typeof color === 'string') {
-	      var lowerColor = color.toLowerCase();
-	      if (util.cssColors.hasOwnProperty(lowerColor)) {
-	        color = util.cssColors[lowerColor];
-	      } else if (color.charAt(0) === '#') {
-	        if (color.length === 4 || color.length === 5) {
-	          /* interpret values of the form #rgb as #rrggbb and #rgba as
-	           * #rrggbbaa */
-	          if (color.length === 5) {
-	            opacity = parseInt(color.slice(4), 16) / 0xf;
-	          }
-	          color = parseInt(color.slice(1, 4), 16);
-	          color = (color & 0xf00) * 0x1100 + (color & 0xf0) * 0x110 + (color & 0xf) * 0x11;
-	        } else if (color.length === 7 || color.length === 9) {
-	          if (color.length === 9) {
-	            opacity = parseInt(color.slice(7), 16) / 0xff;
-	          }
-	          color = parseInt(color.slice(1, 7), 16);
-	        }
-	      } else if (lowerColor === 'transparent') {
-	        opacity = color = 0;
-	      } else if (lowerColor.indexOf('(') >= 0) {
-	        for (var idx = 0; idx < util.cssColorConversions.length; idx += 1) {
-	          var match = util.cssColorConversions[idx].regex.exec(lowerColor);
-	          if (match) {
-	            return util.cssColorConversions[idx].process(lowerColor, match);
-	          }
-	        }
-	      }
-	    }
-	    if (isFinite(color)) {
-	      color = {
-	        r: ((color & 0xff0000) >> 16) / 255,
-	        g: ((color & 0xff00) >> 8) / 255,
-	        b: ((color & 0xff)) / 255
-	      };
-	    }
-	    if (opacity !== undefined && color && color.r !== undefined) {
-	      color.a = opacity;
-	    }
-	    return color;
-	  },
-
-	  /**
-	   * Convert a color (possibly with opacity) and an optional opacity value to
-	   * a color object that always has opacity.  The opacity is guaranteed to be
-	   * within [0-1].  A valid color object is always returned.
-	   *
-	   * @param {geo.geoColor} [color] Any valid color input.  If an invalid value
-	   *    or no value is supplied, the `defaultColor` is used.
-	   * @param {number} [opacity=1] A value from [0-1].  This is multipled with
-	   *    the opacity from `color`.
-	   * @param {geo.geoColorObject} [defaultColor={r: 0, g: 0, b: 0}] The color
-	   *    to use if an invalid color is supplied.
-	   * @returns {geo.geoColorObject} An rgba color object.
-	   * @memberof geo.util
-	   */
-	  convertColorAndOpacity: function (color, opacity, defaultColor) {
-	    color = util.convertColor(color);
-	    if (!color || color.r === undefined || color.g === undefined || color.b === undefined) {
-	      color = util.convertColor(defaultColor || {r: 0, g: 0, b: 0});
-	    }
-	    if (!color || color.r === undefined || color.g === undefined || color.b === undefined) {
-	      color = {r: 0, g: 0, b: 0};
-	    }
-	    color = {
-	      r: isFinite(color.r) && color.r >= 0 ? (color.r <= 1 ? +color.r : 1) : 0,
-	      g: isFinite(color.g) && color.g >= 0 ? (color.g <= 1 ? +color.g : 1) : 0,
-	      b: isFinite(color.b) && color.b >= 0 ? (color.b <= 1 ? +color.b : 1) : 0,
-	      a: util.isNonNullFinite(color.a) && color.a >= 0 && color.a < 1 ? +color.a : 1
-	    };
-	    if (util.isNonNullFinite(opacity) && opacity < 1) {
-	      color.a = opacity <= 0 ? 0 : color.a * opacity;
-	    }
-	    return color;
-	  },
-
-	  /**
-	   * Convert a color to a six or eight digit hex value prefixed with #.
-	   *
-	   * @param {geo.geoColorObject} color The color object to convert.
-	   * @param {boolean} [allowAlpha] If truthy and `color` has a defined `a`
-	   *    value, include the alpha channel in the output.  If `'needed'`, only
-	   *    include the alpha channel if it is set and not 1.
-	   * @returns {string} A color string.
-	   * @memberof geo.util
-	   */
-	  convertColorToHex: function (color, allowAlpha) {
-	    var rgb = util.convertColor(color), value;
-	    if (!rgb.r && !rgb.g && !rgb.b) {
-	      value = '#000000';
-	    } else {
-	      value = '#' + ((1 << 24) + (Math.round(rgb.r * 255) << 16) +
-	                     (Math.round(rgb.g * 255) << 8) +
-	                      Math.round(rgb.b * 255)).toString(16).slice(1);
-	    }
-	    if (rgb.a !== undefined && allowAlpha && (rgb.a < 1 || allowAlpha !== 'needed')) {
-	      value += (256 + Math.round(rgb.a * 255)).toString(16).slice(1);
-	    }
-	    return value;
-	  },
-	  /**
-	   * Convert a color to a css rgba() value.
-	   *
-	   * @param {geo.geoColorObject} color The color object to convert.
-	   * @returns {string} A color string.
-	   * @memberof geo.util
-	   */
-	  convertColorToRGBA: function (color) {
-	    var rgb = util.convertColor(color);
-	    if (!rgb) {
-	      rgb = {r: 0, g: 0, b: 0};
-	    }
-	    if (!util.isNonNullFinite(rgb.a) || rgb.a > 1) {
-	      rgb.a = 1;
-	    }
-	    return 'rgba(' + Math.round(rgb.r * 255) + ', ' + Math.round(rgb.g * 255) +
-	           ', ' + Math.round(rgb.b * 255) + ', ' + +((+rgb.a).toFixed(5)) + ')';
-	  },
-
-	  /**
-	   * Normalize a coordinate object into {@link geo.geoPosition} form.  The
-	   * input can be a 2 or 3 element array or an object with a variety of
-	   * properties.
-	   *
-	   * @param {object|array} p The point to convert.
-	   * @returns {geo.geoPosition} The point as an object with `x`, `y`, and `z`
-	   *    properties.
-	   * @memberof geo.util
-	   */
-	  normalizeCoordinates: function (p) {
-	    p = p || {};
-	    if (Array.isArray(p)) {
-	      return {
-	        x: p[0],
-	        y: p[1],
-	        z: p[2] || 0
-	      };
-	    }
-	    return {
-	      x: setNumeric(
-	        p.x,
-	        p.longitude,
-	        p.lng,
-	        p.lon,
-	        0
-	      ),
-	      y: setNumeric(
-	        p.y,
-	        p.latitude,
-	        p.lat,
-	        0
-	      ),
-	      z: setNumeric(
-	        p.z,
-	        p.elevation,
-	        p.elev,
-	        p.height,
-	        0
-	      )
-	    };
-	  },
-
-	  /**
-	   * Create an integer array contains elements from one integer to another
-	   * integer.
-	   *
-	   * @param {number} start The start integer.
-	   * @param {number} end The end integer.
-	   * @param {number} [step=1] The step.
-	   * @returns {number[]} An array of integers.
-	   */
-	  range: function (start, end, step) {
-	    step = step || 1;
-	    var results = [];
-	    for (var i = start; i <= end; i += step) {
-	      results.push(i);
-	    }
-	    return results;
-	  },
-
-	  /**
-	   * Compare two arrays and return if their contents are equal.
-	   * @param {array} a1 First array to compare.
-	   * @param {array} a2 Second array to compare.
-	   * @returns {boolean} `true` if the contents of the arrays are equal.
-	   * @memberof geo.util
-	   */
-	  compareArrays: function (a1, a2) {
-	    return (a1.length === a2.length && a1.every(function (el, idx) {
-	      return el === a2[idx];
-	    }));
-	  },
-
-	  /**
-	   * Create a `vec3` that is always an array.  This should only be used if it
-	   * will not be used in a WebGL context.  Plain arrays usually use 64-bit
-	   * float values, whereas `vec3` defaults to 32-bit floats.
-	   *
-	   * @returns {array} Zeroed-out vec3 compatible array.
-	   * @memberof geo.util
-	   */
-	  vec3AsArray: function () {
-	    return [0, 0, 0];
-	  },
-
-	  /**
-	   * Create a `mat3` that is always an array.  This should only be used if it
-	   * will not be used in a WebGL context.  Plain arrays usually use 64-bit
-	   * float values, whereas `mat3` defaults to 32-bit floats.
-	   *
-	   * @returns {array} Identity `mat3` compatible array.
-	   * @memberof geo.util
-	   */
-	  mat3AsArray: function () {
-	    return [
-	      1, 0, 0,
-	      0, 1, 0,
-	      0, 0, 1
-	    ];
-	  },
-
-	  /**
-	   * Create a `mat4` that is always an array.  This should only be used if it
-	   * will not be used in a WebGL context.  Plain arrays usually use 64-bit
-	   * float values, whereas `mat4` defaults to 32-bit floats.
-	   *
-	   * @returns {array} Identity `mat4` compatible array.
-	   * @memberof geo.util
-	   */
-	  mat4AsArray: function () {
-	    return [
-	      1, 0, 0, 0,
-	      0, 1, 0, 0,
-	      0, 0, 1, 0,
-	      0, 0, 0, 1
-	    ];
-	  },
-
-	  /**
-	   * Get a buffer for a vgl geometry source.  If a buffer already exists and
-	   * is the correct size, return it.  Otherwise, allocate a new buffer; any
-	   * data in an old buffer is discarded.
-	   *
-	   * @param {vgl.geometryData} geom The geometry to reference and modify.
-	   * @param {string} srcName The name of the source.
-	   * @param {number} len The number of elements for the array.
-	   * @returns {Float32Array} A buffer for the named source.
-	   * @memberof geo.util
-	   */
-	  getGeomBuffer: function (geom, srcName, len) {
-	    var src = geom.sourceByName(srcName), data;
-
-	    data = src.data();
-	    if (data instanceof Float32Array && data.length === len) {
-	      return data;
-	    }
-	    data = new Float32Array(len);
-	    src.setData(data);
-	    return data;
-	  },
-
-	  /**
-	   * Ensure that the input and modifiers properties of all actions are
-	   * objects, not plain strings.
-	   *
-	   * @param {geo.actionRecord[]} actions An array of actions to adjust as
-	   *    needed.
-	   * @memberof geo.util
-	   */
-	  adjustActions: function (actions) {
-	    var action, i;
-	    for (i = 0; i < actions.length; i += 1) {
-	      action = actions[i];
-	      if ($.type(action.input) === 'string') {
-	        var actionEvents = {};
-	        actionEvents[action.input] = true;
-	        action.input = actionEvents;
-	      }
-	      if (!action.modifiers) {
-	        action.modifiers = {};
-	      }
-	      if ($.type(action.modifiers) === 'string') {
-	        var actionModifiers = {};
-	        actionModifiers[action.modifiers] = true;
-	        action.modifiers = actionModifiers;
-	      }
-	    }
-	  },
-
-	  /**
-	   * Add an action to the list of handled actions.
-	   *
-	   * @param {geo.actionRecord[]} actions An array of actions to adjust as
-	   *    needed.
-	   * @param {geo.actionRecord} action An object defining the action.  Use
-	   *    `action`, `name`, and `owner` to make this entry distinct if it will
-	   *    need to be removed later.
-	   * @param {boolean} toEnd The action is added at the beginning of the
-	   *    actions list unless `toEnd` is `true`.  Earlier actions prevent later
-	   *    actions with the similar input and modifiers.
-	   * @memberof geo.util
-	   */
-	  addAction: function (actions, action, toEnd) {
-	    if (toEnd) {
-	      actions.push(action);
-	    } else {
-	      actions.unshift(action);
-	    }
-	    util.adjustActions(actions);
-	  },
-
-	  /**
-	   * Check if an action is in the actions list.  An action matches if the
-	   * `action`, `name`, and `owner` match.  A `null` or `undefined` value will
-	   * match all actions.  If using a {@link geo.actionRecord} object, this is
-	   * the same as passing (`action.action`, `action.name`, `action.owner`).
-	   *
-	   * @param {geo.actionRecord[]} actions An array of actions to search.
-	   * @param {geo.actionRecord|string} action Either an action object or the
-	   *    name of an action.
-	   * @param {string} [name] Optional name associated with the action.
-	   * @param {string} [owner] Optional owner associated with the action.
-	   * @returns {geo.actionRecord?} The first matching action or `null`.
-	   * @memberof geo.util
-	   */
-	  hasAction: function (actions, action, name, owner) {
-	    if (action && action.action) {
-	      name = action.name;
-	      owner = action.owner;
-	      action = action.action;
-	    }
-	    for (var i = 0; i < actions.length; i += 1) {
-	      if ((!action || actions[i].action === action) &&
-	          (!name || actions[i].name === name) &&
-	          (!owner || actions[i].owner === owner)) {
-	        return actions[i];
-	      }
-	    }
-	    return null;
-	  },
-
-	  /**
-	   * Remove all matching actions.  Actions are matched as with `hasAction`.
-	   *
-	   * @param {geo.actionRecord[]} actions An array of actions to adjust as
-	   *    needed.
-	   * @param {geo.actionRecord|string} action Either an action object or the
-	   *    name of an action.
-	   * @param {string} [name] Optional name associated with the action.
-	   * @param {string} [owner] Optional owner associated with the action.
-	   * @returns {number} The number of actions that were removed.
-	   * @memberof geo.util
-	   */
-	  removeAction: function (actions, action, name, owner) {
-	    var found, removed = 0;
-
-	    do {
-	      found = util.hasAction(actions, action, name, owner);
-	      if (found) {
-	        actions.splice($.inArray(found, actions), 1);
-	        removed += 1;
-	      }
-	    } while (found);
-	    return removed;
-	  },
-
-	  /**
-	   * Determine if the current inputs and modifiers match a known action.
-	   *
-	   * @param {object} inputs Aan object where each input that is currently
-	   *    active is truthy.  Common inputs are `left`, `right`, `middle`,
-	   *    `wheel`, `pan`, `rotate`.
-	   * @param {object} modifiers An object where each currently applied
-	   *    modifier is truthy.  Common modifiers are `shift`, `ctrl`, `alt`,
-	   *    `meta`.
-	   * @param {geo.actionRecord[]} actions A list of actions to compare to the
-	   *    inputs and modifiers.  The first action that matches will be
-	   *    returned.
-	   * @returns {geo.actionRecord} A matching action or `undefined`.
-	   * @memberof geo.util
-	   */
-	  actionMatch: function (inputs, modifiers, actions) {
-	    var matched;
-
-	    /* actions must have already been processed by adjustActions */
-	    if (actions.some(function (action) {
-	      for (var input in action.input) {
-	        if (action.input.hasOwnProperty(input)) {
-	          if ((action.input[input] === false && inputs[input]) ||
-	              (action.input[input] && !inputs[input])) {
-	            return false;
-	          }
-	        }
-	      }
-	      for (var modifier in action.modifiers) {
-	        if (action.modifiers.hasOwnProperty(modifier)) {
-	          if ((action.modifiers[modifier] === false && modifiers[modifier]) ||
-	              (action.modifiers[modifier] && !modifiers[modifier])) {
-	            return false;
-	          }
-	        }
-	      }
-	      matched = action;
-	      return true;
-	    })) {
-	      return matched;
-	    }
-	  },
-
-	  /**
-	   * Return recommended defaults for map parameters and osm or tile layer
-	   * paramaters where the expected intent is to use the map in pixel
-	   * coordinates (upper left is (0, 0), lower right is (`width`, `height`).
-	   *
-	   * @example <caption>The returned objects can be modified or
-	   *    extended.</caption>
-	   * var results = pixelCoordinateParams('#map', 10000, 9000);
-	   * var map = geo.map($.extend(results.map, {clampZoom: false}));
-	   * map.createLayer('osm', results.layer);
-	   *
-	   * @param {string} [node] DOM selector for the map container.
-	   * @param {number} width Width of the whole map contents in pixels.
-	   * @param {number} height Height of the whole map contents in pixels.
-	   * @param {number} [tileWidth] If an osm or tile layer is going to be used,
-	   *    the width of a tile.
-	   * @param {number} [tileHeight] If an osm or tile layer is going to be used,
-	   *    the height of a tile.
-	   * @returns {object} An object with `map` and `layer` properties.  `map` is
-	   *    an object that can be passed to {@link geo.map}, and `layer` is an
-	   *    object that can be passed to `map.createLayer`.
-	   * @memberof geo.util
-	   */
-	  pixelCoordinateParams: function (node, width, height, tileWidth, tileHeight) {
-	    var mapW, mapH, tiled;
-	    if (node) {
-	      node = $(node);
-	      mapW = node.innerWidth();
-	      mapH = node.innerHeight();
-	    }
-	    tileWidth = tileWidth || width;
-	    tileHeight = tileHeight || height;
-	    tiled = (tileWidth !== width || tileHeight !== height);
-	    var minLevel = Math.min(0, Math.floor(Math.log(Math.min(
-	          (mapW || tileWidth) / tileWidth,
-	          (mapH || tileHeight) / tileHeight)) / Math.log(2))),
-	        maxLevel = Math.ceil(Math.log(Math.max(
-	          width / tileWidth,
-	          height / tileHeight)) / Math.log(2));
-	    var mapParams = {
-	      node: node,
-	      ingcs: '+proj=longlat +axis=esu',
-	      gcs: '+proj=longlat +axis=enu',
-	      maxBounds: {left: 0, top: 0, right: width, bottom: height},
-	      unitsPerPixel: Math.pow(2, maxLevel),
-	      center: {x: width / 2, y: height / 2},
-	      min: minLevel,
-	      max: maxLevel,
-	      zoom: minLevel,
-	      clampBoundsX: true,
-	      clampBoundsY: true,
-	      clampZoom: true
-	    };
-	    var layerParams = {
-	      maxLevel: maxLevel,
-	      wrapX: false,
-	      wrapY: false,
-	      tileOffset: function () {
-	        return {x: 0, y: 0};
-	      },
-	      attribution: '',
-	      tileWidth: tileWidth,
-	      tileHeight: tileHeight,
-	      tileRounding: Math.ceil,
-	      tilesAtZoom: tiled ? function (level) {
-	        var scale = Math.pow(2, maxLevel - level);
-	        return {
-	          x: Math.ceil(width / tileWidth / scale),
-	          y: Math.ceil(height / tileHeight / scale)
-	        };
-	      } : undefined,
-	      tilesMaxBounds: tiled ? function (level) {
-	        var scale = Math.pow(2, maxLevel - level);
-	        return {
-	          x: Math.floor(width / scale),
-	          y: Math.floor(height / scale)
-	        };
-	      } : undefined
-	    };
-	    return {map: mapParams, layer: layerParams};
-	  },
-
-	  /**
-	   * Return the coordinate associated with the center of the perimeter formed
-	   * from a list of points.  This averages all of the vertices in the perimeter
-	   * weighted by the line length on either side of each point.  Functionally,
-	   * this is the same as the average of all the points of the lines of the
-	   * perimeter.
-	   *
-	   * @param {geo.geoPosition[]} coor An array of coordinates.
-	   * @returns {geo.geoPosition|undefined} The position for the center, or
-	   *    `undefined` if no such position exists.
-	   */
-	  centerFromPerimeter: function (coor) {
-	    var position, p0, p1, w, sumw, i;
-	    if (!coor || !coor.length) {
-	      return;
-	    }
-	    if (coor.length === 1) {
-	      return {x: coor[0].x, y: coor[0].y};
-	    }
-	    position = {x: 0, y: 0};
-	    sumw = 0;
-	    p0 = coor[coor.length - 1];
-	    for (i = 0; i < coor.length; i += 1) {
-	      p1 = p0;
-	      p0 = coor[i];
-	      w = Math.sqrt(Math.pow(p1.x - p0.x, 2) + Math.pow(p1.y - p0.y, 2));
-	      position.x += (p0.x + p1.x) * w;
-	      position.y += (p0.y + p1.y) * w;
-	      sumw += 2 * w;
-	    }
-	    position.x /= sumw;
-	    position.y /= sumw;
-	    // return a copy of p0 if all points are the same
-	    return sumw ? position : {x: p0.x, y: p0.y};
-	  },
-
-	  /**
-	   * Get the square of the Euclidean 2D distance between two points.
-	   *
-	   * @param {geo.geoPosition} pt1 The first point.
-	   * @param {geo.geoPosition} pt2 The second point.
-	   * @returns {number} The distance squared.
-	   */
-	  distance2dSquared: function (pt1, pt2) {
-	    var dx = pt1.x - pt2.x,
-	        dy = pt1.y - pt2.y;
-	    return dx * dx + dy * dy;
-	  },
-
-	  /**
-	   * Get the square of the Euclidean 2D distance between a point and a line
-	   * segment.
-	   *
-	   * @param {geo.geoPosition} pt The point.
-	   * @param {geo.geoPosition} line1 One end of the line.
-	   * @param {geo.geoPosition} line2 The other end of the line.
-	   * @returns {number} The distance squared.
-	   */
-	  distance2dToLineSquared: function (pt, line1, line2) {
-	    var dx = line2.x - line1.x,
-	        dy = line2.y - line1.y,
-	        // we could get the line length from the distance2dSquared function,
-	        // but since we need dx and dy in this function, it is faster to just
-	        // compute it here.
-	        lengthSquared = dx * dx + dy * dy,
-	        t = 0;
-	    if (lengthSquared) {
-	      t = ((pt.x - line1.x) * dx + (pt.y - line1.y) * dy) / lengthSquared;
-	      t = Math.max(0, Math.min(1, t));
-	    }
-	    return util.distance2dSquared(pt, {
-	      x: line1.x + t * dx,
-	      y: line1.y + t * dy
-	    });
-	  },
-
-	  /**
-	   * Get twice the signed area of a 2d triangle.
-	   *
-	   * @param {geo.geoPosition} pt1 A vertex.
-	   * @param {geo.geoPosition} pt2 A vertex.
-	   * @param {geo.geoPosition} pt3 A vertex.
-	   * @returns {number} Twice the signed area.
-	   */
-	  triangleTwiceSignedArea2d: function (pt1, pt2, pt3) {
-	    return (pt2.y - pt1.y) * (pt3.x - pt2.x) - (pt2.x - pt1.x) * (pt3.y - pt2.y);
-	  },
-
-	  /**
-	   * Determine if two line segments cross.  They are not considered crossing if
-	   * they share a vertex.  They are crossing if either of one segment's
-	   * vertices are colinear with the other segment.
-	   *
-	   * @param {geo.geoPosition} seg1pt1 One endpoint of the first segment.
-	   * @param {geo.geoPosition} seg1pt2 The other endpoint of the first segment.
-	   * @param {geo.geoPosition} seg2pt1 One endpoint of the second segment.
-	   * @param {geo.geoPosition} seg2pt2 The other endpoint of the second segment.
-	   * @returns {boolean} True if the segments cross.
-	   */
-	  crossedLineSegments2d: function (seg1pt1, seg1pt2, seg2pt1, seg2pt2) {
-	    /* If the segments don't have any overlap in x or y, they can't cross */
-	    if ((seg1pt1.x > seg2pt1.x && seg1pt1.x > seg2pt2.x &&
-	         seg1pt2.x > seg2pt1.x && seg1pt2.x > seg2pt2.x) ||
-	        (seg1pt1.x < seg2pt1.x && seg1pt1.x < seg2pt2.x &&
-	         seg1pt2.x < seg2pt1.x && seg1pt2.x < seg2pt2.x) ||
-	        (seg1pt1.y > seg2pt1.y && seg1pt1.y > seg2pt2.y &&
-	         seg1pt2.y > seg2pt1.y && seg1pt2.y > seg2pt2.y) ||
-	        (seg1pt1.y < seg2pt1.y && seg1pt1.y < seg2pt2.y &&
-	         seg1pt2.y < seg2pt1.y && seg1pt2.y < seg2pt2.y)) {
-	      return false;
-	    }
-	    /* If any vertex is in common, it is not considered crossing */
-	    if ((seg1pt1.x === seg2pt1.x && seg1pt1.y === seg2pt1.y) ||
-	        (seg1pt1.x === seg2pt2.x && seg1pt1.y === seg2pt2.y) ||
-	        (seg1pt2.x === seg2pt1.x && seg1pt2.y === seg2pt1.y) ||
-	        (seg1pt2.x === seg2pt2.x && seg1pt2.y === seg2pt2.y)) {
-	      return false;
-	    }
-	    /* If the lines cross, the signed area of the triangles formed between one
-	     * segment and the other's vertices will have different signs.  By using
-	     * > 0, colinear points are crossing. */
-	    if (util.triangleTwiceSignedArea2d(seg1pt1, seg1pt2, seg2pt1) *
-	        util.triangleTwiceSignedArea2d(seg1pt1, seg1pt2, seg2pt2) > 0 ||
-	        util.triangleTwiceSignedArea2d(seg2pt1, seg2pt2, seg1pt1) *
-	        util.triangleTwiceSignedArea2d(seg2pt1, seg2pt2, seg1pt2) > 0) {
-	      return false;
-	    }
-	    return true;
-	  },
-
-	  /**
-	   * Check if a line segment crosses any segment from a list of lines.  The
-	   * segment is considered crossing it it touches a line segment, unless that
-	   * line segment shares a vertex with the segment.
-	   *
-	   * @param {geo.geoPosition} pt1 One end of the line segment.
-	   * @param {geo.geoPosition} pt2 The other end of the line segment.
-	   * @param {Array.<geo.geoPosition[]>} lineList A list of open lines.  Each
-	   *    line is a list of vertices.  The line segment is checked against each
-	   *    segment of each line in this list.
-	   * @returns {boolean} True if the segment crosses any line segment.
-	   */
-	  segmentCrossesLineList2d: function (pt1, pt2, lineList) {
-	    var result = lineList.some(function (line) {
-	      return line.some(function (linePt, idx) {
-	        if (idx) {
-	          return util.crossedLineSegments2d(pt1, pt2, line[idx - 1], linePt);
-	        }
-	      });
-	    });
-	    return result;
-	  },
-
-	  /**
-	   * Remove vertices from a chain of 2d line segments so that it is simpler but
-	   * is close to the original overall shape within some tolerance limit.  This
-	   * is the Ramer–Douglas–Peucker algorithm.  The first and last points will
-	   * always remain the same for open lines.  For closed lines (polygons), this
-	   * picks an point that likely to be significant and then reduces it, possibly
-	   * returning a single point.
-	   *
-	   * @param {geo.geoPosition[]} pts A list of points forming the line or
-	   *    polygon.
-	   * @param {number} tolerance The maximum variation allowed.  A value of zero
-	   *    will only remove perfectly colinear points.
-	   * @param {boolean} [closed] If true, this is a polygon rather than an open
-	   *    line.  In this case, it is possible to get back a single point.
-	   * @param {Array.<geo.geoPosition[]>?} [noCrossLines] A falsy value to allow
-	   *    the resultant line to cross itself, an empty array (`[]`) to prevent
-	   *    self-crossing, or an array of line segments to prevent self-crossing
-	   *    and disallow crossing any line segment in the list.  Each entry in the
-	   *    list is an open line (with one segment less than the number of
-	   *    vertices).  If self-crossing is prohibited, the resultant point set
-	   *    might not be as simplified as it could be.
-	   * @returns {geo.geoPosition[]} The new point set.
-	   */
-	  rdpLineSimplify: function (pts, tolerance, closed, noCrossLines) {
-	    if (pts.length <= 2 || tolerance < 0) {
-	      return pts;
-	    }
-	    var i, distSq, maxDistSq = -1, index, toleranceSq = tolerance * tolerance;
-	    if (closed) {
-	      /* If this is closed, find the point that is furthest from the first
-	       * point.  ideally, one would find a point that is guaranteed to be on
-	       * the diameter of the convex hull, but doing so is an O(n^2) operation,
-	       * whereas this is sufficient and only O(n).  The chosen point is
-	       * duplicated at the start and end of the chain. */
-	      for (i = 1; i < pts.length; i += 1) {
-	        distSq = util.distance2dSquared(pts[0], pts[i]);
-	        if (distSq > maxDistSq) {
-	          maxDistSq = distSq;
-	          index = i;
-	        }
-	      }
-	      /* Points could be on any side of the start point, so if all points are
-	       * within 1/2 of the tolerance of the start point, we know all points are
-	       * within the tolerance of each other and therefore this polygon or
-	       * closed line can be simplified to a point. */
-	      if (maxDistSq * 4 <= toleranceSq) {
-	        return pts.slice(index, index + 1);
-	      }
-	      pts = pts.slice(index).concat(pts.slice(0, index + 1));
-	      pts = util.rdpLineSimplify(pts, tolerance, false, noCrossLines);
-	      /* Removed the duplicated first point */
-	      pts.splice(pts.length - 1);
-	      return pts;
-	    }
-	    for (i = 1; i < pts.length - 1; i += 1) {
-	      distSq = util.distance2dToLineSquared(pts[i], pts[0], pts[pts.length - 1]);
-	      if (distSq > maxDistSq) {
-	        maxDistSq = distSq;
-	        index = i;
-	      }
-	    }
-	    /* We can collapse this to a single line if it is within the tolerance and
-	     * we are either allowed to self-cross or it does not self-cross the rest
-	     * of the line. */
-	    if (maxDistSq <= toleranceSq && (!noCrossLines || !util.segmentCrossesLineList2d(
-	         pts[0], pts[pts.length - 1], noCrossLines))) {
-	      return [pts[0], pts[pts.length - 1]];
-	    }
-	    var left = pts.slice(0, index + 1),
-	        right = pts.slice(index),
-	        leftSide = util.rdpLineSimplify(
-	            left, tolerance, false,
-	            noCrossLines ? noCrossLines.concat([right]) : null),
-	        rightSide = util.rdpLineSimplify(
-	            right, tolerance, false,
-	            noCrossLines ? noCrossLines.concat([left]) : null);
-	    return leftSide.slice(0, leftSide.length - 1).concat(rightSide);
-	  },
-
-	  /**
-	   * Escape any character in a string that has a code point >= 127.
-	   *
-	   * @param {string} text The string to escape.
-	   * @returns {string} The escaped string.
-	   * @memberof geo.util
-	   */
-	  escapeUnicodeHTML: function (text) {
-	    return text.replace(/./g, function (k) {
-	      var code = k.charCodeAt();
-	      if (code < 127) {
-	        return k;
-	      }
-	      return '&#' + code.toString(10) + ';';
-	    });
-	  },
-
-	  /**
-	   * Check svg image and html img tags.  If the source is set, load images
-	   * explicitly and convert them to local data:image references.
-	   *
-	   * @param {jQuery.selector} elem A jQuery selector or element set that may
-	   *    contain images.
-	   * @returns {jQuery.Deferred[]} A list of deferred objects that resolve
-	   *    when images are dereferenced.
-	   * @memberof geo.util
-	   */
-	  dereferenceElements: function (elem) {
-	    var deferList = [];
-
-	    $('img,image', elem).each(function () {
-	      var src = $(this);
-	      var key = src.is('image') ? 'href' : 'src';
-	      if (src.attr(key)) {
-	        var img = new Image();
-	        if (src.attr(key).substr(0, 4) === 'http' || src[0].crossOrigin) {
-	          img.crossOrigin = src[0].crossOrigin || 'anonymous';
-	        }
-	        var defer = $.Deferred();
-	        img.onload = function () {
-	          var cvs = document.createElement('canvas');
-	          cvs.width = img.naturalWidth;
-	          cvs.height = img.naturalHeight;
-	          cvs.getContext('2d').drawImage(img, 0, 0);
-	          src.attr(key, cvs.toDataURL('image/png'));
-	          if (src.attr(key).substr(0, 10) !== 'data:image') {
-	            src.remove();
-	          }
-	          defer.resolve();
-	        };
-	        img.onerror = function () {
-	          src.remove();
-	          defer.resolve();
-	        };
-	        img.src = src.attr(key);
-	        deferList.push(defer);
-	      }
-	    });
-	    return deferList;
-	  },
-
-	  dereferenceCssUrlsRegex: /url\(["']?(http[^)"']+|[^:)"']+)["']?\)/g,
-
-	  /**
-	   * Check css text.  Any url(http[s]...) references are dereferenced and
-	   * stored as local base64 urls.
-	   *
-	   * @param {string} css The css to parse for urls.
-	   * @param {jQuery.selector|DOMElement} styleElem The element that receivs
-	   *    the css text after dereferencing or the DOM element that has style
-	   *    that will be updated.
-	   * @param {jQuery.Deferred} styleDefer A Deferred to resolve once
-	   *    dereferencing is complete.
-	   * @param {string} [styleKey] If unset, styleElem is a header element.  If
-	   *    set, styleElem is a DOM element and the named style will be updated.
-	   * @param {string} [baseUrl] If present, this is the base for relative urls.
-	   * @memberof geo.util
-	   */
-	  dereferenceCssUrls: function (css, styleElem, styleDefer, styleKey, baseUrl) {
-	    var deferList = [],
-	        results = [];
-
-	    /* Remove comments to avoid dereferencing commented out sections.
-	     * To match across lines, use [^\0] rather than . */
-	    css = css.replace(/\/\*[^\0]*?\*\//g, '');
-	    /* reduce whitespace to make the css shorter */
-	    css = css.replace(/\r/g, '\n').replace(/\s+\n/g, '\n')
-	             .replace(/\n\s+/g, '\n').replace(/\n\n+/g, '\n');
-	    if (baseUrl) {
-	      var match = /(^[^?#]*)\/[^?#/]*([?#]|$)/g.exec(baseUrl);
-	      baseUrl = match && match[1] ? match[1] + '/' : null;
-	    }
-	    css.replace(util.dereferenceCssUrlsRegex, function (match, url) {
-	      var idx = deferList.length,
-	          defer = $.Deferred(),
-	          xhr = new XMLHttpRequest();
-	      deferList.push(defer);
-	      results.push('');
-
-	      if (/^[^/:][^:]*(\/|$)/g.exec(url) && baseUrl) {
-	        url = baseUrl + url;
-	      }
-	      xhr.open('GET', url, true);
-	      xhr.responseType = 'arraybuffer';
-	      xhr.onload = function () {
-	        if (this.status === 200) {
-	          var response = new Uint8Array(this.response),
-	              data = new Array(response.length),
-	              i;
-	          for (i = 0; i < response.length; i += 1) {
-	            data[i] = String.fromCharCode(response[i]);
-	          }
-	          data = data.join('');
-	          results[idx] = 'url(data:' + xhr.getResponseHeader('content-type') + ';base64,' + btoa(data) + ')';
-	          defer.resolve();
-	        }
-	      };
-	      // if this fails, resolve anyway
-	      xhr.onerror = defer.resolve;
-	      xhr.send();
-	      return match;
-	    });
-	    $.when.apply($, deferList).then(function () {
-	      var idx = 0;
-	      css = css.replace(util.dereferenceCssUrlsRegex, function (match, url) {
-	        idx += 1;
-	        return results[idx - 1];
-	      });
-	      if (styleKey === undefined) {
-	        styleElem.text(css);
-	      } else {
-	        styleElem.style[styleKey] = css;
-	      }
-	      styleDefer.resolve();
-	    });
-	  },
-
-	  /**
-	   * Check if the current browser supports covnerting html to an image via an
-	   * svg foreignObject and canvas.  If this has not been checked before, it
-	   * returns a Deferred that resolves to a boolean (never rejects).  If the
-	   * check has been done before, it returns a boolean.
-	   *
-	   * @returns {boolean|jQuery.Deferred}
-	   */
-	  htmlToImageSupported: function () {
-	    if (m_htmlToImageSupport === undefined) {
-	      var defer = $.Deferred();
-	      var svg = $(svgForeignObject);
-	      svg.attr({
-	        width: '10px',
-	        height: '10px',
-	        'text-rendering': 'optimizeLegibility'
-	      });
-	      $('foreignObject', svg).append('<div/>');
-	      var img = new Image();
-	      img.onload = img.onerror = function () {
-	        var canvas = document.createElement('canvas');
-	        canvas.width = 10;
-	        canvas.height = 10;
-	        var context = canvas.getContext('2d');
-	        context.drawImage(img, 0, 0);
-	        try {
-	          canvas.toDataURL();
-	          m_htmlToImageSupport = true;
-	        } catch (err) {
-	          console.warn(
-	              'This browser does not support converting HTML to an image via ' +
-	              'SVG foreignObject.  Some functionality will be limited.', err);
-	          m_htmlToImageSupport = false;
-	        }
-	        defer.resolve(m_htmlToImageSupport);
-	      };
-	      img.src = 'data:image/svg+xml;base64,' + btoa(util.escapeUnicodeHTML(
-	          new XMLSerializer().serializeToString(svg[0])));
-	      return defer;
-	    }
-	    return m_htmlToImageSupport;
-	  },
-
-	  /**
-	   * Convert an html element to an image.  This attempts to localize any
-	   * images within the element.  If there are other external references, the
-	   * image may not work due to security considerations.
-	   *
-	   * @param {jQuery.selector} elem Either a jquery selector or an HTML
-	   *    element.  This may contain multiple elements.  The direct parent and
-	   *    grandparent of the element are used for class information.
-	   * @param {number} [parents] Number of layers up to travel to get class
-	   *    information.
-	   * @returns {jQuery.Deferred} A jquery deferred object which receives an
-	   *    HTML Image element when resolved.
-	   * @memberof geo.util
-	   */
-	  htmlToImage: function (elem, parents) {
-	    var defer = $.Deferred(),
-	        deferList = [util.htmlToImageSupported()],
-	        container;
-
-	    var parent = $(elem);
-	    elem = $(elem).clone();
-	    while (parents && parents > 0) {
-	      parent = parent.parent();
-	      if (parent.is('div')) {
-	        /* Create a containing div with the parent's class and id (so css
-	         * will be used), but override size and background. */
-	        container = $('<div>').attr({
-	          'class': parent.attr('class'),
-	          id: parent.attr('id')
-	        }).css({
-	          width: '100%',
-	          height: '100%',
-	          background: 'none',
-	          margin: 0
-	        });
-	        container.append(elem);
-	        elem = container;
-	      }
-	      parents -= 1;
-	    }
-	    // canvas elements won't render properly here.
-	    $('canvas', elem).remove();
-	    /* Walk through all of the children of elem and check if any explicitly set
-	     * css property needs to be dereferenced. */
-	    $('*', elem).addBack().each(function () {
-	      var style = this.style;
-	      for (var idx = 0; idx < style.length; idx += 1) {
-	        var key = this.style[idx];
-	        if (this.style[key].match(util.dereferenceCssUrlsRegex)) {
-	          var styleDefer = $.Deferred();
-	          util.dereferenceCssUrls(this.style[key], this, styleDefer, key);
-	          deferList.push(styleDefer);
-	        }
-	      }
-	    });
-	    container = $('<div xmlns="http://www.w3.org/1999/xhtml">');
-	    container.css({
-	      width: parent.width() + 'px',
-	      height: parent.height() + 'px'
-	    });
-	    container.append($('<head>'));
-	    var body = $('<body>');
-	    container.append(body);
-	    /* We must specify the new body as having no background, or we'll clobber
-	     * other layers. */
-	    body.css({
-	      width: parent.width() + 'px',
-	      height: parent.height() + 'px',
-	      background: 'none',
-	      margin: 0
-	    });
-	    body.append(elem);
-	    deferList = deferList.concat(util.dereferenceElements(elem));
-	    /* Get styles and links in order, as order matters in css */
-	    $('style,link[rel="stylesheet"]').each(function () {
-	      var styleElem = $('<style type="text/css">'),
-	          styleDefer = $.Deferred();
-	      if ($(this).is('style')) {
-	        var css = $(this).text();
-	        util.dereferenceCssUrls(css, styleElem, styleDefer);
-	      } else {
-	        var href = $(this).attr('href');
-	        $.get(href).done(function (css) {
-	          util.dereferenceCssUrls(css, styleElem, styleDefer, undefined, href);
-	        }).fail(function (xhr, status, err) {
-	          console.warn('Failed to dereference ' + href, status, err);
-	          styleElem.remove();
-	          styleDefer.resolve();
-	        });
-	      }
-	      deferList.push(styleDefer);
-	      $('head', container).append(styleElem);
-	    });
-
-	    $.when.apply($, deferList).then(function () {
-	      var svg = $(svgForeignObject);
-	      svg.attr({
-	        width: parent.width() + 'px',
-	        height: parent.height() + 'px',
-	        // Adding this via the attr call works in Firefox headless, whereas if
-	        // it is part of the svgForeignObject string, it does not.
-	        'text-rendering': 'optimizeLegibility'
-	      });
-	      $('foreignObject', svg).append(container);
-
-	      var img = new Image();
-	      if (!util.htmlToImageSupported()) {
-	        defer.resolve(img);
-	      } else {
-	        img.onload = function () {
-	          defer.resolve(img);
-	        };
-	        img.onerror = function () {
-	          console.warn('Failed to render html to image');
-	          defer.reject();
-	        };
-	        // Firefox requires the HTML to be base64 encoded.  Chrome doesn't, but
-	        // doing so does no harm.
-	        img.src = 'data:image/svg+xml;base64,' + btoa(util.escapeUnicodeHTML(
-	            new XMLSerializer().serializeToString(svg[0])));
-	      }
-	    });
-	    return defer;
-	  },
-
-	  /**
-	   * Report on one or all of the tracked timings.
-	   *
-	   * @param {string} [name] A name to report on, or `undefined` to report all.
-	   * @returns {object} An object with timing information, or an object with
-	   *    properties for all tracked timings, each of which contains timing
-	   *    information.
-	   * @memberof geo.util
-	   */
-	  timeReport: function (name) {
-	    $.each(m_timingData, function (key, item) {
-	      /* calculate the standard deviation of each item. */
-	      if (item.count) {
-	        item.stddev = Math.sqrt(Math.abs((
-	          item.sum2 - item.sum * item.sum / item.count) / item.count));
-	        item.average = item.sum / item.count;
-	      } else {
-	        item.stddev = 0;
-	        item.average = 0;
-	      }
-	    });
-	    if (name) {
-	      return m_timingData[name];
-	    }
-	    return m_timingData;
-	  },
-
-	  /**
-	   * Note the start time of a function (or any other section of code).  This
-	   * should be paired with `timeFunctionStop`, which will collect statistics on
-	   * the amount of time spent in a function.
-	   *
-	   * @param {string} name Name to use for tracking the timing.
-	   * @param {boolean} reset If `true`, clear old tracking data for this named
-	   *    tracker.
-	   * @memberof geo.util
-	   */
-	  timeFunctionStart: function (name, reset) {
-	    if (!m_timingData[name] || reset) {
-	      m_timingData[name] = {
-	        count: 0, sum: 0, sum2: 0, max: 0, recent: []
-	      };
-	    }
-	    m_timingData[name].start = window.performance.now();
-	  },
-
-	  /**
-	   * Note the stop time of a function (or any other section of code).  This
-	   * should be paired with `timeFunctionStart`.
-	   *
-	   * @param {string} name Name to use for tracking the timing.
-	   * @memberof geo.util
-	   */
-	  timeFunctionStop: function (name) {
-	    if (!m_timingData[name] || !m_timingData[name].start) {
-	      return;
-	    }
-	    var duration = window.performance.now() - m_timingData[name].start;
-	    m_timingData[name].start = null;
-	    m_timingData[name].sum += duration;
-	    m_timingData[name].sum2 += duration * duration;
-	    m_timingData[name].count += 1;
-	    m_timingData[name].max = Math.max(
-	      m_timingData[name].max, duration);
-	    m_timingData[name].recent.push(duration);
-	    if (m_timingData[name].recent.length > m_timingKeepRecent) {
-	      m_timingData[name].recent.splice(
-	          0, m_timingData[name].recent.length - m_timingKeepRecent);
-	    }
-	  },
-
-	  /**
-	   * Start or stop tracking the time spent in `requestAnimationFrame`.  If
-	   * tracked, the results can be fetched via
-	   * `timeFunctionReport('requestAnimationFrame')`.
-	   *
-	   * @param {boolean} [stop] Falsy to start tracking, truthy to start tracking.
-	   * @param {boolean} [reset] If truthy, reset the statistics.
-	   * @param {number} [threshold=15] If present, set the threshold in
-	   *    milliseconds used in tracking slow callbacks.
-	   * @param {number} [keep=200] If present, set the number of recent frame
-	   *    times to track.
-	   * @memberof geo.util
-	   */
-	  timeRequestAnimationFrame: function (stop, reset, threshold, keep) {
-	    if (!m_timingData.requestAnimationFrame || reset) {
-	      m_timingData.requestAnimationFrame = {
-	        count: 0,
-	        sum: 0,
-	        sum2: 0,
-	        max: 0,
-	        above_threshold: 0,
-	        recent: [],
-	        recentsub: []
-	      };
-	    }
-	    if (threshold) {
-	      m_threshold = threshold;
-	    }
-	    if (keep) {
-	      m_timingKeepRecent = keep;
-	    }
-	    if (stop && m_originalRequestAnimationFrame) {
-	      window.requestAnimationFrame = m_originalRequestAnimationFrame;
-	      m_originalRequestAnimationFrame = null;
-	    } else if (!stop && !m_originalRequestAnimationFrame) {
-	      m_originalRequestAnimationFrame = window.requestAnimationFrame;
-	      window.requestAnimationFrame = function (callback) {
-	        return m_originalRequestAnimationFrame.call(window, function (timestamp) {
-	          var track = m_timingData.requestAnimationFrame, recent;
-	          /* Some environments have unsynchronized performance and time
-	           * counters.  The nowDelta factor compensates for this.  For
-	           * instance, our test enviornment has performance.now() values on
-	           * the order of ~3000 and timestamps approximating epoch. */
-	          if (track.timestamp !== timestamp) {
-	            track.nowDelta = window.performance.now() - timestamp;
-	            if (Math.abs(track.nowDelta) < 1000) {
-	              track.nowDelta = 0;
-	            }
-	            track.timestamp = timestamp;
-	            track.subcalls = track.subcalls || 0;
-	            track.start = {
-	              sum: track.sum,
-	              sum2: track.sum2,
-	              count: track.count,
-	              max: track.max,
-	              above_threshold: track.above_threshold
-	            };
-	            track.recent.push([0]);
-	            track.recentsub.push([]);
-	            if (track.recent.length > m_timingKeepRecent) {
-	              track.recent.splice(
-	                  0, track.recent.length - m_timingKeepRecent);
-	              track.recentsub.splice(
-	                  0, track.recentsub.length - m_timingKeepRecent);
-	            }
-	          }
-	          track.subcalls += 1;
-	          callback.apply(this, arguments);
-	          var duration = window.performance.now() - timestamp;
-	          duration -= track.nowDelta;
-	          track.sum = track.start.sum + duration;
-	          track.sum2 = track.start.sum2 + duration * duration;
-	          track.count = track.start.count + 1;
-	          track.max = Math.max(track.max, duration);
-	          track.above_threshold = track.start.above_threshold + (
-	            duration >= m_threshold ? 1 : 0);
-	          track.recent[track.recent.length - 1] = duration;
-	          recent = track.recentsub[track.recent.length - 1];
-	          recent.push({
-	            total_duration: duration,
-	            duration: duration - (recent.length ?
-	              recent[recent.length - 1].total_duration : 0),
-	            callback: callback.name || callback
-	          });
-	        });
-	      };
-	    }
-	  },
-
-	  /**
-	   * Test if an item is an object.  This uses typeof not instanceof, since
-	   * instanceof will return false for some things that we expect to be objects.
-	   *
-	   * @param {*} value The item to test.
-	   * @returns {boolean} True if the tested item is an object.
-	   */
-	  isObject: function (value) {
-	    var type = typeof value;
-	    return value !== null && value !== undefined && (type === 'object' || type === 'function');
-	  },
-
-	  ///////////////////////////////////////////////////////////////////////////
-	  /*
-	   * Utility member properties.
-	   */
-	  ///////////////////////////////////////////////////////////////////////////
-
-	  /**
-	   * Radius of the earth in meters, from the equatorial radius of SRID 4326.
-	   * @memberof geo.util
-	   */
-	  radiusEarth: proj4.WGS84.a,
-
-	  /**
-	   * A regular expression string that will parse a number (integer or floating
-	   * point) for CSS properties.
-	   * @memberof geo.util
-	   */
-	  cssNumberRegex: '[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?',
-
-	  /**
-	   * A dictionary of conversion factors for angular CSS measurements.
-	   * @memberof geo.util
-	   */
-	  cssAngleUnitsBase: {deg: 360, grad: 400, rad: 2 * Math.PI, turn: 1},
-
-	  /**
-	   * The predefined CSS colors.  See
-	   * {@link https://drafts.csswg.org/css-color}.
-	   *
-	   * @memberof geo.util
-	   */
-	  cssColors: {
-	    aliceblue: 0xf0f8ff,
-	    antiquewhite: 0xfaebd7,
-	    aqua: 0x00ffff,
-	    aquamarine: 0x7fffd4,
-	    azure: 0xf0ffff,
-	    beige: 0xf5f5dc,
-	    bisque: 0xffe4c4,
-	    black: 0x000000,
-	    blanchedalmond: 0xffebcd,
-	    blue: 0x0000ff,
-	    blueviolet: 0x8a2be2,
-	    brown: 0xa52a2a,
-	    burlywood: 0xdeb887,
-	    cadetblue: 0x5f9ea0,
-	    chartreuse: 0x7fff00,
-	    chocolate: 0xd2691e,
-	    coral: 0xff7f50,
-	    cornflowerblue: 0x6495ed,
-	    cornsilk: 0xfff8dc,
-	    crimson: 0xdc143c,
-	    cyan: 0x00ffff,
-	    darkblue: 0x00008b,
-	    darkcyan: 0x008b8b,
-	    darkgoldenrod: 0xb8860b,
-	    darkgray: 0xa9a9a9,
-	    darkgreen: 0x006400,
-	    darkgrey: 0xa9a9a9,
-	    darkkhaki: 0xbdb76b,
-	    darkmagenta: 0x8b008b,
-	    darkolivegreen: 0x556b2f,
-	    darkorange: 0xff8c00,
-	    darkorchid: 0x9932cc,
-	    darkred: 0x8b0000,
-	    darksalmon: 0xe9967a,
-	    darkseagreen: 0x8fbc8f,
-	    darkslateblue: 0x483d8b,
-	    darkslategray: 0x2f4f4f,
-	    darkslategrey: 0x2f4f4f,
-	    darkturquoise: 0x00ced1,
-	    darkviolet: 0x9400d3,
-	    deeppink: 0xff1493,
-	    deepskyblue: 0x00bfff,
-	    dimgray: 0x696969,
-	    dimgrey: 0x696969,
-	    dodgerblue: 0x1e90ff,
-	    firebrick: 0xb22222,
-	    floralwhite: 0xfffaf0,
-	    forestgreen: 0x228b22,
-	    fuchsia: 0xff00ff,
-	    gainsboro: 0xdcdcdc,
-	    ghostwhite: 0xf8f8ff,
-	    gold: 0xffd700,
-	    goldenrod: 0xdaa520,
-	    gray: 0x808080,
-	    green: 0x008000,
-	    greenyellow: 0xadff2f,
-	    grey: 0x808080,
-	    honeydew: 0xf0fff0,
-	    hotpink: 0xff69b4,
-	    indianred: 0xcd5c5c,
-	    indigo: 0x4b0082,
-	    ivory: 0xfffff0,
-	    khaki: 0xf0e68c,
-	    lavender: 0xe6e6fa,
-	    lavenderblush: 0xfff0f5,
-	    lawngreen: 0x7cfc00,
-	    lemonchiffon: 0xfffacd,
-	    lightblue: 0xadd8e6,
-	    lightcoral: 0xf08080,
-	    lightcyan: 0xe0ffff,
-	    lightgoldenrodyellow: 0xfafad2,
-	    lightgray: 0xd3d3d3,
-	    lightgreen: 0x90ee90,
-	    lightgrey: 0xd3d3d3,
-	    lightpink: 0xffb6c1,
-	    lightsalmon: 0xffa07a,
-	    lightseagreen: 0x20b2aa,
-	    lightskyblue: 0x87cefa,
-	    lightslategray: 0x778899,
-	    lightslategrey: 0x778899,
-	    lightsteelblue: 0xb0c4de,
-	    lightyellow: 0xffffe0,
-	    lime: 0x00ff00,
-	    limegreen: 0x32cd32,
-	    linen: 0xfaf0e6,
-	    magenta: 0xff00ff,
-	    maroon: 0x800000,
-	    mediumaquamarine: 0x66cdaa,
-	    mediumblue: 0x0000cd,
-	    mediumorchid: 0xba55d3,
-	    mediumpurple: 0x9370db,
-	    mediumseagreen: 0x3cb371,
-	    mediumslateblue: 0x7b68ee,
-	    mediumspringgreen: 0x00fa9a,
-	    mediumturquoise: 0x48d1cc,
-	    mediumvioletred: 0xc71585,
-	    midnightblue: 0x191970,
-	    mintcream: 0xf5fffa,
-	    mistyrose: 0xffe4e1,
-	    moccasin: 0xffe4b5,
-	    navajowhite: 0xffdead,
-	    navy: 0x000080,
-	    oldlace: 0xfdf5e6,
-	    olive: 0x808000,
-	    olivedrab: 0x6b8e23,
-	    orange: 0xffa500,
-	    orangered: 0xff4500,
-	    orchid: 0xda70d6,
-	    palegoldenrod: 0xeee8aa,
-	    palegreen: 0x98fb98,
-	    paleturquoise: 0xafeeee,
-	    palevioletred: 0xdb7093,
-	    papayawhip: 0xffefd5,
-	    peachpuff: 0xffdab9,
-	    peru: 0xcd853f,
-	    pink: 0xffc0cb,
-	    plum: 0xdda0dd,
-	    powderblue: 0xb0e0e6,
-	    purple: 0x800080,
-	    rebeccapurple: 0x663399,
-	    red: 0xff0000,
-	    rosybrown: 0xbc8f8f,
-	    royalblue: 0x4169e1,
-	    saddlebrown: 0x8b4513,
-	    salmon: 0xfa8072,
-	    sandybrown: 0xf4a460,
-	    seagreen: 0x2e8b57,
-	    seashell: 0xfff5ee,
-	    sienna: 0xa0522d,
-	    silver: 0xc0c0c0,
-	    skyblue: 0x87ceeb,
-	    slateblue: 0x6a5acd,
-	    slategray: 0x708090,
-	    slategrey: 0x708090,
-	    snow: 0xfffafa,
-	    springgreen: 0x00ff7f,
-	    steelblue: 0x4682b4,
-	    tan: 0xd2b48c,
-	    teal: 0x008080,
-	    thistle: 0xd8bfd8,
-	    tomato: 0xff6347,
-	    turquoise: 0x40e0d0,
-	    violet: 0xee82ee,
-	    wheat: 0xf5deb3,
-	    white: 0xffffff,
-	    whitesmoke: 0xf5f5f5,
-	    yellow: 0xffff00,
-	    yellowgreen: 0x9acd32
-	  }
-	};
-
-	///////////////////////////////////////////////////////////////////////////
-	/*
-	 * Utility member properties that need to refer to util functions and
-	 * properties.
-	 */
-	///////////////////////////////////////////////////////////////////////////
-
-	/**
-	 * A list of regex and processing functions for color conversions to rgb
-	 * objects.  Each entry is a {@link geo.util.cssColorConversionRecord}.  In
-	 * general, these conversions are somewhat more forgiving than the css
-	 * specification (see https://drafts.csswg.org/css-color/) in that
-	 * percentages may be mixed with numbers, and that floating point values are
-	 * accepted for all numbers.  Commas are optional.  As per the latest draft
-	 * standard, `rgb` and `rgba` are aliases of each other, as are `hsl` and
-	 * `hsla`.
-	 * @alias cssColorConversions
-	 * @memberof geo.util
-	 */
-	util.cssColorConversions = [{
-	  name: 'rgb',
-	  regex: new RegExp(
-	    '^\\s*rgba?' +
-	    '\\(\\s*(' + util.cssNumberRegex + ')\\s*(%?)\\s*' +
-	    ',?\\s*(' + util.cssNumberRegex + ')\\s*(%?)\\s*' +
-	    ',?\\s*(' + util.cssNumberRegex + ')\\s*(%?)\\s*' +
-	    '([/,]?\\s*(' + util.cssNumberRegex + ')\\s*(%?)\\s*)?' +
-	    '\\)\\s*$'),
-	  process: function (color, match) {
-	    color = {
-	      r: Math.min(1, Math.max(0, +match[1] / (match[2] ? 100 : 255))),
-	      g: Math.min(1, Math.max(0, +match[3] / (match[4] ? 100 : 255))),
-	      b: Math.min(1, Math.max(0, +match[5] / (match[6] ? 100 : 255)))
-	    };
-	    if (match[7]) {
-	      color.a = Math.min(1, Math.max(0, +match[8] / (match[9] ? 100 : 1)));
-	    }
-	    return color;
-	  }
-	}, {
-	  name: 'hsl',
-	  regex: new RegExp(
-	    '^\\s*hsla?' +
-	    '\\(\\s*(' + util.cssNumberRegex + ')\\s*(deg|grad|rad|turn)?\\s*' +
-	    ',?\\s*(' + util.cssNumberRegex + ')\\s*%\\s*' +
-	    ',?\\s*(' + util.cssNumberRegex + ')\\s*%\\s*' +
-	    '([/,]?\\s*(' + util.cssNumberRegex + ')\\s*(%?)\\s*)?' +
-	    '\\)\\s*$'),
-	  process: function (color, match) {
-	    /* Conversion from https://www.w3.org/TR/2011/REC-css3-color-20110607
-	     */
-	    var hue_to_rgb = function (m1, m2, h) {
-	      h = h - Math.floor(h);
-	      if (h * 6 < 1) {
-	        return m1 + (m2 - m1) * h * 6;
-	      }
-	      if (h * 6 < 3) {
-	        return m2;
-	      }
-	      if (h * 6 < 4) {
-	        return m1 + (m2 - m1) * (2 / 3 - h) * 6;
-	      }
-	      return m1;
-	    };
-
-	    var h = +match[1] / (util.cssAngleUnitsBase[match[2]] || 360),
-	        s = Math.min(1, Math.max(0, +match[3] / 100)),
-	        l = Math.min(1, Math.max(0, +match[4] / 100)),
-	        m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s,
-	        m1 = l * 2 - m2;
-	    color = {
-	      r: hue_to_rgb(m1, m2, h + 1 / 3),
-	      g: hue_to_rgb(m1, m2, h),
-	      b: hue_to_rgb(m1, m2, h - 1 / 3)
-	    };
-	    if (match[5]) {
-	      color.a = Math.min(1, Math.max(0, +match[6] / (match[7] ? 100 : 1)));
-	    }
-	    return color;
-	  }
-	}];
-
-	/* Add additional utilities to the main object. */
-	$.extend(util, throttle, mockVGL);
-
-
-/***/ }),
-/* 84 */
-/***/ (function(module, exports) {
-
-	/**
-	 * Based on the following jquery throttle / debounce plugin:
-	 *
-	 * jQuery throttle / debounce - v1.1 - 3/7/2010
-	 * http://benalman.com/projects/jquery-throttle-debounce-plugin/
-	 *
-	 * @copyright 2010 "Cowboy" Ben Alman
-	 * Dual licensed under the MIT and GPL licenses.
-	 * http://benalman.com/about/license/
-	 *
-	 * The implementation included here is modified to support a callback
-	 * method that can accumulate values between actual invocations of
-	 * the throttled method.
-	 */
-
-	/**
-	 * Throttle execution of a function. Especially useful for rate limiting
-	 * execution of handlers on events like resize and scroll. If you want to
-	 * rate-limit execution of a function to a single time see
-	 * {@link geo.util.debounce}.
-	 *
-	 * In this visualization, | is a throttled-function call and X is the actual
-	 * callback execution:
-	 *
-	 * ```
-	 * Throttled with `no_trailing` specified as false or unspecified:
-	 * ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
-	 * X    X    X    X    X    X        X    X    X    X    X    X
-	 *
-	 * Throttled with `no_trailing` specified as true:
-	 * ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
-	 * X    X    X    X    X             X    X    X    X    X
-	 * ```
-	 *
-	 * This is also used to handle debouncing a function.
-	 *
-	 * @alias geo.util.throttle
-	 * @param {number} delay A zero-or-greater delay in milliseconds. For event
-	 *    callbacks, values around 100 or 250 (or even higher) are most useful.
-	 * @param {boolean} [no_trailing=false] If no_trailing is
-	 *    true, callback will only execute every `delay` milliseconds while the
-	 *    throttled-function is being called. If no_trailing is false or
-	 *    unspecified, callback will be executed one final time after the last
-	 *    throttled-function call. (After the throttled-function has not been
-	 *    called for `delay` milliseconds, the internal counter is reset)
-	 * @param {function} callback A function to be executed after `delay`
-	 *    milliseconds. The `this` context and all arguments are passed through,
-	 *    as-is, to `callback` when the throttled-function is executed.
-	 * @param {function} [accumulator] A function to be executed (synchronously)
-	 *    during **each** call to the wrapped function.  Typically, this
-	 *    this method is used to accumulate values that the callback uses
-	 *    when it finally executes.
-	 * @param {boolean} [debounce_mode] See the `at_begin` parameter of the
-	 *    `geo.util.debounce` function.
-	 * @returns {function} The throttled version of `callback`.
-	 *
-	 * @example
-	 * var throttled = geo.util.throttle( delay, [ no_trailing, ] callback );
-	 * $('selector').bind( 'someevent', throttled );
-	 * $('selector').unbind( 'someevent', throttled );
-	 */
-	var throttle = function (delay, no_trailing, callback, accumulator, debounce_mode) {
-	  // After wrapper has stopped being called, this timeout ensures that
-	  // `callback` is executed at the proper times in `throttle` and `end`
-	  // debounce modes.
-	  var timeout_id,
-
-	    // Keep track of the last time `callback` was executed.
-	      last_exec = 0;
-
-	  // `no_trailing` defaults to falsy.
-	  if (typeof no_trailing !== 'boolean') {
-	    debounce_mode = accumulator;
-	    accumulator = callback;
-	    callback = no_trailing;
-	    no_trailing = undefined;
-	  }
-
-	  // accumulator defaults to no-op
-	  if (typeof accumulator !== 'function') {
-	    debounce_mode = accumulator;
-	    accumulator = function () {};
-	  }
-
-	  // The `wrapper` function encapsulates all of the throttling / debouncing
-	  // functionality and when executed will limit the rate at which `callback`
-	  // is executed.
-	  function wrapper() {
-	    var that = this,
-	        elapsed = +new Date() - last_exec,
-	        args = arguments;
-
-	    // Execute `callback` and update the `last_exec` timestamp.
-	    function exec() {
-	      last_exec = +new Date();
-	      callback.apply(that, args);
-	    }
-
-	    // If `debounce_mode` is true (at_begin) this is used to clear the flag
-	    // to allow future `callback` executions.
-	    function clear() {
-	      timeout_id = undefined;
-	    }
-
-	    // always call the accumulator first
-	    accumulator.apply(that, args);
-
-	    if (debounce_mode && !timeout_id) {
-	      // Since `wrapper` is being called for the first time and
-	      // `debounce_mode` is true (at_begin), execute `callback`.
-	      exec();
-	    }
-
-	    // Clear any existing timeout.
-	    void (
-	      timeout_id && clearTimeout(timeout_id)
-	    );
-
-	    if (debounce_mode === undefined && elapsed > delay) {
-	      // In throttle mode, if `delay` time has been exceeded, execute
-	      // `callback`.
-	      exec();
-
-	    } else if (no_trailing !== true) {
-	      /*
-	       * In trailing throttle mode, since `delay` time has not been
-	       * exceeded, schedule `callback` to execute `delay` ms after most
-	       * recent execution.
-	       *
-	       * If `debounce_mode` is true (at_begin), schedule `clear` to execute
-	       * after `delay` ms.
-	       *
-	       * If `debounce_mode` is false (at end), schedule `callback` to
-	       * execute after `delay` ms.
-	       */
-	      timeout_id = setTimeout(
-	        debounce_mode ?
-	          clear :
-	          exec,
-	        debounce_mode === undefined ?
-	          delay - elapsed :
-	          delay
-	      );
-	    }
-	  }
-
-	  // Return the wrapper function.
-	  return wrapper;
-	};
-
-	/**
-	 * Debounce execution of a function. Debouncing, unlike throttling,
-	 * guarantees that a function is only executed a single time, either at the
-	 * very beginning of a series of calls, or at the very end. If you want to
-	 * simply rate-limit execution of a function, see the <jQuery.throttle>
-	 * method.
-	 *
-	 * In this visualization, | is a debounced-function call and X is the actual
-	 * callback execution:
-	 *
-	 * ::
-	 *
-	 *   Debounced with `at_begin` specified as false or unspecified:
-	 *   ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
-	 *                            X                                 X
-	 *
-	 *   Debounced with `at_begin` specified as true:
-	 *   ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
-	 *   X                                 X
-	 *
-	 * The bulk of the work is handled by the `geo.util.throttle` function.
-	 *
-	 * @param {number} delay A zero-or-greater delay in milliseconds. For event
-	 *    callbacks, values around 100 or 250 (or even higher) are most useful.
-	 * @param {boolean} [at_begin=false] If at_begin is false or
-	 *    unspecified, callback will only be executed `delay` milliseconds after
-	 *    the last debounced-function call. If at_begin is true, callback will be
-	 *    executed only at the first debounced-function call. (After the
-	 *    throttled-function has not been called for `delay` milliseconds, the
-	 *    internal counter is reset)
-	 * @param {function} callback A function to be executed after delay milliseconds.
-	 *    The `this` context and all arguments are passed through, as-is, to
-	 *    `callback` when the debounced-function is executed.
-	 * @param {function} [accumulator] A function to be executed (synchronously)
-	 *    during **each** call to the wrapped function.  Typically, this
-	 *    this method is used to accumulate values that the callback uses
-	 *    when it finally executes.
-	 *
-	 * @returns {function} A new, debounced, function.
-	 *
-	 * @example
-	 * var debounced = geo.util.debounce( delay, [ at_begin, ] callback );
-	 * $('selector').bind( 'someevent', debounced );
-	 * $('selector').unbind( 'someevent', debounced );
-	 *
-	 */
-	var debounce = function (delay, at_begin, callback, accumulator) {
-	  if (typeof at_begin !== 'boolean') {
-	    accumulator = callback;
-	    callback = at_begin;
-	    at_begin = false;
-	  }
-	  accumulator = accumulator || function () {};
-	  return throttle(delay, false, callback, accumulator, !!at_begin);
-	};
-
-	module.exports = {
-	  throttle: throttle,
-	  debounce: debounce
-	};
-
-
-/***/ }),
-/* 85 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/* eslint-disable camelcase */
-	/* eslint-disable underscore/prefer-constant */
-
-	var $ = __webpack_require__(1);
-	var vgl = __webpack_require__(86);
-	var vglRenderer = __webpack_require__(200);
-
-	var _renderWindow, _supported;
-
-	module.exports = {};
-
-	/**
-	 * Replace vgl.renderer with a mocked version for testing in a non-webGL state.
-	 * Use restoreVGLRenderer to unmock.  Call vgl.mockCounts() to get the number
-	 * of times different webGL functions have been called.
-	 *
-	 * @param {boolean} [supported=true] If false, then the vgl renderer will
-	 *      indicate that this is an unsupported browser environment.
-	 * @alias geo.util.mockVGLRenderer
-	 */
-	module.exports.mockVGLRenderer = function mockVGLRenderer(supported) {
-	  'use strict';
-	  var vgl = __webpack_require__(86);
-
-	  if (supported === undefined) {
-	    supported = true;
-	  }
-
-	  if (vgl._mocked) {
-	    throw new Error('VGL renderer already mocked');
-	  }
-
-	  var mockCounts = {};
-	  var count = function (name) {
-	    mockCounts[name] = (mockCounts[name] || 0) + 1;
-	  };
-	  var noop = function (name) {
-	    return function () {
-	      count(name);
-	    };
-	  };
-	  var _id = 0,
-	      incID = function (name) {
-	        return function () {
-	          count(name);
-	          _id += 1;
-	          return _id;
-	        };
-	      };
-	  /* The context largely does nothing. */
-	  var default_context = {
-	    activeTexture: noop('activeTexture'),
-	    attachShader: noop('attachShader'),
-	    bindAttribLocation: noop('bindAttribLocation'),
-	    bindBuffer: noop('bindBuffer'),
-	    bindFramebuffer: noop('bindFramebuffer'),
-	    bindTexture: noop('bindTexture'),
-	    blendFuncSeparate: noop('blendFuncSeparate'),
-	    bufferData: noop('bufferData'),
-	    bufferSubData: noop('bufferSubData'),
-	    checkFramebufferStatus: function (key) {
-	      count('checkFramebufferStatus');
-	      if (key === vgl.GL.FRAMEBUFFER) {
-	        return vgl.GL.FRAMEBUFFER_COMPLETE;
-	      }
-	    },
-	    clear: noop('clear'),
-	    clearColor: noop('clearColor'),
-	    clearDepth: noop('clearDepth'),
-	    compileShader: noop('compileShader'),
-	    createBuffer: incID('createBuffer'),
-	    createFramebuffer: noop('createFramebuffer'),
-	    createProgram: incID('createProgram'),
-	    createShader: incID('createShader'),
-	    createTexture: incID('createTexture'),
-	    deleteBuffer: noop('deleteBuffer'),
-	    deleteProgram: noop('deleteProgram'),
-	    deleteShader: noop('deleteShader'),
-	    deleteTexture: noop('deleteTexture'),
-	    depthFunc: noop('depthFunc'),
-	    disable: noop('disable'),
-	    disableVertexAttribArray: noop('disableVertexAttribArray'),
-	    drawArrays: noop('drawArrays'),
-	    enable: noop('enable'),
-	    enableVertexAttribArray: noop('enableVertexAttribArray'),
-	    finish: noop('finish'),
-	    getExtension: incID('getExtension'),
-	    getParameter: function (key) {
-	      count('getParameter');
-	      if (key === vgl.GL.DEPTH_BITS) {
-	        return 16;
-	      }
-	    },
-	    getProgramParameter: function (id, key) {
-	      count('getProgramParameter');
-	      if (key === vgl.GL.LINK_STATUS) {
-	        return true;
-	      }
-	    },
-	    getShaderInfoLog: function () {
-	      count('getShaderInfoLog');
-	      return 'log';
-	    },
-	    getShaderParameter: function (id, key) {
-	      count('getShaderParameter');
-	      if (key === vgl.GL.COMPILE_STATUS) {
-	        return true;
-	      }
-	    },
-	    getUniformLocation: incID('getUniformLocation'),
-	    isEnabled: function (key) {
-	      count('isEnabled');
-	      if (key === vgl.GL.BLEND) {
-	        return true;
-	      }
-	    },
-	    linkProgram: noop('linkProgram'),
-	    pixelStorei: noop('pixelStorei'),
-	    shaderSource: noop('shaderSource'),
-	    texImage2D: noop('texImage2D'),
-	    texParameteri: noop('texParameteri'),
-	    uniform1iv: noop('uniform1iv'),
-	    uniform1fv: noop('uniform1fv'),
-	    uniform2fv: noop('uniform2fv'),
-	    uniform3fv: noop('uniform3fv'),
-	    uniform4fv: noop('uniform4fv'),
-	    uniformMatrix3fv: noop('uniformMatrix3fv'),
-	    uniformMatrix4fv: noop('uniformMatrix4fv'),
-	    useProgram: noop('useProgram'),
-	    vertexAttribPointer: noop('vertexAttribPointer'),
-	    vertexAttrib3fv: noop('vertexAttrib3fv'),
-	    viewport: noop('viewport')
-	  };
-
-	  _renderWindow = vgl.renderWindow;
-	  var mockedRenderWindow = function () {
-	    /* Temporarily put back the original definition of renderWindow so that the
-	     * class instance will be instantiated correctly. */
-	    vgl.renderWindow = _renderWindow;
-	    var m_this = new vgl.renderWindow(),
-	        m_context;
-	    vgl.renderWindow = mockedRenderWindow;
-
-	    m_this._setup = function () {
-	      var i, renderers = m_this.renderers(),
-	          wsize = m_this.windowSize(),
-	          wpos = m_this.windowPosition();
-
-	      m_context = $.extend({}, default_context);
-
-	      for (i = 0; i < renderers.length; i += 1) {
-	        if ((renderers[i].width() > wsize[0]) ||
-	            renderers[i].width() === 0 ||
-	            (renderers[i].height() > wsize[1]) ||
-	            renderers[i].height() === 0) {
-	          renderers[i].resize(wpos[0], wpos[1], wsize[0], wsize[1]);
-	        }
-	      }
-	      return true;
-	    };
-	    m_this.context = function () {
-	      return m_context;
-	    };
-	    return m_this;
-	  };
-	  vgl.renderWindow = mockedRenderWindow;
-
-	  _supported = vglRenderer.supported;
-	  vglRenderer.supported = function () {
-	    return !!supported;
-	  };
-
-	  vgl._mocked = true;
-	  vgl.mockCounts = function () {
-	    return mockCounts;
-	  };
-	};
-
-	/**
-	 * Unmock the vgl renderer.
-	 * @alias geo.util.restoreVGLRenderer
-	 */
-	module.exports.restoreVGLRenderer = function () {
-	  if (vgl._mocked) {
-	    vgl.renderWindow = _renderWindow;
-	    vglRenderer.supported = _supported;
-	    delete vgl._mocked;
-	    delete vgl.mockCounts;
-	  }
-	};
-
-
-/***/ }),
-/* 86 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["vgl"] = __webpack_require__(87);
-	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
-
-/***/ }),
-/* 87 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*** IMPORTS FROM imports-loader ***/
-	var mat4 = __webpack_require__(88);
-	var vec4 = __webpack_require__(112);
-	var vec3 = __webpack_require__(138);
-	var vec2 = __webpack_require__(171);
-	var $ = __webpack_require__(1);
-
-	(function (root, factory) {
-	  if (true) {
-	    // AMD. Register as an anonymous module unless amdModuleId is set
-	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function () {
-	      return (root['vgl'] = factory());
-	    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-	  } else if (typeof exports === 'object') {
-	    // Node. Does not work with strict CommonJS, but
-	    // only CommonJS-like environments that support module.exports,
-	    // like Node.
-	    module.exports = factory();
-	  } else {
-	    root['vgl'] = factory();
-	  }
-	}(this, function () {
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/* exported vgl, inherit */
-	//////////////////////////////////////////////////////////////////////////////
-
-	if (typeof ogs === 'undefined') {
-	  var ogs = {};
-	}
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create namespace for the given name
-	 *
-	 * @param ns_string
-	 * @returns {*|{}}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	ogs.namespace = function (ns_string) {
-	  'use strict';
-
-	  var parts = ns_string.split('.'), parent = ogs, i;
-
-	  // strip redundant leading global
-	  if (parts[0] === 'ogs') {
-	    parts = parts.slice(1);
-	  }
-	  for (i = 0; i < parts.length; i += 1) {
-	    // create a property if it doesn't exist
-	    if (typeof parent[parts[i]] === 'undefined') {
-	      parent[parts[i]] = {};
-	    }
-	    parent = parent[parts[i]];
-	  }
-	  return parent;
-	};
-
-	/** vgl namespace */
-	var vgl = ogs.namespace('gl');
-	window.vgl = vgl;
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Convenient function to define JS inheritance
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.inherit = function (C, P) {
-	  'use strict';
-
-	  var F = inherit.func();
-	  F.prototype = P.prototype;
-	  C.prototype = new F();
-	  C.prototype.constructor = C;
-	};
-	vgl.inherit.func = function () {
-	  'use strict';
-	  return function () {};
-	};
-
-	window.inherit = vgl.inherit;
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Convenient function to get size of an object
-	 *
-	 * @param obj
-	 * @returns {number} *
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	Object.size = function (obj) {
-	  'use strict';
-
-	  var size = 0, key;
-	  for (key in obj) {
-	    if (obj.hasOwnProperty(key)) {
-	      size += 1;
-	    }
-	  }
-	  return size;
-	};
-
-	/* Polyfill for Math.log2 */
-	if (!Math.log2) {
-	  Math.log2 = function (val) {
-	    return Math.log(val) / Math.log(2);
-	  };
-	}
-
-	vgl.version = '0.3.10';
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Wrap GL enums. Currently to get values of the enums we need to create
-	 * or access the context.
-	 *
-	 * Using enums from here:
-	 * https://github.com/toji/dart-gl-enums/blob/master/lib/gl_enums.dart
-	 *
-	 * @class
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.GL = {
-	  ACTIVE_ATTRIBUTES : 0x8B89,
-	  ACTIVE_TEXTURE : 0x84E0,
-	  ACTIVE_UNIFORMS : 0x8B86,
-	  ALIASED_LINE_WIDTH_RANGE : 0x846E,
-	  ALIASED_POINT_SIZE_RANGE : 0x846D,
-	  ALPHA : 0x1906,
-	  ALPHA_BITS : 0x0D55,
-	  ALWAYS : 0x0207,
-	  ARRAY_BUFFER : 0x8892,
-	  ARRAY_BUFFER_BINDING : 0x8894,
-	  ATTACHED_SHADERS : 0x8B85,
-	  BACK : 0x0405,
-	  BLEND : 0x0BE2,
-	  BLEND_COLOR : 0x8005,
-	  BLEND_DST_ALPHA : 0x80CA,
-	  BLEND_DST_RGB : 0x80C8,
-	  BLEND_EQUATION : 0x8009,
-	  BLEND_EQUATION_ALPHA : 0x883D,
-	  BLEND_EQUATION_RGB : 0x8009,
-	  BLEND_SRC_ALPHA : 0x80CB,
-	  BLEND_SRC_RGB : 0x80C9,
-	  BLUE_BITS : 0x0D54,
-	  BOOL : 0x8B56,
-	  BOOL_VEC2 : 0x8B57,
-	  BOOL_VEC3 : 0x8B58,
-	  BOOL_VEC4 : 0x8B59,
-	  BROWSER_DEFAULT_WEBGL : 0x9244,
-	  BUFFER_SIZE : 0x8764,
-	  BUFFER_USAGE : 0x8765,
-	  BYTE : 0x1400,
-	  CCW : 0x0901,
-	  CLAMP_TO_EDGE : 0x812F,
-	  COLOR_ATTACHMENT0 : 0x8CE0,
-	  COLOR_BUFFER_BIT : 0x00004000,
-	  COLOR_CLEAR_VALUE : 0x0C22,
-	  COLOR_WRITEMASK : 0x0C23,
-	  COMPILE_STATUS : 0x8B81,
-	  COMPRESSED_TEXTURE_FORMATS : 0x86A3,
-	  CONSTANT_ALPHA : 0x8003,
-	  CONSTANT_COLOR : 0x8001,
-	  CONTEXT_LOST_WEBGL : 0x9242,
-	  CULL_FACE : 0x0B44,
-	  CULL_FACE_MODE : 0x0B45,
-	  CURRENT_PROGRAM : 0x8B8D,
-	  CURRENT_VERTEX_ATTRIB : 0x8626,
-	  CW : 0x0900,
-	  DECR : 0x1E03,
-	  DECR_WRAP : 0x8508,
-	  DELETE_STATUS : 0x8B80,
-	  DEPTH_ATTACHMENT : 0x8D00,
-	  DEPTH_BITS : 0x0D56,
-	  DEPTH_BUFFER_BIT : 0x00000100,
-	  DEPTH_CLEAR_VALUE : 0x0B73,
-	  DEPTH_COMPONENT : 0x1902,
-	  DEPTH_COMPONENT16 : 0x81A5,
-	  DEPTH_FUNC : 0x0B74,
-	  DEPTH_RANGE : 0x0B70,
-	  DEPTH_STENCIL : 0x84F9,
-	  DEPTH_STENCIL_ATTACHMENT : 0x821A,
-	  DEPTH_TEST : 0x0B71,
-	  DEPTH_WRITEMASK : 0x0B72,
-	  DITHER : 0x0BD0,
-	  DONT_CARE : 0x1100,
-	  DST_ALPHA : 0x0304,
-	  DST_COLOR : 0x0306,
-	  DYNAMIC_DRAW : 0x88E8,
-	  ELEMENT_ARRAY_BUFFER : 0x8893,
-	  ELEMENT_ARRAY_BUFFER_BINDING : 0x8895,
-	  EQUAL : 0x0202,
-	  FASTEST : 0x1101,
-	  FLOAT : 0x1406,
-	  FLOAT_MAT2 : 0x8B5A,
-	  FLOAT_MAT3 : 0x8B5B,
-	  FLOAT_MAT4 : 0x8B5C,
-	  FLOAT_VEC2 : 0x8B50,
-	  FLOAT_VEC3 : 0x8B51,
-	  FLOAT_VEC4 : 0x8B52,
-	  FRAGMENT_SHADER : 0x8B30,
-	  FRAMEBUFFER : 0x8D40,
-	  FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1,
-	  FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0,
-	  FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3,
-	  FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2,
-	  FRAMEBUFFER_BINDING : 0x8CA6,
-	  FRAMEBUFFER_COMPLETE : 0x8CD5,
-	  FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6,
-	  FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9,
-	  FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7,
-	  FRAMEBUFFER_UNSUPPORTED : 0x8CDD,
-	  FRONT : 0x0404,
-	  FRONT_AND_BACK : 0x0408,
-	  FRONT_FACE : 0x0B46,
-	  FUNC_ADD : 0x8006,
-	  FUNC_REVERSE_SUBTRACT : 0x800B,
-	  FUNC_SUBTRACT : 0x800A,
-	  GENERATE_MIPMAP_HINT : 0x8192,
-	  GEQUAL : 0x0206,
-	  GREATER : 0x0204,
-	  GREEN_BITS : 0x0D53,
-	  HIGH_FLOAT : 0x8DF2,
-	  HIGH_INT : 0x8DF5,
-	  INCR : 0x1E02,
-	  INCR_WRAP : 0x8507,
-	  INT : 0x1404,
-	  INT_VEC2 : 0x8B53,
-	  INT_VEC3 : 0x8B54,
-	  INT_VEC4 : 0x8B55,
-	  INVALID_ENUM : 0x0500,
-	  INVALID_FRAMEBUFFER_OPERATION : 0x0506,
-	  INVALID_OPERATION : 0x0502,
-	  INVALID_VALUE : 0x0501,
-	  INVERT : 0x150A,
-	  KEEP : 0x1E00,
-	  LEQUAL : 0x0203,
-	  LESS : 0x0201,
-	  LINEAR : 0x2601,
-	  LINEAR_MIPMAP_LINEAR : 0x2703,
-	  LINEAR_MIPMAP_NEAREST : 0x2701,
-	  LINES : 0x0001,
-	  LINE_LOOP : 0x0002,
-	  LINE_STRIP : 0x0003,
-	  LINE_WIDTH : 0x0B21,
-	  LINK_STATUS : 0x8B82,
-	  LOW_FLOAT : 0x8DF0,
-	  LOW_INT : 0x8DF3,
-	  LUMINANCE : 0x1909,
-	  LUMINANCE_ALPHA : 0x190A,
-	  MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D,
-	  MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C,
-	  MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD,
-	  MAX_RENDERBUFFER_SIZE : 0x84E8,
-	  MAX_TEXTURE_IMAGE_UNITS : 0x8872,
-	  MAX_TEXTURE_SIZE : 0x0D33,
-	  MAX_VARYING_VECTORS : 0x8DFC,
-	  MAX_VERTEX_ATTRIBS : 0x8869,
-	  MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C,
-	  MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB,
-	  MAX_VIEWPORT_DIMS : 0x0D3A,
-	  MEDIUM_FLOAT : 0x8DF1,
-	  MEDIUM_INT : 0x8DF4,
-	  MIRRORED_REPEAT : 0x8370,
-	  NEAREST : 0x2600,
-	  NEAREST_MIPMAP_LINEAR : 0x2702,
-	  NEAREST_MIPMAP_NEAREST : 0x2700,
-	  NEVER : 0x0200,
-	  NICEST : 0x1102,
-	  NONE : 0,
-	  NOTEQUAL : 0x0205,
-	  NO_ERROR : 0,
-	  ONE : 1,
-	  ONE_MINUS_CONSTANT_ALPHA : 0x8004,
-	  ONE_MINUS_CONSTANT_COLOR : 0x8002,
-	  ONE_MINUS_DST_ALPHA : 0x0305,
-	  ONE_MINUS_DST_COLOR : 0x0307,
-	  ONE_MINUS_SRC_ALPHA : 0x0303,
-	  ONE_MINUS_SRC_COLOR : 0x0301,
-	  OUT_OF_MEMORY : 0x0505,
-	  PACK_ALIGNMENT : 0x0D05,
-	  POINTS : 0x0000,
-	  POLYGON_OFFSET_FACTOR : 0x8038,
-	  POLYGON_OFFSET_FILL : 0x8037,
-	  POLYGON_OFFSET_UNITS : 0x2A00,
-	  RED_BITS : 0x0D52,
-	  RENDERBUFFER : 0x8D41,
-	  RENDERBUFFER_ALPHA_SIZE : 0x8D53,
-	  RENDERBUFFER_BINDING : 0x8CA7,
-	  RENDERBUFFER_BLUE_SIZE : 0x8D52,
-	  RENDERBUFFER_DEPTH_SIZE : 0x8D54,
-	  RENDERBUFFER_GREEN_SIZE : 0x8D51,
-	  RENDERBUFFER_HEIGHT : 0x8D43,
-	  RENDERBUFFER_INTERNAL_FORMAT : 0x8D44,
-	  RENDERBUFFER_RED_SIZE : 0x8D50,
-	  RENDERBUFFER_STENCIL_SIZE : 0x8D55,
-	  RENDERBUFFER_WIDTH : 0x8D42,
-	  RENDERER : 0x1F01,
-	  REPEAT : 0x2901,
-	  REPLACE : 0x1E01,
-	  RGB : 0x1907,
-	  RGB565 : 0x8D62,
-	  RGB5_A1 : 0x8057,
-	  RGBA : 0x1908,
-	  RGBA4 : 0x8056,
-	  SAMPLER_2D : 0x8B5E,
-	  SAMPLER_CUBE : 0x8B60,
-	  SAMPLES : 0x80A9,
-	  SAMPLE_ALPHA_TO_COVERAGE : 0x809E,
-	  SAMPLE_BUFFERS : 0x80A8,
-	  SAMPLE_COVERAGE : 0x80A0,
-	  SAMPLE_COVERAGE_INVERT : 0x80AB,
-	  SAMPLE_COVERAGE_VALUE : 0x80AA,
-	  SCISSOR_BOX : 0x0C10,
-	  SCISSOR_TEST : 0x0C11,
-	  SHADER_TYPE : 0x8B4F,
-	  SHADING_LANGUAGE_VERSION : 0x8B8C,
-	  SHORT : 0x1402,
-	  SRC_ALPHA : 0x0302,
-	  SRC_ALPHA_SATURATE : 0x0308,
-	  SRC_COLOR : 0x0300,
-	  STATIC_DRAW : 0x88E4,
-	  STENCIL_ATTACHMENT : 0x8D20,
-	  STENCIL_BACK_FAIL : 0x8801,
-	  STENCIL_BACK_FUNC : 0x8800,
-	  STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802,
-	  STENCIL_BACK_PASS_DEPTH_PASS : 0x8803,
-	  STENCIL_BACK_REF : 0x8CA3,
-	  STENCIL_BACK_VALUE_MASK : 0x8CA4,
-	  STENCIL_BACK_WRITEMASK : 0x8CA5,
-	  STENCIL_BITS : 0x0D57,
-	  STENCIL_BUFFER_BIT : 0x00000400,
-	  STENCIL_CLEAR_VALUE : 0x0B91,
-	  STENCIL_FAIL : 0x0B94,
-	  STENCIL_FUNC : 0x0B92,
-	  STENCIL_INDEX : 0x1901,
-	  STENCIL_INDEX8 : 0x8D48,
-	  STENCIL_PASS_DEPTH_FAIL : 0x0B95,
-	  STENCIL_PASS_DEPTH_PASS : 0x0B96,
-	  STENCIL_REF : 0x0B97,
-	  STENCIL_TEST : 0x0B90,
-	  STENCIL_VALUE_MASK : 0x0B93,
-	  STENCIL_WRITEMASK : 0x0B98,
-	  STREAM_DRAW : 0x88E0,
-	  SUBPIXEL_BITS : 0x0D50,
-	  TEXTURE : 0x1702,
-	  TEXTURE0 : 0x84C0,
-	  TEXTURE1 : 0x84C1,
-	  TEXTURE10 : 0x84CA,
-	  TEXTURE11 : 0x84CB,
-	  TEXTURE12 : 0x84CC,
-	  TEXTURE13 : 0x84CD,
-	  TEXTURE14 : 0x84CE,
-	  TEXTURE15 : 0x84CF,
-	  TEXTURE16 : 0x84D0,
-	  TEXTURE17 : 0x84D1,
-	  TEXTURE18 : 0x84D2,
-	  TEXTURE19 : 0x84D3,
-	  TEXTURE2 : 0x84C2,
-	  TEXTURE20 : 0x84D4,
-	  TEXTURE21 : 0x84D5,
-	  TEXTURE22 : 0x84D6,
-	  TEXTURE23 : 0x84D7,
-	  TEXTURE24 : 0x84D8,
-	  TEXTURE25 : 0x84D9,
-	  TEXTURE26 : 0x84DA,
-	  TEXTURE27 : 0x84DB,
-	  TEXTURE28 : 0x84DC,
-	  TEXTURE29 : 0x84DD,
-	  TEXTURE3 : 0x84C3,
-	  TEXTURE30 : 0x84DE,
-	  TEXTURE31 : 0x84DF,
-	  TEXTURE4 : 0x84C4,
-	  TEXTURE5 : 0x84C5,
-	  TEXTURE6 : 0x84C6,
-	  TEXTURE7 : 0x84C7,
-	  TEXTURE8 : 0x84C8,
-	  TEXTURE9 : 0x84C9,
-	  TEXTURE_2D : 0x0DE1,
-	  TEXTURE_BINDING_2D : 0x8069,
-	  TEXTURE_BINDING_CUBE_MAP : 0x8514,
-	  TEXTURE_CUBE_MAP : 0x8513,
-	  TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516,
-	  TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518,
-	  TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A,
-	  TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515,
-	  TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517,
-	  TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519,
-	  TEXTURE_MAG_FILTER : 0x2800,
-	  TEXTURE_MIN_FILTER : 0x2801,
-	  TEXTURE_WRAP_S : 0x2802,
-	  TEXTURE_WRAP_T : 0x2803,
-	  TRIANGLES : 0x0004,
-	  TRIANGLE_FAN : 0x0006,
-	  TRIANGLE_STRIP : 0x0005,
-	  UNPACK_ALIGNMENT : 0x0CF5,
-	  UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243,
-	  UNPACK_FLIP_Y_WEBGL : 0x9240,
-	  UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241,
-	  UNSIGNED_BYTE : 0x1401,
-	  UNSIGNED_INT : 0x1405,
-	  UNSIGNED_SHORT : 0x1403,
-	  UNSIGNED_SHORT_4_4_4_4 : 0x8033,
-	  UNSIGNED_SHORT_5_5_5_1 : 0x8034,
-	  UNSIGNED_SHORT_5_6_5 : 0x8363,
-	  VALIDATE_STATUS : 0x8B83,
-	  VENDOR : 0x1F00,
-	  VERSION : 0x1F02,
-	  VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F,
-	  VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622,
-	  VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A,
-	  VERTEX_ATTRIB_ARRAY_POINTER : 0x8645,
-	  VERTEX_ATTRIB_ARRAY_SIZE : 0x8623,
-	  VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624,
-	  VERTEX_ATTRIB_ARRAY_TYPE : 0x8625,
-	  VERTEX_SHADER : 0x8B31,
-	  VIEWPORT : 0x0BA2,
-	  ZERO : 0
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class timestamp
-	 *
-	 * @class
-	 * @returns {vgl.timestamp}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	var m_globalModifiedTime = 0;
-
-	vgl.timestamp = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.timestamp)) {
-	    return new vgl.timestamp();
-	  }
-
-	  var m_modifiedTime = 0;
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update modified time
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.modified = function () {
-	    m_globalModifiedTime += 1;
-	    m_modifiedTime = m_globalModifiedTime;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get modified time
-	   *
-	   * @returns {number}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.getMTime = function () {
-	    return m_modifiedTime;
-	  };
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class object
-	 *
-	 * @class
-	 * @returns {vgl.object}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.object = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.object)) {
-	    return new vgl.object();
-	  }
-
-	  /** @private */
-	  var m_modifiedTime = vgl.timestamp();
-	  m_modifiedTime.modified();
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Mark the object modified
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.modified = function () {
-	    m_modifiedTime.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return modified time of the object
-	   *
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getMTime = function () {
-	    return m_modifiedTime.getMTime();
-	  };
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class event
-	 *
-	 * @class event
-	 * @returns {vgl.event}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.event = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.event)) {
-	    return new vgl.event();
-	  }
-	  vgl.object.call(this);
-
-	  return this;
-	};
-
-	inherit(vgl.event, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 *  types
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.event.keyPress = 'vgl.event.keyPress';
-	vgl.event.mousePress = 'vgl.event.mousePress';
-	vgl.event.mouseRelease = 'vgl.event.mouseRelease';
-	vgl.event.contextMenu = 'vgl.event.contextMenu';
-	vgl.event.configure = 'vgl.event.configure';
-	vgl.event.enable = 'vgl.event.enable';
-	vgl.event.mouseWheel = 'vgl.event.mouseWheel';
-	vgl.event.keyRelease = 'vgl.event.keyRelease';
-	vgl.event.middleButtonPress = 'vgl.event.middleButtonPress';
-	vgl.event.startInteraction = 'vgl.event.startInteraction';
-	vgl.event.enter = 'vgl.event.enter';
-	vgl.event.rightButtonPress = 'vgl.event.rightButtonPress';
-	vgl.event.middleButtonRelease = 'vgl.event.middleButtonRelease';
-	vgl.event.char = 'vgl.event.char';
-	vgl.event.disable = 'vgl.event.disable';
-	vgl.event.endInteraction = 'vgl.event.endInteraction';
-	vgl.event.mouseMove = 'vgl.event.mouseMove';
-	vgl.event.mouseOut = 'vgl.event.mouseOut';
-	vgl.event.expose = 'vgl.event.expose';
-	vgl.event.timer = 'vgl.event.timer';
-	vgl.event.leftButtonPress = 'vgl.event.leftButtonPress';
-	vgl.event.leave = 'vgl.event.leave';
-	vgl.event.rightButtonRelease = 'vgl.event.rightButtonRelease';
-	vgl.event.leftButtonRelease = 'vgl.event.leftButtonRelease';
-	vgl.event.click = 'vgl.event.click';
-	vgl.event.dblClick = 'vgl.event.dblClick';
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class boundingObject
-	 *
-	 * @class
-	 * @return {vgl.boundingObject}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.boundingObject = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.boundingObject)) {
-	    return new vgl.boundingObject();
-	  }
-	  vgl.object.call(this);
-
-	  /** @private */
-	  var m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-	      m_computeBoundsTimestamp = vgl.timestamp(),
-	      m_boundsDirtyTimestamp = vgl.timestamp();
-
-	  m_computeBoundsTimestamp.modified();
-	  m_boundsDirtyTimestamp.modified();
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get current bounds of the object
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bounds = function () {
-	    return m_bounds;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if bounds are valid
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasValidBounds = function (bounds) {
-	    if (bounds[0] === Number.MAX_VALUE ||
-	        bounds[1] === -Number.MAX_VALUE ||
-	        bounds[2] === Number.MAX_VALUE ||
-	        bounds[3] === -Number.MAX_VALUE ||
-	        bounds[4] === Number.MAX_VALUE ||
-	        bounds[5] === -Number.MAX_VALUE) {
-	      return false;
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set current bounds of the object
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setBounds = function (minX, maxX, minY, maxY, minZ, maxZ) {
-	    if (!this.hasValidBounds([minX, maxX, minY, maxY, minZ, maxZ])) {
-	      return;
-	    }
-
-	    m_bounds[0] = minX;
-	    m_bounds[1] = maxX;
-	    m_bounds[2] = minY;
-	    m_bounds[3] = maxY;
-	    m_bounds[4] = minZ;
-	    m_bounds[5] = maxZ;
-
-	    this.modified();
-	    m_computeBoundsTimestamp.modified();
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Reset bounds to default values
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetBounds = function () {
-	    m_bounds[0] = Number.MAX_VALUE;
-	    m_bounds[1] = -Number.MAX_VALUE;
-	    m_bounds[2] = Number.MAX_VALUE;
-	    m_bounds[3] = -Number.MAX_VALUE;
-	    m_bounds[4] = Number.MAX_VALUE;
-	    m_bounds[5] = -Number.MAX_VALUE;
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute bounds of the object
-	   *
-	   * Should be implemented by the concrete class
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return bounds computation modification time
-	   *
-	   * @returns {vgl.timestamp}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBoundsTimestamp = function () {
-	    return m_computeBoundsTimestamp;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return bounds dirty timestamp
-	   *
-	   * @returns {vgl.timestamp}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.boundsDirtyTimestamp = function () {
-	    return m_boundsDirtyTimestamp;
-	  };
-
-	  this.resetBounds();
-
-	  return this;
-	};
-
-	vgl.boundingObject.ReferenceFrame = {
-	  'Relative' : 0,
-	  'Absolute' : 1
-	};
-
-	inherit(vgl.boundingObject, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class node
-	 *
-	 * @class
-	 * @returns {vgl.node}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.node = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.node)) {
-	    return new vgl.node();
-	  }
-	  vgl.boundingObject.call(this);
-
-	  /** @private */
-	  var m_parent = null,
-	      m_material = null,
-	      m_visible = true,
-	      m_overlay = false;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Accept visitor for scene traversal
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.accept = function (visitor) {
-	    visitor.visit(this);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return active material used by the node
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.material = function () {
-	    return m_material;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set material to be used the node
-	   *
-	   * @param material
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setMaterial = function (material) {
-	    if (material !== m_material) {
-	      m_material = material;
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if the node is visible or node
-	   *
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.visible = function () {
-	    return m_visible;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Turn ON/OFF visibility of the node
-	   *
-	   * @param flag
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setVisible = function (flag) {
-	    if (flag !== m_visible) {
-	      m_visible = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return current parent of the node
-	   *
-	   * @returns {null}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parent = function () {
-	    return m_parent;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set parent of the node
-	   *
-	   * @param parent
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setParent = function (parent) {
-	    if (parent !== m_parent) {
-	      if (m_parent !== null) {
-	        m_parent.removeChild(this);
-	      }
-	      m_parent = parent;
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if the node is an overlay node
-	   *
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.overlay = function () {
-	    return m_overlay;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set if the node is an overlay node or not
-	   *
-	   * @param flag
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setOverlay = function (flag) {
-	    if (m_overlay !== flag) {
-	      m_overlay = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /*
-	   * Traverse parent and their parent and so on
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.ascend = function (visitor) {
-	    visitor = visitor; /* unused parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Traverse children
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.traverse = function (visitor) {
-	    visitor = visitor; /* unused parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Mark that the bounds are modified
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.boundsModified = function () {
-	    // @todo Implement this
-	    this.boundsDirtyTimestamp().modified();
-
-	    if (m_parent !== null) {
-	      m_parent.boundsModified();
-	    }
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.node, vgl.boundingObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class groupNode
-	 *
-	 * @class
-	 * @returns {vgl.groupNode}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.groupNode = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.groupNode)) {
-	    return new vgl.groupNode();
-	  }
-	  vgl.node.call(this);
-
-	  var m_children = [];
-
-	  // Reference to base class methods
-	  this.b_setVisible = this.setVisible;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Turn on / off visibility
-	   *
-	   * @param flag
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setVisible = function (flag) {
-	    var i;
-
-	    if (this.b_setVisible(flag) !== true) {
-	      return false;
-	    }
-
-	    for (i = 0; i < m_children.length; i += 1) {
-	      m_children[i].setVisible(flag);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Make the incoming node as child of the group node
-	   *
-	   * @param childNode
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addChild = function (childNode) {
-	    if (childNode instanceof vgl.node) {
-	      if (m_children.indexOf(childNode) === -1) {
-	        childNode.setParent(this);
-	        m_children.push(childNode);
-	        this.boundsDirtyTimestamp().modified();
-	        return true;
-	      }
-	      return false;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove parent-child relationship between the group and incoming node
-	   *
-	   * @param childNode
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeChild = function (childNode) {
-	    if (childNode.parent() === this) {
-	      var index = m_children.indexOf(childNode);
-	      if (index >= 0) {
-	        m_children.splice(index, 1);
-	        childNode.setParent(null);
-	        this.boundsDirtyTimestamp().modified();
-	        return true;
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove parent-child relationship between child nodes and the group node
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeChildren = function () {
-	    while (m_children.length) {
-	      this.removeChild(m_children[0]);
-	    }
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return children of this group node
-	   *
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.children = function () {
-	    return m_children;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return true if this group node has node as a child, false otherwise.
-	   *
-	   * @param node
-	   * @returns {bool}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasChild = function (node) {
-	    var i = 0, child = false;
-
-	    for (i = 0; i < m_children.length; i += 1) {
-	      if (m_children[i] === node) {
-	        child = true;
-	        break;
-	      }
-	    }
-
-	    return child;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Accept a visitor and traverse the scene tree
-	   *
-	   * @param visitor
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.accept = function (visitor) {
-	    visitor.visit(this);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Traverse the scene
-	   *
-	   * @param visitor
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.traverse = function (visitor) {
-	    switch (visitor.type()) {
-	      case visitor.UpdateVisitor:
-	        this.traverseChildrenAndUpdateBounds(visitor);
-	        break;
-	      case visitor.CullVisitor:
-	        this.traverseChildren(visitor);
-	        break;
-	      default:
-	        break;
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Traverse all of the children and update the bounds for each
-	   *
-	   * @param visitor
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.traverseChildrenAndUpdateBounds = function (visitor) {
-	    var i;
-
-	    if (this.parent() && this.boundsDirtyTimestamp().getMTime() >
-	      this.computeBoundsTimestamp().getMTime()) {
-	      // Flag parents bounds dirty.
-	      this.parent().boundsDirtyTimestamp().modified();
-	    }
-
-	    this.computeBounds();
-
-	    if (visitor.mode() === visitor.TraverseAllChildren) {
-	      for (i = 0; i < m_children.length; i += 1) {
-	        m_children[i].accept(visitor);
-	        this.updateBounds(m_children[i]);
-	      }
-	    }
-
-	    this.computeBoundsTimestamp().modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Traverse children of the group node
-	   *
-	   * @param visitor
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.traverseChildren = function (visitor) {
-	    var i;
-
-	    if (visitor.mode() === visitor.TraverseAllChildren) {
-	      for (i = 0; i < m_children.length; i += 1) {
-	        m_children[i].accept(visitor);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute bounds for the group node
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	    var i = 0;
-
-	    if (this.computeBoundsTimestamp().getMTime() >
-	        this.boundsDirtyTimestamp().getMTime()) {
-	      return;
-	    }
-
-	    for (i = 0; i < m_children.length; i += 1) {
-	      this.updateBounds(m_children[i]);
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update bounds for the group node
-	   *
-	   * This method is used internally to update bounds of the group node by
-	   * traversing each of its child.
-	   *
-	   * @param child
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.updateBounds = function (child) {
-	    // FIXME: This check should not be required and possibly is incorrect
-	    if (child.overlay()) {
-	      return;
-	    }
-
-	    // Make sure that child bounds are up to date
-	    child.computeBounds();
-
-	    var bounds = this.bounds(),
-	        childBounds = child.bounds(),
-	        istep = 0,
-	        jstep = 0,
-	        i;
-
-	    for (i = 0; i < 3; i += 1) {
-	      istep = i * 2;
-	      jstep = i * 2 + 1;
-	      if (childBounds[istep] < bounds[istep]) {
-	        bounds[istep] = childBounds[istep];
-	      }
-	      if (childBounds[jstep] > bounds[jstep]) {
-	        bounds[jstep] = childBounds[jstep];
-	      }
-	    }
-
-	    this.setBounds(bounds[0], bounds[1], bounds[2], bounds[3],
-	                   bounds[4], bounds[5]);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.groupNode, vgl.node);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec3, mat4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class actor
-	 *
-	 * @class
-	 * @returns {vgl.actor}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.actor = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.actor)) {
-	    return new vgl.actor();
-	  }
-	  vgl.node.call(this);
-
-	  /** @private */
-	  var m_this = this,
-	      m_transformMatrix = mat4.create(),
-	      m_referenceFrame = vgl.boundingObject.ReferenceFrame.Relative,
-	      m_mapper = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get transformation matrix used by the actor
-	   *
-	   * @returns {mat4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.matrix = function () {
-	    return m_transformMatrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set transformation matrix for the actor
-	   *
-	   * @param {mat4} 4X4 transformation matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setMatrix = function (tmatrix) {
-	    if (tmatrix !== m_transformMatrix) {
-	      m_transformMatrix = tmatrix;
-	      m_this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get reference frame for the transformations
-	   *
-	   * @returns {String} Possible values are Absolute or Relative
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.referenceFrame = function () {
-	    return m_referenceFrame;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set reference frame for the transformations
-	   *
-	   * @param {vgl.boundingObject.ReferenceFrame}
-	   * referenceFrame Possible values are (Absolute | Relative)
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setReferenceFrame = function (referenceFrame) {
-	    if (referenceFrame !== m_referenceFrame) {
-	      m_referenceFrame = referenceFrame;
-	      m_this.modified();
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return mapper where actor gets it behavior and data
-	   *
-	   * @returns {vgl.mapper}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.mapper = function () {
-	    return m_mapper;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Connect an actor to its data source
-	   *
-	   * @param {vgl.mapper}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setMapper = function (mapper) {
-	    if (mapper !== m_mapper) {
-	      m_mapper = mapper;
-	      m_this.boundsModified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * @todo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.accept = function (visitor) {
-	    visitor = visitor; /* ignore this parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * @todo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.ascend = function (visitor) {
-	    visitor = visitor; /* ignore this parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute object space to world space matrix
-	   * @todo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeLocalToWorldMatrix = function (matrix, visitor) {
-	    matrix = matrix; /* ignore this parameter */
-	    visitor = visitor; /* ignore this parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute world space to object space matrix
-	   * @todo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeWorldToLocalMatrix = function (matrix, visitor) {
-	    matrix = matrix; /* ignore this parameter */
-	    visitor = visitor; /* ignore this parameter */
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute actor bounds
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	    if (m_mapper === null || m_mapper === undefined) {
-	      m_this.resetBounds();
-	      return;
-	    }
-
-	    var computeBoundsTimestamp = m_this.computeBoundsTimestamp(),
-	        mapperBounds, minPt, maxPt, newBounds;
-
-	    if (m_this.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime() ||
-	      m_mapper.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime()) {
-
-	      m_mapper.computeBounds();
-	      mapperBounds = m_mapper.bounds();
-
-	      minPt = [mapperBounds[0], mapperBounds[2], mapperBounds[4]];
-	      maxPt = [mapperBounds[1], mapperBounds[3], mapperBounds[5]];
-
-	      vec3.transformMat4(minPt, minPt, m_transformMatrix);
-	      vec3.transformMat4(maxPt, maxPt, m_transformMatrix);
-
-	      newBounds = [
-	        minPt[0] > maxPt[0] ? maxPt[0] : minPt[0],
-	        minPt[0] > maxPt[0] ? minPt[0] : maxPt[0],
-	        minPt[1] > maxPt[1] ? maxPt[1] : minPt[1],
-	        minPt[1] > maxPt[1] ? minPt[1] : maxPt[1],
-	        minPt[2] > maxPt[2] ? maxPt[2] : minPt[2],
-	        minPt[2] > maxPt[2] ? minPt[2] : maxPt[2]
-	      ];
-
-	      m_this.setBounds(newBounds[0], newBounds[1],
-	                     newBounds[2], newBounds[3],
-	                     newBounds[4], newBounds[5]);
-
-	      computeBoundsTimestamp.modified();
-	    }
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.actor, vgl.node);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Freeze javascript object
-	 *
-	 * @param obj
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.freezeObject = function (obj) {
-	  'use strict';
-
-	  /**
-	   * Freezes an object, using Object.freeze if available, otherwise returns
-	   * the object unchanged.  This function should be used in setup code to prevent
-	   * errors from completely halting JavaScript execution in legacy browsers.
-	   *
-	   * @exports freezeObject
-	   */
-	  var freezedObject = Object.freeze ? Object.freeze(obj) : undefined;
-	  if (typeof freezedObject === 'undefined') {
-	    return obj;
-	  }
-
-	  return freezedObject;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Returns the first parameter if not undefined,
-	 * otherwise the second parameter.
-	 *
-	 * @class
-	 * @returns {vgl.defaultValue}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.defaultValue = function (a, b) {
-	  'use strict';
-
-	  if (typeof a !== 'undefined') {
-	    return a;
-	  }
-	  return b;
-	};
-
-	vgl.defaultValue.EMPTY_OBJECT = vgl.freezeObject({});
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class graphicsObject
-	 *
-	 * @class
-	 * @param type
-	 * @returns {vgl.graphicsObject}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.graphicsObject = function (type) {
-	  'use strict';
-
-	  type = type; /* unused parameter */
-	  if (!(this instanceof vgl.graphicsObject)) {
-	    return new vgl.graphicsObject();
-	  }
-	  vgl.object.call(this);
-
-	  var m_this = this;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Setup (initialize) the object
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._setup = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove any resources acquired before deletion
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._cleanup = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind and activate
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bind = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind and deactivate
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   *
-	   * TODO: Change it to unbind (simple)
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.undoBind = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the object
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove the object and release its graphics resources
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.remove = function (renderState) {
-	    m_this._cleanup(renderState);
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.graphicsObject, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, Uint16Array*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of geojson reader
-	 *
-	 * This contains code that reads a geoJSON file and produces rendering
-	 * primitives from it.
-	 *
-	 * @class
-	 * @returns {vgl.geojsonReader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.geojsonReader = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.geojsonReader)) {
-	    return new vgl.geojsonReader();
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read scalars
-	   *
-	   * @param coordinates
-	   * @param geom
-	   * @param size_estimate
-	   * @param idx
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readScalars = function (coordinates, geom, size_estimate, idx) {
-	    var array = null,
-	        s = null,
-	        r = null,
-	        g = null,
-	        b = null;
-
-	    if (this.m_scalarFormat === 'values' && coordinates.length === 4) {
-	      s = coordinates[3];
-	      array = geom.sourceData(vgl.vertexAttributeKeys.Scalar);
-
-	      if (!array) {
-	        array = new vgl.sourceDataSf();
-	        if (this.m_scalarRange) {
-	          array.setScalarRange(this.m_scalarRange[0], this.m_scalarRange[1]);
-	        }
-	        if (size_estimate !== undefined) {
-	          //array.length = size_estimate; //no, slow on Safari
-	          array.data().length = size_estimate;
-	        }
-	        geom.addSource(array);
-	      }
-	      if (size_estimate === undefined) {
-	        array.pushBack(s);
-	      } else {
-	        array.insertAt(idx, s);
-	      }
-	    } else if (this.m_scalarFormat === 'rgb' && coordinates.length === 6) {
-	      array = geom.sourceData(vgl.vertexAttributeKeys.Color);
-	      if (!array) {
-	        array = new vgl.sourceDataC3fv();
-	        if (size_estimate !== undefined) {
-	          array.length = size_estimate * 3;
-	        }
-	        geom.addSource(array);
-	      }
-	      r = coordinates[3];
-	      g = coordinates[4];
-	      b = coordinates[5];
-	      if (size_estimate === undefined) {
-	        array.pushBack([r, g, b]);
-	      } else {
-	        array.insertAt(idx, [r, g, b]);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read point data
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readPoint = function (coordinates) {
-	    var geom = new vgl.geometryData(),
-	        vglpoints = new vgl.points(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        indices = new Uint16Array(1),
-	        x = null,
-	        y = null,
-	        z = null,
-	        i = null;
-
-	    geom.addSource(vglcoords);
-	    for (i = 0; i < 1; i += 1) {
-	      indices[i] = i;
-
-	      x = coordinates[0];
-	      y = coordinates[1];
-	      z = 0.0;
-	      if (coordinates.length > 2) {
-	        z = coordinates[2];
-	      }
-
-	      //console.log('read ' + x + ',' + y + ',' + z);
-	      vglcoords.pushBack([x, y, z]);
-
-	      //attributes
-	      this.readScalars(coordinates, geom);
-	    }
-
-	    vglpoints.setIndices(indices);
-	    geom.addPrimitive(vglpoints);
-	    geom.setName('aPoint');
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read multipoint data
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readMultiPoint = function (coordinates) {
-	    var geom = new vgl.geometryData(),
-	        vglpoints = new vgl.points(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        indices = new Uint16Array(coordinates.length),
-	        pntcnt = 0,
-	        estpntcnt = coordinates.length,
-	        x = null,
-	        y = null,
-	        z = null,
-	        i;
-
-	    //preallocate with size estimate
-	    vglcoords.data().length = estpntcnt * 3; //x,y,z
-
-	    for (i = 0; i < coordinates.length; i += 1) {
-	      indices[i] = i;
-	      x = coordinates[i][0];
-	      y = coordinates[i][1];
-	      z = 0.0;
-	      if (coordinates[i].length > 2) {
-	        z = coordinates[i][2];
-	      }
-
-	      //console.log('read ' + x + ',' + y + ',' + z);
-	      vglcoords.insertAt(pntcnt, [x, y, z]);
-
-	      //attributes
-	      this.readScalars(coordinates[i], geom, estpntcnt, pntcnt);
-
-	      pntcnt += 1;
-	    }
-
-	    vglpoints.setIndices(indices);
-	    geom.addPrimitive(vglpoints);
-	    geom.addSource(vglcoords);
-	    geom.setName('manyPoints');
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read line string data
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readLineString = function (coordinates) {
-	    var geom = new vgl.geometryData(),
-	        vglline = new vgl.lineStrip(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        indices = [],
-	        i = null,
-	        x = null,
-	        y = null,
-	        z = null;
-
-	    vglline.setIndicesPerPrimitive(coordinates.length);
-
-	    for (i = 0; i < coordinates.length; i += 1) {
-	      indices.push(i);
-	      x = coordinates[i][0];
-	      y = coordinates[i][1];
-	      z = 0.0;
-	      if (coordinates[i].length > 2) {
-	        z = coordinates[i][2];
-	      }
-
-	      //console.log('read ' + x + ',' + y + ',' + z);
-	      vglcoords.pushBack([x, y, z]);
-
-	      //attributes
-	      this.readScalars(coordinates[i], geom);
-	    }
-
-	    vglline.setIndices(indices);
-	    geom.addPrimitive(vglline);
-	    geom.addSource(vglcoords);
-	    geom.setName('aLineString');
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read multi line string
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readMultiLineString = function (coordinates) {
-	    var geom = new vgl.geometryData(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        pntcnt = 0,
-	        //lines should be at least 2 verts long, underest OK
-	        estpntcnt = coordinates.length * 2,
-	        i = null,
-	        j = null,
-	        x = null,
-	        y = null,
-	        z = null,
-	        indices = null,
-	        vglline = null,
-	        thisLineLength = null;
-
-	    // Preallocate with size estimate
-	    vglcoords.data().length = estpntcnt * 3; //x,y,z
-
-	    for (j = 0; j < coordinates.length; j += 1) {
-	      indices = [];
-	      //console.log('getting line ' + j);
-	      vglline = new vgl.lineStrip();
-	      thisLineLength = coordinates[j].length;
-	      vglline.setIndicesPerPrimitive(thisLineLength);
-	      for (i = 0; i < thisLineLength; i += 1) {
-	        indices.push(pntcnt);
-	        x = coordinates[j][i][0];
-	        y = coordinates[j][i][1];
-	        z = 0.0;
-	        if (coordinates[j][i].length > 2) {
-	          z = coordinates[j][i][2];
-	        }
-
-	        //console.log('read ' + x + ',' + y + ',' + z);
-	        vglcoords.insertAt(pntcnt, [x, y, z]);
-
-	        //attributes
-	        this.readScalars(coordinates[j][i], geom, estpntcnt * 2, pntcnt);
-
-	        pntcnt += 1;
-	      }
-
-	      vglline.setIndices(indices);
-	      geom.addPrimitive(vglline);
-	    }
-
-	    geom.setName('aMultiLineString');
-	    geom.addSource(vglcoords);
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read polygon data
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readPolygon = function (coordinates) {
-	    //TODO: ignoring holes given in coordinates[1...]
-	    //TODO: ignoring convex
-	    //TODO: implement ear clipping in VGL instead of this to handle both
-	    var geom = new vgl.geometryData(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        x = null,
-	        y = null,
-	        z = null,
-	        thisPolyLength = coordinates[0].length,
-	        vl = 1,
-	        i = null,
-	        indices = null,
-	        vgltriangle = null;
-
-	    for (i = 0; i < thisPolyLength; i += 1) {
-	      x = coordinates[0][i][0];
-	      y = coordinates[0][i][1];
-	      z = 0.0;
-	      if (coordinates[0][i].length > 2) {
-	        z = coordinates[0][i][2];
-	      }
-
-	      //console.log('read ' + x + ',' + y + ',' + z);
-	      vglcoords.pushBack([x, y, z]);
-
-	      //attributes
-	      this.readScalars(coordinates[0][i], geom);
-
-	      if (i > 1) {
-	        //console.log('Cutting new triangle 0,'+ vl+ ','+ i);
-	        indices = new Uint16Array([0, vl, i]);
-	        vgltriangle = new vgl.triangles();
-	        vgltriangle.setIndices(indices);
-	        geom.addPrimitive(vgltriangle);
-	        vl = i;
-	      }
-	    }
-
-	    geom.setName('POLY');
-	    geom.addSource(vglcoords);
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read multi polygon data
-	   *
-	   * @param coordinates
-	   * @returns {vgl.geometryData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readMultiPolygon = function (coordinates) {
-	    var geom = new vgl.geometryData(),
-	        vglcoords = new vgl.sourceDataP3fv(),
-	        ccount = 0,
-	        numPolys = coordinates.length,
-	        pntcnt = 0,
-	        estpntcnt = numPolys * 3, // assume triangles, underest is fine
-	        vgltriangle = new vgl.triangles(),
-	        indexes = [],
-	        i = null,
-	        j = null,
-	        x = null,
-	        y = null,
-	        z = null,
-	        thisPolyLength = null,
-	        vf = null,
-	        vl = null,
-	        flip = null,
-	        flipped = false,
-	        tcount = 0;
-
-	    //var time1 = new Date().getTime()
-	    //var a = 0;
-	    //var b = 0;
-	    //var c = 0;
-	    //var d = 0;
-
-	    //preallocate with size estimate
-	    vglcoords.data().length = numPolys * 3; //x,y,z
-	    for (j = 0; j < numPolys; j += 1) {
-	      //console.log('getting poly ' + j);
-
-	      thisPolyLength = coordinates[j][0].length;
-	      vf = ccount;
-	      vl = ccount + 1;
-	      flip = [false, false, false];
-	      for (i = 0; i < thisPolyLength; i += 1) {
-	        //var timea = new Date().getTime()
-
-	        x = coordinates[j][0][i][0];
-	        y = coordinates[j][0][i][1];
-	        z = 0.0;
-	        if (coordinates[j][0][i].length > 2) {
-	          z = coordinates[j][0][i][2];
-	        }
-	        flipped = false;
-	        if (x > 180) {
-	          flipped = true;
-	          x = x - 360;
-	        }
-	        if (i === 0) {
-	          flip[0] = flipped;
-	        } else {
-	          flip[1 + (i - 1) % 2] = flipped;
-	        }
-	        //var timeb = new Date().getTime();
-	        //console.log('read ' + x + ',' + y + ',' + z);
-
-	        vglcoords.insertAt(pntcnt, [x, y, z]);
-	        //var timec = new Date().getTime();
-
-	        //attributes
-	        this.readScalars(coordinates[j][0][i], geom, estpntcnt, pntcnt);
-	        pntcnt += 1;
-	        //var timed = new Date().getTime()
-
-	        if (i > 1) {
-	          //if (vl < 50) {
-	          //console.log('Cutting new triangle ' + tcount + ':' + vf + ',' +
-	          //            vl + ',' + ccount);
-	          //console.log(indexes);
-	          //}
-	          if (flip[0] === flip[1] && flip[1] === flip[2]) {
-	            //indexes = indexes.concat([vf,vl,ccount]); //no, very slow in Safari
-	            indexes[tcount * 3 + 0] = vf;
-	            indexes[tcount * 3 + 1] = vl;
-	            indexes[tcount * 3 + 2] = ccount;
-	            tcount += 1;
-	          }
-	          //else {
-	          //  //TODO: duplicate triangles that straddle boundary on either side
-	          //}
-
-	          vl = ccount;
-	        }
-	        ccount += 1;
-	        //var timee = new Date().getTime()
-	        //a = a + (timeb-timea)
-	        //b = b + (timec-timeb)
-	        //c = c + (timed-timec)
-	        //d = d + (timee-timed)
-	      }
-	    }
-	    vgltriangle.setIndices(indexes);
-	    geom.addPrimitive(vgltriangle);
-
-	    //console.log('NUMPOLYS ' + pntcnt);
-	    //console.log('RMP: ', a, ',', b, ',', c, ',', d)
-	    //var time2 = new Date().getTime()
-
-	    geom.setName('aMultiPoly');
-	    geom.addSource(vglcoords);
-	    //var time3 = new Date().getTime()
-	    //console.log('RMP: ', time2-time1, ',', time3-time2)
-
-	    return geom;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * @param object
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readGJObjectInt = function (object) {
-	    if (!object.hasOwnProperty('type')) {
-	      //console.log('uh oh, not a geojson object');
-	      return null;
-	    }
-
-	    //look for properties type annotation
-	    if (object.properties &&
-	        object.properties.ScalarFormat &&
-	        object.properties.ScalarFormat === 'values') {
-	      this.m_scalarFormat = 'values';
-	      if (object.properties.ScalarRange) {
-	        this.m_scalarRange = object.properties.ScalarRange;
-	      }
-	    }
-	    if (object.properties &&
-	        object.properties.ScalarFormat &&
-	        object.properties.ScalarFormat === 'rgb') {
-	      this.m_scalarFormat = 'rgb';
-	    }
-
-	    //TODO: ignoring 'crs' and 'bbox' and misc meta data on all of these,
-	    //best to handle as references into original probably
-	    var ret,
-	        type = object.type,
-	        next = null,
-	        nextset = null,
-	        i = null;
-
-	    switch (type) {
-	      case 'Point':
-	        //console.log('parsed Point');
-	        ret = this.readPoint(object.coordinates);
-	        break;
-	      case 'MultiPoint':
-	        //console.log('parsed MultiPoint');
-	        ret = this.readMultiPoint(object.coordinates);
-	        break;
-	      case 'LineString':
-	        //console.log('parsed LineString');
-	        ret = this.readLineString(object.coordinates);
-	        break;
-	      case 'MultiLineString':
-	        //console.log('parsed MultiLineString');
-	        ret = this.readMultiLineString(object.coordinates);
-	        break;
-	      case 'Polygon':
-	        //console.log('parsed Polygon');
-	        ret = this.readPolygon(object.coordinates);
-	        break;
-	      case 'MultiPolygon':
-	        //console.log('parsed MultiPolygon');
-	        ret = this.readMultiPolygon(object.coordinates);
-	        break;
-	      case 'GeometryCollection':
-	        //console.log('parsed GeometryCollection');
-	        nextset = [];
-	        for (i = 0; i < object.geometries.length; i += 1) {
-	          next = this.readGJObject(object.geometries[i]);
-	          nextset.push(next);
-	        }
-	        ret = nextset;
-	        break;
-	      case 'Feature':
-	        //console.log('parsed Feature');
-	        next = this.readGJObject(object.geometry);
-	        ret = next;
-	        break;
-	      case 'FeatureCollection':
-	        //console.log('parsed FeatureCollection');
-	        nextset = [];
-	        for (i = 0; i < object.features.length; i += 1) {
-	          next = this.readGJObject(object.features[i]);
-	          nextset.push(next);
-	        }
-	        ret = nextset;
-	        break;
-	      default:
-	        console.log('Don\'t understand type ' + type);
-	        ret = null;
-	        break;
-	    }
-	    return ret;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * @param object
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readGJObject = function (object) {
-	    //var time1, time2;
-	    var ret;
-	    //time1 = new Date().getTime()
-	    ret = this.readGJObjectInt(object);
-	    //time2 = new Date().getTime()
-	    //console.log('ELAPSED: ', time2-time1)
-	    return ret;
-	  };
-
-	  /**
-	   * Linearize geometries
-	   *
-	   * @param geoms
-	   * @param geom
-	   */
-	  this.linearizeGeoms = function (geoms, geom) {
-	    var i = null;
-
-	    if (Object.prototype.toString.call(geom) === '[object Array]') {
-	      for (i = 0; i < geom.length; i += 1) {
-	        this.linearizeGeoms(geoms, geom[i]);
-	      }
-	    } else {
-	      geoms.push(geom);
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Read geometries from geojson object
-	   *
-	   * @param object
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readGeomObject = function (object) {
-	    var geom,
-	        geoms = [];
-
-	    geom = this.readGJObject(object);
-	    this.linearizeGeoms(geoms, geom);
-	    return geoms;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Given a buffer get rendering primitives
-	   *
-	   * @param buffer
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getPrimitives = function (buffer) {
-	    //console.log('Parsing geoJSON');
-	    if (!buffer) {
-	      return [];
-	    }
-
-	    var obj = JSON.parse(buffer),
-	        geom = this.readGJObject(obj),
-	        geoms = [];
-
-	    this.m_scalarFormat = 'none';
-	    this.m_scalarRange = null;
-
-	    this.linearizeGeoms(geoms, geom);
-
-	    return { 'geoms': geoms,
-	             'scalarFormat': this.m_scalarFormat,
-	             'scalarRange': this.m_scalarRange };
-	  };
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	vgl.data = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.data)) {
-	    return new vgl.data();
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return data type. Should be implemented by the derived class
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.type = function () {
-	  };
-	};
-
-	vgl.data.raster = 0;
-	vgl.data.point = 1;
-	vgl.data.lineString = 2;
-	vgl.data.polygon = 3;
-	vgl.data.geometry = 10;
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, Uint16Array, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class primitive
-	 *
-	 * @class
-	 * @return {vgl.primitive}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.primitive = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.primitive)) {
-	    return new vgl.primitive();
-	  }
-
-	  /** @private */
-	  var m_indicesPerPrimitive = 0,
-	      m_primitiveType = 0,
-	      m_indicesValueType = 0,
-	      m_indices = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get indices of the primitive
-	   *
-	   * @returns {null}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.indices = function () {
-	    return m_indices;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create indices array for the primitive
-	   * @param type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.createIndices = function (type) {
-	    void type;
-	    // TODO Check for the type
-	    m_indices = new Uint16Array();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return the number of indices
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.numberOfIndices = function () {
-	    return m_indices.length;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return size of indices in bytes
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.sizeInBytes = function () {
-	    return m_indices.length * Uint16Array.BYTES_PER_ELEMENT;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /*
-	   * Return primitive type g
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.primitiveType = function () {
-	    return m_primitiveType;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set primitive type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPrimitiveType = function (type) {
-	    m_primitiveType = type;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return count of indices that form a primitives
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.indicesPerPrimitive = function () {
-	    return m_indicesPerPrimitive;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set count of indices that form a primitive
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setIndicesPerPrimitive = function (count) {
-	    m_indicesPerPrimitive = count;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return indices value type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.indicesValueType = function () {
-	    return m_indicesValueType;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set indices value type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setIndicesValueType = function (type) {
-	    m_indicesValueType = type;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set indices from a array
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setIndices = function (indicesArray) {
-	    // TODO Check for the type
-	    m_indices = new Uint16Array(indicesArray);
-	  };
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class triangleStrip
-	 *
-	 * @returns {vgl.triangleStrip}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.triangleStrip = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.triangleStrip)) {
-	    return new vgl.triangleStrip();
-	  }
-
-	  vgl.primitive.call(this);
-
-	  this.setPrimitiveType(vgl.GL.TRIANGLE_STRIP);
-	  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
-	  this.setIndicesPerPrimitive(3);
-
-	  return this;
-	};
-
-	inherit(vgl.triangleStrip, vgl.primitive);
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class triangles
-	 *
-	 * @returns {vgl.triangles}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.triangles = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.triangles)) {
-	    return new vgl.triangles();
-	  }
-	  vgl.primitive.call(this);
-
-	  this.setPrimitiveType(vgl.GL.TRIANGLES);
-	  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
-	  this.setIndicesPerPrimitive(3);
-
-	  return this;
-	};
-
-	inherit(vgl.triangles, vgl.primitive);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * create a instance of lines primitive type
-	 *
-	 * @returns {vgl.lines}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.lines = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.lines)) {
-	    return new vgl.lines();
-	  }
-	  vgl.primitive.call(this);
-
-	  this.setPrimitiveType(vgl.GL.LINES);
-	  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
-	  this.setIndicesPerPrimitive(2);
-
-	  return this;
-	};
-	inherit(vgl.lines, vgl.primitive);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * create a instance of line strip primitive type
-	 *
-	 * @returns {vgl.lineStrip}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.lineStrip = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.lineStrip)) {
-	    return new vgl.lineStrip();
-	  }
-	  vgl.primitive.call(this);
-
-	  this.setPrimitiveType(vgl.GL.LINE_STRIP);
-	  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
-	  this.setIndicesPerPrimitive(2);
-
-	  return this;
-	};
-	inherit(vgl.lineStrip, vgl.primitive);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class points
-	 *
-	 * @returns {vgl.points}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.points = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.points)) {
-	    return new vgl.points();
-	  }
-	  vgl.primitive.call(this);
-
-	  this.setPrimitiveType(vgl.GL.POINTS);
-	  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
-	  this.setIndicesPerPrimitive(1);
-
-	  return this;
-	};
-
-	inherit(vgl.points, vgl.primitive);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class vertexDataP3f
-	 *
-	 * @returns {vgl.vertexDataP3f}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.vertexDataP3f = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.vertexDataP3f)) {
-	    return new vgl.vertexDataP3f();
-	  }
-
-	  /** @private */
-	  this.m_position = [];
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class vertexDataP3N3f
-	 *
-	 * @class
-	 * @returns {vgl.vertexDataP3N3f}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.vertexDataP3N3f = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.vertexDataP3N3f)) {
-	    return new vgl.vertexDataP3N3f();
-	  }
-
-	  this.m_position = [];
-	  this.m_normal = [];
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class vertexDataP3T3f
-	 *
-	 * @class
-	 * @returns {vgl.vertexDataP3T3f}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.vertexDataP3T3f = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.vertexDataP3T3f)) {
-	    return new vgl.vertexDataP3T3f();
-	  }
-
-	  this.m_position = [];
-	  this.m_texCoordinate = [];
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceData
-	 * @class
-	 * @returns {vgl.sourceData}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceData = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceData)) {
-	    return new vgl.sourceData(arg);
-	  }
-
-	  arg = arg || {};
-	  var m_attributesMap = {},
-	      m_data = [],
-	      m_name = arg.name || 'Source ' + new Date().toISOString(),
-
-	      ////////////////////////////////////////////////////////////////////////
-	      /**
-	       * Attribute data for the source
-	       */
-	      ////////////////////////////////////////////////////////////////////////
-	      vglAttributeData = function () {
-	        // Number of components per group
-	        // Type of data type (GL_FLOAT etc)
-	        this.m_numberOfComponents = 0;
-	        // Size of data type
-	        this.m_dataType = 0;
-	        this.m_dataTypeSize = 0;
-	        // Specifies whether fixed-point data values should be normalized
-	        // (true) or converted directly as fixed-point values (false)
-	        // when they are accessed.
-	        this.m_normalized = false;
-	        // Strides for each attribute.
-	        this.m_stride = 0;
-	        // Offset
-	        this.m_offset = 0;
-	      };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return raw data for this source
-	   *
-	   * @returns {Array or Float32Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.data = function () {
-	    return m_data;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return raw data for this source
-	   *
-	   * @returns {Array or Float32Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getData = function () {
-	    return this.data();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * If the raw data is not a Float32Array, convert it to one.  Then, return
-	   * raw data for this source
-	   *
-	   * @returns {Float32Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.dataToFloat32Array = function () {
-	    if (!(m_data instanceof Float32Array)) {
-	      m_data = new Float32Array(m_data);
-	    }
-	    return m_data;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set data for this source
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setData = function (data) {
-	    if (!(data instanceof Array) && !(data instanceof Float32Array)) {
-	      console.log('[error] Requires array');
-	      return;
-	    }
-	    if (data instanceof Float32Array) {
-	      m_data = data;
-	    } else {
-	      m_data = data.slice(0);
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add new attribute data to the source
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addAttribute = function (key, dataType, sizeOfDataType, offset, stride,
-	                               noOfComponents, normalized) {
-
-	    if (!m_attributesMap.hasOwnProperty(key)) {
-	      /* jshint newcap: false */
-	      //jscs:disable requireCapitalizedConstructors
-	      var newAttr = new vglAttributeData();
-	      //jscs:enable requireCapitalizedConstructors
-	      /* jshint newcap: true */
-	      newAttr.m_dataType = dataType;
-	      newAttr.m_dataTypeSize = sizeOfDataType;
-	      newAttr.m_offset = offset;
-	      newAttr.m_stride = stride;
-	      newAttr.m_numberOfComponents = noOfComponents;
-	      newAttr.m_normalized = normalized;
-	      m_attributesMap[key] = newAttr;
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return size of the source data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.sizeOfArray = function () {
-	    return Object.size(m_data);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return length of array
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.lengthOfArray = function () {
-	    return m_data.length;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return size of the source data in bytes
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  /*
-	    * TODO: code below is probably wrong.
-	    *   Example:
-	    *            format P3N3f
-	    *            m_data = [ 1, 2, 3, 4, 5, 6 ]; // contains one vertex,
-	    *                                    // one normal, m_data.length == 6
-	    *
-	    *       The inner loop computes:
-	    *             sizeInBytes += 3 * 4; // for position
-	    *             sizeInBytes += 3 * 4; // for normal
-	    *
-	    *        Then sizeInBytes *= 6; // m_data.length == 6
-	    *        which gives sizeInBytes == 144 bytes when it should have been 4*6 = 24
-	    */
-	  this.sizeInBytes = function () {
-	    var sizeInBytes = 0,
-	        keys = this.keys(), i;
-
-	    for (i = 0; i < keys.length; i += 1) {
-	      sizeInBytes += this.attributeNumberOfComponents(keys[i]) *
-	                     this.sizeOfAttributeDataType(keys[i]);
-	    }
-
-	    sizeInBytes *= this.sizeOfArray();
-
-	    return sizeInBytes;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if there is attribute exists of a given key type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasKey = function (key) {
-	    return m_attributesMap.hasOwnProperty(key);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return keys of all attributes
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.keys = function () {
-	    return Object.keys(m_attributesMap);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return number of attributes of source data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.numberOfAttributes = function () {
-	    return Object.size(m_attributesMap);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return number of components of the attribute data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.attributeNumberOfComponents = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_numberOfComponents;
-	    }
-
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return if the attribute data is normalized
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.normalized = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_normalized;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return size of the attribute data type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.sizeOfAttributeDataType = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_dataTypeSize;
-	    }
-
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return attribute data type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.attributeDataType = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_dataType;
-	    }
-
-	    return undefined;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return attribute offset
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.attributeOffset = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_offset;
-	    }
-
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return attribute stride
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.attributeStride = function (key) {
-	    if (m_attributesMap.hasOwnProperty(key)) {
-	      return m_attributesMap[key].m_stride;
-	    }
-
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Virtual function to insert new vertex data at the end
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.pushBack = function (vertexData) {
-	    void vertexData;
-	    // Should be implemented by the base class
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Insert new data block to the raw data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.insert = function (data) {
-	    var i;
-
-	    //m_data = m_data.concat(data); //no, slow on Safari
-	    /* If we will are given a Float32Array and don't have any other data, use
-	     * it directly. */
-	    if (!m_data.length && data.length && data instanceof Float32Array) {
-	      m_data = data;
-	      return;
-	    }
-	    /* If our internal array is immutable and we will need to change it, create
-	     * a regular mutable array from it. */
-	    if (!m_data.slice && (m_data.length || !data.slice)) {
-	      m_data = Array.prototype.slice.call(m_data);
-	    }
-	    if (!data.length) {
-	      /* data is a singular value, so append it to our array */
-	      m_data[m_data.length] = data;
-	    } else {
-	      /* We don't have any data currently, so it is faster to copy the data
-	       * using slice. */
-	      if (!m_data.length && data.slice) {
-	        m_data = data.slice(0);
-	      } else {
-	        for (i = 0; i < data.length; i += 1) {
-	          m_data[m_data.length] = data[i];
-	        }
-	      }
-	    }
-	  };
-
-	  this.insertAt = function (index, data) {
-	    var i;
-
-	    if (!data.length) {
-	      m_data[index] = data;
-	    } else {
-	      for (i = 0; i < data.length; i += 1) {
-	        m_data[index * data.length + i] = data[i];
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return name of the source data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.name = function () {
-	    return m_name;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set name of the source data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setName = function (name) {
-	    m_name = name;
-	  };
-
-	  return this;
-	};
-
-	vgl.sourceDataAnyfv = function (size, key, arg) {
-	  'use strict';
-	  if (!(this instanceof vgl.sourceDataAnyfv)) {
-	    return new vgl.sourceDataAnyfv(size, key, arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-	  this.addAttribute(key, vgl.GL.FLOAT,
-	                    4, 0, size * 4, size, false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value);
-	  };
-
-	  return this;
-	};
-	inherit(vgl.sourceDataAnyfv, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataP3T3f
-	 *
-	 * @returns {vgl.sourceDataP3T3f}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataP3T3f = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataP3T3f)) {
-	    return new vgl.sourceDataP3T3f(arg);
-	  }
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Position, vgl.GL.FLOAT, 4, 0, 6 * 4, 3,
-	                    false);
-	  this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, vgl.GL.FLOAT, 4, 12,
-	                    6 * 4, 3, false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value.m_position);
-	    this.insert(value.m_texCoordinate);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataP3T3f, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataP3N3f
-	 *
-	 * @returns {vgl.sourceDataP3N3f}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataP3N3f = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataP3N3f)) {
-	    return new vgl.sourceDataP3N3f(arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Position, vgl.GL.FLOAT, 4, 0, 6 * 4, 3,
-	                    false);
-	  this.addAttribute(vgl.vertexAttributeKeys.Normal, vgl.GL.FLOAT, 4, 12, 6 * 4, 3,
-	                    false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value.m_position);
-	    this.insert(value.m_normal);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataP3N3f, vgl.sourceData);
-
-	/////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataP3fv
-	 *
-	 * @returns {vgl.sourceDataP3fv}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataP3fv = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataP3fv)) {
-	    return new vgl.sourceDataP3fv(arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Position, vgl.GL.FLOAT, 4, 0, 3 * 4, 3,
-	                    false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataP3fv, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataT2fv
-	 *
-	 * @returns {vgl.sourceDataT2fv}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataT2fv = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataT2fv)) {
-	    return new vgl.sourceDataT2fv(arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, vgl.GL.FLOAT, 4, 0,
-	                    2 * 4, 2, false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataT2fv, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataC3fv
-	 *
-	 * @returns {vgl.sourceDataC3fv}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataC3fv = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataC3fv)) {
-	    return new vgl.sourceDataC3fv(arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Color, vgl.GL.FLOAT, 4, 0, 3 * 4, 3, false);
-
-	  this.pushBack = function (value) {
-	    this.insert(value);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataC3fv, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataSf meant to hold scalar float values
-	 *
-	 * @class
-	 * @returns {vgl.sourceDataSf}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataSf = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataSf)) {
-	    return new vgl.sourceDataSf(arg);
-	  }
-
-	  var m_min = null,
-	      m_max = null,
-	      m_fixedmin = null,
-	      m_fixedmax = null;
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Scalar, vgl.GL.FLOAT, 4, 0, 4, 1, false);
-
-	  this.pushBack = function (value) {
-	    if (m_max === null || value > m_max) {
-	      m_max = value;
-	    }
-	    if (m_min === null || value < m_min) {
-	      m_min = value;
-	    }
-	    //this.insert(value); //no, slow on Safari
-	    this.data()[this.data().length] = value;
-	  };
-
-	  this.insertAt = function (index, value) {
-	    if (m_max === null || value > m_max) {
-	      m_max = value;
-	    }
-	    if (m_min === null || value < m_min) {
-	      m_min = value;
-	    }
-	    //call superclass ??
-	    //vgl.sourceData.insertAt.call(this, index, value);
-	    this.data()[index] = value;
-	  };
-
-	  this.scalarRange = function () {
-	    if (m_fixedmin === null || m_fixedmax === null) {
-	      return [m_min, m_max];
-	    }
-
-	    return [m_fixedmin, m_fixedmax];
-	  };
-
-	  this.setScalarRange = function (min, max) {
-	    m_fixedmin = min;
-	    m_fixedmax = max;
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataSf, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class sourceDataDf meant to hold data float values
-	 *
-	 * This source array is the best way to pass a array of floats to the shader
-	 * that has one entry for each of the vertices.
-	 *
-	 * @class
-	 * @returns {vgl.sourceDataDf}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.sourceDataDf = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.sourceDataDf)) {
-	    return new vgl.sourceDataDf(arg);
-	  }
-
-	  vgl.sourceData.call(this, arg);
-
-	  this.addAttribute(vgl.vertexAttributeKeys.Scalar, vgl.GL.FLOAT,
-	                    4, 0, 4, 1, false);
-
-	  this.pushBack = function (value) {
-	    this.data()[this.data().length] = value;
-	  };
-
-	  this.insertAt = function (index, value) {
-	    this.data()[index] = value;
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.sourceDataDf, vgl.sourceData);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class geometryData
-	 *
-	 * @class
-	 * @returns {vgl.geometryData}
-	 */
-	/////////////////////////////////////////////////////////////////////////////
-	vgl.geometryData = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.geometryData)) {
-	    return new vgl.geometryData();
-	  }
-	  vgl.data.call(this);
-
-	  /** @private */
-	  var m_name = '',
-	      m_primitives = [],
-	      m_sources = [],
-	      m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
-	      m_computeBoundsTimestamp = vgl.timestamp(),
-	      m_boundsDirtyTimestamp = vgl.timestamp();
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return type
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.type = function () {
-	    return vgl.data.geometry;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return ID of the geometry data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.name = function () {
-	    return m_name;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set name of the geometry data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setName = function (name) {
-	    m_name = name;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add new source
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addSource = function (source, sourceName) {
-	    // @todo Check if the incoming source has duplicate keys
-
-	    if (sourceName !== undefined) {
-	      source.setName(sourceName);
-	    }
-	    // NOTE This might not work on IE8 or lower
-	    if (m_sources.indexOf(source) === -1) {
-	      m_sources.push(source);
-
-	      if (source.hasKey(vgl.vertexAttributeKeys.Position)) {
-	        m_boundsDirtyTimestamp.modified();
-	      }
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return source for a given index. Returns 0 if not found.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.source = function (index) {
-	    if (index < m_sources.length) {
-	      return m_sources[index];
-	    }
-
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return source with a specified name.  Returns 0 if not found.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.sourceByName = function (sourceName) {
-	    for (var i = 0; i < m_sources.length; i += 1) {
-	      if (m_sources[i].name() === sourceName) {
-	        return m_sources[i];
-	      }
-	    }
-	    return 0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return number of sources
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.numberOfSources = function () {
-	    return m_sources.length;
-	  };
-
-	  /**
-	   * Return source data given a key
-	   */
-	  this.sourceData = function (key) {
-	    var i;
-
-	    for (i = 0; i < m_sources.length; i += 1) {
-	      if (m_sources[i].hasKey(key)) {
-	        return m_sources[i];
-	      }
-	    }
-
-	    return null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add new primitive
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addPrimitive = function (primitive) {
-	    m_primitives.push(primitive);
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return primitive for a given index. Returns null if not found.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.primitive = function (index) {
-	    if (index < m_primitives.length) {
-	      return m_primitives[index];
-	    }
-
-	    return null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return number of primitives
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.numberOfPrimitives = function () {
-	    return m_primitives.length;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return bounds [minX, maxX, minY, maxY, minZ, maxZ]
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bounds = function () {
-	    if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) {
-	      this.computeBounds();
-	    }
-	    return m_bounds;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if bounds are dirty or mark them as such.
-	   *
-	   * @param dirty: true to set bounds as dirty.
-	   * Return true if bounds are dirty.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.boundsDirty = function (dirty) {
-	    if (dirty) {
-	      m_boundsDirtyTimestamp.modified();
-	    }
-	    return m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Reset bounds
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetBounds = function () {
-	    m_bounds[0] = 0.0;
-	    m_bounds[1] = 0.0;
-	    m_bounds[2] = 0.0;
-	    m_bounds[3] = 0.0;
-	    m_bounds[4] = 0.0;
-	    m_bounds[5] = 0.0;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set bounds
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setBounds = function (minX, maxX, minY, maxY, minZ, maxZ) {
-	    m_bounds[0] = minX;
-	    m_bounds[1] = maxX;
-	    m_bounds[2] = minY;
-	    m_bounds[3] = maxY;
-	    m_bounds[4] = minZ;
-	    m_bounds[5] = maxZ;
-
-	    m_computeBoundsTimestamp.modified();
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute bounds
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	    if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) {
-	      var attr = vgl.vertexAttributeKeys.Position,
-	          sourceData = this.sourceData(attr),
-	          data = sourceData.data(),
-	          numberOfComponents = sourceData.attributeNumberOfComponents(attr),
-	          stride = sourceData.attributeStride(attr),
-	          offset = sourceData.attributeOffset(attr),
-	          sizeOfDataType = sourceData.sizeOfAttributeDataType(attr),
-	          count = data.length,
-	          j, ib, jb, maxv, minv,
-	          value = null,
-	          vertexIndex;
-
-	      // We advance by index, not by byte
-	      stride /= sizeOfDataType;
-	      offset /= sizeOfDataType;
-
-	      this.resetBounds();
-
-	      for (j = 0; j < numberOfComponents; j += 1) {
-	        ib = j * 2;
-	        jb = j * 2 + 1;
-	        if (count) {
-	          maxv = minv = m_bounds[jb] = data[offset + j];
-	        } else {
-	          maxv = minv = 0;
-	        }
-	        for (vertexIndex = offset + stride + j; vertexIndex < count;
-	             vertexIndex += stride) {
-	          value = data[vertexIndex];
-	          if (value > maxv) {
-	            maxv = value;
-	          }
-	          if (value < minv) {
-	            minv = value;
-	          }
-	        }
-	        m_bounds[ib] = minv; m_bounds[jb] = maxv;
-	      }
-
-	      m_computeBoundsTimestamp.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Returns the vertex closest to a given position
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.findClosestVertex = function (point) {
-	    var attr = vgl.vertexAttributeKeys.Position,
-	        sourceData = this.sourceData(attr),
-	        sizeOfDataType = sourceData.sizeOfAttributeDataType(attr),
-	        numberOfComponents = sourceData.attributeNumberOfComponents(attr),
-	        data = sourceData.data(),
-	        stride = sourceData.attributeStride(attr) / sizeOfDataType,
-	        offset = sourceData.attributeOffset(attr) / sizeOfDataType,
-	        minDist = Number.MAX_VALUE,
-	        minIndex = null,
-	        vi, vPos, dx, dy, dz, dist, i;
-
-	    // assume positions are always triplets
-	    if (numberOfComponents !== 3) {
-	      console.log('[warning] Find closest vertex assumes three' +
-	        'component vertex ');
-	    }
-
-	    if (!point.z) {
-	      point = {x: point.x, y: point.y, z: 0};
-	    }
-
-	    for (vi = offset, i = 0; vi < data.length; vi += stride, i += 1) {
-	      vPos = [data[vi],
-	              data[vi + 1],
-	              data[vi + 2]];
-
-	      dx = vPos[0] - point.x;
-	      dy = vPos[1] - point.y;
-	      dz = vPos[2] - point.z;
-	      dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
-	      if (dist < minDist) {
-	        minDist = dist;
-	        minIndex = i;
-	      }
-	    }
-	    return minIndex;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Returns the requested vertex position
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getPosition = function (index) {
-	    var attr = vgl.vertexAttributeKeys.Position,
-	        sourceData = this.sourceData(attr),
-	        sizeOfDataType = sourceData.sizeOfAttributeDataType(attr),
-	        numberOfComponents = sourceData.attributeNumberOfComponents(attr),
-	        data = sourceData.data(),
-	        stride = sourceData.attributeStride(attr) / sizeOfDataType,
-	        offset = sourceData.attributeOffset(attr) / sizeOfDataType;
-
-	    // assume positions are always triplets
-	    if (numberOfComponents !== 3) {
-	      console.log('[warning] getPosition assumes three component data');
-	    }
-
-	    return [data[offset + index * stride],
-	            data[offset + index * stride + 1],
-	            data[offset + index * stride + 2]];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Returns the scalar corresponding to a given vertex index
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getScalar = function (index) {
-	    var attr = vgl.vertexAttributeKeys.Scalar,
-	        sourceData = this.sourceData(attr),
-	        sizeOfDataType, data, stride, offset;
-
-	    if (!sourceData) {
-	      return null;
-	    }
-
-	    sizeOfDataType = sourceData.sizeOfAttributeDataType(attr);
-	    data = sourceData.data();
-	    stride = sourceData.attributeStride(attr) / sizeOfDataType;
-	    offset = sourceData.attributeOffset(attr) / sizeOfDataType;
-
-	    if (index * stride + offset >= data.length) {
-	      console.log('access out of bounds in getScalar');
-	    }
-
-	    return data[index * stride + offset];
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.geometryData, vgl.data);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, Float32Array, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class mapper
-	 *
-	 * @class
-	 * @returns {vgl.mapper}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.mapper = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.mapper)) {
-	    return new vgl.mapper(arg);
-	  }
-	  vgl.boundingObject.call(this);
-
-	  /** @private */
-	  arg = arg || {};
-
-	  var m_color = [0.0, 1.0, 1.0],
-	      m_geomData = null,
-	      m_buffers = [],
-	      m_bufferVertexAttributeMap = {},
-	      m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
-	      m_glCompileTimestamp = vgl.timestamp(),
-	      m_context = null,
-	      m_this = this;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Delete cached VBO if any
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.deleteVertexBufferObjects = function (renderState) {
-	    var i;
-	    var context = m_context;
-	    if (renderState) {
-	      context = renderState.m_context;
-	    }
-	    if (context) {
-	      for (i = 0; i < m_buffers.length; i += 1) {
-	        context.deleteBuffer(m_buffers[i]);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create new VBO for all its geometryData sources and primitives
-	   *
-	   * @private
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  function createVertexBufferObjects(renderState) {
-	    if (m_geomData) {
-	      if (renderState) {
-	        m_context = renderState.m_context;
-	      }
-	      var numberOfSources = m_geomData.numberOfSources(),
-	          i, j, k, bufferId = null, keys, ks, numberOfPrimitives, data;
-
-	      for (i = 0; i < numberOfSources; i += 1) {
-	        bufferId = m_context.createBuffer();
-	        m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, bufferId);
-	        data = m_geomData.source(i).data();
-	        if (!(data instanceof Float32Array)) {
-	          data = new Float32Array(data);
-	        }
-	        m_context.bufferData(vgl.GL.ARRAY_BUFFER, data,
-	                      m_dynamicDraw ? vgl.GL.DYNAMIC_DRAW :
-	                      vgl.GL.STATIC_DRAW);
-
-	        keys = m_geomData.source(i).keys();
-	        ks = [];
-
-	        for (j = 0; j < keys.length; j += 1) {
-	          ks.push(keys[j]);
-	        }
-
-	        m_bufferVertexAttributeMap[i] = ks;
-	        m_buffers[i] = bufferId;
-	      }
-
-	      numberOfPrimitives = m_geomData.numberOfPrimitives();
-	      for (k = 0; k < numberOfPrimitives; k += 1) {
-	        bufferId = m_context.createBuffer();
-	        m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, bufferId);
-	        m_context.bufferData(vgl.GL.ARRAY_BUFFER,
-	          m_geomData.primitive(k).indices(), vgl.GL.STATIC_DRAW);
-	        m_buffers[i] = bufferId;
-	        i += 1;
-	      }
-
-	      m_glCompileTimestamp.modified();
-	    }
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Clear cache related to buffers
-	   *
-	   * @private
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  function cleanUpDrawObjects(renderState) {
-	    void renderState;
-	    m_bufferVertexAttributeMap = {};
-	    m_buffers = [];
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Setup draw objects; Delete old ones and create new ones
-	   *
-	   * @private
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  function setupDrawObjects(renderState) {
-	    // Delete buffer objects from past if any.
-	    m_this.deleteVertexBufferObjects(renderState);
-
-	    // Clear any cache related to buffers
-	    cleanUpDrawObjects(renderState);
-
-	    // Now construct the new ones.
-	    createVertexBufferObjects(renderState);
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute bounds of the data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	    if (m_geomData === null || typeof m_geomData === 'undefined') {
-	      this.resetBounds();
-	      return;
-	    }
-
-	    var computeBoundsTimestamp = this.computeBoundsTimestamp(),
-	        boundsDirtyTimestamp = this.boundsDirtyTimestamp(),
-	        geomBounds = null;
-
-	    if (boundsDirtyTimestamp.getMTime() > computeBoundsTimestamp.getMTime()) {
-	      geomBounds = m_geomData.bounds();
-
-	      this.setBounds(geomBounds[0], geomBounds[1], geomBounds[2],
-	        geomBounds[3], geomBounds[4], geomBounds[5]);
-
-	      computeBoundsTimestamp.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get solid color of the geometry
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.color = function () {
-	    return m_color;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set solid color of the geometry. Default is teal [1.0, 1.0, 1.0]
-	   *
-	   * @param r Red component of the color [0.0 - 1.0]
-	   * @param g Green component of the color [0.0 - 1.0]
-	   * @param b Blue component of the color [0.0 - 1.0]
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setColor = function (r, g, b) {
-	    m_color[0] = r;
-	    m_color[1] = g;
-	    m_color[2] = b;
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return stored geometry data if any
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.geometryData = function () {
-	    return m_geomData;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Connect mapper to its geometry data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setGeometryData = function (geom) {
-	    if (m_geomData !== geom) {
-	      m_geomData = geom;
-
-	      this.modified();
-	      this.boundsDirtyTimestamp().modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update the buffer used for a named source.
-	   *
-	   * @param {String} sourceName The name of the source to update.
-	   * @param {Object[] or Float32Array} values The values to use for the source.
-	   *    If not specified, use the source's own buffer.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.updateSourceBuffer = function (sourceName, values, renderState) {
-	    if (renderState) {
-	      m_context = renderState.m_context;
-	    }
-	    if (!m_context) {
-	      return false;
-	    }
-	    var bufferIndex = -1;
-	    for (var i = 0; i < m_geomData.numberOfSources(); i += 1) {
-	      if (m_geomData.source(i).name() === sourceName) {
-	        bufferIndex = i;
-	        break;
-	      }
-	    }
-	    if (bufferIndex < 0 || bufferIndex >= m_buffers.length) {
-	      return false;
-	    }
-	    if (!values) {
-	      values = m_geomData.source(i).dataToFloat32Array();
-	    }
-	    m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_buffers[bufferIndex]);
-	    if (values instanceof Float32Array) {
-	      m_context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0, values);
-	    } else {
-	      m_context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0,
-	                              new Float32Array(values));
-	    }
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get the buffer used for a named source.  If the current buffer isn't a
-	   * Float32Array, it is converted to one.  This array can then be modified
-	   * directly, after which updateSourceBuffer can be called to update the
-	   * GL array.
-	   *
-	   * @param {String} sourceName The name of the source to update.
-	   * @returns {Float32Array} An array used for this source.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getSourceBuffer = function (sourceName) {
-	    var source = m_geomData.sourceByName(sourceName);
-	    if (!source) {
-	      return new Float32Array();
-	    }
-	    return source.dataToFloat32Array();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the mapper
-	   *
-	   * @param {object} renderState: the current renderering state object.
-	   * @param {boolean} noUndoBindVertexData: if true, do not unbind vertex data.
-	   *    This may be desirable if the render function is subclassed.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function (renderState, noUndoBindVertexData) {
-	    if (this.getMTime() > m_glCompileTimestamp.getMTime() ||
-	        renderState.m_contextChanged) {
-	      setupDrawObjects(renderState);
-	    }
-	    m_context = renderState.m_context;
-
-	    // Fixed vertex color
-	    m_context.vertexAttrib3fv(vgl.vertexAttributeKeys.Color, this.color());
-
-	    // TODO Use renderState
-	    var bufferIndex = 0,
-	        j = 0, i, noOfPrimitives = null, primitive = null;
-
-	    for (i in m_bufferVertexAttributeMap) {
-	      if (m_bufferVertexAttributeMap.hasOwnProperty(i)) {
-	        m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,
-	                             m_buffers[bufferIndex]);
-	        for (j = 0; j < m_bufferVertexAttributeMap[i].length; j += 1) {
-	          renderState.m_material
-	              .bindVertexData(renderState, m_bufferVertexAttributeMap[i][j]);
-	        }
-	        bufferIndex += 1;
-	      }
-	    }
-
-	    noOfPrimitives = m_geomData.numberOfPrimitives();
-	    for (j = 0; j < noOfPrimitives; j += 1, bufferIndex += 1) {
-	      primitive = m_geomData.primitive(j);
-	      if (!primitive.numberOfIndices()) {
-	        continue;
-	      }
-	      m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_buffers[bufferIndex]);
-	      switch (primitive.primitiveType()) {
-	        case vgl.GL.POINTS:
-	          m_context.drawArrays(vgl.GL.POINTS, 0, primitive.numberOfIndices());
-	          break;
-	        case vgl.GL.LINES:
-	          m_context.drawArrays(vgl.GL.LINES, 0, primitive.numberOfIndices());
-	          break;
-	        case vgl.GL.LINE_STRIP:
-	          m_context.drawArrays(vgl.GL.LINE_STRIP, 0, primitive.numberOfIndices());
-	          break;
-	        case vgl.GL.TRIANGLES:
-	          m_context.drawArrays(vgl.GL.TRIANGLES, 0, primitive.numberOfIndices());
-	          break;
-	        case vgl.GL.TRIANGLE_STRIP:
-	          m_context.drawArrays(vgl.GL.TRIANGLE_STRIP, 0, primitive.numberOfIndices());
-	          break;
-	      }
-	      m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, null);
-	    }
-
-	    /* If we are rendering multiple features in the same context, we must
-	     * unbind the vertex data to make sure the next feature has a known state.
-	     * This is optional.
-	     */
-	    if (!noUndoBindVertexData) {
-	      this.undoBindVertexData(renderState);
-	    }
-	  };
-
-	  /**
-	   * Unbind the vertex data,
-	   */
-	  this.undoBindVertexData = function (renderState) {
-	    var i, j;
-
-	    for (i in m_bufferVertexAttributeMap) {
-	      if (m_bufferVertexAttributeMap.hasOwnProperty(i)) {
-	        for (j = 0; j < m_bufferVertexAttributeMap[i].length; j += 1) {
-	          renderState.m_material
-	              .undoBindVertexData(renderState, m_bufferVertexAttributeMap[i][j]);
-	        }
-	      }
-	    }
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.mapper, vgl.boundingObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	vgl.groupMapper = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.groupMapper)) {
-	    return new vgl.groupMapper();
-	  }
-	  vgl.mapper.call(this);
-
-	  /** @private */
-	  var m_createMappersTimestamp = vgl.timestamp(),
-	      m_mappers = [],
-	      m_geomDataArray = [];
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return stored geometry data if any
-	   *
-	   * @param index optional
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.geometryData = function (index) {
-	    if (index !== undefined && index < m_geomDataArray.length) {
-	      return m_geomDataArray[index];
-	    }
-
-	    if (m_geomDataArray.length > 0) {
-	      return m_geomDataArray[0];
-	    }
-
-	    return null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Connect mapper to its geometry data
-	   *
-	   * @param geom {vgl.geomData}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setGeometryData = function (geom) {
-	    if (m_geomDataArray.length === 1) {
-	      if (m_geomDataArray[0] === geom) {
-	        return;
-	      }
-	    }
-	    m_geomDataArray = [];
-	    m_geomDataArray.push(geom);
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return stored geometry data array if any
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.geometryDataArray = function () {
-	    return m_geomDataArray;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Connect mapper to its geometry data
-	   *
-	   * @param geoms {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setGeometryDataArray = function (geoms) {
-	    if (geoms instanceof Array) {
-	      if (m_geomDataArray !== geoms) {
-	        m_geomDataArray = [];
-	        m_geomDataArray = geoms;
-	        this.modified();
-	        return true;
-	      }
-	    } else {
-	      console.log('[error] Requies array of geometry data');
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute bounds of the data
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeBounds = function () {
-	    if (m_geomDataArray === null ||
-	        m_geomDataArray === undefined) {
-	      this.resetBounds();
-	      return;
-	    }
-
-	    var computeBoundsTimestamp = this.computeBoundsTimestamp(),
-	        boundsDirtyTimestamp = this.boundsDirtyTimestamp(),
-	        m_bounds = this.bounds(),
-	        geomBounds = null,
-	        i = null;
-
-	    if (boundsDirtyTimestamp.getMTime() >
-	        computeBoundsTimestamp.getMTime()) {
-
-	      for (i = 0; i < m_geomDataArray.length; i += 1) {
-	        geomBounds = m_geomDataArray[i].bounds();
-
-	        if (m_bounds[0] > geomBounds[0]) {
-	          m_bounds[0] = geomBounds[0];
-	        }
-	        if (m_bounds[1] < geomBounds[1]) {
-	          m_bounds[1] = geomBounds[1];
-	        }
-	        if (m_bounds[2] > geomBounds[2]) {
-	          m_bounds[2] = geomBounds[2];
-	        }
-	        if (m_bounds[3] < geomBounds[3]) {
-	          m_bounds[3] = geomBounds[3];
-	        }
-	        if (m_bounds[4] > geomBounds[4]) {
-	          m_bounds[4] = geomBounds[4];
-	        }
-	        if (m_bounds[5] < geomBounds[5]) {
-	          m_bounds[5] = geomBounds[5];
-	        }
-	      }
-
-	      this.modified();
-	      computeBoundsTimestamp.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the mapper
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function (renderState) {
-	    var i = null;
-
-	    if (this.getMTime() > m_createMappersTimestamp.getMTime()) {
-	      // NOTE Hoping that it will release the graphics resources
-	      for (i = 0; i < m_geomDataArray.length; i += 1) {
-	        m_mappers.push(vgl.mapper());
-	        m_mappers[i].setGeometryData(m_geomDataArray[i]);
-	      }
-	      m_createMappersTimestamp.modified();
-	    }
-
-	    for (i = 0; i < m_mappers.length; i += 1) {
-	      m_mappers[i].render(renderState);
-	    }
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.groupMapper, vgl.mapper);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	vgl.materialAttributeType = {
-	  'Undefined' : 0x0,
-	  'ShaderProgram' : 0x1,
-	  'Texture' : 0x2,
-	  'Blend' : 0x3,
-	  'Depth' : 0x4
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class materialAttribute
-	 *
-	 * @class
-	 * @param type
-	 * @returns {vgl.materialAttribute}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.materialAttribute = function (type) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.materialAttribute)) {
-	    return new vgl.materialAttribute(type);
-	  }
-	  vgl.graphicsObject.call(this);
-
-	  /** @private */
-	  var m_this = this,
-	      m_type = type,
-	      m_enabled = true;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return tyep of the material attribute
-	   *
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.type = function () {
-	    return m_type;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return if material attribute is enabled or not
-	   *
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.enabled = function () {
-	    return m_enabled;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind and activate vertex specific data
-	   *
-	   * @param renderState
-	   * @param key
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bindVertexData = function (renderState, key) {
-	    renderState = renderState; /* unused parameter */
-	    key = key; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind and deactivate vertex specific data
-	   *
-	   * @param renderState
-	   * @param key
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.undoBindVertexData = function (renderState, key) {
-	    renderState = renderState; /* unused parameter */
-	    key = key; /* unused parameter */
-	    return false;
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.materialAttribute, vgl.graphicsObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of clas blendFunction
-	 *
-	 * @class
-	 * @param source
-	 * @param destination
-	 * @returns {vgl.blendFunction}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.blendFunction = function (source, destination) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.blendFunction)) {
-	    return new vgl.blendFunction(source, destination);
-	  }
-
-	  /** @private */
-	  var m_source = source,
-	      m_destination = destination;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Apply blend function to the current state
-	   *
-	   * @param {vgl.renderState}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.apply = function (renderState) {
-	    renderState.m_context.blendFuncSeparate(m_source, m_destination,
-	                         vgl.GL.ONE, vgl.GL.ONE_MINUS_SRC_ALPHA);
-	  };
-
-	  return this;
-	};
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class blend
-	 *
-	 * @returns {vgl.blend}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.blend = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.blend)) {
-	    return new vgl.blend();
-	  }
-	  vgl.materialAttribute.call(
-	    this, vgl.materialAttributeType.Blend);
-
-	  /** @private */
-	  var m_wasEnabled = false,
-	      m_blendFunction = vgl.blendFunction(vgl.GL.SRC_ALPHA,
-	                                          vgl.GL.ONE_MINUS_SRC_ALPHA);
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind blend attribute
-	   *
-	   * @param {vgl.renderState}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bind = function (renderState) {
-	    m_wasEnabled = renderState.m_context.isEnabled(vgl.GL.BLEND);
-
-	    if (this.enabled()) {
-	      renderState.m_context.enable(vgl.GL.BLEND);
-	      m_blendFunction.apply(renderState);
-	    } else {
-	      renderState.m_context.disable(vgl.GL.BLEND);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind blend attribute
-	   *
-	   * @param {vgl.renderState}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.undoBind = function (renderState) {
-	    if (m_wasEnabled) {
-	      renderState.m_context.enable(vgl.GL.BLEND);
-	    } else {
-	      renderState.m_context.disable(vgl.GL.BLEND);
-	    }
-
-	    return true;
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.blend, vgl.materialAttribute);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class material
-	 *
-	 * @class
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.material = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.material)) {
-	    return new vgl.material();
-	  }
-	  vgl.graphicsObject.call(this);
-
-	  // / Private member variables
-	  var m_this = this,
-	      m_shaderProgram = new vgl.shaderProgram(),
-	      m_binNumber = 100,
-	      m_textureAttributes = {},
-	      m_attributes = {};
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return bin number for the material
-	   *
-	   * @default 100
-	   * @returns {number}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.binNumber = function () {
-	    return m_binNumber;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set bin number for the material
-	   *
-	   * @param binNo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setBinNumber = function (binNo) {
-	    m_binNumber = binNo;
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if incoming attribute already exists in the material
-	   *
-	   * @param attr
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.exists = function (attr) {
-	    if (attr.type() === vgl.materialAttributeType.Texture) {
-	      return m_textureAttributes.hasOwnProperty(attr.textureUnit());
-	    }
-	    return m_attributes.hasOwnProperty(attr.type());
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get uniform given a name
-
-	   * @param name Uniform name
-	   * @returns {vgl.uniform}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.uniform = function (name) {
-	    return m_shaderProgram ? m_shaderProgram.uniform(name) : null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get material attribute
-
-	   * @param {number} type attribute type
-	   * @param {number} textureUnit attribute texture unit if type is Texture.
-	   * @returns {vgl.materialAttribute}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.attribute = function (type, textureUnit) {
-	    if (m_attributes.hasOwnProperty(type)) {
-	      return m_attributes[type];
-	    }
-
-	    if (type === vgl.materialAttributeType.Texture && m_textureAttributes.hasOwnProperty(textureUnit)) {
-	      return m_textureAttributes[textureUnit];
-	    }
-
-	    return null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set a new attribute for the material
-	   *
-	   * This method replace any existing attribute except for textures as
-	   * materials can have multiple textures.
-	   *
-	   * @param attr
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setAttribute = function (attr) {
-	    if (attr.type() === vgl.materialAttributeType.Texture) {
-	      if (m_textureAttributes[attr.textureUnit()] === attr) {
-	        return false;
-	      }
-	      m_textureAttributes[attr.textureUnit()] = attr;
-	      m_this.modified();
-	      return true;
-	    }
-
-	    if (m_attributes[attr.type()] === attr) {
-	      return false;
-	    }
-
-	    // Shader is a very special attribute
-	    if (attr.type() === vgl.materialAttributeType.ShaderProgram) {
-	      m_shaderProgram = attr;
-	    }
-
-	    m_attributes[attr.type()] = attr;
-	    m_this.modified();
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add a new attribute to the material.
-	   *
-	   * @param attr
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addAttribute = function (attr) {
-	    if (m_this.exists(attr)) {
-	      return false;
-	    }
-
-	    if (attr.type() === vgl.materialAttributeType.Texture) {
-	      // TODO Currently we don't check if we are replacing or not.
-	      // It would be nice to have a flag for it.
-	      m_textureAttributes[attr.textureUnit()] = attr;
-	      m_this.modified();
-	      return true;
-	    }
-
-	    // Shader is a very special attribute
-	    if (attr.type() === vgl.materialAttributeType.ShaderProgram) {
-	      m_shaderProgram = attr;
-	    }
-
-	    m_attributes[attr.type()] = attr;
-	    m_this.modified();
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return shader program used by the material
-	   *
-	   * @returns {vgl.shaderProgram}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.shaderProgram = function () {
-	    return m_shaderProgram;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Setup (initialize) the material attribute
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._setup = function (renderState) {
-	    void renderState; /* unused parameter */
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove any resources acquired before deletion
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._cleanup = function (renderState) {
-	    for (var key in m_attributes) {
-	      if (m_attributes.hasOwnProperty(key)) {
-	        m_attributes[key]._cleanup(renderState);
-	      }
-	    }
-
-	    for (key in m_textureAttributes) {
-	      if (m_textureAttributes.hasOwnProperty(key)) {
-	        m_textureAttributes[key]._cleanup(renderState);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind and activate material states
-	   *
-	   * @param renderState
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bind = function (renderState) {
-	    var key = null;
-
-	    m_shaderProgram.bind(renderState);
-
-	    for (key in m_attributes) {
-	      if (m_attributes.hasOwnProperty(key)) {
-	        if (m_attributes[key] !== m_shaderProgram) {
-	          m_attributes[key].bind(renderState);
-	        }
-	      }
-	    }
-
-	    for (key in m_textureAttributes) {
-	      if (m_textureAttributes.hasOwnProperty(key)) {
-	        m_textureAttributes[key].bind(renderState);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo-bind and de-activate material states
-	   *
-	   * @param renderState
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.undoBind = function (renderState) {
-	    var key = null;
-	    for (key in m_attributes) {
-	      if (m_attributes.hasOwnProperty(key)) {
-	        m_attributes[key].undoBind(renderState);
-	      }
-	    }
-
-	    for (key in m_textureAttributes) {
-	      if (m_textureAttributes.hasOwnProperty(key)) {
-	        m_textureAttributes[key].undoBind(renderState);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind vertex data
-	   *
-	   * @param renderState
-	   * @param key
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bindVertexData = function (renderState, key) {
-	    var i = null;
-
-	    for (i in m_attributes) {
-	      if (m_attributes.hasOwnProperty(i)) {
-	        m_attributes[i].bindVertexData(renderState, key);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind vertex data
-	   *
-	   * @param renderState
-	   * @param key
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.undoBindVertexData = function (renderState, key) {
-	    var i = null;
-
-	    for (i in m_attributes) {
-	      if (m_attributes.hasOwnProperty(i)) {
-	        m_attributes[i].undoBindVertexData(renderState, key);
-	      }
-	    }
-	  };
-
-	  return m_this;
-	};
-
-	vgl.material.RenderBin = {
-	  'Base' : 0,
-	  'Default' : 100,
-	  'Opaque' : 100,
-	  'Transparent' : 1000,
-	  'Overlay' : 10000
-	};
-
-	inherit(vgl.material, vgl.graphicsObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec2, vec3, vec4, mat4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class renderState
-	 *
-	 * @returns {vgl.renderState}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.renderState = function () {
-	  'use strict';
-
-	  this.m_context = null;
-	  this.m_modelViewMatrix = mat4.create();
-	  this.m_normalMatrix = mat4.create();
-	  this.m_projectionMatrix = null;
-	  this.m_material = null;
-	  this.m_mapper = null;
-	};
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class renderer *
-	 *
-	 * @returns {vgl.renderer}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.renderer = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.renderer)) {
-	    return new vgl.renderer(arg);
-	  }
-	  vgl.graphicsObject.call(this);
-	  arg = arg || {};
-
-	  /** @private */
-	  var m_this = this;
-	  m_this.m_renderWindow = null;
-	  m_this.m_contextChanged = false;
-	  m_this.m_sceneRoot = new vgl.groupNode();
-	  m_this.m_camera = new vgl.camera(arg);
-	  m_this.m_nearClippingPlaneTolerance = null;
-	  m_this.m_x = 0;
-	  m_this.m_y = 0;
-	  m_this.m_width = 0;
-	  m_this.m_height = 0;
-	  m_this.m_resizable = true;
-	  m_this.m_resetScene = true;
-	  m_this.m_layer = 0;
-	  m_this.m_renderPasses = null;
-	  m_this.m_resetClippingRange = true;
-	  m_this.m_depthBits = null;
-
-	  m_this.m_camera.addChild(m_this.m_sceneRoot);
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get width of the renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.width = function () {
-	    return m_this.m_width;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get height of the renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.height = function () {
-	    return m_this.m_height;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get layer this renderer is associated with
-	   *
-	   * @return {Number}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.layer = function () {
-	    return m_this.m_layer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set the layer this renderer is associated with.
-	   *
-	   * @param layerNo
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setLayer = function (layerNo) {
-	    m_this.m_layer = layerNo;
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.isResizable = function () {
-	    return m_this.m_resizable;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setResizable = function (r) {
-	    m_this.m_resizable = r;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return render window (owner) of the renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.renderWindow = function () {
-	    return m_this.m_renderWindow;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set render window for the renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setRenderWindow = function (renWin) {
-	    if (m_this.m_renderWindow !== renWin) {
-	      if (m_this.m_renderWindow) {
-	        m_this.m_renderWindow.removeRenderer(this);
-	      }
-	      m_this.m_renderWindow = renWin;
-	      m_this.m_contextChanged = true;
-	      m_this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get background color
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.backgroundColor = function () {
-	    return m_this.m_camera.clearColor();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set background color of the renderer
-	   *
-	   * @param r
-	   * @param g
-	   * @param b
-	   * @param a
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setBackgroundColor = function (r, g, b, a) {
-	    m_this.m_camera.setClearColor(r, g, b, a);
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get scene root
-	   *
-	   * @returns {vgl.groupNode}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.sceneRoot = function () {
-	    return m_this.m_sceneRoot;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get main camera of the renderer
-	   *
-	   * @returns {vgl.camera}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.camera = function () {
-	    return m_this.m_camera;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the scene
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function () {
-	    var i, renSt, children, actor = null, sortedActors = [],
-	        mvMatrixInv = mat4.create(), clearColor = null;
-
-	    renSt = new vgl.renderState();
-	    renSt.m_renderer = m_this;
-	    renSt.m_context = m_this.renderWindow().context();
-	    if (!m_this.m_depthBits || m_this.m_contextChanged) {
-	      m_this.m_depthBits = renSt.m_context.getParameter(vgl.GL.DEPTH_BITS);
-	    }
-	    renSt.m_contextChanged = m_this.m_contextChanged;
-
-	    if (m_this.m_renderPasses) {
-	      for (i = 0; i < m_this.m_renderPasses.length; i += 1) {
-	        if (m_this.m_renderPasses[i].render(renSt)) {
-	          // Stop the rendering if render pass returns false
-	          console.log('returning');
-	          m_this.m_renderPasses[i].remove(renSt);
-	          return;
-	        }
-	        m_this.m_renderPasses[i].remove(renSt);
-	      }
-	    }
-
-	    renSt.m_context.enable(vgl.GL.DEPTH_TEST);
-	    renSt.m_context.depthFunc(vgl.GL.LEQUAL);
-
-	    /*jshint bitwise: false */
-	    if (m_this.m_camera.clearMask() & vgl.GL.COLOR_BUFFER_BIT) {
-	      clearColor = m_this.m_camera.clearColor();
-	      renSt.m_context.clearColor(clearColor[0], clearColor[1],
-	                                 clearColor[2], clearColor[3]);
-	    }
-
-	    if (m_this.m_camera.clearMask() & vgl.GL.DEPTH_BUFFER_BIT) {
-	      renSt.m_context.clearDepth(m_this.m_camera.clearDepth());
-	    }
-	    /*jshint bitwise: true */
-
-	    renSt.m_context.clear(m_this.m_camera.clearMask());
-
-	    // Set the viewport for this renderer
-	    renSt.m_context.viewport(m_this.m_x, m_this.m_y,
-	                             m_this.m_width, m_this.m_height);
-
-	    children = m_this.m_sceneRoot.children();
-
-	    if (children.length > 0 && m_this.m_resetScene) {
-	      m_this.resetCamera();
-	      m_this.m_resetScene = false;
-	    }
-
-	    for (i = 0; i < children.length; i += 1) {
-	      actor = children[i];
-
-	      // Compute the bounds even if the actor is not visible
-	      actor.computeBounds();
-
-	      // If bin number is < 0, then don't even bother
-	      // rendering the data
-	      if (actor.visible() && actor.material().binNumber() >= 0) {
-	        sortedActors.push([actor.material().binNumber(), actor]);
-	      }
-	    }
-
-	    // Now perform sorting
-	    sortedActors.sort(function (a, b) { return a[0] - b[0]; });
-
-	    for (i = 0; i < sortedActors.length; i += 1) {
-	      actor = sortedActors[i][1];
-	      if (actor.referenceFrame() ===
-	          vgl.boundingObject.ReferenceFrame.Relative) {
-	        var view = m_this.m_camera.viewMatrix();
-	        /* If the view matrix is a plain array, keep it as such.  This is
-	         * intended to preserve precision, and will only be the case if the
-	         * view matrix was created by delibrately setting it as an array. */
-	        if (view instanceof Array) {
-	          renSt.m_modelViewMatrix = new Array(16);
-	        }
-	        mat4.multiply(renSt.m_modelViewMatrix, view, actor.matrix());
-	        renSt.m_projectionMatrix = m_this.m_camera.projectionMatrix();
-	        renSt.m_modelViewAlignment = m_this.m_camera.viewAlignment();
-	      } else {
-	        renSt.m_modelViewMatrix = actor.matrix();
-	        renSt.m_modelViewAlignment = null;
-	        renSt.m_projectionMatrix = mat4.create();
-	        mat4.ortho(renSt.m_projectionMatrix,
-	                   0, m_this.m_width, 0, m_this.m_height, -1, 1);
-	      }
-
-	      mat4.invert(mvMatrixInv, renSt.m_modelViewMatrix);
-	      mat4.transpose(renSt.m_normalMatrix, mvMatrixInv);
-	      renSt.m_material = actor.material();
-	      renSt.m_mapper = actor.mapper();
-
-	      // TODO Fix this shortcut
-	      renSt.m_material.bind(renSt);
-	      renSt.m_mapper.render(renSt);
-	      renSt.m_material.undoBind(renSt);
-	    }
-
-	    renSt.m_context.finish();
-	    m_this.m_contextChanged = false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Automatically set up the camera based on visible actors
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetCamera = function () {
-	    m_this.m_camera.computeBounds();
-
-	    var vn = m_this.m_camera.directionOfProjection(),
-	        visibleBounds = m_this.m_camera.bounds(),
-	        center = [
-	          (visibleBounds[0] + visibleBounds[1]) / 2.0,
-	          (visibleBounds[2] + visibleBounds[3]) / 2.0,
-	          (visibleBounds[4] + visibleBounds[5]) / 2.0
-	        ],
-	        diagonals = [
-	          visibleBounds[1] - visibleBounds[0],
-	          visibleBounds[3] - visibleBounds[2],
-	          visibleBounds[5] - visibleBounds[4]
-	        ],
-	        radius = 0.0,
-	        aspect = m_this.m_camera.viewAspect(),
-	        angle = m_this.m_camera.viewAngle(),
-	        distance = null,
-	        vup = null;
-
-	    if (diagonals[0] > diagonals[1]) {
-	      if (diagonals[0] > diagonals[2]) {
-	        radius = diagonals[0] / 2.0;
-	      } else {
-	        radius = diagonals[2] / 2.0;
-	      }
-	    } else {
-	      if (diagonals[1] > diagonals[2]) {
-	        radius = diagonals[1] / 2.0;
-	      } else {
-	        radius = diagonals[2] / 2.0;
-	      }
-	    }
-
-	    // @todo Need to figure out what's happening here
-	    if (aspect >= 1.0) {
-	      angle = 2.0 * Math.atan(Math.tan(angle * 0.5) / aspect);
-	    } else {
-	      angle = 2.0 * Math.atan(Math.tan(angle * 0.5) * aspect);
-	    }
-
-	    distance = radius / Math.sin(angle * 0.5);
-	    vup = m_this.m_camera.viewUpDirection();
-
-	    if (Math.abs(vec3.dot(vup, vn)) > 0.999) {
-	      m_this.m_camera.setViewUpDirection(-vup[2], vup[0], vup[1]);
-	    }
-
-	    m_this.m_camera.setFocalPoint(center[0], center[1], center[2]);
-	    m_this.m_camera.setPosition(center[0] + distance * -vn[0],
-	      center[1] + distance * -vn[1], center[2] + distance * -vn[2]);
-
-	    m_this.resetCameraClippingRange(visibleBounds);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	  * Check whether or not whether or not the bounds are valid
-	  */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasValidBounds = function (bounds) {
-	    if (bounds[0] === Number.MAX_VALUE ||
-	        bounds[1] === -Number.MAX_VALUE ||
-	        bounds[2] === Number.MAX_VALUE ||
-	        bounds[3] === -Number.MAX_VALUE ||
-	        bounds[4] === Number.MAX_VALUE ||
-	        bounds[5] === -Number.MAX_VALUE) {
-	      return false;
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Recalculate camera's clipping range
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetCameraClippingRange = function (bounds) {
-	    if (typeof bounds === 'undefined') {
-	      m_this.m_camera.computeBounds();
-	      bounds = m_this.m_camera.bounds();
-	    }
-
-	    if (!m_this.hasValidBounds(bounds)) {
-	      return;
-	    }
-
-	    var vn = m_this.m_camera.viewPlaneNormal(),
-	        position = m_this.m_camera.position(),
-	        a = -vn[0],
-	        b = -vn[1],
-	        c = -vn[2],
-	        d = -(a * position[0] + b * position[1] + c * position[2]),
-	        range = vec2.create(),
-	        dist = null,
-	        i = null,
-	        j = null,
-	        k = null;
-
-	    if (!m_this.m_resetClippingRange) {
-	      return;
-	    }
-
-	    // Set the max near clipping plane and the min far clipping plane
-	    range[0] = a * bounds[0] + b * bounds[2] + c * bounds[4] + d;
-	    range[1] = 1e-18;
-
-	    // Find the closest / farthest bounding box vertex
-	    for (k = 0; k < 2; k += 1) {
-	      for (j = 0; j < 2; j += 1) {
-	        for (i = 0; i < 2; i += 1) {
-	          dist = a * bounds[i] + b * bounds[2 + j] + c * bounds[4 + k] + d;
-	          range[0] = (dist < range[0]) ? (dist) : (range[0]);
-	          range[1] = (dist > range[1]) ? (dist) : (range[1]);
-	        }
-	      }
-	    }
-
-	    // Do not let the range behind the camera throw off the calculation.
-	    if (range[0] < 0.0) {
-	      range[0] = 0.0;
-	    }
-
-	    // Give ourselves a little breathing room
-	    range[0] = 0.99 * range[0] - (range[1] - range[0]) * 0.5;
-	    range[1] = 1.01 * range[1] + (range[1] - range[0]) * 0.5;
-
-	    // Make sure near is not bigger than far
-	    range[0] = (range[0] >= range[1]) ? (0.01 * range[1]) : (range[0]);
-
-	    // Make sure near is at least some fraction of far - this prevents near
-	    // from being behind the camera or too close in front. How close is too
-	    // close depends on the resolution of the depth buffer.
-	    if (!m_this.m_nearClippingPlaneTolerance) {
-	      m_this.m_nearClippingPlaneTolerance = 0.01;
-
-	      if (m_this.m_depthBits && m_this.m_depthBits > 16) {
-	        m_this.m_nearClippingPlaneTolerance = 0.001;
-	      }
-	    }
-
-	    // make sure the front clipping range is not too far from the far clippnig
-	    // range, this is to make sure that the zbuffer resolution is effectively
-	    // used.
-	    if (range[0] < m_this.m_nearClippingPlaneTolerance * range[1]) {
-	      range[0] = m_this.m_nearClippingPlaneTolerance * range[1];
-	    }
-
-	    m_this.m_camera.setClippingRange(range[0], range[1]);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Resize viewport given a width and height
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resize = function (width, height) {
-	    if (!width || !height) {
-	      return;
-	    }
-	    // @note: where do m_this.m_x and m_this.m_y come from?
-	    m_this.positionAndResize(m_this.m_x, m_this.m_y, width, height);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Resize viewport given a position, width and height
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.positionAndResize = function (x, y, width, height) {
-	    var i;
-
-	    // TODO move this code to camera
-	    if (x < 0 || y < 0 || width <= 0 || height <= 0) {
-	      console.log('[error] Invalid position and resize values',
-	        x, y, width, height);
-	      return;
-	    }
-
-	    //If we're allowing this renderer to resize ...
-	    if (m_this.m_resizable) {
-	      m_this.m_width = width;
-	      m_this.m_height = height;
-
-	      m_this.m_camera.setViewAspect(width / height);
-	      m_this.m_camera.setParallelExtents({width: width, height: height});
-	      m_this.modified();
-	    }
-
-	    if (m_this.m_renderPasses) {
-	      for (i = 0; i < m_this.m_renderPasses.length; i += 1) {
-	        m_this.m_renderPasses[i].resize(width, height);
-	        m_this.m_renderPasses[i].renderer().positionAndResize(x, y, width, height);
-	      }
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add new actor to the collection
-	   *
-	   * @param actor
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addActor = function (actor) {
-	    if (actor instanceof vgl.actor) {
-	      m_this.m_sceneRoot.addChild(actor);
-	      m_this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return true if this renderer has this actor attached, false otherwise.
-	   *
-	   * @param actor
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasActor = function (actor) {
-	    return m_this.m_sceneRoot.hasChild(actor);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add an array of actors to the collection
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addActors = function (actors) {
-	    var i = null;
-	    if (actors instanceof Array) {
-	      for (i = 0; i < actors.length; i += 1) {
-	        m_this.m_sceneRoot.addChild(actors[i]);
-	      }
-	      m_this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove the actor from the collection
-	   *
-	   * @param actor
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeActor = function (actor) {
-	    if (m_this.m_sceneRoot.children().indexOf(actor) !== -1) {
-	      /* When we remove an actor, free the VBOs of the mapper and mark the
-	       * mapper as modified; it will reallocate VBOs as necessary. */
-	      if (actor.mapper()) {
-	        actor.mapper().deleteVertexBufferObjects();
-	        actor.mapper().modified();
-	      }
-	      m_this.m_sceneRoot.removeChild(actor);
-	      m_this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove actors from the collection
-	   *
-	   * @param actors
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeActors = function (actors) {
-	    if (!(actors instanceof Array)) {
-	      return false;
-	    }
-
-	    var i;
-	    for (i = 0; i < actors.length; i += 1) {
-	      m_this.m_sceneRoot.removeChild(actors[i]);
-	    }
-	    m_this.modified();
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove all actors for a renderer
-	   *
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeAllActors = function () {
-	    return m_this.m_sceneRoot.removeChildren();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Transform a point in the world space to display space
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.worldToDisplay = function (worldPt, viewMatrix, projectionMatrix, width,
-	                                 height) {
-	    var viewProjectionMatrix = mat4.create(),
-	        winX = null,
-	        winY = null,
-	        winZ = null,
-	        winW = null,
-	        clipPt = null;
-
-	    mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix);
-
-	    // Transform world to clipping coordinates
-	    clipPt = vec4.create();
-	    vec4.transformMat4(clipPt, worldPt, viewProjectionMatrix);
-
-	    if (clipPt[3] !== 0.0) {
-	      clipPt[0] = clipPt[0] / clipPt[3];
-	      clipPt[1] = clipPt[1] / clipPt[3];
-	      clipPt[2] = clipPt[2] / clipPt[3];
-	      clipPt[3] = 1.0;
-	    }
-
-	    winX = (((clipPt[0]) + 1) / 2.0) * width;
-	    // We calculate -point3D.getY() because the screen Y axis is
-	    // oriented top->down
-	    winY = ((1 - clipPt[1]) / 2.0) * height;
-	    winZ = clipPt[2];
-	    winW = clipPt[3];
-
-	    return vec4.fromValues(winX, winY, winZ, winW);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Transform a point in display space to world space
-	   * @param displayPt
-	   * @param viewMatrix
-	   * @param projectionMatrix
-	   * @param width
-	   * @param height
-	   * @returns {vec4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.displayToWorld = function (displayPt, viewMatrix, projectionMatrix,
-	                                 width, height) {
-	    var x = (2.0 * displayPt[0] / width) - 1,
-	        y = -(2.0 * displayPt[1] / height) + 1,
-	        z = displayPt[2],
-	        viewProjectionInverse = mat4.create(),
-	        worldPt = null;
-
-	    mat4.multiply(viewProjectionInverse, projectionMatrix, viewMatrix);
-	    mat4.invert(viewProjectionInverse, viewProjectionInverse);
-
-	    worldPt = vec4.fromValues(x, y, z, 1);
-	    vec4.transformMat4(worldPt, worldPt, viewProjectionInverse);
-	    if (worldPt[3] !== 0.0) {
-	      worldPt[0] = worldPt[0] / worldPt[3];
-	      worldPt[1] = worldPt[1] / worldPt[3];
-	      worldPt[2] = worldPt[2] / worldPt[3];
-	      worldPt[3] = 1.0;
-	    }
-
-	    return worldPt;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get the focusDisplayPoint
-	   * @returns {vec4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.focusDisplayPoint = function () {
-	    var focalPoint = m_this.m_camera.focalPoint(),
-	        focusWorldPt = vec4.fromValues(
-	        focalPoint[0], focalPoint[1], focalPoint[2], 1);
-
-	    return m_this.worldToDisplay(
-	      focusWorldPt, m_this.m_camera.viewMatrix(),
-	      m_this.m_camera.projectionMatrix(), m_this.m_width, m_this.m_height);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Will the scene be reset.
-	   * @returns {bool}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetScene = function () {
-	    return m_this.m_resetScene;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * If true the scene will be reset, otherwise the scene will not be
-	   * automatically reset.
-	   *
-	   * @param reset
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setResetScene = function (reset) {
-	    if (m_this.m_resetScene !== reset) {
-	      m_this.m_resetScene = reset;
-	      m_this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Will the clipping range be reset
-	   * @returns {bool}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resetClippingRange = function () {
-	    return m_this.m_resetClippingRange;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * If true the camera clipping range will be reset, otherwise the scene will
-	   * not be automatically reset.
-	   *
-	   * @param reset
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setResetClippingRange = function (reset) {
-	    if (m_this.m_resetClippingRange !== reset) {
-	      m_this.m_resetClippingRange = reset;
-	      m_this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addRenderPass = function (renPass) {
-	    var i;
-
-	    if (m_this.m_renderPasses) {
-	      for (i = 0; i < m_this.m_renderPasses.length; i += 1) {
-	        if (renPass === m_this.m_renderPasses[i]) {
-	          return;
-	        }
-	      }
-	    }
-
-	    m_this.m_renderPasses = [];
-	    m_this.m_renderPasses.push(renPass);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeRenderPass = function (renPass) {
-	    renPass = renPass; // TODO Implement this
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._cleanup = function (renderState) {
-	    var children = m_this.m_sceneRoot.children();
-	    for (var i = 0; i < children.length; i += 1) {
-	      var actor = children[i];
-	      actor.material()._cleanup(renderState);
-	      actor.mapper()._cleanup(renderState);
-	    }
-
-	    m_this.m_sceneRoot.removeChildren();
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.renderer, vgl.graphicsObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class renderWindow
-	 *
-	 * @class
-	 * @returns {vgl.renderWindow}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.renderWindow = function (canvas) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.renderWindow)) {
-	    return new vgl.renderWindow(canvas);
-	  }
-	  vgl.graphicsObject.call(this);
-
-	  /** @private */
-	  var m_this = this,
-	      m_x = 0,
-	      m_y = 0,
-	      m_width = 400,
-	      m_height = 400,
-	      m_canvas = canvas,
-	      m_activeRender = null,
-	      m_renderers = [],
-	      m_context = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get size of the render window
-	   *
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.windowSize = function () {
-	    return [m_width, m_height];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set size of the render window
-	   *
-	   * @param width
-	   * @param height
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setWindowSize = function (width, height) {
-
-	    if (m_width !== width || m_height !== height) {
-	      m_width = width;
-	      m_height = height;
-
-	      m_this.modified();
-
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get window position (top left coordinates)
-	   *
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.windowPosition = function () {
-	    return [m_x, m_y];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set window position (top left coordinates)
-	   *
-	   * @param x
-	   * @param y
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setWindowPosition = function (x, y) {
-	    if ((m_x !== x) || (m_y !== y)) {
-	      m_x = x;
-	      m_y = y;
-	      m_this.modified();
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return all renderers contained in the render window
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.renderers = function () {
-	    return m_renderers;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get active renderer of the the render window
-	   *
-	   * @returns vgl.renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.activeRenderer = function () {
-	    return m_activeRender;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add renderer to the render window
-	   *
-	   * @param ren
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.addRenderer = function (ren) {
-	    if (m_this.hasRenderer(ren) === false) {
-	      m_renderers.push(ren);
-	      ren.setRenderWindow(m_this);
-	      if (m_activeRender === null) {
-	        m_activeRender = ren;
-	      }
-	      if (ren.layer() !== 0) {
-	        ren.camera().setClearMask(vgl.GL.DepthBufferBit);
-	      }
-	      m_this.modified();
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Remove renderer from the render window
-	   *
-	   * @param ren
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.removeRenderer = function (ren) {
-	    var index = m_renderers.indexOf(ren);
-	    if (index !== -1) {
-	      if (m_activeRender === ren) {
-	        m_activeRender = null;
-	      }
-	      m_renderers.splice(index, 1);
-	      m_this.modified();
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return a renderer at a given index
-	   *
-	   * @param index
-	   * @returns {vgl.renderer}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getRenderer = function (index) {
-	    if (index < m_renderers.length) {
-	      return m_renderers[index];
-	    }
-
-	    console.log('[WARNING] Out of index array');
-	    return null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if the renderer exists
-	   *
-	   * @param ren
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.hasRenderer = function (ren) {
-	    var i;
-	    for (i = 0; i < m_renderers.length; i += 1) {
-	      if (ren === m_renderers[i]) {
-	        return true;
-	      }
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Resize window
-	   *
-	   * @param width
-	   * @param height
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.resize = function (width, height) {
-	    m_this.positionAndResize(m_x, m_y, width, height);
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Resize and reposition the window
-	   *
-	   * @param x
-	   * @param y
-	   * @param width
-	   * @param height
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.positionAndResize = function (x, y, width, height) {
-	    m_x = x;
-	    m_y = y;
-	    m_width = width;
-	    m_height = height;
-	    var i;
-	    for (i = 0; i < m_renderers.length; i += 1) {
-	      m_renderers[i].positionAndResize(m_x, m_y, m_width, m_height);
-	    }
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create the window
-	   *
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._setup = function (renderState) {
-	    renderState = renderState; /* unused parameter */
-	    m_context = null;
-
-	    try {
-	      // Try to grab the standard context. If it fails, fallback to
-	      // experimental.
-	      m_context = m_canvas.getContext('webgl') ||
-	            m_canvas.getContext('experimental-webgl');
-
-	      // Set width and height of renderers if not set already
-	      var i;
-	      for (i = 0; i < m_renderers.length; i += 1) {
-	        if ((m_renderers[i].width() > m_width) ||
-	            m_renderers[i].width() === 0 ||
-	            (m_renderers[i].height() > m_height) ||
-	            m_renderers[i].height() === 0) {
-	          m_renderers[i].resize(m_x, m_y, m_width, m_height);
-	        }
-	      }
-
-	      return true;
-	    } catch (e) {
-	    }
-
-	    // If we don't have a GL context, give up now
-	    if (!m_context) {
-	      console('[ERROR] Unable to initialize WebGL. Your browser may not support it.');
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return current GL context
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.context = function () {
-	    return m_context;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Delete this window and release any graphics resources
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._cleanup = function (renderState) {
-	    var i;
-	    for (i = 0; i < m_renderers.length; i += 1) {
-	      m_renderers[i]._cleanup(renderState);
-	    }
-	    vgl.clearCachedShaders(renderState ? renderState.m_context : null);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the scene
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function () {
-	    var i;
-	    m_renderers.sort(function (a, b) { return a.layer() - b.layer(); });
-	    for (i = 0; i < m_renderers.length; i += 1) {
-	      m_renderers[i].render();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get the focusDisplayPoint from the activeRenderer
-	   * @returns {vec4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.focusDisplayPoint = function () {
-	    return m_activeRender.focusDisplayPoint();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Transform a point in display space to world space
-	   * @param {Number} x
-	   * @param {Number} y
-	   * @param {vec4} focusDisplayPoint
-	   * @returns {vec4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.displayToWorld = function (x, y, focusDisplayPoint, ren) {
-	    ren = ren === undefined ? ren = m_activeRender : ren;
-
-	    var camera = ren.camera();
-	    if (!focusDisplayPoint) {
-	      focusDisplayPoint = ren.focusDisplayPoint();
-	    }
-
-	    return ren.displayToWorld(
-	      vec4.fromValues(x, y, focusDisplayPoint[2], 1.0), camera.viewMatrix(),
-	      camera.projectionMatrix(), m_width, m_height);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Transform a point in display space to world space
-	   * @param {Number} x
-	   * @param {Number} y
-	   * @param {vec4} focusDisplayPoint
-	   * @returns {vec4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.worldToDisplay = function (x, y, z, ren) {
-	    ren = ren === undefined ? ren = m_activeRender : ren;
-	    var camera = ren.camera();
-	    return ren.worldToDisplay(
-	      vec4.fromValues(x, y, z, 1.0), camera.viewMatrix(),
-	      camera.projectionMatrix(), m_width, m_height);
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.renderWindow, vgl.graphicsObject);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec3, vec4, mat4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class camera
-	 *
-	 * @class
-	 * @returns {vgl.camera}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.camera = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.camera)) {
-	    return new vgl.camera(arg);
-	  }
-	  vgl.groupNode.call(this);
-	  arg = arg || {};
-
-	  /** @private */
-	  var m_viewAngle = (Math.PI * 30) / 180.0,
-	      m_position = vec4.fromValues(0.0, 0.0, 1.0, 1.0),
-	      m_focalPoint = vec4.fromValues(0.0, 0.0, 0.0, 1.0),
-	      m_centerOfRotation = vec3.fromValues(0.0, 0.0, 0.0),
-	      m_viewUp = vec4.fromValues(0.0, 1.0, 0.0, 0.0),
-	      m_rightDir = vec4.fromValues(1.0, 0.0, 0.0, 0.0),
-	      m_near = 0.01,
-	      m_far = 10000.0,
-	      m_viewAspect = 1.0,
-	      m_directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0),
-	      m_viewPlaneNormal = vec4.fromValues(0.0, 0.0, 1.0, 0.0),
-	      m_viewMatrix = mat4.create(),
-	      m_projectionMatrix = mat4.create(),
-	      m_computeModelViewMatrixTime = vgl.timestamp(),
-	      m_computeProjectMatrixTime = vgl.timestamp(),
-	      m_left = -1.0,
-	      m_right = 1.0,
-	      m_top = +1.0,
-	      m_bottom = -1.0,
-	      m_parallelExtents = {zoom: 1, tilesize: 256},
-	      m_enableTranslation = true,
-	      m_enableRotation = true,
-	      m_enableScale = true,
-	      m_enableParallelProjection = false,
-	      m_clearColor = [0.0, 0.0, 0.0, 0.0],
-	      m_clearDepth = 1.0,
-	      /*jshint bitwise: false */
-	      m_clearMask = vgl.GL.COLOR_BUFFER_BIT |
-	                    vgl.GL.DEPTH_BUFFER_BIT;
-	  /*jshint bitwise: true */
-
-	  if (arg.parallelProjection !== undefined) {
-	    m_enableParallelProjection = arg.parallelProjection ? true : false;
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get view angle of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewAngle = function () {
-	    return m_viewAngle;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set view angle of the camera in degrees, which is converted to radians.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewAngleDegrees = function (a) {
-	    this.setViewAngle(Math.PI * a / 180.0);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set view angle of the camera in degrees, which is converted to radians.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewAngle = function (a) {
-	    if (m_enableScale) {
-	      m_viewAngle = a;
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get position of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.position = function () {
-	    return m_position;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set position of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPosition = function (x, y, z) {
-	    if (m_enableTranslation) {
-	      m_position = vec4.fromValues(x, y, z, 1.0);
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get focal point of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.focalPoint = function () {
-	    return m_focalPoint;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set focal point of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setFocalPoint = function (x, y, z) {
-	    if (m_enableRotation && m_enableTranslation) {
-	      m_focalPoint = vec4.fromValues(x, y, z, 1.0);
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get view-up direction of camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewUpDirection = function () {
-	    return m_viewUp;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set view-up direction of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewUpDirection = function (x, y, z) {
-	    m_viewUp = vec4.fromValues(x, y, z, 0);
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get center of rotation for camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.centerOfRotation = function () {
-	    return m_centerOfRotation;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set center of rotation for camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setCenterOfRotation = function (centerOfRotation) {
-	    m_centerOfRotation = centerOfRotation;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get clipping range of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.clippingRange = function () {
-	    return [m_near, m_far];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set clipping range of the camera
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setClippingRange = function (near, far) {
-	    m_near = near;
-	    m_far = far;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get view aspect
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewAspect = function () {
-	    return m_viewAspect;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set view aspect
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewAspect = function (aspect) {
-	    m_viewAspect = aspect;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return active mode for scaling (enabled / disabled)
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.enableScale = function () {
-	    return m_enableScale;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Enable/disable scaling
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setEnableScale = function (flag) {
-	    if (flag !== m_enableScale) {
-	      m_enableScale = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return m_enableScale;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return active mode for rotation (enabled / disabled)
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.enableRotation = function () {
-	    return m_enableRotation;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Enable / disable rotation
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setEnableRotation = function (flag) {
-	    if (flag !== m_enableRotation) {
-	      m_enableRotation = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return m_enableRotation;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return active mode for translation (enabled/disabled)
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.enableTranslation = function () {
-	    return m_enableTranslation;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Enable / disable translation
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setEnableTranslation = function (flag) {
-	    if (flag !== m_enableTranslation) {
-	      m_enableTranslation = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return m_enableTranslation;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return if parallel projection is enabled
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.isEnabledParallelProjection = function () {
-	    return m_enableParallelProjection;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Enable / disable parallel projection
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.enableParallelProjection = function (flag) {
-	    if (flag !== m_enableParallelProjection) {
-	      m_enableParallelProjection = flag;
-	      this.modified();
-	      return true;
-	    }
-
-	    return m_enableParallelProjection;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Enable / disable parallel projection
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setEnableParallelProjection = function (flag) {
-	    return this.enableParallelProjection(flag);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get parallel projection parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parallelProjection = function () {
-	    return {left: m_left, right: m_right, top: m_top, bottom: m_bottom};
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set parallel projection parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setParallelProjection = function (left, right, top, bottom) {
-	    m_left = left;
-	    m_right = right;
-	    m_top = top;
-	    m_bottom = bottom;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get parallel projection extents parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parallelExtents = function () {
-	    return m_parallelExtents;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set parallel projection extents parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setParallelExtents = function (extents) {
-	    var prop = ['width', 'height', 'zoom', 'tilesize'], mod = false, i, key;
-	    for (i = 0; i < prop.length; i += 1) {
-	      key = prop[i];
-	      if (extents[key] !== undefined &&
-	          extents[key] !== m_parallelExtents[key]) {
-	        m_parallelExtents[key] = extents[key];
-	        mod = true;
-	      }
-	    }
-	    if (mod && m_parallelExtents.width && m_parallelExtents.height &&
-	        m_parallelExtents.zoom !== undefined && m_parallelExtents.tilesize) {
-	      var unitsPerPixel = this.unitsPerPixel(m_parallelExtents.zoom,
-	                                             m_parallelExtents.tilesize);
-	      m_right = unitsPerPixel * m_parallelExtents.width / 2;
-	      m_left = -m_right;
-	      m_top = unitsPerPixel * m_parallelExtents.height / 2;
-	      m_bottom = -m_top;
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute the units per pixel.
-	   *
-	   * @param zoom: tile zoom level.
-	   * @param tilesize: number of pixels per tile (defaults to 256).
-	   * @returns: unitsPerPixel.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.unitsPerPixel = function (zoom, tilesize) {
-	    tilesize = tilesize || 256;
-	    return 360.0 * Math.pow(2, -zoom) / tilesize;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return direction of projection
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.directionOfProjection = function () {
-	    this.computeDirectionOfProjection();
-	    return m_directionOfProjection;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return view plane normal direction
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewPlaneNormal = function () {
-	    this.computeViewPlaneNormal();
-	    return m_viewPlaneNormal;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return view-matrix for the camera This method does not compute the
-	   * view-matrix for the camera. It is assumed that a call to computeViewMatrix
-	   * has been made earlier.
-	   *
-	   * @returns {mat4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewMatrix = function () {
-	    return this.computeViewMatrix();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set the view-matrix for the camera and mark that it is up to date so that
-	   * it won't be recomputed unless something else changes.
-	   *
-	   * @param {mat4} view: new view matrix.
-	   * @param {boolean} preserveType: if true, clone the input using slice.  This
-	   *    can be used to ensure the array is a specific precision.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewMatrix = function (view, preserveType) {
-	    if (!preserveType) {
-	      mat4.copy(m_viewMatrix, view);
-	    } else {
-	      m_viewMatrix = view.slice();
-	    }
-	    m_computeModelViewMatrixTime.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return camera projection matrix This method does not compute the
-	   * projection-matrix for the camera. It is assumed that a call to
-	   * computeProjectionMatrix has been made earlier.
-	   *
-	   * @returns {mat4}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.projectionMatrix = function () {
-	    return this.computeProjectionMatrix();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set the projection-matrix for the camera and mark that it is up to date so
-	   * that it won't be recomputed unless something else changes.
-	   *
-	   * @param {mat4} proj: new projection matrix.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setProjectionMatrix = function (proj) {
-	    mat4.copy(m_projectionMatrix, proj);
-	    m_computeProjectMatrixTime.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return clear mask used by this camera
-	   *
-	   * @returns {number}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.clearMask = function () {
-	    return m_clearMask;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set clear mask for camera
-	   *
-	   * @param mask
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setClearMask = function (mask) {
-	    m_clearMask = mask;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get clear color (background color) of the camera
-	   *
-	   * @returns {Array}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.clearColor = function () {
-	    return m_clearColor;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set clear color (background color) for the camera
-	   *
-	   * @param color RGBA
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setClearColor = function (r, g, b, a) {
-	    m_clearColor[0] = r;
-	    m_clearColor[1] = g;
-	    m_clearColor[2] = b;
-	    m_clearColor[3] = a;
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   * @returns {{1.0: null}}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.clearDepth = function () {
-	    return m_clearDepth;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *
-	   * @param depth
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setClearDepth = function (depth) {
-	    m_clearDepth = depth;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute direction of projection
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeDirectionOfProjection = function () {
-	    vec3.subtract(m_directionOfProjection, m_focalPoint, m_position);
-	    vec3.normalize(m_directionOfProjection, m_directionOfProjection);
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute view plane normal
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeViewPlaneNormal = function () {
-	    m_viewPlaneNormal[0] = -m_directionOfProjection[0];
-	    m_viewPlaneNormal[1] = -m_directionOfProjection[1];
-	    m_viewPlaneNormal[2] = -m_directionOfProjection[2];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Move camera closer or further away from the scene
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.zoom = function (d, dir) {
-	    if (d === 0) {
-	      return;
-	    }
-
-	    if (!m_enableTranslation) {
-	      return;
-	    }
-
-	    d = d * vec3.distance(m_focalPoint, m_position);
-	    if (!dir) {
-	      dir = m_directionOfProjection;
-	      m_position[0] = m_focalPoint[0] - d * dir[0];
-	      m_position[1] = m_focalPoint[1] - d * dir[1];
-	      m_position[2] = m_focalPoint[2] - d * dir[2];
-	    } else {
-	      m_position[0] = m_position[0] + d * dir[0];
-	      m_position[1] = m_position[1] + d * dir[1];
-	      m_position[2] = m_position[2] + d * dir[2];
-	    }
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Move camera sideways
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.pan = function (dx, dy, dz) {
-	    if (!m_enableTranslation) {
-	      return;
-	    }
-
-	    m_position[0] += dx;
-	    m_position[1] += dy;
-	    m_position[2] += dz;
-
-	    m_focalPoint[0] += dx;
-	    m_focalPoint[1] += dy;
-	    m_focalPoint[2] += dz;
-
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute camera coordinate axes
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeOrthogonalAxes = function () {
-	    this.computeDirectionOfProjection();
-	    vec3.cross(m_rightDir, m_directionOfProjection, m_viewUp);
-	    vec3.normalize(m_rightDir, m_rightDir);
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Rotate camera around center of rotation
-	   * @param dx Rotation around vertical axis in degrees
-	   * @param dy Rotation around horizontal axis in degrees
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.rotate = function (dx, dy) {
-	    if (!m_enableRotation) {
-	      return;
-	    }
-
-	    // Convert degrees into radians
-	    dx = 0.5 * dx * (Math.PI / 180.0);
-	    dy = 0.5 * dy * (Math.PI / 180.0);
-
-	    var mat = mat4.create(),
-	        inverseCenterOfRotation = new vec3.create();
-
-	    mat4.identity(mat);
-
-	    inverseCenterOfRotation[0] = -m_centerOfRotation[0];
-	    inverseCenterOfRotation[1] = -m_centerOfRotation[1];
-	    inverseCenterOfRotation[2] = -m_centerOfRotation[2];
-
-	    mat4.translate(mat, mat, m_centerOfRotation);
-	    mat4.rotate(mat, mat, dx, m_viewUp);
-	    mat4.rotate(mat, mat, dy, m_rightDir);
-	    mat4.translate(mat, mat, inverseCenterOfRotation);
-
-	    vec4.transformMat4(m_position, m_position, mat);
-	    vec4.transformMat4(m_focalPoint, m_focalPoint, mat);
-
-	    // Update viewup vector
-	    vec4.transformMat4(m_viewUp, m_viewUp, mat);
-	    vec4.normalize(m_viewUp, m_viewUp);
-
-	    // Update direction of projection
-	    this.computeOrthogonalAxes();
-
-	    // Mark modified
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute camera view matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeViewMatrix = function () {
-	    if (m_computeModelViewMatrixTime.getMTime() < this.getMTime()) {
-	      mat4.lookAt(m_viewMatrix, m_position, m_focalPoint, m_viewUp);
-	      m_computeModelViewMatrixTime.modified();
-	    }
-	    return m_viewMatrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Check if the texture should be aligned to the screen.  Alignment only
-	   * occurs if the parallel extents contain width, height, and a
-	   * close-to-integer zoom level, and if the units-per-pixel value has been
-	   * computed.  The camera must either be in parallel projection mode OR must
-	   * have a perspective camera which is oriented along the z-axis without any
-	   * rotation.
-	   *
-	   * @returns: either null if no alignment should occur, or an alignment object
-	   *           with the rounding value and offsets.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewAlignment = function () {
-	    if (!m_enableParallelProjection) {
-	      /* If we aren't in parallel projection mode, ensure that the projection
-	       * matrix meets strict specifications */
-	      var proj = this.projectionMatrix();
-	      if (proj[1] || proj[2] || proj[3] || proj[4] || proj[6] || proj[7] ||
-	          proj[8] || proj[9] || proj[12] || proj[13] || proj[15]) {
-	        return null;
-	      }
-	    }
-	    var unitsPerPixel = this.unitsPerPixel(m_parallelExtents.zoom,
-	                                           m_parallelExtents.tilesize);
-	    /* If we don't have screen dimensions, we can't know how to align pixels */
-	    if (!m_parallelExtents.width || !m_parallelExtents.height ||
-	        !unitsPerPixel) {
-	      return null;
-	    }
-	    /* If we aren't at an integer zoom level, we shouldn't align pixels.  If
-	     * we are really close to an integer zoom level, that is good enough. */
-	    if (parseFloat(m_parallelExtents.zoom.toFixed(6)) !==
-	        parseFloat(m_parallelExtents.zoom.toFixed(0))) {
-	      return null;
-	    }
-	    var align = {roundx: unitsPerPixel, roundy: unitsPerPixel, dx: 0, dy: 0};
-	    /* If the screen is an odd number of pixels, shift the view center to the
-	     * center of a pixel so that the pixels fit discretely across the screen.
-	     * If an even number of pixels, align the view center between pixels for
-	     * the same reason. */
-	    if (m_parallelExtents.width % 2) {
-	      align.dx = unitsPerPixel * 0.5;
-	    }
-	    if (m_parallelExtents.height % 2) {
-	      align.dy = unitsPerPixel * 0.5;
-	    }
-	    return align;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute camera projection matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.computeProjectionMatrix = function () {
-	    if (m_computeProjectMatrixTime.getMTime() < this.getMTime()) {
-	      if (!m_enableParallelProjection) {
-	        mat4.perspective(m_projectionMatrix, m_viewAngle, m_viewAspect, m_near, m_far);
-	      } else {
-	        mat4.ortho(m_projectionMatrix,
-	          m_left, m_right, m_bottom, m_top, m_near, m_far);
-	      }
-
-	      m_computeProjectMatrixTime.modified();
-	    }
-
-	    return m_projectionMatrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Convert a zoom level and window size to a camera height.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.zoomToHeight = function (zoom, width, height) {
-	    return vgl.zoomToHeight(zoom, width, height, m_viewAngle);
-	  };
-
-	  this.computeDirectionOfProjection();
-
-	  return this;
-	};
-
-	inherit(vgl.camera, vgl.groupNode);
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Convert a zoom level and window size to a camera height.
-	 *
-	 * @param zoom: Zoom level, as used in OSM maps.
-	 * @param width: width of the window.
-	 * @param height: height of the window.
-	 * @returns: perspective camera height.
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.zoomToHeight = function (zoom, width, height, viewAngle) {
-	  'use strict';
-	  viewAngle = viewAngle || (30 * Math.PI / 180.0);
-	  var newZ = 360 * Math.pow(2, -zoom);
-	  newZ /= Math.tan(viewAngle / 2) * 2 * 256 / height;
-	  return newZ;
-	};
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Convert a camera height and window size to a zoom level.
-	 *
-	 * @param z: perspective camera height.
-	 * @param width: width of the window.
-	 * @param height: height of the window.
-	 * @returns: zoom level.
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.heightToZoom = function (z, width, height, viewAngle) {
-	  'use strict';
-	  viewAngle = viewAngle || (30 * Math.PI / 180.0);
-	  z *= Math.tan(viewAngle / 2) * 2 * 256 / height;
-	  var zoom = -Math.log2(z / 360);
-	  return zoom;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit, $*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class interactorStyle
-	 *
-	 * @class vgl.interactorStyle
-	 * interactorStyle is a base class for all interactor styles
-	 * @returns {vgl.interactorStyle}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-	vgl.interactorStyle = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.interactorStyle)) {
-	    return new vgl.interactorStyle();
-	  }
-	  vgl.object.call(this);
-
-	  // Private member variables
-	  var m_that = this,
-	      m_viewer = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return viewer referenced by the interactor style
-	   *
-	   * @returns {null}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.viewer = function () {
-	    return m_viewer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set viewer for the interactor style
-	   *
-	   * @param viewer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setViewer = function (viewer) {
-	    if (viewer !== m_viewer) {
-	      m_viewer = viewer;
-	      $(m_viewer).on(vgl.event.mousePress, m_that.handleMouseDown);
-	      $(m_viewer).on(vgl.event.mouseRelease, m_that.handleMouseUp);
-	      $(m_viewer).on(vgl.event.mouseMove, m_that.handleMouseMove);
-	      $(m_viewer).on(vgl.event.mouseOut, m_that.handleMouseOut);
-	      $(m_viewer).on(vgl.event.mouseWheel, m_that.handleMouseWheel);
-	      $(m_viewer).on(vgl.event.keyPress, m_that.handleKeyPress);
-	      $(m_viewer).on(vgl.event.mouseContextMenu, m_that.handleContextMenu);
-	      $(m_viewer).on(vgl.event.click, m_that.handleClick);
-	      $(m_viewer).on(vgl.event.dblClick, m_that.handleDoubleClick);
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse down event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseDown = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse up event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseUp = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseMove = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseOut = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse wheel event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseWheel = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle click event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleClick = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle double click event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleDoubleClick = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle key press event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleKeyPress = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle context menu event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleContextMenu = function (event) {
-	    event = event; /* unused parameter */
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Reset to default
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.reset = function () {
-	    return true;
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.interactorStyle, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of trackballInteractorStyle
-	 *
-	 * @class vgl.trackballInteractorStyle
-	 * @returns {vgl.trackballInteractorStyle}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.trackballInteractorStyle = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.trackballInteractorStyle)) {
-	    return new vgl.trackballInteractorStyle();
-	  }
-	  vgl.interactorStyle.call(this);
-	  var m_that = this,
-	      m_leftMouseBtnDown = false,
-	      m_rightMouseBtnDown = false,
-	      m_midMouseBtnDown = false,
-	      m_outsideCanvas,
-	      m_currPos = {x: 0, y: 0},
-	      m_lastPos = {x: 0, y: 0};
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseMove = function (event) {
-	    var width = m_that.viewer().renderWindow().windowSize()[0],
-	        height = m_that.viewer().renderWindow().windowSize()[1],
-	        ren = m_that.viewer().renderWindow().activeRenderer(),
-	        cam = ren.camera(), coords = m_that.viewer().relMouseCoords(event),
-	        fp, fdp, fwp, dp1, dp2, wp1, wp2, dx, dy, dz, m_zTrans;
-
-	    m_outsideCanvas = false;
-	    m_currPos = {x: 0, y: 0};
-
-	    if ((coords.x < 0) || (coords.x > width)) {
-	      m_currPos.x = 0;
-	      m_outsideCanvas = true;
-	    } else {
-	      m_currPos.x = coords.x;
-	    }
-	    if ((coords.y < 0) || (coords.y > height)) {
-	      m_currPos.y = 0;
-	      m_outsideCanvas = true;
-	    } else {
-	      m_currPos.y = coords.y;
-	    }
-	    if (m_outsideCanvas === true) {
-	      return;
-	    }
-
-	    fp = cam.focalPoint();
-	    fwp = vec4.fromValues(fp[0], fp[1], fp[2], 1);
-	    fdp = ren.worldToDisplay(fwp, cam.viewMatrix(),
-	                              cam.projectionMatrix(), width, height);
-
-	    dp1 = vec4.fromValues(m_currPos.x, m_currPos.y, fdp[2], 1.0);
-	    dp2 = vec4.fromValues(m_lastPos.x, m_lastPos.y, fdp[2], 1.0);
-
-	    wp1 = ren.displayToWorld(dp1, cam.viewMatrix(), cam.projectionMatrix(),
-	                             width, height);
-	    wp2 = ren.displayToWorld(dp2, cam.viewMatrix(), cam.projectionMatrix(),
-	                             width, height);
-
-	    dx = wp1[0] - wp2[0];
-	    dy = wp1[1] - wp2[1];
-	    dz = wp1[2] - wp2[2];
-
-	    if (m_midMouseBtnDown) {
-	      cam.pan(-dx, -dy, -dz);
-	      m_that.viewer().render();
-	    }
-	    if (m_leftMouseBtnDown) {
-	      cam.rotate((m_lastPos.x - m_currPos.x),
-	      (m_lastPos.y - m_currPos.y));
-	      ren.resetCameraClippingRange();
-	      m_that.viewer().render();
-	    }
-	    if (m_rightMouseBtnDown) {
-	      /// 2.0 is the speed up factor
-	      m_zTrans = 2.0 * (m_currPos.y - m_lastPos.y) / height;
-
-	      // Calculate zoom scale here
-	      if (m_zTrans > 0) {
-	        cam.zoom(1 - Math.abs(m_zTrans));
-	      } else {
-	        cam.zoom(1 + Math.abs(m_zTrans));
-	      }
-	      ren.resetCameraClippingRange();
-	      m_that.viewer().render();
-	    }
-	    m_lastPos.x = m_currPos.x;
-	    m_lastPos.y = m_currPos.y;
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse down event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseDown = function (event) {
-	    var coords;
-
-	    if (event.button === 0) {
-	      m_leftMouseBtnDown = true;
-	    }
-	    if (event.button === 1) {
-	      m_midMouseBtnDown = true;
-	    }
-	    if (event.button === 2) {
-	      m_rightMouseBtnDown = true;
-	    }
-	    coords = m_that.viewer().relMouseCoords(event);
-	    if (coords.x < 0) {
-	      m_lastPos.x = 0;
-	    } else {
-	      m_lastPos.x = coords.x;
-	    }
-	    if (coords.y < 0) {
-	      m_lastPos.y = 0;
-	    } else {
-	      m_lastPos.y = coords.y;
-	    }
-	    return false;
-	  };
-
-	  // @note We never get mouse up from scroll bar: See the bug report here
-	  // http://bugs.jquery.com/ticket/8184
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse up event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseUp = function (event) {
-	    if (event.button === 0) {
-	      m_leftMouseBtnDown = false;
-	    }
-	    if (event.button === 1) {
-	      m_midMouseBtnDown = false;
-	    }
-	    if (event.button === 2) {
-	      m_rightMouseBtnDown = false;
-	    }
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse wheel event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseWheel = function (event) {
-	    var ren = m_that.viewer().renderWindow().activeRenderer(),
-	        cam = ren.camera();
-
-	    // TODO Compute zoom factor intelligently
-	    if (event.originalEvent.wheelDelta < 0) {
-	      cam.zoom(0.9);
-	    } else {
-	      cam.zoom(1.1);
-	    }
-	    ren.resetCameraClippingRange();
-	    m_that.viewer().render();
-	    return true;
-	  };
-
-	  return this;
-	};
-	inherit(vgl.trackballInteractorStyle, vgl.interactorStyle);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of pvwInteractorStyle (for ParaViewWeb)
-	 *
-	 * @class vgl.pvwInteractorStyle
-	 * @returns {vgl.pvwInteractorStyle}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.pvwInteractorStyle = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.pvwInteractorStyle)) {
-	    return new vgl.pvwInteractorStyle();
-	  }
-	  vgl.trackballInteractorStyle.call(this);
-	  var m_that = this,
-	      m_leftMouseButtonDown = false,
-	      m_rightMouseButtonDown = false,
-	      m_middleMouseButtonDown = false,
-	      m_width,
-	      m_height,
-	      m_renderer,
-	      m_camera,
-	      m_outsideCanvas,
-	      m_coords,
-	      m_currentMousePos,
-	      m_focalPoint,
-	      m_focusWorldPt,
-	      m_focusDisplayPt,
-	      m_displayPt1,
-	      m_displayPt2,
-	      m_worldPt1,
-	      m_worldPt2,
-	      m_dx,
-	      m_dy,
-	      m_dz,
-	      m_zTrans,
-	      m_mouseLastPos = {
-	        x: 0,
-	        y: 0
-	      };
-
-	  function render() {
-	    m_renderer.resetCameraClippingRange();
-	    m_that.viewer().render();
-	  }
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseMove = function (event) {
-	    var rens = [], i = null, secCameras = [], deltaxy = null;
-	    m_width = m_that.viewer().renderWindow().windowSize()[0];
-	    m_height = m_that.viewer().renderWindow().windowSize()[1];
-	    m_renderer = m_that.viewer().renderWindow().activeRenderer();
-	    m_camera = m_renderer.camera();
-	    m_outsideCanvas = false;
-	    m_coords = m_that.viewer().relMouseCoords(event);
-	    m_currentMousePos = {
-	      x: 0,
-	      y: 0
-	    };
-
-	    // Get secondary cameras
-	    rens = m_that.viewer().renderWindow().renderers();
-	    for (i = 0; i < rens.length; i += 1) {
-	      if (m_renderer !== rens[i]) {
-	        secCameras.push(rens[i].camera());
-	      }
-	    }
-
-	    if ((m_coords.x < 0) || (m_coords.x > m_width)) {
-	      m_currentMousePos.x = 0;
-	      m_outsideCanvas = true;
-	    } else {
-	      m_currentMousePos.x = m_coords.x;
-	    }
-	    if ((m_coords.y < 0) || (m_coords.y > m_height)) {
-	      m_currentMousePos.y = 0;
-	      m_outsideCanvas = true;
-	    } else {
-	      m_currentMousePos.y = m_coords.y;
-	    }
-	    if (m_outsideCanvas === true) {
-	      return;
-	    }
-	    m_focalPoint = m_camera.focalPoint();
-	    m_focusWorldPt = vec4.fromValues(m_focalPoint[0], m_focalPoint[1],
-	                                     m_focalPoint[2], 1);
-	    m_focusDisplayPt = m_renderer.worldToDisplay(m_focusWorldPt,
-	        m_camera.viewMatrix(), m_camera.projectionMatrix(), m_width, m_height);
-	    m_displayPt1 = vec4.fromValues(
-	      m_currentMousePos.x, m_currentMousePos.y, m_focusDisplayPt[2], 1.0);
-	    m_displayPt2 = vec4.fromValues(
-	      m_mouseLastPos.x, m_mouseLastPos.y, m_focusDisplayPt[2], 1.0);
-	    m_worldPt1 = m_renderer.displayToWorld(
-	      m_displayPt1, m_camera.viewMatrix(), m_camera.projectionMatrix(),
-	      m_width, m_height);
-	    m_worldPt2 = m_renderer.displayToWorld(
-	      m_displayPt2, m_camera.viewMatrix(), m_camera.projectionMatrix(),
-	      m_width, m_height);
-
-	    m_dx = m_worldPt1[0] - m_worldPt2[0];
-	    m_dy = m_worldPt1[1] - m_worldPt2[1];
-	    m_dz = m_worldPt1[2] - m_worldPt2[2];
-
-	    if (m_middleMouseButtonDown) {
-	      m_camera.pan(-m_dx, -m_dy, -m_dz);
-	      render();
-	    }
-	    if (m_leftMouseButtonDown) {
-	      deltaxy = [(m_mouseLastPos.x - m_currentMousePos.x),
-	      (m_mouseLastPos.y - m_currentMousePos.y)];
-	      m_camera.rotate(deltaxy[0], deltaxy[1]);
-
-	      // Apply rotation to all other cameras
-	      for (i = 0; i < secCameras.length; i += 1) {
-	        secCameras[i].rotate(deltaxy[0], deltaxy[1]);
-	      }
-
-	      // Apply rotation to all other cameras
-	      for (i = 0; i < rens.length; i += 1) {
-	        rens[i].resetCameraClippingRange();
-	      }
-	      render();
-	    }
-	    if (m_rightMouseButtonDown) {
-	      /// 2.0 is the speed up factor.
-	      m_zTrans = 2.0 * (m_currentMousePos.y - m_mouseLastPos.y) / m_height;
-
-	      // Calculate zoom scale here
-	      if (m_zTrans > 0) {
-	        m_camera.zoom(1 - Math.abs(m_zTrans));
-	      } else {
-	        m_camera.zoom(1 + Math.abs(m_zTrans));
-	      }
-	      render();
-	    }
-	    m_mouseLastPos.x = m_currentMousePos.x;
-	    m_mouseLastPos.y = m_currentMousePos.y;
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse down event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseDown = function (event) {
-	    if (event.button === 0) {
-	      m_leftMouseButtonDown = true;
-	    }
-	    if (event.button === 1) {
-	      m_middleMouseButtonDown = true;
-	    }
-	    if (event.button === 2) {
-	      m_rightMouseButtonDown = true;
-	    }
-	    m_coords = m_that.viewer().relMouseCoords(event);
-	    if (m_coords.x < 0) {
-	      m_mouseLastPos.x = 0;
-	    } else {
-	      m_mouseLastPos.x = m_coords.x;
-	    }
-	    if (m_coords.y < 0) {
-	      m_mouseLastPos.y = 0;
-	    } else {
-	      m_mouseLastPos.y = m_coords.y;
-	    }
-	    return false;
-	  };
-
-	  // @note We never get mouse up from scroll bar: See the bug report here
-	  // http://bugs.jquery.com/ticket/8184
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse up event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseUp = function (event) {
-	    if (event.button === 0) {
-	      m_leftMouseButtonDown = false;
-	    }
-	    if (event.button === 1) {
-	      m_middleMouseButtonDown = false;
-	    }
-	    if (event.button === 2) {
-	      m_rightMouseButtonDown = false;
-	    }
-	    return false;
-	  };
-
-	  return this;
-	};
-	inherit(vgl.pvwInteractorStyle, vgl.trackballInteractorStyle);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global window, vgl, inherit, $*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class viewer
-	 *
-	 * @param canvas
-	 * @returns {vgl.viewer}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.viewer = function (canvas, options) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.viewer)) {
-	    return new vgl.viewer(canvas, options);
-	  }
-
-	  vgl.object.call(this);
-
-	  var m_that = this,
-	      m_canvas = canvas,
-	      m_ready = true,
-	      m_interactorStyle = null,
-	      m_renderer = vgl.renderer(options),
-	      m_renderWindow = vgl.renderWindow(m_canvas);
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get canvas of the viewer
-	   *
-	   * @returns {*}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.canvas = function () {
-	    return m_canvas;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Return render window of the viewer
-	   *
-	   * @returns {vgl.renderWindow}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.renderWindow = function () {
-	    return m_renderWindow;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Initialize the viewer
-	   *
-	   * This is a must call or otherwise render context may not initialized
-	   * properly.
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.init = function () {
-	    if (m_renderWindow !== null) {
-	      m_renderWindow._setup();
-	    } else {
-	      console.log('[ERROR] No render window attached');
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   *  Remove the viewer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.exit = function (renderState) {
-	    if (m_renderWindow !== null) {
-	      m_renderWindow._cleanup(renderState);
-	    } else {
-	      console.log('[ERROR] No render window attached');
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get interactor style of the viewer
-	   *
-	   * @returns {vgl.interactorStyle}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.interactorStyle = function () {
-	    return m_interactorStyle;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set interactor style to be used by the viewer
-	   *
-	   * @param {vgl.interactorStyle} style
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setInteractorStyle = function (style) {
-	    if (style !== m_interactorStyle) {
-	      m_interactorStyle = style;
-	      m_interactorStyle.setViewer(this);
-	      this.modified();
-	    }
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse down event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseDown = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      // Only prevent default action for right mouse button
-	      if (event.button === 2) {
-	        fixedEvent.preventDefault();
-	      }
-	      fixedEvent.state = 'down';
-	      fixedEvent.type = vgl.event.mousePress;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse up event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseUp = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.state = 'up';
-	      fixedEvent.type = vgl.event.mouseRelease;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseMove = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.mouseMove;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse wheel scroll
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseWheel = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.mouseWheel;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle mouse move event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleMouseOut = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.mouseOut;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle key press event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleKeyPress = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.keyPress;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return true;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle context menu event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleContextMenu = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.contextMenu;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle click event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleClick = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.click;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Handle double click event
-	   *
-	   * @param event
-	   * @returns {boolean}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.handleDoubleClick = function (event) {
-	    if (m_ready === true) {
-	      var fixedEvent = $.event.fix(event || window.event);
-	      fixedEvent.preventDefault();
-	      fixedEvent.type = vgl.event.dblClick;
-	      $(m_that).trigger(fixedEvent);
-	    }
-
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get mouse coodinates related to canvas
-	   *
-	   * @param event
-	   * @returns {{x: number, y: number}}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.relMouseCoords = function (event) {
-	    if (event.pageX === undefined || event.pageY === undefined) {
-	      throw 'Missing attributes pageX and pageY on the event';
-	    }
-
-	    var totalOffsetX = 0,
-	        totalOffsetY = 0,
-	        canvasX = 0,
-	        canvasY = 0,
-	        currentElement = m_canvas;
-
-	    do {
-	      totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
-	      totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
-	      currentElement = currentElement.offsetParent;
-	    } while (currentElement);
-
-	    canvasX = event.pageX - totalOffsetX;
-	    canvasY = event.pageY - totalOffsetY;
-
-	    return {
-	      x: canvasX,
-	      y: canvasY
-	    };
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function () {
-	    m_renderWindow.render();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind canvas mouse events to their default handlers
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.bindEventHandlers = function () {
-	    $(m_canvas).on('mousedown', this.handleMouseDown);
-	    $(m_canvas).on('mouseup', this.handleMouseUp);
-	    $(m_canvas).on('mousemove', this.handleMouseMove);
-	    $(m_canvas).on('mousewheel', this.handleMouseWheel);
-	    $(m_canvas).on('contextmenu', this.handleContextMenu);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo earlier binded  handlers for canvas mouse events
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.unbindEventHandlers = function () {
-	    $(m_canvas).off('mousedown', this.handleMouseDown);
-	    $(m_canvas).off('mouseup', this.handleMouseUp);
-	    $(m_canvas).off('mousemove', this.handleMouseMove);
-	    $(m_canvas).off('mousewheel', this.handleMouseWheel);
-	    $(m_canvas).off('contextmenu', this.handleContextMenu);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Initialize
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this._init = function () {
-	    this.bindEventHandlers();
-	    m_renderWindow.addRenderer(m_renderer);
-	  };
-
-	  this._init();
-	  return this;
-	};
-
-	inherit(vgl.viewer, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class shader
-	 *
-	 * @param type
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.shader = function (type) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.shader)) {
-	    return new vgl.shader(type);
-	  }
-	  vgl.object.call(this);
-
-	  var m_shaderContexts = [],
-	      m_shaderType = type,
-	      m_shaderSource = '';
-
-	  /**
-	   * A shader can be associated with multiple contexts.  Each context needs to
-	   * be compiled and attached separately.  These are tracked in the
-	   * m_shaderContexts array.
-	   *
-	   * @param renderState a renderState that includes a m_context value.
-	   * @return an object with context, compileTimestamp, and, if compiled, a
-	   *    shaderHandle entry.
-	   */
-	  this._getContextEntry = function (renderState) {
-	    var context = renderState.m_context, i, entry;
-	    for (i = 0; i < m_shaderContexts.length; i += 1) {
-	      if (m_shaderContexts[i].context === context) {
-	        return m_shaderContexts[i];
-	      }
-	    }
-	    entry = {
-	      context: context,
-	      compileTimestamp: vgl.timestamp()
-	    };
-	    m_shaderContexts.push(entry);
-	    return entry;
-	  };
-
-	  /**
-	   * Remove the context from the list of tracked contexts.  This allows the
-	   * associated shader handle to be GCed.  Does nothing if the context is not
-	   * in the list of tracked contexts.
-	   *
-	   * @param renderState a renderState that includes a m_context value.
-	   */
-	  this.removeContext = function (renderState) {
-	    var context = renderState.m_context, i;
-	    for (i = 0; i < m_shaderContexts.length; i += 1) {
-	      if (m_shaderContexts[i].context === context) {
-	        m_shaderContexts.splice(i, 1);
-	        return;
-	      }
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get shader handle
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.shaderHandle = function (renderState) {
-	    var entry = this._getContextEntry(renderState);
-	    return entry.shaderHandle;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get type of the shader
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.shaderType = function () {
-	    return m_shaderType;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get shader source
-	   *
-	   * @returns {string}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.shaderSource = function () {
-	    return m_shaderSource;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set shader source
-	   *
-	   * @param {string} source
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setShaderSource = function (source) {
-	    m_shaderSource = source;
-	    this.modified();
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compile the shader
-	   *
-	   * @returns {null}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.compile = function (renderState) {
-	    var entry = this._getContextEntry(renderState);
-	    if (this.getMTime() < entry.compileTimestamp.getMTime()) {
-	      return entry.shaderHandle;
-	    }
-
-	    renderState.m_context.deleteShader(entry.shaderHandle);
-	    entry.shaderHandle = renderState.m_context.createShader(m_shaderType);
-	    renderState.m_context.shaderSource(entry.shaderHandle, m_shaderSource);
-	    renderState.m_context.compileShader(entry.shaderHandle);
-
-	    // See if it compiled successfully
-	    if (!renderState.m_context.getShaderParameter(entry.shaderHandle,
-	        vgl.GL.COMPILE_STATUS)) {
-	      console.log('[ERROR] An error occurred compiling the shaders: ' +
-	                  renderState.m_context.getShaderInfoLog(entry.shaderHandle));
-	      console.log(m_shaderSource);
-	      renderState.m_context.deleteShader(entry.shaderHandle);
-	      return null;
-	    }
-
-	    entry.compileTimestamp.modified();
-
-	    return entry.shaderHandle;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Attach shader to the program
-	   *
-	   * @param programHandle
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.attachShader = function (renderState, programHandle) {
-	    renderState.m_context.attachShader(
-	        programHandle, this.shaderHandle(renderState));
-	  };
-	};
-
-	inherit(vgl.shader, vgl.object);
-
-	/* We can use the same shader multiple times if it is identical.  This caches
-	 * the last N shaders and will reuse them when possible.  The cache keeps the
-	 * most recently requested shader at the front.  If you are doing anything more
-	 * to a shader then creating it and setting its source once, do not use this
-	 * cache.
-	 */
-	(function () {
-	  'use strict';
-	  var m_shaderCache = [],
-	      m_shaderCacheMaxSize = 10;
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get a shader from the cache.  Create a new shader if necessary using a
-	   * specific source.
-	   *
-	   * @param type One of vgl.GL.*_SHADER
-	   * @param context the GL context for the shader.
-	   * @param {string} source the source code of the shader.
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  vgl.getCachedShader = function (type, context, source) {
-	    for (var i = 0; i < m_shaderCache.length; i += 1) {
-	      if (m_shaderCache[i].type === type &&
-	          m_shaderCache[i].context === context &&
-	          m_shaderCache[i].source === source) {
-	        if (i) {
-	          m_shaderCache.splice(0, 0, m_shaderCache.splice(i, 1)[0]);
-	        }
-	        return m_shaderCache[0].shader;
-	      }
-	    }
-	    var shader = new vgl.shader(type);
-	    shader.setShaderSource(source);
-	    m_shaderCache.unshift({
-	      type: type,
-	      context: context,
-	      source: source,
-	      shader: shader
-	    });
-	    if (m_shaderCache.length >= m_shaderCacheMaxSize) {
-	      m_shaderCache.splice(m_shaderCacheMaxSize,
-	                           m_shaderCache.length - m_shaderCacheMaxSize);
-	    }
-	    return shader;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Clear the shader cache.
-	   *
-	   * @param context the GL context to clear, or null for clear all.
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  vgl.clearCachedShaders = function (context) {
-	    for (var i = m_shaderCache.length - 1; i >= 0; i -= 1) {
-	      if (context === null || context === undefined ||
-	          m_shaderCache[i].context === context) {
-	        m_shaderCache.splice(i, 1);
-	      }
-	    }
-	  };
-	})();
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit, $*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instace of class shaderProgram
-	 *
-	 * @class
-	 * @returns {vgl.shaderProgram}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-
-	var getBaseUrl = (function () {
-	  'use strict';
-	  var baseUrl = '.';
-	  var scripts = document.getElementsByTagName('script');
-	  /* When run in certain environments, there may be no scripts loaded.  For
-	   * instance, jQuery's $.getScript won't add it to a script tag. */
-	  if (scripts.length > 0) {
-	    var index = scripts.length - 1;
-	    var vglScript = scripts[index];
-	    index = vglScript.src.lastIndexOf('/');
-	    baseUrl = vglScript.src.substring(0, index);
-	  }
-	  return function () { return baseUrl; };
-	})();
-
-	vgl.shaderProgram = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.shaderProgram)) {
-	    return new vgl.shaderProgram();
-	  }
-	  vgl.materialAttribute.call(
-	    this, vgl.materialAttributeType.ShaderProgram);
-
-	  /** @private */
-	  var m_this = this,
-	      m_programHandle = 0,
-	      m_compileTimestamp = vgl.timestamp(),
-	      m_bindTimestamp = vgl.timestamp(),
-	      m_shaders = [],
-	      m_uniforms = [],
-	      m_vertexAttributes = {},
-	      m_uniformNameToLocation = {},
-	      m_vertexAttributeNameToLocation = {};
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create a particular shader type using GLSL shader strings from a file
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.loadFromFile = function (type, sourceUrl) {
-	    var shader, success = false;
-	    $.ajax({
-	      url: sourceUrl,
-	      type: 'GET',
-	      dataType: 'text',
-	      async: false,
-	      success: function (result) {
-	        //console.log(result);
-	        shader = vgl.shader(type);
-	        shader.setShaderSource(result);
-	        m_this.addShader(shader);
-	        success = true;
-	      }
-	    });
-	    return success;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create a particular shader type using GLSL shader strings from a file
-	   * relative to VGL load URL.
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.loadShader = function (type, file) {
-	    return this.loadFromFile(type, getBaseUrl() + '/shaders/' + file);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Query uniform location in the program
-	   *
-	   * @param name
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.queryUniformLocation = function (renderState, name) {
-	    return renderState.m_context.getUniformLocation(m_programHandle, name);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Query attribute location in the program
-	   *
-	   * @param name
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.queryAttributeLocation = function (renderState, name) {
-	    return renderState.m_context.getAttribLocation(m_programHandle, name);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add a new shader to the program
-	   *
-	   * @param shader
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.addShader = function (shader) {
-	    if (m_shaders.indexOf(shader) > -1) {
-	      return false;
-	    }
-
-	    var i;
-	    for (i = m_shaders.length - 2; i >= 0; i -= 1) {
-	      if (m_shaders[i].shaderType() === shader.shaderType()) {
-	        m_shaders.splice(i, 1);
-	      }
-	    }
-
-	    m_shaders.push(shader);
-	    m_this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add a new uniform to the program
-	   *
-	   * @param uniform
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.addUniform = function (uniform) {
-	    if (m_uniforms.indexOf(uniform) > -1) {
-	      return false;
-	    }
-
-	    m_uniforms.push(uniform);
-	    m_this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Add a new vertex attribute to the program
-	   *
-	   * @param attr
-	   * @param key
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.addVertexAttribute = function (attr, key) {
-	    m_vertexAttributes[key] = attr;
-	    m_this.modified();
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get uniform location
-	   *
-	   * This method does not perform any query into the program but relies on
-	   * the fact that it depends on a call to queryUniformLocation earlier.
-	   *
-	   * @param name
-	   * @returns {number}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.uniformLocation = function (name) {
-	    return m_uniformNameToLocation[name];
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get attribute location
-	   *
-	   * This method does not perform any query into the program but relies on the
-	   * fact that it depends on a call to queryUniformLocation earlier.
-	   *
-	   * @param name
-	   * @returns {number}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.attributeLocation = function (name) {
-	    return m_vertexAttributeNameToLocation[name];
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get uniform object using name as the key
-	   *
-	   * @param name
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.uniform = function (name) {
-	    var i;
-	    for (i = 0; i < m_uniforms.length; i += 1) {
-	      if (m_uniforms[i].name() === name) {
-	        return m_uniforms[i];
-	      }
-	    }
-
-	    return null;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update all uniforms
-	   *
-	   * This method should be used directly unless required
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.updateUniforms = function (renderState) {
-	    var i;
-
-	    for (i = 0; i < m_uniforms.length; i += 1) {
-	      m_uniforms[i].callGL(renderState,
-	        m_uniformNameToLocation[m_uniforms[i].name()]);
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Link shader program
-	   *
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.link = function (renderState) {
-	    renderState.m_context.linkProgram(m_programHandle);
-
-	    // If creating the shader program failed, alert
-	    if (!renderState.m_context.getProgramParameter(m_programHandle,
-	        vgl.GL.LINK_STATUS)) {
-	      console.log('[ERROR] Unable to initialize the shader program.');
-	      return false;
-	    }
-
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Use the shader program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.use = function (renderState) {
-	    renderState.m_context.useProgram(m_programHandle);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Peform any initialization required
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this._setup = function (renderState) {
-	    if (m_programHandle === 0) {
-	      m_programHandle = renderState.m_context.createProgram();
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Peform any clean up required when the program gets deleted
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this._cleanup = function (renderState) {
-	    m_this.deleteVertexAndFragment(renderState);
-	    m_this.deleteProgram(renderState);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Delete the shader program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.deleteProgram = function (renderState) {
-	    renderState.m_context.deleteProgram(m_programHandle);
-	    m_programHandle = 0;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Delete vertex and fragment shaders
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.deleteVertexAndFragment = function (renderState) {
-	    var i;
-	    for (i = 0; i < m_shaders.length; i += 1) {
-	      renderState.m_context.detachShader(m_shaders[i].shaderHandle(renderState));
-	      renderState.m_context.deleteShader(m_shaders[i].shaderHandle(renderState));
-	      m_shaders[i].removeContext(renderState);
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compile and link a shader
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.compileAndLink = function (renderState) {
-	    var i;
-
-	    if (m_compileTimestamp.getMTime() >= this.getMTime()) {
-	      return;
-	    }
-
-	    m_this._setup(renderState);
-
-	    // Compile shaders
-	    for (i = 0; i < m_shaders.length; i += 1) {
-	      m_shaders[i].compile(renderState);
-	      m_shaders[i].attachShader(renderState, m_programHandle);
-	    }
-
-	    m_this.bindAttributes(renderState);
-
-	    // link program
-	    if (!m_this.link(renderState)) {
-	      console.log('[ERROR] Failed to link Program');
-	      m_this._cleanup(renderState);
-	    }
-
-	    m_compileTimestamp.modified();
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind the program with its shaders
-	   *
-	   * @param renderState
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.bind = function (renderState) {
-	    var i = 0;
-
-	    if (m_bindTimestamp.getMTime() < m_this.getMTime()) {
-
-	      // Compile shaders
-	      m_this.compileAndLink(renderState);
-
-	      m_this.use(renderState);
-	      m_this.bindUniforms(renderState);
-	      m_bindTimestamp.modified();
-	    } else {
-	      m_this.use(renderState);
-	    }
-
-	    // Call update callback.
-	    for (i = 0; i < m_uniforms.length; i += 1) {
-	      m_uniforms[i].update(renderState, m_this);
-	    }
-
-	    // Now update values to GL.
-	    m_this.updateUniforms(renderState);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo binding of the shader program
-	   *
-	   * @param renderState
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.undoBind = function (renderState) {
-	    // REF https://www.khronos.org/opengles/sdk/docs/man/xhtml/glUseProgram.xml
-	    // If program is 0, then the current rendering state refers to an invalid
-	    // program object, and the results of vertex and fragment shader execution
-	    // due to any glDrawArrays or glDrawElements commands are undefined
-	    renderState.m_context.useProgram(null);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind vertex data
-	   *
-	   * @param renderState
-	   * @param key
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.bindVertexData = function (renderState, key) {
-	    if (m_vertexAttributes.hasOwnProperty(key)) {
-	      m_vertexAttributes[key].bindVertexData(renderState, key);
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind vetex data
-	   *
-	   * @param renderState
-	   * @param key
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.undoBindVertexData = function (renderState, key) {
-	    if (m_vertexAttributes.hasOwnProperty(key)) {
-	      m_vertexAttributes[key].undoBindVertexData(renderState, key);
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind uniforms
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.bindUniforms = function (renderState) {
-	    var i;
-	    for (i = 0; i < m_uniforms.length; i += 1) {
-	      m_uniformNameToLocation[m_uniforms[i].name()] = this
-	          .queryUniformLocation(renderState, m_uniforms[i].name());
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind vertex attributes
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.bindAttributes = function (renderState) {
-	    var key, name;
-	    for (key in m_vertexAttributes) {
-	      if (m_vertexAttributes.hasOwnProperty(key)) {
-	        name = m_vertexAttributes[key].name();
-	        renderState.m_context.bindAttribLocation(m_programHandle, key, name);
-	        m_vertexAttributeNameToLocation[name] = key;
-	      }
-	    }
-	  };
-
-	  return m_this;
-	};
-
-	inherit(vgl.shaderProgram, vgl.materialAttribute);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global Uint8Array, vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class texture
-	 *
-	 * @class
-	 * @returns {vgl.texture}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.texture = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.texture)) {
-	    return new vgl.texture();
-	  }
-	  vgl.materialAttribute.call(
-	    this, vgl.materialAttributeType.Texture);
-
-	  this.m_width = 0;
-	  this.m_height = 0;
-	  this.m_depth = 0;
-
-	  this.m_textureHandle = null;
-	  this.m_textureUnit = 0;
-
-	  this.m_pixelFormat = vgl.GL.RGBA;
-	  this.m_pixelDataType = vgl.GL.UNSIGNED_BYTE;
-	  this.m_internalFormat = vgl.GL.RGBA;
-	  this.m_nearestPixel = false;
-
-	  this.m_image = null;
-
-	  var m_setupTimestamp = vgl.timestamp(),
-	      m_that = this;
-
-	  function activateTextureUnit(renderState) {
-	    switch (m_that.m_textureUnit) {
-	      case 0:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE0);
-	        break;
-	      case 1:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE1);
-	        break;
-	      case 2:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE2);
-	        break;
-	      case 3:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE3);
-	        break;
-	      case 4:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE4);
-	        break;
-	      case 5:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE5);
-	        break;
-	      case 6:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE6);
-	        break;
-	      case 7:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE7);
-	        break;
-	      case 8:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE8);
-	        break;
-	      case 9:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE9);
-	        break;
-	      case 10:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE10);
-	        break;
-	      case 11:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE11);
-	        break;
-	      case 12:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE12);
-	        break;
-	      case 13:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE13);
-	        break;
-	      case 14:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE14);
-	        break;
-	      case 15:
-	        renderState.m_context.activeTexture(vgl.GL.TEXTURE15);
-	        break;
-	      default:
-	        throw '[error] Texture unit ' + m_that.m_textureUnit +
-	              ' is not supported';
-	    }
-	  }
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create texture, update parameters, and bind data
-	   *
-	   * @param renderState
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setup = function (renderState) {
-	    // Activate the texture unit first
-	    activateTextureUnit(renderState);
-
-	    renderState.m_context.deleteTexture(this.m_textureHandle);
-	    this.m_textureHandle = renderState.m_context.createTexture();
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MIN_FILTER,
-	        this.m_nearestPixel ? vgl.GL.NEAREST : vgl.GL.LINEAR);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MAG_FILTER,
-	        this.m_nearestPixel ? vgl.GL.NEAREST : vgl.GL.LINEAR);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
-
-	    if (this.m_image !== null) {
-	      renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT, 1);
-	      renderState.m_context.pixelStorei(vgl.GL.UNPACK_FLIP_Y_WEBGL, true);
-
-	      this.updateDimensions();
-	      this.computeInternalFormatUsingImage();
-
-	      // console.log('m_internalFormat ' + this.m_internalFormat);
-	      // console.log('m_pixelFormat ' + this.m_pixelFormat);
-	      // console.log('m_pixelDataType ' + this.m_pixelDataType);
-
-	      // FOR now support only 2D textures
-	      renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, this.m_internalFormat,
-	        this.m_pixelFormat, this.m_pixelDataType, this.m_image);
-	    } else {
-	      renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, this.m_internalFormat,
-	        this.m_width, this.m_height, 0, this.m_pixelFormat, this.m_pixelDataType, null);
-	    }
-
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
-	    m_setupTimestamp.modified();
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create texture and if already created use it
-	   *
-	   * @param renderState
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.bind = function (renderState) {
-	    // TODO Call setup via material setup
-	    if (this.getMTime() > m_setupTimestamp.getMTime()) {
-	      this.setup(renderState);
-	    }
-
-	    activateTextureUnit(renderState);
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Turn off the use of this texture
-	   *
-	   * @param renderState
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.undoBind = function (renderState) {
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get image used by the texture
-	   *
-	   * @returns {vgl.image}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.image = function () {
-	    return this.m_image;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set image for the texture
-	   *
-	   * @param {vgl.image} image
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setImage = function (image) {
-	    if (image !== null) {
-	      this.m_image = image;
-	      this.updateDimensions();
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get nearest pixel flag for the texture
-	   *
-	   * @returns boolean
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.nearestPixel = function () {
-	    return this.m_nearestPixel;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set nearest pixel flag for the texture
-	   *
-	   * @param {boolean} nearest pixel flag
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setNearestPixel = function (nearest) {
-	    nearest = nearest ? true : false;
-	    if (nearest !== this.m_nearestPixel) {
-	      this.m_nearestPixel = nearest;
-	      this.modified();
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get texture unit of the texture
-	   *
-	   * @returns {number}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.textureUnit = function () {
-	    return this.m_textureUnit;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set texture unit of the texture. Default is 0.
-	   *
-	   * @param {number} unit
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setTextureUnit = function (unit) {
-	    if (this.m_textureUnit === unit) {
-	      return false;
-	    }
-
-	    this.m_textureUnit = unit;
-	    this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get width of the texture
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.width = function () {
-	    return this.m_width;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set width of the texture
-	   *
-	   * @param {number} width
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setWidth = function (width) {
-	    if (m_that.m_width !== width) {
-	      m_that.m_width = width;
-	      m_that.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get width of the texture
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.height = function () {
-	    return m_that.m_height;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set height of the texture
-	   *
-	   * @param {number} height
-	   * @returns {vgl.texture}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setHeight = function (height) {
-	    if (m_that.m_height !== height) {
-	      m_that.m_height = height;
-	      m_that.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get depth of the texture
-	   *
-	   * @returns {number}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.depth = function () {
-	    return this.m_depth;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set depth of the texture
-	   *
-	   * @param {number} depth
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setDepth = function (depth) {
-	    if (this.m_image === null) {
-	      return false;
-	    }
-
-	    this.m_depth = depth;
-	    this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get the texture handle (id) of the texture
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.textureHandle = function () {
-	    return this.m_textureHandle;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get internal format of the texture
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.internalFormat = function () {
-	    return this.m_internalFormat;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set internal format of the texture
-	   *
-	   * @param internalFormat
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setInternalFormat = function (internalFormat) {
-	    if (this.m_internalFormat !== internalFormat) {
-	      this.m_internalFormat = internalFormat;
-	      this.modified();
-	      return true;
-	    }
-
-	    return false;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get pixel format of the texture
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.pixelFormat = function () {
-	    return this.m_pixelFormat;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set pixel format of the texture
-	   *
-	   * @param pixelFormat
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setPixelFormat = function (pixelFormat) {
-	    if (this.m_image === null) {
-	      return false;
-	    }
-
-	    this.m_pixelFormat = pixelFormat;
-	    this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get pixel data type
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.pixelDataType = function () {
-	    return this.m_pixelDataType;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set pixel data type
-	   *
-	   * @param pixelDataType
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setPixelDataType = function (pixelDataType) {
-	    if (this.m_image === null) {
-	      return false;
-	    }
-
-	    this.m_pixelDataType = pixelDataType;
-
-	    this.modified();
-
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Compute internal format of the texture
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.computeInternalFormatUsingImage = function () {
-	    // Currently image does not define internal format
-	    // and hence it's pixel format is the only way to query
-	    // information on how color has been stored.
-	    // switch (this.m_image.pixelFormat()) {
-	    // case vgl.GL.RGB:
-	    // this.m_internalFormat = vgl.GL.RGB;
-	    // break;
-	    // case vgl.GL.RGBA:
-	    // this.m_internalFormat = vgl.GL.RGBA;
-	    // break;
-	    // case vgl.GL.Luminance:
-	    // this.m_internalFormat = vgl.GL.Luminance;
-	    // break;
-	    // case vgl.GL.LuminanceAlpha:
-	    // this.m_internalFormat = vgl.GL.LuminanceAlpha;
-	    // break;
-	    // // Do nothing when image pixel format is none or undefined.
-	    // default:
-	    // break;
-	    // };
-
-	    // TODO Fix this
-	    this.m_internalFormat = vgl.GL.RGBA;
-	    this.m_pixelFormat = vgl.GL.RGBA;
-	    this.m_pixelDataType = vgl.GL.UNSIGNED_BYTE;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update texture dimensions
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.updateDimensions = function () {
-	    if (this.m_image !== null) {
-	      this.m_width = this.m_image.width;
-	      this.m_height = this.m_image.height;
-	      this.m_depth = 0; // Only 2D images are supported now
-	    }
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.texture, vgl.materialAttribute);
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class lookupTable
-	 *
-	 * @class
-	 * @returns {vgl.lookupTable}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.lookupTable = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.lookupTable)) {
-	    return new vgl.lookupTable();
-	  }
-	  vgl.texture.call(this);
-
-	  var m_setupTimestamp = vgl.timestamp(),
-	      m_range = [0, 0];
-
-	  this.m_colorTable = //paraview bwr colortable
-	    [0.07514311, 0.468049805, 1, 1,
-	     0.247872569, 0.498782363, 1, 1,
-	     0.339526309, 0.528909511, 1, 1,
-	     0.409505078, 0.558608486, 1, 1,
-	     0.468487184, 0.588057293, 1, 1,
-	     0.520796675, 0.617435078, 1, 1,
-	     0.568724526, 0.646924167, 1, 1,
-	     0.613686735, 0.676713218, 1, 1,
-	     0.656658579, 0.707001303, 1, 1,
-	     0.698372844, 0.738002964, 1, 1,
-	     0.739424025, 0.769954435, 1, 1,
-	     0.780330104, 0.803121429, 1, 1,
-	     0.821573924, 0.837809045, 1, 1,
-	     0.863634967, 0.874374691, 1, 1,
-	     0.907017747, 0.913245283, 1, 1,
-	     0.936129275, 0.938743558, 0.983038586, 1,
-	     0.943467973, 0.943498599, 0.943398095, 1,
-	     0.990146732, 0.928791426, 0.917447482, 1,
-	     1, 0.88332677, 0.861943246, 1,
-	     1, 0.833985467, 0.803839606, 1,
-	     1, 0.788626485, 0.750707739, 1,
-	     1, 0.746206642, 0.701389973, 1,
-	     1, 0.70590052, 0.654994046, 1,
-	     1, 0.667019783, 0.610806959, 1,
-	     1, 0.6289553, 0.568237474, 1,
-	     1, 0.591130233, 0.526775617, 1,
-	     1, 0.552955184, 0.485962266, 1,
-	     1, 0.513776083, 0.445364274, 1,
-	     1, 0.472800903, 0.404551679, 1,
-	     1, 0.428977855, 0.363073592, 1,
-	     1, 0.380759558, 0.320428137, 1,
-	     0.961891484, 0.313155629, 0.265499262, 1,
-	     0.916482116, 0.236630659, 0.209939162, 1].map(
-	             function (x) { return x * 255; });
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create lookup table, initialize parameters, and bind data to it
-	   *
-	   * @param {vgl.renderState} renderState
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setup = function (renderState) {
-	    if (this.textureUnit() === 0) {
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE0);
-	    } else if (this.textureUnit() === 1) {
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE1);
-	    }
-
-	    renderState.m_context.deleteTexture(this.m_textureHandle);
-	    this.m_textureHandle = renderState.m_context.createTexture();
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MIN_FILTER, vgl.GL.LINEAR);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MAG_FILTER, vgl.GL.LINEAR);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
-	    renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT, 1);
-
-	    this.m_width = this.m_colorTable.length / 4;
-	    this.m_height = 1;
-	    this.m_depth = 0;
-	    renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D,
-	        0, vgl.GL.RGBA, this.m_width, this.m_height, this.m_depth,
-	        vgl.GL.RGBA, vgl.GL.UNSIGNED_BYTE, new Uint8Array(this.m_colorTable));
-
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
-	    m_setupTimestamp.modified();
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get color table used by the lookup table
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.colorTable = function () {
-	    return this.m_colorTable;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set color table used by the lookup table
-	   *
-	   * @param colors
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setColorTable = function (colors) {
-	    if (this.m_colorTable === colors) {
-	      return false;
-	    }
-
-	    this.m_colorTable = colors;
-	    this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get scalar range
-	   *
-	   * @returns {Array}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.range = function () {
-	    return m_range;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set scalar range for the lookup table
-	   *
-	   * @param range
-	   * @returns {boolean}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.setRange = function (range) {
-	    if (m_range === range) {
-	      return false;
-	    }
-	    m_range = range;
-	    this.modified();
-	    return true;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Given a [min,max] range update the lookup table range
-	   *
-	   * @param range
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.updateRange = function (range) {
-	    if (!(range instanceof Array)) {
-	      console.log('[error] Invalid data type for range. Requires array [min,max]');
-	    }
-
-	    if (range[0] < m_range[0]) {
-	      m_range[0] = range[0];
-	      this.modified();
-	    }
-
-	    if (range[1] > m_range[1]) {
-	      m_range[1] = range[1];
-	      this.modified();
-	    }
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.lookupTable, vgl.texture);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, mat4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class uniform
-	 *
-	 * @param type
-	 * @param name
-	 * @returns {vgl.uniform} OpenGL uniform encapsulation
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.uniform = function (type, name) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.uniform)) {
-	    return new vgl.uniform(type, name);
-	  }
-
-	  this.getTypeNumberOfComponents = function (type) {
-	    switch (type) {
-	      case vgl.GL.FLOAT:
-	      case vgl.GL.INT:
-	      case vgl.GL.BOOL:
-	        return 1;
-
-	      case vgl.GL.FLOAT_VEC2:
-	      case vgl.GL.INT_VEC2:
-	      case vgl.GL.BOOL_VEC2:
-	        return 2;
-
-	      case vgl.GL.FLOAT_VEC3:
-	      case vgl.GL.INT_VEC3:
-	      case vgl.GL.BOOL_VEC3:
-	        return 3;
-
-	      case vgl.GL.FLOAT_VEC4:
-	      case vgl.GL.INT_VEC4:
-	      case vgl.GL.BOOL_VEC4:
-	        return 4;
-
-	      case vgl.GL.FLOAT_MAT3:
-	        return 9;
-
-	      case vgl.GL.FLOAT_MAT4:
-	        return 16;
-
-	      default:
-	        return 0;
-	    }
-	  };
-
-	  var m_type = type,
-	      m_name = name,
-	      m_dataArray = [];
-
-	  m_dataArray.length = this.getTypeNumberOfComponents(m_type);
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get name of the uniform
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.name = function () {
-	    return m_name;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get type of the uniform
-	   *
-	   * @returns {*}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.type = function () {
-	    return m_type;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get value of the uniform
-	   *
-	   * @returns {Array}
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.get = function () {
-	    return m_dataArray;
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set value of the uniform
-	   *
-	   * @param value
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.set = function (value) {
-	    var i = 0, lendata = m_dataArray.length;
-	    if (lendata !== 1) {
-	      for (i = 0; i < lendata; i += 1) {
-	        m_dataArray[i] = value[i];
-	      }
-	    } else {
-	      m_dataArray[0] = value;
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Call GL and pass updated values to the current shader
-	   *
-	   * @param location
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.callGL = function (renderState, location) {
-	    switch (m_type) {
-	      case vgl.GL.BOOL:
-	      case vgl.GL.INT:
-	        renderState.m_context.uniform1iv(location, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT:
-	        renderState.m_context.uniform1fv(location, m_dataArray);
-	        break;
-	      case vgl.GL.BOOL_VEC2:
-	      case vgl.GL.INT_VEC2:
-	        renderState.m_context.uniform2iv(location, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT_VEC2:
-	        renderState.m_context.uniform2fv(location, m_dataArray);
-	        break;
-	      case vgl.GL.BOOL_VEC3:
-	      case vgl.GL.INT_VEC3:
-	        renderState.m_context.uniform3iv(location, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT_VEC3:
-	        renderState.m_context.uniform3fv(location, m_dataArray);
-	        break;
-	      case vgl.GL.BOOL_VEC4:
-	      case vgl.GL.INT_VEC4:
-	        renderState.m_context.uniform4iv(location, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT_VEC4:
-	        renderState.m_context.uniform4fv(location, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT_MAT3:
-	        renderState.m_context.uniformMatrix3fv(location, vgl.GL.FALSE, m_dataArray);
-	        break;
-	      case vgl.GL.FLOAT_MAT4:
-	        renderState.m_context.uniformMatrix4fv(location, vgl.GL.FALSE, m_dataArray);
-	        break;
-	      default:
-	        break;
-	    }
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Virtual method to update the uniform
-	   *
-	   * Should be implemented by the derived class.
-	   *
-	   * @param renderState
-	   * @param program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.update = function (renderState, program) {
-	    void renderState; /* unused parameter */
-	    void program; /* unused parameter */
-	    // Should be implemented by the derived class
-	  };
-
-	  return this;
-	};
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create new instance of class modelViewUniform
-	 *
-	 * @param name
-	 * @returns {vgl.modelViewUniform}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.modelViewUniform = function (name) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.modelViewUniform)) {
-	    return new vgl.modelViewUniform(name);
-	  }
-
-	  if (!name) {
-	    name = 'modelViewMatrix';
-	  }
-
-	  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
-
-	  this.set(mat4.create());
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update the uniform given a render state and shader program
-	   *
-	   * @param {vgl.renderState} renderState
-	   * @param {vgl.shaderProgram} program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.update = function (renderState, program) {
-	    void program; /* unused parameter */
-	    this.set(renderState.m_modelViewMatrix);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.modelViewUniform, vgl.uniform);
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create new instance of class modelViewOriginUniform.
-	 *
-	 * @param name
-	 * @param {array} origin a triplet of floats.
-	 * @returns {vgl.modelViewUniform}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.modelViewOriginUniform = function (name, origin) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.modelViewOriginUniform)) {
-	    return new vgl.modelViewOriginUniform(name, origin);
-	  }
-
-	  if (!name) {
-	    name = 'modelViewMatrix';
-	  }
-	  origin = origin || [0, 0, 0];
-
-	  var m_origin = [origin[0], origin[1], origin[2] || 0];
-
-	  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
-
-	  this.set(mat4.create());
-
-	  /**
-	   * Change the origin used by the uniform view matrix.
-	   *
-	   * @param {array} origin a triplet of floats.
-	   */
-	  this.setOrigin = function (origin) {
-	    origin = origin || [0, 0, 0];
-	    m_origin = [origin[0], origin[1], origin[2] || 0];
-	  };
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update the uniform given a render state and shader program.  This offsets
-	   * the modelViewMatrix by the origin, and, if the model view should be
-	   * aligned, aligns it appropriately.  The alignment must be done after the
-	   * origin offset to maintain precision.
-	   *
-	   * @param {vgl.renderState} renderState
-	   * @param {vgl.shaderProgram} program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.update = function (renderState, program) {
-	    void program; /* unused parameter */
-	    var view = renderState.m_modelViewMatrix;
-	    if (renderState.m_modelViewAlignment) {
-	      /* adjust alignment before origin.  Otherwise, a changing origin can
-	       * affect the rounding choice and result in a 1 pixe jitter. */
-	      var align = renderState.m_modelViewAlignment;
-	      /* Don't modify the orignal matrix.  If we are in an environment where
-	       * you can't slice an Float32Array, switch to a regular array */
-	      view = view.slice ? view.slice() : Array.prototype.slice.call(view);
-	      /* view[12] and view[13] are the x and y offsets.  align.round is the
-	       * units-per-pixel, and align.dx and .dy are either 0 or half the size of
-	       * a unit-per-pixel.  The alignment guarantees that the texels are
-	       * aligned with screen pixels. */
-	      view[12] = Math.round(view[12] / align.roundx) * align.roundx + align.dx;
-	      view[13] = Math.round(view[13] / align.roundy) * align.roundy + align.dy;
-	    }
-	    view = mat4.translate(mat4.create(), view, m_origin);
-	    this.set(view);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.modelViewOriginUniform, vgl.uniform);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class projectionUniform
-	 *
-	 * @param name
-	 * @returns {vgl.projectionUniform}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.projectionUniform = function (name) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.projectionUniform)) {
-	    return new vgl.projectionUniform(name);
-	  }
-
-	  if (!name) {
-	    name = 'projectionMatrix';
-	  }
-
-	  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
-
-	  this.set(mat4.create());
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update the uniform given a render state and shader program
-	   *
-	   * @param renderState
-	   * @param program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.update = function (renderState, program) {
-	    void program; /* unused parameter */
-	    this.set(renderState.m_projectionMatrix);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.projectionUniform, vgl.uniform);
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class floatUniform
-	 *
-	 * @param name
-	 * @param value
-	 * @returns {vgl.floatUniform}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.floatUniform = function (name, value) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.floatUniform)) {
-	    return new vgl.floatUniform(name, value);
-	  }
-
-	  if (!name) {
-	    name = 'floatUniform';
-	  }
-
-	  value = value === undefined ? 1.0 : value;
-
-	  vgl.uniform.call(this, vgl.GL.FLOAT, name);
-
-	  this.set(value);
-	};
-
-	inherit(vgl.floatUniform, vgl.uniform);
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create new instance of class normalMatrixUniform
-	 *
-	 * @param name
-	 * @returns {vgl.normalMatrixUniform}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.normalMatrixUniform = function (name) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.normalMatrixUniform)) {
-	    return new vgl.normalMatrixUniform(name);
-	  }
-
-	  if (!name) {
-	    name = 'normalMatrix';
-	  }
-
-	  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
-
-	  this.set(mat4.create());
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Update the uniform given a render state and shader program
-	   *
-	   * @param {vgl.renderState} renderState
-	   * @param {vgl.shaderProgram} program
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.update = function (renderState, program) {
-	    void program; /* unused parameter */
-	    this.set(renderState.m_normalMatrix);
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.normalMatrixUniform, vgl.uniform);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Keys to identify vertex attributes
-	 *
-	 * @type {{Position: number, Normal: number, TextureCoordinate: number,
-	 *         Color: number, Scalar: number, Scalar2: number, Scalar3: number,
-	 *         Scalar4: number, Scalar5: number, Scalar6: number, Scalar7: number,
-	 *         CountAttributeIndex: number}}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.vertexAttributeKeys = {
-	  'Position' : 0,
-	  'Normal' : 1,
-	  'TextureCoordinate' : 2,
-	  'Color' : 3,
-	  'Scalar': 4,
-	  'CountAttributeIndex' : 5
-	};
-
-	vgl.vertexAttributeKeysIndexed = {
-	  'Zero' : 0,
-	  'One' : 1,
-	  'Two' : 2,
-	  'Three' : 3,
-	  'Four' : 4,
-	  'Five' : 5,
-	  'Six' : 6,
-	  'Seven' : 7,
-	  'Eight' : 8,
-	  'Nine' : 9
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of vertexAttribute
-	 *
-	 * @param {string} name
-	 * @returns {vgl.vertexAttribute}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.vertexAttribute = function (name) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.vertexAttribute)) {
-	    return new vgl.vertexAttribute(name);
-	  }
-
-	  var m_name = name;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get name of the vertex attribute
-	   *
-	   * @returns {string}
-	   */
-	  //////////////////////////////////////////////////////////////////////////////
-	  this.name = function () {
-	    return m_name;
-	  };
-
-	  //////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Bind vertex data to the given render state
-	   *
-	   * @param {vgl.renderState} renderState
-	   * @param {vgl.vertexAttributeKeys} key
-	   */
-	  //////////////////////////////////////////////////////////////////////////////
-	  this.bindVertexData = function (renderState, key) {
-	    var geometryData = renderState.m_mapper.geometryData(),
-	        sourceData = geometryData.sourceData(key),
-	        program = renderState.m_material.shaderProgram();
-
-	    renderState.m_context.vertexAttribPointer(program.attributeLocation(
-	        m_name), sourceData
-	        .attributeNumberOfComponents(key), sourceData.attributeDataType(key),
-	                           sourceData.normalized(key), sourceData
-	                               .attributeStride(key), sourceData
-	                               .attributeOffset(key));
-
-	    renderState.m_context.enableVertexAttribArray(program.attributeLocation(m_name));
-	  };
-
-	  //////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Undo bind vertex data for a given render state
-	   *
-	   * @param {vgl.renderState} renderState
-	   * @param {vgl.vertexAttributeKeys} key
-	   */
-	  //////////////////////////////////////////////////////////////////////////////
-	  this.undoBindVertexData = function (renderState, key) {
-	    key = key; /* unused parameter */
-
-	    var program = renderState.m_material.shaderProgram();
-
-	    renderState.m_context.disableVertexAttribArray(program.attributeLocation(m_name));
-	  };
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	///////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class source
-	 *
-	 * @returns {vgl.source}
-	 */
-	///////////////////////////////////////////////////////////////////////////////
-	vgl.source = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.source)) {
-	    return new vgl.source();
-	  }
-
-	  vgl.object.call(this);
-
-	  /////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Virtual function to create a source instance
-	   */
-	  /////////////////////////////////////////////////////////////////////////////
-	  this.create = function () {
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.source, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class planeSource
-	 *
-	 * @class
-	 * @returns {vgl.planeSource}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.planeSource = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.planeSource)) {
-	    return new vgl.planeSource();
-	  }
-	  vgl.source.call(this);
-
-	  var m_origin = [0.0, 0.0, 0.0],
-	      m_point1 = [1.0, 0.0, 0.0],
-	      m_point2 = [0.0, 1.0, 0.0],
-	      m_normal = [0.0, 0.0, 1.0],
-	      m_xresolution = 1,
-	      m_yresolution = 1,
-	      m_geom = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set origin of the plane
-	   *
-	   * @param x
-	   * @param y
-	   * @param z
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setOrigin = function (x, y, z) {
-	    m_origin[0] = x;
-	    m_origin[1] = y;
-	    m_origin[2] = z;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set point that defines the first axis of the plane
-	   *
-	   * @param x
-	   * @param y
-	   * @param z
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPoint1 = function (x, y, z) {
-	    m_point1[0] = x;
-	    m_point1[1] = y;
-	    m_point1[2] = z;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set point that defines the first axis of the plane
-	   *
-	   * @param x
-	   * @param y
-	   * @param z
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPoint2 = function (x, y, z) {
-	    m_point2[0] = x;
-	    m_point2[1] = y;
-	    m_point2[2] = z;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create a plane geometry given input parameters
-	   *
-	   * @returns {null}
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.create = function () {
-	    m_geom = new vgl.geometryData();
-
-	    var x = [], tc = [], v1 = [], v2 = [],
-	        pts = [], i, j, ii, numPts,
-	        posIndex = 0, normIndex = 0, colorIndex = 0, texCoordIndex = 0,
-	        positions = [], normals = [], colors = [],
-	        texCoords = [], indices = [], tristrip = null,
-	        sourcePositions = null, sourceColors = null, sourceTexCoords;
-
-	    x.length = 3;
-	    tc.length = 2;
-	    v1.length = 3;
-	    v2.length = 3;
-	    pts.length = 3;
-
-	    // Check input
-	    for (i = 0; i < 3; i += 1) {
-	      v1[i] = m_point1[i] - m_origin[i];
-	      v2[i] = m_point2[i] - m_origin[i];
-	    }
-
-	    // TODO Compute center and normal
-	    // Set things up; allocate memory
-	    numPts = (m_xresolution + 1) * (m_yresolution + 1);
-	    positions.length = 3 * numPts;
-	    normals.length = 3 * numPts;
-	    texCoords.length = 2 * numPts;
-	    indices.length = numPts;
-
-	    for (i = 0; i < (m_yresolution + 1); i += 1) {
-	      tc[1] = i / m_yresolution;
-
-	      for (j = 0; j < (m_xresolution + 1); j += 1) {
-	        tc[0] = j / m_xresolution;
-
-	        for (ii = 0; ii < 3; ii += 1) {
-	          x[ii] = m_origin[ii] + tc[0] * v1[ii] + tc[1] * v2[ii];
-	        }
-
-	        //jshint plusplus: false
-	        positions[posIndex++] = x[0];
-	        positions[posIndex++] = x[1];
-	        positions[posIndex++] = x[2];
-
-	        colors[colorIndex++] = 1.0;
-	        colors[colorIndex++] = 1.0;
-	        colors[colorIndex++] = 1.0;
-
-	        normals[normIndex++] = m_normal[0];
-	        normals[normIndex++] = m_normal[1];
-	        normals[normIndex++] = m_normal[2];
-
-	        texCoords[texCoordIndex++] = tc[0];
-	        texCoords[texCoordIndex++] = tc[1];
-	        //jshint plusplus: true
-	      }
-	    }
-
-	    /// Generate polygon connectivity
-	    for (i = 0; i < m_yresolution; i += 1) {
-	      for (j = 0; j < m_xresolution; j += 1) {
-	        pts[0] = j + i * (m_xresolution + 1);
-	        pts[1] = pts[0] + 1;
-	        pts[2] = pts[0] + m_xresolution + 2;
-	        pts[3] = pts[0] + m_xresolution + 1;
-	      }
-	    }
-
-	    for (i = 0; i < numPts; i += 1) {
-	      indices[i] = i;
-	    }
-
-	    tristrip = new vgl.triangleStrip();
-	    tristrip.setIndices(indices);
-
-	    sourcePositions = vgl.sourceDataP3fv();
-	    sourcePositions.pushBack(positions);
-
-	    sourceColors = vgl.sourceDataC3fv();
-	    sourceColors.pushBack(colors);
-
-	    sourceTexCoords = vgl.sourceDataT2fv();
-	    sourceTexCoords.pushBack(texCoords);
-
-	    m_geom.addSource(sourcePositions);
-	    m_geom.addSource(sourceColors);
-	    m_geom.addSource(sourceTexCoords);
-	    m_geom.addPrimitive(tristrip);
-
-	    return m_geom;
-	  };
-	};
-
-	inherit(vgl.planeSource, vgl.source);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class pointSource
-	 *
-	 * @class
-	 * @returns {vgl.pointSource}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.pointSource = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.pointSource)) {
-	    return new vgl.pointSource();
-	  }
-	  vgl.source.call(this);
-
-	  var m_this = this,
-	      m_positions = [],
-	      m_colors = [],
-	      m_textureCoords = [],
-	      m_size = [],
-	      m_geom = null;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get positions for the points
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getPositions = function () {
-	    return m_positions;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set positions for the source
-	   *
-	   * @param positions
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPositions = function (positions) {
-	    if (positions instanceof Array) {
-	      m_positions = positions;
-	    } else {
-	      console
-	          .log('[ERROR] Invalid data type for positions. Array is required.');
-	    }
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get colors for the points
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getColors = function () {
-	    return m_colors;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set colors for the points
-	   *
-	   * @param colors
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setColors = function (colors) {
-	    if (colors instanceof Array) {
-	      m_colors = colors;
-	    } else {
-	      console.log('[ERROR] Invalid data type for colors. Array is required.');
-	    }
-
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get size for the points
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getSize = function () {
-	    return m_size;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set colors for the points
-	   *
-	   * @param colors
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setSize = function (size) {
-	    m_size = size;
-	    this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set texture coordinates for the points
-	   *
-	   * @param texcoords
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setTextureCoordinates = function (texcoords) {
-	    if (texcoords instanceof Array) {
-	      m_textureCoords = texcoords;
-	    } else {
-	      console.log('[ERROR] Invalid data type for ' +
-	                  'texture coordinates. Array is required.');
-	    }
-	    m_this.modified();
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create a point geometry given input parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.create = function () {
-	    m_geom = new vgl.geometryData();
-
-	    if (m_positions.length % 3 !== 0) {
-	      console.log('[ERROR] Invalid length of the points array');
-	      return;
-	    }
-
-	    var numPts = m_positions.length / 3,
-	        i = 0,
-	        indices = [],
-	        pointsPrimitive,
-	        sourcePositions,
-	        sourceColors,
-	        sourceTexCoords,
-	        sourceSize;
-
-	    indices.length = numPts;
-	    for (i = 0; i < numPts; i += 1) {
-	      indices[i] = i;
-	    }
-
-	    /// Generate array of size if needed
-	    sourceSize = vgl.sourceDataDf();
-	    if (numPts !== m_size.length) {
-	      for (i = 0; i < numPts; i += 1) {
-	        sourceSize.pushBack(m_size);
-	      }
-	    } else {
-	      sourceSize.setData(m_size);
-	    }
-	    m_geom.addSource(sourceSize);
-
-	    pointsPrimitive = new vgl.points();
-	    pointsPrimitive.setIndices(indices);
-
-	    sourcePositions = vgl.sourceDataP3fv();
-	    sourcePositions.pushBack(m_positions);
-	    m_geom.addSource(sourcePositions);
-
-	    if ((m_colors.length > 0) && m_colors.length === m_positions.length) {
-	      sourceColors = vgl.sourceDataC3fv();
-	      sourceColors.pushBack(m_colors);
-	      m_geom.addSource(sourceColors);
-	    } else if ((m_colors.length > 0) && m_colors.length !== m_positions.length) {
-	      console
-	          .log('[ERROR] Number of colors are different than number of points');
-	    }
-
-	    if (m_textureCoords.length > 0 &&
-	        m_textureCoords.length === m_positions.length) {
-	      sourceTexCoords = vgl.sourceDataT2fv();
-	      sourceTexCoords.pushBack(m_textureCoords);
-	      m_geom.addSource(sourceTexCoords);
-	    } else if (m_textureCoords.length > 0 &&
-	        (m_textureCoords.length / 2) !== (m_positions.length / 3)) {
-	      console
-	          .log('[ERROR] Number of texture coordinates are different than ' +
-	               'number of points');
-	    }
-
-	    m_geom.addPrimitive(pointsPrimitive);
-
-	    return m_geom;
-	  };
-	};
-
-	inherit(vgl.pointSource, vgl.source);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class lineSource
-	 *
-	 * @class
-	 * @returns {vgl.lineSource}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.lineSource = function (positions, colors) {
-	  'use strict';
-
-	  if (!(this instanceof vgl.lineSource)) {
-	    return new vgl.lineSource();
-	  }
-	  vgl.source.call(this);
-
-	  var m_positions = positions,
-	      m_colors = colors;
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set start positions for the lines
-	   *
-	   * @param positions
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setPositions = function (positions) {
-	    if (positions instanceof Array) {
-	      m_positions = positions;
-	      this.modified();
-	      return true;
-	    }
-
-	    console
-	      .log('[ERROR] Invalid data type for positions. Array is required.');
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Set colors for the lines
-	   *
-	   * @param colors
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setColors = function (colors) {
-	    if (colors instanceof Array) {
-	      m_colors = colors;
-	      this.modified();
-	      return true;
-	    }
-
-	    console.log('[ERROR] Invalid data type for colors. Array is required.');
-	    return false;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create a point geometry given input parameters
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.create = function () {
-	    if (!m_positions) {
-	      console.log('[error] Invalid positions');
-	      return;
-	    }
-
-	    if (m_positions.length % 3 !== 0) {
-	      console.log('[error] Line source requires 3d points');
-	      return;
-	    }
-
-	    if (m_positions.length % 3 !== 0) {
-	      console.log('[ERROR] Invalid length of the points array');
-	      return;
-	    }
-
-	    var m_geom = new vgl.geometryData(),
-	        numPts = m_positions.length / 3,
-	        i,
-	        indices = [],
-	        linesPrimitive,
-	        sourcePositions,
-	        sourceColors;
-
-	    indices.length = numPts;
-
-	    for (i = 0; i < numPts; i += 1) {
-	      indices[i] = i;
-	    }
-
-	    linesPrimitive = new vgl.lines();
-	    linesPrimitive.setIndices(indices);
-
-	    sourcePositions = vgl.sourceDataP3fv();
-	    sourcePositions.pushBack(m_positions);
-	    m_geom.addSource(sourcePositions);
-
-	    if (m_colors && (m_colors.length > 0) &&
-	         m_colors.length === m_positions.length) {
-	      sourceColors = vgl.sourceDataC3fv();
-	      sourceColors.pushBack(m_colors);
-	      m_geom.addSource(sourceColors);
-	    } else if (m_colors && (m_colors.length > 0) &&
-	             m_colors.length !== m_positions.length) {
-	      console
-	        .log('[error] Number of colors are different than number of points');
-	    }
-
-	    m_geom.addPrimitive(linesPrimitive);
-
-	    return m_geom;
-	  };
-	};
-
-	inherit(vgl.lineSource, vgl.source);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global document, vgl, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class utils
-	 *
-	 * Utility class provides helper functions such as functions to create
-	 * shaders, geometry etc.
-	 *
-	 * @returns {vgl.utils}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.utils)) {
-	    return new vgl.utils();
-	  }
-	  vgl.object.call(this);
-
-	  return this;
-	};
-
-	inherit(vgl.utils, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Helper function to compute power of 2 number
-	 *
-	 * @param value
-	 * @param pow
-	 *
-	 * @returns {number}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.computePowerOfTwo = function (value, pow) {
-	  'use strict';
-	  pow = pow || 1;
-	  while (pow < value) {
-	    pow *= 2;
-	  }
-	  return pow;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of default vertex shader that uses a texture
-	 *
-	 * Helper function to create default vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createTextureVertexShader = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute vec3 textureCoord;',
-	    'uniform mediump float pointSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying highp vec3 iTextureCoord;',
-	    'void main(void)',
-	    '{',
-	    'gl_PointSize = pointSize;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    ' iTextureCoord = textureCoord;', '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of default fragment shader that uses a texture
-	 *
-	 * Helper function to create default fragment shader with sampler
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createTextureFragmentShader = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'varying highp vec3 iTextureCoord;',
-	    'uniform sampler2D sampler2d;',
-	    'uniform mediump float opacity;',
-	    'void main(void) {',
-	    'gl_FragColor = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, ' +
-	                        'iTextureCoord.t)).xyz, opacity);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create variation of createTextureFragmentShader which uses texture alpha
-	 *
-	 * Helper function to create default fragment shader with sampler
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createRgbaTextureFragmentShader = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'varying highp vec3 iTextureCoord;',
-	    'uniform sampler2D sampler2d;',
-	    'uniform mediump float opacity;',
-	    'void main(void) {',
-	    '  mediump vec4 color = vec4(texture2D(sampler2d, vec2(' +
-	                                'iTextureCoord.s, iTextureCoord.t)).xyzw);',
-	    '  color.w *= opacity;',
-	    '  gl_FragColor = color;',
-	    '}'
-	  ].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of default vertex shader
-	 *
-	 * Helper function to create default vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createVertexShader = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute vec3 vertexColor;',
-	    'uniform mediump float pointSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying mediump vec3 iVertexColor;',
-	    'varying highp vec3 iTextureCoord;',
-	    'void main(void)',
-	    '{',
-	    'gl_PointSize = pointSize;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    ' iVertexColor = vertexColor;', '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of default vertex shader
-	 *
-	 * Helper function to create default vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointVertexShader = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute vec3 vertexColor;',
-	    'attribute float vertexSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying mediump vec3 iVertexColor;',
-	    'varying highp vec3 iTextureCoord;',
-	    'void main(void)',
-	    '{',
-	    'gl_PointSize =  vertexSize;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    ' iVertexColor = vertexColor;', '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of vertex shader with a solid color
-	 *
-	 * Helper function to create default vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createVertexShaderSolidColor = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'uniform mediump float pointSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'void main(void)',
-	    '{',
-	    'gl_PointSize = pointSize;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of vertex shader that passes values through
-	 * for color mapping
-	 *
-	 * Helper function to create default vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createVertexShaderColorMap = function (context, min, max) {
-	  'use strict';
-	  min = min; /* unused parameter */
-	  max = max; /* unused parameter */
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute float vertexScalar;',
-	    'uniform mediump float pointSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'uniform float lutMin;',
-	    'uniform float lutMax;',
-	    'varying mediump float iVertexScalar;',
-	    'void main(void)',
-	    '{',
-	    'gl_PointSize = pointSize;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    'iVertexScalar = (vertexScalar-lutMin)/(lutMax-lutMin);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of default fragment shader
-	 *
-	 * Helper function to create default fragment shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createFragmentShader = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = ['varying mediump vec3 iVertexColor;',
-	                              'uniform mediump float opacity;',
-	                              'void main(void) {',
-	                              'gl_FragColor = vec4(iVertexColor, opacity);',
-	                              '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a Phong vertex shader
-	 *
-	 * Helper function to create Phong vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPhongVertexShader = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute highp vec3 vertexPosition;',
-	    'attribute mediump vec3 vertexNormal;',
-	    'attribute mediump vec3 vertexColor;',
-
-	    'uniform highp mat4 projectionMatrix;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 normalMatrix;',
-
-	    'varying highp vec4 varPosition;',
-	    'varying mediump vec3 varNormal;',
-	    'varying mediump vec3 varVertexColor;',
-
-	    'void main(void)',
-	    '{',
-	    'varPosition = modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    'gl_Position = projectionMatrix * varPosition;',
-	    'varNormal = vec3(normalMatrix * vec4(vertexNormal, 0.0));',
-	    'varVertexColor = vertexColor;',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of Phong fragment shader
-	 *
-	 * Helper function to create Phong fragment shader
-	 *
-	 * NOTE: Shader assumes directional light
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPhongFragmentShader = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'uniform mediump float opacity;',
-	    'precision mediump float;',
-	    'varying vec3 varNormal;',
-	    'varying vec4 varPosition;',
-	    'varying mediump vec3 varVertexColor;',
-	    'const vec3 lightPos = vec3(0.0, 0.0,10000.0);',
-	    'const vec3 ambientColor = vec3(0.01, 0.01, 0.01);',
-	    'const vec3 specColor = vec3(0.0, 0.0, 0.0);',
-
-	    'void main() {',
-	    'vec3 normal = normalize(varNormal);',
-	    'vec3 lightDir = normalize(lightPos);',
-	    'vec3 reflectDir = -reflect(lightDir, normal);',
-	    'vec3 viewDir = normalize(-varPosition.xyz);',
-
-	    'float lambertian = max(dot(lightDir, normal), 0.0);',
-	    'vec3 color = vec3(0.0);',
-	    'if(lambertian > 0.0) {',
-	    '  color = lambertian * varVertexColor;',
-	    '}',
-	    'gl_FragColor = vec4(color * opacity, 1.0 - opacity);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of fragment shader with an assigned constant color.
-	 *
-	 * Helper function to create default fragment shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createFragmentShaderSolidColor = function (context, color) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'uniform mediump float opacity;',
-	    'void main(void) {',
-	    'gl_FragColor = vec4(' + color[0] + ',' + color[1] + ',' + color[2] + ', opacity);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of fragment shader that maps values into colors bia lookup table
-	 *
-	 * Helper function to create default fragment shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createFragmentShaderColorMap = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'varying mediump float iVertexScalar;',
-	    'uniform sampler2D sampler2d;',
-	    'uniform mediump float opacity;',
-	    'void main(void) {',
-	    'gl_FragColor = vec4(texture2D(sampler2d, vec2(iVertexScalar, ' +
-	            '0.0)).xyz, opacity);',
-	    '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of vertex shader for point sprites
-	 *
-	 * Helper function to create default point sprites vertex shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointSpritesVertexShader = function (context) {
-	  'use strict';
-	  var vertexShaderSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute vec3 vertexColor;',
-	    'uniform mediump vec2 pointSize;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'uniform float height;',
-	    'varying mediump vec3 iVertexColor;',
-	    'varying highp float iVertexScalar;',
-	    'void main(void)',
-	    '{',
-	    'mediump float realPointSize = pointSize.y;',
-	    'if (pointSize.x > pointSize.y) {',
-	    '  realPointSize = pointSize.x;}',
-	    'gl_PointSize = realPointSize ;',
-	    'iVertexScalar = vertexPosition.z;',
-	    'gl_Position = projectionMatrix * modelViewMatrix * ' +
-	            'vec4(vertexPosition.xy, height, 1.0);',
-	    ' iVertexColor = vertexColor;', '}'].join('\n');
-	  return vgl.getCachedShader(vgl.GL.VERTEX_SHADER, context,
-	                             vertexShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of fragment shader for point sprites
-	 *
-	 * Helper function to create default point sprites fragment shader
-	 *
-	 * @param context
-	 * @returns {vgl.shader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointSpritesFragmentShader = function (context) {
-	  'use strict';
-	  var fragmentShaderSource = [
-	    'varying mediump vec3 iVertexColor;',
-	    'varying highp float iVertexScalar;',
-	    'uniform sampler2D opacityLookup;',
-	    'uniform highp float lutMin;',
-	    'uniform highp float lutMax;',
-	    'uniform sampler2D scalarsToColors;',
-	    'uniform int useScalarsToColors;',
-	    'uniform int useVertexColors;',
-	    'uniform mediump vec2 pointSize;',
-	    'uniform mediump float vertexColorWeight;',
-	    'void main(void) {',
-	    'mediump vec2 realTexCoord;',
-	    'if (pointSize.x > pointSize.y) {',
-	    '  realTexCoord = vec2(1.0, pointSize.y/pointSize.x) * gl_PointCoord;',
-	    '} else {',
-	    '  realTexCoord = vec2(pointSize.x/pointSize.y, 1.0) * gl_PointCoord;',
-	    '}',
-	    'highp float texOpacity = texture2D(opacityLookup, realTexCoord).w;',
-	    'if (useScalarsToColors == 1) {',
-	    '  gl_FragColor = vec4(texture2D(scalarsToColors, vec2((' +
-	            'iVertexScalar - lutMin)/(lutMax - lutMin), 0.0)).xyz, ' +
-	            'texOpacity);',
-	    '} else if (useVertexColors == 1) {',
-	    '  gl_FragColor = vec4(iVertexColor, texOpacity);',
-	    '} else {',
-	    '  gl_FragColor = vec4(texture2D(opacityLookup, realTexCoord).xyz, texOpacity);',
-	    '}}'
-	  ].join('\n');
-	  return vgl.getCachedShader(vgl.GL.FRAGMENT_SHADER, context,
-	                             fragmentShaderSource);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of texture material
-	 *
-	 * Helper function to create a texture material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createTextureMaterial = function (isRgba, origin) {
-	  'use strict';
-	  var mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createTextureVertexShader(vgl.GL),
-	      fragmentShader = null,
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      texCoordVertAttr = new vgl.vertexAttribute('textureCoord'),
-	      pointsizeUniform = new vgl.floatUniform('pointSize', 5.0),
-	      modelViewUniform,
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	      samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'),
-	      opacityUniform = null;
-	  if (origin !== undefined) {
-	    modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix',
-	                                                      origin);
-	  } else {
-	    modelViewUniform = new vgl.modelViewUniform('modelViewMatrix');
-	  }
-
-	  samplerUniform.set(0);
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(texCoordVertAttr,
-	                          vgl.vertexAttributeKeys.TextureCoordinate);
-	  prog.addUniform(pointsizeUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-
-	  if (isRgba) {
-	    fragmentShader = vgl.utils.createRgbaTextureFragmentShader(vgl.GL);
-	  } else {
-	    fragmentShader = vgl.utils.createTextureFragmentShader(vgl.GL);
-	  }
-	  opacityUniform = new vgl.floatUniform('opacity', 1.0);
-	  prog.addUniform(opacityUniform);
-
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of geometry material
-	 *
-	 * Helper function to create geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createGeometryMaterial = function () {
-	  'use strict';
-	  var mat = new vgl.material(),
-	      prog = new vgl.shaderProgram(),
-	      pointSize = 5.0,
-	      opacity = 1.0,
-	      vertexShader = vgl.utils.createVertexShader(vgl.GL),
-	      fragmentShader = vgl.utils.createFragmentShader(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      colorVertAttr = new vgl.vertexAttribute('vertexColor'),
-	      pointsizeUniform = new vgl.floatUniform('pointSize', pointSize),
-	      opacityUniform = new vgl.floatUniform('opacity', opacity),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix');
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color);
-	  prog.addUniform(pointsizeUniform);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of geometry material
-	 *
-	 * Helper function to create geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointGeometryMaterial = function (opacity) {
-	  'use strict';
-	  opacity = opacity === undefined ? 1.0 : opacity;
-	  var mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createPointVertexShader(vgl.GL),
-	      fragmentShader = vgl.utils.createFragmentShader(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      colorVertAttr = new vgl.vertexAttribute('vertexColor'),
-	      sizeVertAttr = new vgl.vertexAttribute('vertexSize'),
-	      opacityUniform = new vgl.floatUniform('opacity', opacity),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix');
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color);
-	  prog.addVertexAttribute(sizeVertAttr, vgl.vertexAttributeKeys.Scalar);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of geometry material with the phong shader
-	 *
-	 * Helper function to create color phong shaded geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPhongMaterial = function () {
-	  'use strict';
-	  var mat = new vgl.material(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createPhongVertexShader(vgl.GL),
-	      fragmentShader = vgl.utils.createPhongFragmentShader(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      normalVertAttr = new vgl.vertexAttribute('vertexNormal'),
-	      colorVertAttr = new vgl.vertexAttribute('vertexColor'),
-	      opacityUniform = new vgl.floatUniform('opacity', 1.0),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      normalUniform = new vgl.normalMatrixUniform('normalMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix');
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(normalVertAttr, vgl.vertexAttributeKeys.Normal);
-	  prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addUniform(normalUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  //mat.addAttribute(blend);
-	  mat.addAttribute(prog);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of colored geometry material
-	 *
-	 * Helper function to create color geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createColorMaterial = function () {
-	  'use strict';
-	  var mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createVertexShader(vgl.GL),
-	      fragmentShader = vgl.utils.createFragmentShader(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      texCoordVertAttr = new vgl.vertexAttribute('textureCoord'),
-	      colorVertAttr = new vgl.vertexAttribute('vertexColor'),
-	      pointsizeUniform = new vgl.floatUniform('pointSize', 5.0),
-	      opacityUniform = new vgl.floatUniform('opacity', 1.0),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix');
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color);
-	  prog.addVertexAttribute(texCoordVertAttr,
-	                          vgl.vertexAttributeKeys.TextureCoordinate);
-	  prog.addUniform(pointsizeUniform);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of geometry material
-	 *
-	 * Helper function to create geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createColorMappedMaterial = function (lut) {
-	  'use strict';
-	  if (!lut) {
-	    lut = new vgl.lookupTable();
-	  }
-
-	  var scalarRange = lut.range(),
-	      mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createVertexShaderColorMap(
-	        vgl.GL, scalarRange[0], scalarRange[1]),
-	      fragmentShader = vgl.utils.createFragmentShaderColorMap(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      scalarVertAttr = new vgl.vertexAttribute('vertexScalar'),
-	      pointsizeUniform = new vgl.floatUniform('pointSize', 5.0),
-	      opacityUniform = new vgl.floatUniform('opacity', 1.0),
-	      lutMinUniform = new vgl.floatUniform('lutMin', scalarRange[0]),
-	      lutMaxUniform = new vgl.floatUniform('lutMax', scalarRange[1]),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	      samplerUniform = new vgl.uniform(vgl.GL.FLOAT, 'sampler2d'),
-	      lookupTable = lut;
-
-	  samplerUniform.set(0);
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(scalarVertAttr, vgl.vertexAttributeKeys.Scalar);
-	  prog.addUniform(pointsizeUniform);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(lutMinUniform);
-	  prog.addUniform(lutMaxUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-	  mat.addAttribute(lookupTable);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Update color mapped material
-	 *
-	 * @param mat
-	 * @param scalarRange
-	 * @param lut
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.updateColorMappedMaterial = function (mat, lut) {
-	  'use strict';
-	  if (!mat) {
-	    console.log('[warning] Invalid material. Nothing to update.');
-	    return;
-	  }
-
-	  if (!lut) {
-	    console.log('[warning] Invalid lookup table. Nothing to update.');
-	    return;
-	  }
-
-	  var lutMin = mat.shaderProgram().uniform('lutMin'),
-	      lutMax = mat.shaderProgram().uniform('lutMax');
-
-	  lutMin.set(lut.range()[0]);
-	  lutMax.set(lut.range()[1]);
-
-	  // This will replace the existing lookup table
-	  mat.setAttribute(lut);
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of solid color material
-	 *
-	 * Helper function to create geometry material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createSolidColorMaterial = function (color) {
-	  'use strict';
-	  if (!color) {
-	    color = [1.0, 1.0, 1.0];
-	  }
-
-	  var mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createVertexShaderSolidColor(vgl.GL),
-	      fragmentShader = vgl.utils.createFragmentShaderSolidColor(vgl.GL, color),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      pointsizeUniform = new vgl.floatUniform('pointSize', 5.0),
-	      opacityUniform = new vgl.floatUniform('opacity', 1.0),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix');
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addUniform(pointsizeUniform);
-	  prog.addUniform(opacityUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of point sprites material
-	 *
-	 * Helper function to create point sprites material
-	 *
-	 * @returns {vgl.material}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointSpritesMaterial = function (image, lut) {
-	  'use strict';
-	  var scalarRange = lut === undefined ? [0, 1] : lut.range(),
-	      mat = new vgl.material(),
-	      blend = new vgl.blend(),
-	      prog = new vgl.shaderProgram(),
-	      vertexShader = vgl.utils.createPointSpritesVertexShader(vgl.GL),
-	      fragmentShader = vgl.utils.createPointSpritesFragmentShader(vgl.GL),
-	      posVertAttr = new vgl.vertexAttribute('vertexPosition'),
-	      colorVertAttr = new vgl.vertexAttribute('vertexColor'),
-	      heightUniform = new vgl.floatUniform('height', 0.0),
-	      vertexColorWeightUniform =
-	        new vgl.floatUniform('vertexColorWeight', 0.0),
-	      lutMinUniform = new vgl.floatUniform('lutMin', scalarRange[0]),
-	      lutMaxUniform = new vgl.floatUniform('lutMax', scalarRange[1]),
-	      modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	      projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	      samplerUniform = new vgl.uniform(vgl.GL.INT, 'opacityLookup'),
-	      scalarsToColors = new vgl.uniform(vgl.GL.INT, 'scalarsToColors'),
-	      useScalarsToColors = new vgl.uniform(vgl.GL.INT, 'useScalarsToColors'),
-	      useVertexColors = new vgl.uniform(vgl.GL.INT, 'useVertexColors'),
-	      pointSize = new vgl.uniform(vgl.GL.FLOAT_VEC2, 'pointSize'),
-	      texture = new vgl.texture();
-
-	  samplerUniform.set(0);
-	  scalarsToColors.set(1);
-	  useScalarsToColors.set(0);
-	  useVertexColors.set(0);
-	  pointSize.set([1.0, 1.0]);
-
-	  prog.addVertexAttribute(posVertAttr, vgl.vertexAttributeKeys.Position);
-	  prog.addVertexAttribute(colorVertAttr, vgl.vertexAttributeKeys.Color);
-	  prog.addUniform(heightUniform);
-	  prog.addUniform(vertexColorWeightUniform);
-	  prog.addUniform(modelViewUniform);
-	  prog.addUniform(projectionUniform);
-	  prog.addUniform(samplerUniform);
-	  prog.addUniform(useVertexColors);
-	  prog.addUniform(useScalarsToColors);
-	  prog.addUniform(pointSize);
-	  prog.addShader(fragmentShader);
-	  prog.addShader(vertexShader);
-	  mat.addAttribute(prog);
-	  mat.addAttribute(blend);
-
-	  if (lut) {
-	    prog.addUniform(scalarsToColors);
-	    useScalarsToColors.set(1);
-	    prog.addUniform(lutMinUniform);
-	    prog.addUniform(lutMaxUniform);
-	    lut.setTextureUnit(1);
-	    mat.addAttribute(lut);
-	  }
-
-	  texture.setImage(image);
-	  texture.setTextureUnit(0);
-	  mat.addAttribute(texture);
-	  return mat;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of an actor that contains a plane geometry
-	 *
-	 * Function to create a plane node This method will create a plane actor
-	 * with texture coordinates, eventually normal, and plane material.
-	 *
-	 * @returns {vgl.actor}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPlane = function (originX, originY, originZ,
-	                                 point1X, point1Y, point1Z,
-	                                 point2X, point2Y, point2Z) {
-	  'use strict';
-	  var mapper = new vgl.mapper(),
-	      planeSource = new vgl.planeSource(),
-	      mat = vgl.utils.createGeometryMaterial(),
-	      actor = new vgl.actor();
-
-	  planeSource.setOrigin(originX, originY, originZ);
-	  planeSource.setPoint1(point1X, point1Y, point1Z);
-	  planeSource.setPoint2(point2X, point2Y, point2Z);
-
-	  mapper.setGeometryData(planeSource.create());
-	  actor.setMapper(mapper);
-	  actor.setMaterial(mat);
-
-	  return actor;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of an actor that contains a texture plane geometry
-	 *
-	 * Helper function to create a plane textured node This method will create
-	 * a plane actor with texture coordinates, eventually normal, and plane
-	 * material.
-	 *
-	 * @returns {vgl.actor}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createTexturePlane = function (originX, originY, originZ,
-	                                        point1X, point1Y, point1Z,
-	                                        point2X, point2Y, point2Z,
-	                                        isRgba) {
-	  'use strict';
-	  var mapper = new vgl.mapper(),
-	      planeSource = new vgl.planeSource(),
-	      mat = vgl.utils.createTextureMaterial(isRgba,
-	                                            [originX, originY, originZ]),
-	      actor = new vgl.actor();
-
-	  planeSource.setPoint1(point1X - originX, point1Y - originY, point1Z - originZ);
-	  planeSource.setPoint2(point2X - originX, point2Y - originY, point2Z - originZ);
-	  mapper.setGeometryData(planeSource.create());
-
-	  actor.setMapper(mapper);
-	  actor.setMaterial(mat);
-
-	  return actor;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of an actor that contains points
-	 *
-	 * Helper function to create a point node This method will create a point
-	 * actor with texture coordinates, eventually normal, and plane material.
-	 *
-	 * @returns {vgl.actor}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPoints = function (positions, size, colors, texcoords, opacity) {
-	  'use strict';
-	  if (!positions) {
-	    console.log('[ERROR] Cannot create points without positions');
-	    return null;
-	  }
-
-	  opacity = opacity === undefined ? 1.0 : opacity;
-	  var mapper = new vgl.mapper(),
-	      pointSource = new vgl.pointSource(),
-	      mat = vgl.utils.createPointGeometryMaterial(opacity),
-	      actor = new vgl.actor();
-
-	  pointSource.setPositions(positions);
-	  if (colors) {
-	    pointSource.setColors(colors);
-	  }
-
-	  if (texcoords) {
-	    pointSource.setTextureCoordinates(texcoords);
-	  }
-
-	  if (size) {
-	    pointSource.setSize(size);
-	  } else {
-	    pointSource.setSize(1.0);
-	  }
-
-	  mapper.setGeometryData(pointSource.create());
-	  actor.setMapper(mapper);
-	  actor.setMaterial(mat);
-
-	  return actor;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of an actor that contains point sprites
-	 *
-	 * Helper function to create a point sprites node This method will create
-	 * a point sprites actor with texture coordinates, normals, and a point sprites
-	 * material.
-	 *
-	 * @returns {vgl.actor}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createPointSprites = function (image, positions, colors,
-	                                              texcoords) {
-	  'use strict';
-	  if (!image) {
-	    console.log('[ERROR] Point sprites requires an image');
-	    return null;
-	  }
-
-	  if (!positions) {
-	    console.log('[ERROR] Cannot create points without positions');
-	    return null;
-	  }
-
-	  var mapper = new vgl.mapper(),
-	      pointSource = new vgl.pointSource(),
-	      mat = vgl.utils.createPointSpritesMaterial(image),
-	      actor = new vgl.actor();
-
-	  pointSource.setPositions(positions);
-	  if (colors) {
-	    pointSource.setColors(colors);
-	  }
-
-	  if (texcoords) {
-	    pointSource.setTextureCoordinates(texcoords);
-	  }
-
-	  mapper.setGeometryData(pointSource.create());
-	  actor.setMapper(mapper);
-	  actor.setMaterial(mat);
-
-	  return actor;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create lines given positions, colors, and desired length
-	 *
-	 * @param positions
-	 * @param colors
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createLines = function (positions, colors) {
-	  'use strict';
-	  if (!positions) {
-	    console.log('[ERROR] Cannot create points without positions');
-	    return null;
-	  }
-
-	  var mapper = new vgl.mapper(),
-	      lineSource = new vgl.lineSource(),
-	      mat = vgl.utils.createGeometryMaterial(),
-	      actor = new vgl.actor();
-
-	  lineSource.setPositions(positions);
-	  if (colors) {
-	    lineSource.setColors(colors);
-	  }
-
-	  mapper.setGeometryData(lineSource.create());
-	  actor.setMapper(mapper);
-	  actor.setMaterial(mat);
-
-	  return actor;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create color legend
-	 *
-	 * @param lookupTable
-	 * @param width
-	 * @param height
-	 * @param origin
-	 * @param divs
-	 * @returns {Array}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.createColorLegend = function (varname, lookupTable, origin,
-	                                             width, height, countMajor,
-	                                             countMinor) {
-	  'use strict';
-
-	  if (!lookupTable) {
-	    console.log('[error] Invalid lookup table');
-	    return [];
-	  }
-
-	  //////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Create labels for the legend
-	   *
-	   * @param ticks
-	   * @param range
-	   * @param divs
-	   */
-	  //////////////////////////////////////////////////////////////////////////////
-	  function createLabels(varname, positions, range) {
-	    if (!positions) {
-	      console.log('[error] Create labels requires positions (x,y,z) array');
-	      return;
-	    }
-
-	    if (positions.length % 3 !== 0) {
-	      console.log('[error] Create labels require positions array contain 3d points');
-	      return;
-	    }
-
-	    if (!range) {
-	      console.log('[error] Create labels requires Valid range');
-	      return;
-	    }
-
-	    var actor = null,
-	        size = vgl.utils.computePowerOfTwo(48),
-	        index = 0,
-	        actors = [],
-	        origin = [],
-	        pt1 = [],
-	        pt2 = [],
-	        delta = (positions[6] - positions[0]),
-	        axisLabelOffset = 4, i;
-
-	    origin.length = 3;
-	    pt1.length = 3;
-	    pt2.length = 3;
-
-	    // For now just create labels for end points
-	    for (i = 0; i < 2; i += 1) {
-	      index = i * (positions.length - 3);
-
-	      origin[0] = positions[index] - delta;
-	      origin[1] = positions[index + 1] - 2 * delta;
-	      origin[2] = positions[index + 2];
-
-	      pt1[0] = positions[index] + delta;
-	      pt1[1] = origin[1];
-	      pt1[2] = origin[2];
-
-	      pt2[0] = origin[0];
-	      pt2[1] = positions[1];
-	      pt2[2] = origin[2];
-
-	      actor = vgl.utils.createTexturePlane(
-	        origin[0], origin[1], origin[2],
-	        pt1[0], pt1[1], pt1[2],
-	        pt2[0], pt2[1], pt2[2], true);
-
-	      actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute);
-	      actor.material().setBinNumber(vgl.material.RenderBin.Overlay);
-	      actor.material().addAttribute(vgl.utils.create2DTexture(
-	        range[i].toFixed(2).toString(), 12, null));
-	      actors.push(actor);
-	    }
-
-	    // Create axis label
-	    origin[0] = (positions[0] + positions[positions.length - 3] - size) * 0.5;
-	    origin[1] = positions[1] + axisLabelOffset;
-	    origin[2] = positions[2];
-
-	    pt1[0] = origin[0] + size;
-	    pt1[1] = origin[1];
-	    pt1[2] = origin[2];
-
-	    pt2[0] = origin[0];
-	    pt2[1] = origin[1] + size;
-	    pt2[2] = origin[2];
-
-	    actor = vgl.utils.createTexturePlane(
-	      origin[0], origin[1], origin[2],
-	      pt1[0], pt1[1], pt1[2],
-	      pt2[0], pt2[1], pt2[2], true);
-	    actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute);
-	    actor.material().setBinNumber(vgl.material.RenderBin.Overlay);
-	    actor.material().addAttribute(vgl.utils.create2DTexture(
-	      varname, 24, null));
-	    actors.push(actor);
-
-	    return actors;
-	  }
-
-	  //////////////////////////////////////////////////////////////////////////////
-	  // TODO Currently we assume that the ticks are laid on x-axis
-	  // and this is on a 2D plane (ignoring Z axis. For now lets
-	  // not draw minor ticks.
-	  /**
-	   * Create ticks and labels
-	   *
-	   * @param originX
-	   * @param originY
-	   * @param originZ
-	   * @param pt1X
-	   * @param pt1Y
-	   * @param pt1Z
-	   * @param pt2X
-	   * @param pt2Y
-	   * @param pt2Z
-	   * @param divs
-	   * @param heightMajor
-	   * @param heightMinor
-	   * @returns {Array} Returns array of vgl.actor
-	   */
-	  //////////////////////////////////////////////////////////////////////////////
-	  function createTicksAndLabels(varname, lut,
-	                        originX, originY, originZ,
-	                        pt1X, pt1Y, pt1Z,
-	                        pt2X, pt2Y, pt2Z,
-	                        countMajor, countMinor,
-	                        heightMajor, heightMinor) {
-	    heightMinor = heightMinor; /* unused parameter */
-	    var width = pt2X - pt1X,
-	        index = null,
-	        delta = width / countMajor,
-	        positions = [],
-	        actors = [];
-
-	    for (index = 0; index <= countMajor; index += 1) {
-	      positions.push(pt1X + delta * index);
-	      positions.push(pt1Y);
-	      positions.push(pt1Z);
-
-	      positions.push(pt1X + delta * index);
-	      positions.push(pt1Y + heightMajor);
-	      positions.push(pt1Z);
-	    }
-
-	    // TODO: Fix this
-	    //actor = vgl.utils.createLines(positions, null);
-	    //actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute);
-	    //actor.material().setBinNumber(vgl.material.RenderBin.Overlay);
-	    //actors.push(actor);
-
-	    actors = actors.concat(createLabels(varname, positions, lut.range()));
-	    return actors;
-	  }
-
-	  // TODO Currently we create only one type of legend
-	  var pt1X = origin[0] + width,
-	      pt1Y = origin[1],
-	      pt1Z = 0.0,
-	      pt2X = origin[0],
-	      pt2Y = origin[1] + height,
-	      pt2Z = 0.0,
-	      actors = [],
-	      actor = null,
-	      mat = null,
-	      group = vgl.groupNode();
-
-	  actor = vgl.utils.createTexturePlane(
-	    origin[0], origin[1], origin[2],
-	    pt1X, pt1Y, pt1Z,
-	    pt2X, pt2Y, pt2Z, true
-	  );
-
-	  mat = actor.material();
-	  mat.addAttribute(lookupTable);
-	  actor.setMaterial(mat);
-	  group.addChild(actor);
-	  actor.material().setBinNumber(vgl.material.RenderBin.Overlay);
-	  actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute);
-	  actors.push(actor);
-	  actors = actors.concat(createTicksAndLabels(
-	                          varname,
-	                          lookupTable,
-	                          origin[0], origin[1], origin[1],
-	                          pt2X, pt1Y, pt1Z,
-	                          pt1X, pt1Y, pt1Z,
-	                          countMajor, countMinor, 5, 3));
-
-	  // TODO This needs to change so that we can return a group node
-	  // which should get appended to the scene graph
-	  return actors;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create 2D texture by rendering text using canvas2D context
-	 *
-	 * @param textToWrite
-	 * @param textSize
-	 * @param color
-	 * @returns {vgl.texture}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.utils.create2DTexture = function (textToWrite, textSize,
-	  color, font, alignment, baseline, bold) {
-	  'use strict';
-
-	  var canvas = document.getElementById('textRendering'),
-	      ctx = null,
-	      texture = vgl.texture();
-
-	  font = font || 'sans-serif';
-	  alignment = alignment || 'center';
-	  baseline = baseline || 'bottom';
-
-	  if (typeof bold === 'undefined') {
-	    bold = true;
-	  }
-
-	  if (!canvas) {
-	    canvas = document.createElement('canvas');
-	  }
-	  ctx = canvas.getContext('2d');
-
-	  canvas.setAttribute('id', 'textRendering');
-	  canvas.style.display = 'none';
-
-	  // Make width and height equal so that we get pretty looking text.
-	  canvas.height = vgl.utils.computePowerOfTwo(8 * textSize);
-	  canvas.width = canvas.height;
-
-	  ctx.fillStyle = 'rgba(0, 0, 0, 0)';
-	  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
-
-	  // This determines the text colour, it can take a hex value or rgba value
-	  // (e.g. rgba(255,0,0,0.5))
-	  ctx.fillStyle = 'rgba(200, 85, 10, 1.0)';
-
-	  // This determines the alignment of text, e.g. left, center, right
-	  ctx.textAlign = alignment;
-
-	  // This determines the baseline of the text, e.g. top, middle, bottom
-	  ctx.textBaseline = baseline;
-
-	  // This determines the size of the text and the font family used
-	  ctx.font = 4 * textSize + 'px ' + font;
-	  if (bold) {
-	    ctx.font = 'bold ' + ctx.font;
-	  }
-
-	  ctx.fillText(textToWrite, canvas.width / 2, canvas.height / 2, canvas.width);
-
-	  texture.setImage(canvas);
-	  texture.updateDimensions();
-
-	  return texture;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, vec4, inherit*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class picker
-	 *
-	 * @class vgl.picker
-	 * @returns {vgl.picker}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.picker = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.picker)) {
-	    return new vgl.picker();
-	  }
-	  vgl.object.call(this);
-
-	  /** @private */
-	  var m_actors = [];
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Get actors intersected
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getActors = function () {
-	    return m_actors;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Perform pick operation
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.pick = function (selectionX, selectionY, renderer) {
-	    // Check if variables are acceptable
-	    if (selectionX === undefined) {
-	      return 0;
-	    }
-	    if (selectionY === undefined) {
-	      return 0;
-	    }
-	    if (renderer === undefined) {
-	      return 0;
-	    }
-
-	    // Clean list of actors intersected previously
-	    m_actors = [];
-
-	    //
-	    var camera = renderer.camera(),
-	        width = renderer.width(),
-	        height = renderer.height(),
-	        fpoint = camera.focalPoint(),
-	        focusWorldPt = vec4.fromValues(fpoint[0], fpoint[1], fpoint[2], 1.0),
-	        focusDisplayPt = renderer.worldToDisplay(
-	          focusWorldPt, camera.viewMatrix(),
-	        camera.projectionMatrix(), width, height),
-	        displayPt = vec4.fromValues(selectionX,
-	                      selectionY, focusDisplayPt[2], 1.0),
-	        // Convert selection point into world coordinates
-	        worldPt = renderer.displayToWorld(displayPt, camera.viewMatrix(),
-	                    camera.projectionMatrix(), width, height),
-	        cameraPos = camera.position(), ray = [], actors, count, i, bb,
-	        tmin, tmax, tymin, tymax, tzmin, tzmax, actor;
-
-	    for (i = 0; i < 3; i += 1) {
-	      ray[i] = worldPt[i] - cameraPos[i];
-	    }
-
-	    // Go through all actors and check if intersects
-	    actors = renderer.sceneRoot().children();
-	    count = 0;
-
-	    for (i = 0; i < actors.length; i += 1) {
-	      actor = actors[i];
-	      if (actor.visible() === true) {
-	        bb = actor.bounds();
-	        // Ray-aabb intersection - Smits' method
-	        if (ray[0] >= 0) {
-	          tmin = (bb[0] - cameraPos[0]) / ray[0];
-	          tmax = (bb[1] - cameraPos[0]) / ray[0];
-	        } else {
-	          tmin = (bb[1] - cameraPos[0]) / ray[0];
-	          tmax = (bb[0] - cameraPos[0]) / ray[0];
-	        }
-	        if (ray[1] >= 0) {
-	          tymin = (bb[2] - cameraPos[1]) / ray[1];
-	          tymax = (bb[3] - cameraPos[1]) / ray[1];
-	        } else {
-	          tymin = (bb[3] - cameraPos[1]) / ray[1];
-	          tymax = (bb[2] - cameraPos[1]) / ray[1];
-	        }
-	        if ((tmin > tymax) || (tymin > tmax)) {
-	          //jscs:disable disallowKeywords
-	          continue;
-	          //jscs:enable disallowKeywords
-	        }
-
-	        if (tymin > tmin) {
-	          tmin = tymin;
-	        }
-	        if (tymax < tmax) {
-	          tmax = tymax;
-	        }
-	        if (ray[2] >= 0) {
-	          tzmin = (bb[4] - cameraPos[2]) / ray[2];
-	          tzmax = (bb[5] - cameraPos[2]) / ray[2];
-	        } else {
-	          tzmin = (bb[5] - cameraPos[2]) / ray[2];
-	          tzmax = (bb[4] - cameraPos[2]) / ray[2];
-	        }
-	        if ((tmin > tzmax) || (tzmin > tmax)) {
-	          //jscs:disable disallowKeywords
-	          continue;
-	          //jscs:enable disallowKeywords
-	        }
-	        if (tzmin > tmin) {
-	          tmin = tzmin;
-	        }
-	        if (tzmax < tmax) {
-	          tmax = tzmax;
-	        }
-
-	        m_actors[count] = actor;
-	        count += 1;
-	      }
-	    }
-	    return count;
-	  };
-
-	  return this;
-	};
-
-	inherit(vgl.picker, vgl.object);
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, $*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of shapefile reader
-	 *
-	 * This contains code that reads a shapefile and produces vgl geometries
-	 *
-	 * @class
-	 * @returns {vgl.shapefileReader}
-	 */
-	//////////////////////////////////////////////////////////////////////////////
-	vgl.shapefileReader = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.shapefileReader)) {
-	    return new vgl.shapefileReader();
-	  }
-
-	  var m_that = this;
-	  var SHP_NULL = 0;
-	  var SHP_POINT = 1;
-	  var SHP_POLYGON = 5;
-	  var SHP_POLYLINE = 3;
-
-	  this.int8 = function (data, offset) {
-	    return data.charCodeAt(offset);
-	  };
-
-	  /*jshint bitwise: false */
-	  this.bint32 = function (data, offset) {
-	    return (
-	      ((data.charCodeAt(offset) & 0xff) << 24) +
-	        ((data.charCodeAt(offset + 1) & 0xff) << 16) +
-	        ((data.charCodeAt(offset + 2) & 0xff) << 8) +
-	        (data.charCodeAt(offset + 3) & 0xff)
-	    );
-	  };
-
-	  this.lint32 = function (data, offset) {
-	    return (
-	      ((data.charCodeAt(offset + 3) & 0xff) << 24) +
-	        ((data.charCodeAt(offset + 2) & 0xff) << 16) +
-	        ((data.charCodeAt(offset + 1) & 0xff) << 8) +
-	        (data.charCodeAt(offset) & 0xff)
-	    );
-	  };
-
-	  this.bint16 = function (data, offset) {
-	    return (
-	      ((data.charCodeAt(offset) & 0xff) << 8) +
-	        (data.charCodeAt(offset + 1) & 0xff)
-	    );
-	  };
-
-	  this.lint16 = function (data, offset) {
-	    return (
-	      ((data.charCodeAt(offset + 1) & 0xff) << 8) +
-	        (data.charCodeAt(offset) & 0xff)
-	    );
-	  };
-
-	  this.ldbl64 = function (data, offset) {
-	    var b0 = data.charCodeAt(offset) & 0xff;
-	    var b1 = data.charCodeAt(offset + 1) & 0xff;
-	    var b2 = data.charCodeAt(offset + 2) & 0xff;
-	    var b3 = data.charCodeAt(offset + 3) & 0xff;
-	    var b4 = data.charCodeAt(offset + 4) & 0xff;
-	    var b5 = data.charCodeAt(offset + 5) & 0xff;
-	    var b6 = data.charCodeAt(offset + 6) & 0xff;
-	    var b7 = data.charCodeAt(offset + 7) & 0xff;
-
-	    var sign = 1 - 2 * (b7 >> 7);
-	    var exp = (((b7 & 0x7f) << 4) + ((b6 & 0xf0) >> 4)) - 1023;
-	    //var frac = (b6 & 0x0f) * Math.pow (2, -4) + b5 * Math.pow (2, -12) + b4 *
-	    // Math.pow (2, -20) + b3 * Math.pow (2, -28) + b2 * Math.pow (2, -36) + b1 *
-	    // Math.pow (2, -44) + b0 * Math.pow (2, -52);
-
-	    //return sign * (1 + frac) * Math.pow (2, exp);
-	    var frac = (b6 & 0x0f) * Math.pow(2, 48) + b5 * Math.pow(2, 40) + b4 *
-	                 Math.pow(2, 32) + b3 * Math.pow(2, 24) + b2 *
-	                 Math.pow(2, 16) + b1 * Math.pow(2, 8) + b0;
-
-	    return sign * (1 + frac * Math.pow(2, -52)) * Math.pow(2, exp);
-	  };
-
-	  this.lfloat32 = function (data, offset) {
-	    var b0 = data.charCodeAt(offset) & 0xff;
-	    var b1 = data.charCodeAt(offset + 1) & 0xff;
-	    var b2 = data.charCodeAt(offset + 2) & 0xff;
-	    var b3 = data.charCodeAt(offset + 3) & 0xff;
-
-	    var sign = 1 - 2 * (b3 >> 7);
-	    var exp = (((b3 & 0x7f) << 1) + ((b2 & 0xfe) >> 7)) - 127;
-	    var frac = (b2 & 0x7f) * Math.pow(2, 16) + b1 * Math.pow(2, 8) + b0;
-
-	    return sign * (1 + frac * Math.pow(2, -23)) * Math.pow(2, exp);
-	  };
-	  /*jshint bitwise: true */
-
-	  this.str = function (data, offset, length) {
-	    var chars = [];
-	    var index = offset;
-	    while (index < offset + length) {
-	      var c = data[index];
-	      if (c.charCodeAt(0) !== 0) {
-	        chars.push(c);
-	      } else {
-	        break;
-	      }
-	      index += 1;
-	    }
-	    return chars.join('');
-	  };
-
-	  this.readHeader = function (data) {
-	    var code = this.bint32(data, 0);
-	    var length = this.bint32(data, 24);
-	    var version = this.lint32(data, 28);
-	    var shapetype = this.lint32(data, 32);
-
-	    /*
-	    var xmin = this.ldbl64(data, 36);
-	    var ymin = this.ldbl64(data, 44);
-	    var xmax = this.ldbl64(data, 52);
-	    var ymax = this.ldbl64(data, 60);
-	    */
-	    return {
-	      code: code,
-	      length: length,
-	      version: version,
-	      shapetype: shapetype
-	      // bounds: new Box (vect (xmin, ymin), vect (xmax, ymax))
-	    };
-	  };
-
-	  this.loadShx = function (data) {
-	    var indices = [];
-	    var appendIndex = function (offset) {
-	      indices.push(2 * m_that.bint32(data, offset));
-	      return offset + 8;
-	    };
-	    var offset = 100;
-	    while (offset < data.length) {
-	      offset = appendIndex(offset);
-	    }
-	    return indices;
-	  };
-
-	  this.Shapefile = function (options) {
-	    var path = options.path;
-	    $.ajax({
-	      url: path + '.shx',
-	      mimeType: 'text/plain; charset=x-user-defined',
-	      success: function (data) {
-	        var indices = this.loadShx(data);
-	        $.ajax({
-	          url: path + '.shp',
-	          mimeType: 'text/plain; charset=x-user-defined',
-	          success: function (data) {
-	            $.ajax({
-	              url: path + '.dbf',
-	              mimeType: 'text/plain; charset=x-user-defined',
-	              success: function (dbf_data) {
-	                var layer = this.loadShp(data, dbf_data, indices, options);
-	                options.success(layer);
-	              }
-	            });
-	          }
-	        });
-	      }
-	    });
-	  };
-
-	  this.localShapefile = function (options) {
-	    var shxFile = options.shx;
-	    var shpFile = options.shp;
-	    var dbfFile = options.dbf;
-	    var shxReader = new FileReader();
-	    shxReader.onloadend = function () {
-	      var indices = m_that.loadShx(shxReader.result);
-	      var shpReader = new FileReader();
-
-	      shpReader.onloadend = function () {
-	        var shpData = shpReader.result;
-
-	        var dbfReader = new FileReader();
-	        dbfReader.onloadend = function () {
-	          var dbfData = dbfReader.result;
-	          var layer = m_that.loadShp(shpData, dbfData, indices, options);
-	          options.success(layer);
-	        };
-	        dbfReader.readAsBinaryString(dbfFile);
-	      };
-	      shpReader.readAsBinaryString(shpFile);
-	    };
-	    shxReader.readAsBinaryString(shxFile);
-	  };
-
-	  this.loadDBF = function (data) {
-	    var readHeader = function (offset) {
-	      var name = m_that.str(data, offset, 10);
-	      var type = m_that.str(data, offset + 11, 1);
-	      var length = m_that.int8(data, offset + 16);
-	      return {
-	        name: name,
-	        type: type,
-	        length: length
-	      };
-	    };
-
-	    // Level of the dBASE file
-	    var level = m_that.int8(data, 0);
-	    if (level === 4) {
-	      throw 'Level 7 dBASE not supported';
-	    }
-
-	    // Date of last update
-	    /*
-	    var year = m_that.int8(data, 1);
-	    var month = m_that.int8(data, 2);
-	    var day = m_that.int8(data, 3);
-	    */
-
-	    var num_entries = m_that.lint32(data, 4);
-	    var header_size = m_that.lint16(data, 8);
-	    var record_size = m_that.lint16(data, 10);
-
-	    var FIELDS_START = 32;
-	    var HEADER_LENGTH = 32;
-
-	    var header_offset = FIELDS_START;
-	    var headers = [];
-	    while (header_offset < header_size - 1) {
-	      headers.push(readHeader(header_offset));
-	      header_offset += HEADER_LENGTH;
-	    }
-
-	    var records = [];
-	    var record_offset = header_size;
-	    while (record_offset < header_size + num_entries * record_size) {
-	      var declare = m_that.str(data, record_offset, 1);
-	      if (declare === '*') {
-	        // Record size in the header include the size of the delete indicator
-	        record_offset += record_size;
-	      } else {
-	        // Move offset to the start of the actual data
-	        record_offset += 1;
-	        var record = {};
-	        for (var i = 0; i < headers.length; i += 1) {
-	          var header = headers[i];
-	          var value;
-	          if (header.type === 'C') {
-	            value = m_that.str(data, record_offset, header.length).trim();
-	          } else if (header.type === 'N') {
-	            value = parseFloat(m_that.str(data, record_offset, header.length));
-	          }
-	          record_offset += header.length;
-	          record[header.name] = value;
-	        }
-	        records.push(record);
-	      }
-	    }
-	    return records;
-	  };
-
-	  this.loadShp = function (data, dbf_data, indices, options) {
-	    options = options; /* unused parameter */
-	    var features = [];
-	    var readRing = function (offset, start, end) {
-	      var ring = [];
-	      for (var i = end - 1; i >= start; i -= 1) {
-	        var x = m_that.ldbl64(data, offset + 16 * i);
-	        var y = m_that.ldbl64(data, offset + 16 * i + 8);
-	        ring.push([x, y]);
-	      }
-	      //if (ring.length <= 3)
-	      // return [];
-	      return ring;
-	    };
-
-	    var readRecord = function (offset) {
-	      // var index = m_that.bint32(data, offset);
-	      // var record_length = m_that.bint32(data, offset + 4);
-	      var record_offset = offset + 8;
-	      var geom_type = m_that.lint32(data, record_offset);
-	      var num_parts, num_points, parts_start, points_start, i,
-	          start, end, ring, rings;
-
-	      if (geom_type === SHP_NULL) {
-	        console.log('NULL Shape');
-	        //return offset + 12;
-	      } else if (geom_type === SHP_POINT) {
-	        var x = m_that.ldbl64(data, record_offset + 4);
-	        var y = m_that.ldbl64(data, record_offset + 12);
-
-	        features.push({
-	          type: 'Point',
-	          attr: {},
-	          geom: [[x, y]]
-	        });
-	      } else if (geom_type === SHP_POLYGON) {
-	        num_parts = m_that.lint32(data, record_offset + 36);
-	        num_points = m_that.lint32(data, record_offset + 40);
-
-	        parts_start = offset + 52;
-	        points_start = offset + 52 + 4 * num_parts;
-
-	        rings = [];
-	        for (i = 0; i < num_parts; i += 1) {
-	          start = m_that.lint32(data, parts_start + i * 4);
-	          if (i + 1 < num_parts) {
-	            end = m_that.lint32(data, parts_start + (i + 1) * 4);
-	          } else {
-	            end = num_points;
-	          }
-	          ring = readRing(points_start, start, end);
-	          rings.push(ring);
-	        }
-	        features.push({
-	          type: 'Polygon',
-	          attr: {},
-	          geom: [rings]
-	        });
-	      } else if (geom_type === SHP_POLYLINE) {
-	        num_parts = m_that.lint32(data, record_offset + 36);
-	        num_points = m_that.lint32(data, record_offset + 40);
-
-	        parts_start = offset + 52;
-	        points_start = offset + 52 + 4 * num_parts;
-
-	        rings = [];
-	        for (i = 0; i < num_parts; i += 1) {
-	          start = m_that.lint32(data, parts_start + i * 4);
-	          if (i + 1 < num_parts) {
-	            end = m_that.lint32(data, parts_start + (i + 1) * 4);
-	          } else {
-	            end = num_points;
-	          }
-	          ring = readRing(points_start, start, end);
-	          rings.push(ring);
-	        }
-	        features.push({
-	          type: 'Polyline',
-	          attr: {},
-	          geom: [rings]
-	        });
-	      } else {
-	        throw 'Not Implemented: ' + geom_type;
-	      }
-	      //return offset + 2 * record_length + SHP_HEADER_LEN;
-	    };
-
-	    var attr = this.loadDBF(dbf_data), i;
-
-	    //var offset = 100;
-	    //while (offset < length * 2) {
-	    // offset = readRecord (offset);
-	    //}
-	    for (i = 0; i < indices.length; i += 1) {
-	      var offset = indices[i];
-	      readRecord(offset);
-	    }
-
-	    var layer = []; //new Layer ();
-
-	    for (i = 0; i < features.length; i += 1) {
-	      var feature = features[i];
-	      feature.attr = attr[i];
-	      layer.push(feature);
-	    }
-	    return layer;
-	  };
-
-	  return this;
-	};
-
-	//////////////////////////////////////////////////////////////////////////////
-	/**
-	 * @module vgl
-	 */
-
-	/*global vgl, mat4, unescape, Float32Array, Int8Array, Uint16Array*/
-	//////////////////////////////////////////////////////////////////////////////
-
-	//////////////////////////////////////////////////////////////////////////////
-	//
-	// vbgModule.vtkReader class
-	// This contains code that unpack a json base64 encoded vtkdataset,
-	// such as those produced by ParaView's webGL exporter (where much
-	// of the code originated from) and convert it to VGL representation.
-	//
-	//////////////////////////////////////////////////////////////////////////////
-
-	vgl.vtkReader = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.vtkReader)) {
-	    return new vgl.vtkReader();
-	  }
-
-	  var m_base64Chars =
-	    ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
-	     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
-	     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
-	     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-	     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'],
-	      m_reverseBase64Chars = [],
-	      m_vtkRenderedList = {},
-	      m_vtkObjectCount = 0,
-	      m_vtkScene = null,
-	      m_node = null,
-	      END_OF_INPUT = -1,
-	      m_base64Str = '',
-	      m_base64Count = 0,
-	      m_pos = 0,
-	      m_viewer = null,
-	      i = 0;
-
-	  //initialize the array here if not already done.
-	  if (m_reverseBase64Chars.length === 0) {
-	    for (i = 0; i < m_base64Chars.length; i += 1) {
-	      m_reverseBase64Chars[m_base64Chars[i]] = i;
-	    }
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * ntos
-	   *
-	   * @param n
-	   * @returns unescaped n
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.ntos = function (n) {
-	    var unN;
-
-	    unN = n.toString(16);
-	    if (unN.length === 1) {
-	      unN = '0' + unN;
-	    }
-	    unN = '%' + unN;
-
-	    return unescape(unN);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * readReverseBase64
-	   *
-	   * @returns
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readReverseBase64 = function () {
-	    var nextCharacter;
-
-	    if (!m_base64Str) {
-	      return END_OF_INPUT;
-	    }
-
-	    while (true) {
-	      if (m_base64Count >= m_base64Str.length) {
-	        return END_OF_INPUT;
-	      }
-	      nextCharacter = m_base64Str.charAt(m_base64Count);
-	      m_base64Count += 1;
-
-	      if (m_reverseBase64Chars[nextCharacter]) {
-	        return m_reverseBase64Chars[nextCharacter];
-	      }
-	      if (nextCharacter === 'A') {
-	        return 0;
-	      }
-	    }
-
-	    return END_OF_INPUT;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * decode64
-	   *
-	   * @param str
-	   * @returns result
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.decode64 = function (str) {
-	    var result = '',
-	        inBuffer = new Array(4),
-	        done = false;
-
-	    m_base64Str = str;
-	    m_base64Count = 0;
-
-	    while (!done &&
-	           (inBuffer[0] = this.readReverseBase64()) !== END_OF_INPUT &&
-	           (inBuffer[1] = this.readReverseBase64()) !== END_OF_INPUT) {
-	      inBuffer[2] = this.readReverseBase64();
-	      inBuffer[3] = this.readReverseBase64();
-	      /*jshint bitwise: false */
-	      result += this.ntos((((inBuffer[0] << 2) & 0xff) | inBuffer[1] >> 4));
-	      if (inBuffer[2] !== END_OF_INPUT) {
-	        result += this.ntos((((inBuffer[1] << 4) & 0xff) | inBuffer[2] >> 2));
-	        if (inBuffer[3] !== END_OF_INPUT) {
-	          result += this.ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3]));
-	        } else {
-	          done = true;
-	        }
-	      } else {
-	        done = true;
-	      }
-	      /*jshint bitwise: true */
-	    }
-
-	    return result;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * readNumber
-	   *
-	   * @param ss
-	   * @returns v
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readNumber = function (ss) {
-	    //jshint plusplus: false, bitwise: false
-	    var v = ((ss[m_pos++]) +
-	             (ss[m_pos++] << 8) +
-	             (ss[m_pos++] << 16) +
-	             (ss[m_pos++] << 24));
-	    //jshint plusplus: true, bitwise: true
-	    return v;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * readF3Array
-	   *
-	   * @param numberOfPoints
-	   * @param ss
-	   * @returns points
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readF3Array = function (numberOfPoints, ss) {
-	    var size = numberOfPoints * 4 * 3, test = new Int8Array(size),
-	        points = null, i;
-
-	    for (i = 0; i < size; i += 1) {
-	      test[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    points = new Float32Array(test.buffer);
-
-	    return points;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * readColorArray
-	   *
-	   * @param numberOfPoints
-	   * @param ss
-	   * @param vglcolors
-	   * @returns points
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.readColorArray = function (numberOfPoints, ss, vglcolors) {
-	    var i, idx = 0, tmp = new Array(numberOfPoints * 3);
-	    //jshint plusplus: false
-	    for (i = 0; i < numberOfPoints; i += 1) {
-	      tmp[idx++] = ss[m_pos++] / 255.0;
-	      tmp[idx++] = ss[m_pos++] / 255.0;
-	      tmp[idx++] = ss[m_pos++] / 255.0;
-	      m_pos++;
-	    }
-	    //jshint plusplus: true
-	    vglcolors.insert(tmp);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parseObject
-	   *
-	   * @param buffer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parseObject = function (vtkObject) {
-	    var geom = new vgl.geometryData(), mapper = vgl.mapper(), ss = [],
-	        type = null, data = null, size, matrix = null, material = null,
-	        actor, colorMapData, shaderProg, opacityUniform, lookupTable,
-	        colorTable, windowSize, width, height, position;
-
-	    //dehexlify
-	    //data = this.decode64(vtkObject.data);
-	    data = atob(vtkObject.data);
-	    //jshint bitwise: false
-	    for (i = 0; i < data.length; i += 1) {
-	      ss[i] = data.charCodeAt(i) & 0xff;
-	    }
-	    //jshint bitwise: true
-
-	    //Determine the Object type
-	    m_pos = 0;
-	    size = this.readNumber(ss);
-	    type = String.fromCharCode(ss[m_pos]);
-	    m_pos += 1;
-	    geom.setName(type);
-
-	    // Lines
-	    if (type === 'L') {
-	      matrix = this.parseLineData(geom, ss);
-	      material = vgl.utils.createGeometryMaterial();
-	    // Mesh
-	    } else if (type === 'M') {
-	      matrix = this.parseMeshData(geom, ss);
-	      material = vgl.utils.createPhongMaterial();
-	    // Points
-	    } else if (type === 'P') {
-	      matrix = this.parsePointData(geom, ss);
-	      material = vgl.utils.createGeometryMaterial();
-	    // ColorMap
-	    } else if (type === 'C') {
-	      colorMapData = this.parseColorMapData(geom, ss, size);
-	      colorTable = [];
-
-	      for (i = 0; i < colorMapData.colors.length; i += 1) {
-	        colorTable.push(colorMapData.colors[i][1]);
-	        colorTable.push(colorMapData.colors[i][2]);
-	        colorTable.push(colorMapData.colors[i][3]);
-	        colorTable.push(colorMapData.colors[i][0] * 255);
-	      }
-
-	      lookupTable = new vgl.lookupTable();
-	      lookupTable.setColorTable(colorTable);
-
-	      windowSize = m_viewer.renderWindow().windowSize();
-	      width = colorMapData.size[0] * windowSize[0];
-	      height = colorMapData.size[1] * windowSize[1];
-
-	      position = [colorMapData.position[0] * windowSize[0],
-	                  (1 - colorMapData.position[1]) * windowSize[1], 0];
-	      position[1] = position[1] - height;
-
-	      // For now hardcode the height
-	      height = 30;
-
-	      return vgl.utils.createColorLegend(colorMapData.title,
-	          lookupTable, position, width, height, 3, 0);
-	    // Unknown
-	    } else {
-	      console.log('Ignoring unrecognized encoded data type ' + type);
-	    }
-
-	    mapper.setGeometryData(geom);
-
-	    //default opacity === solid. If were transparent, set it lower.
-	    if (vtkObject.hasTransparency) {
-	      shaderProg = material.shaderProgram();
-	      opacityUniform = shaderProg.uniform('opacity');
-	      console.log('opacity ', vtkObject.opacity);
-	      opacityUniform.set(vtkObject.opacity);
-	      material.setBinNumber(1000);
-	    }
-
-	    actor = vgl.actor();
-	    actor.setMapper(mapper);
-	    actor.setMaterial(material);
-	    actor.setMatrix(mat4.transpose(mat4.create(), matrix));
-
-	    return [actor];
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parseLineData
-	   *
-	   * @param geom, ss
-	   * @returns matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parseLineData = function (geom, ss) {
-	    var vglpoints = null, vglcolors = null, vgllines = null,
-	        matrix = mat4.create(),
-	        numberOfIndex, numberOfPoints, points,
-	        temp, index, size, m, i,
-	        p = null, idx = 0;
-
-	    numberOfPoints = this.readNumber(ss);
-	    p = new Array(numberOfPoints * 3);
-
-	    //Getting Points
-	    vglpoints = new vgl.sourceDataP3fv();
-	    points = this.readF3Array(numberOfPoints, ss);
-
-	    //jshint plusplus: false
-	    for (i = 0; i < numberOfPoints; i += 1) {
-	      p[idx++] = points[i * 3];
-	      p[idx++] = points[i * 3 + 1];
-	      p[idx++] = points[i * 3 + 2];
-	    }
-	    //jshint plusplus: true
-	    vglpoints.insert(p);
-	    geom.addSource(vglpoints);
-
-	    //Getting Colors
-	    vglcolors = new vgl.sourceDataC3fv();
-	    this.readColorArray(numberOfPoints, ss, vglcolors);
-	    geom.addSource(vglcolors);
-
-	    //Getting connectivity
-	    vgllines = new vgl.lines();
-	    geom.addPrimitive(vgllines);
-	    numberOfIndex = this.readNumber(ss);
-
-	    temp = new Int8Array(numberOfIndex * 2);
-	    for (i = 0; i < numberOfIndex * 2; i += 1) {
-	      temp[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    index = new Uint16Array(temp.buffer);
-	    vgllines.setIndices(index);
-	    vgllines.setPrimitiveType(vgl.GL.LINES);
-
-	    //Getting Matrix
-	    size = 16 * 4;
-	    temp = new Int8Array(size);
-	    for (i = 0; i < size; i += 1) {
-	      temp[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    m = new Float32Array(temp.buffer);
-	    mat4.copy(matrix, m);
-
-	    return matrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parseMeshData
-	   *
-	   * @param geom, ss
-	   * @returns matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parseMeshData = function (geom, ss) {
-	    var vglpoints = null, vglcolors = null,
-	        normals = null, matrix = mat4.create(),
-	        vgltriangles = null, numberOfIndex, numberOfPoints,
-	        points, temp, index, size, m, i,
-	        pn = null, idx = 0;
-
-	    numberOfPoints = this.readNumber(ss);
-	    pn = new Array(numberOfPoints * 6);
-	    //Getting Points
-	    vglpoints = new vgl.sourceDataP3N3f();
-	    points = this.readF3Array(numberOfPoints, ss);
-
-	    //Getting Normals
-	    normals = this.readF3Array(numberOfPoints, ss);
-	    //jshint plusplus: false
-	    for (i = 0; i < numberOfPoints; i += 1) {
-	      pn[idx++] = points[i * 3];
-	      pn[idx++] = points[i * 3 + 1];
-	      pn[idx++] = points[i * 3 + 2];
-	      pn[idx++] = normals[i * 3];
-	      pn[idx++] = normals[i * 3 + 1];
-	      pn[idx++] = normals[i * 3 + 2];
-	    }
-	    //jshint plusplus: true
-	    vglpoints.insert(pn);
-	    geom.addSource(vglpoints);
-
-	    //Getting Colors
-	    vglcolors = new vgl.sourceDataC3fv();
-	    this.readColorArray(numberOfPoints, ss, vglcolors);
-	    geom.addSource(vglcolors);
-
-	    //Getting connectivity
-	    temp = [];
-	    vgltriangles = new vgl.triangles();
-	    numberOfIndex = this.readNumber(ss);
-
-	    temp = new Int8Array(numberOfIndex * 2);
-	    for (i = 0; i < numberOfIndex * 2; i += 1) {
-	      temp[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    index = new Uint16Array(temp.buffer);
-	    vgltriangles.setIndices(index);
-	    geom.addPrimitive(vgltriangles);
-
-	    //Getting Matrix
-	    size = 16 * 4;
-	    temp = new Int8Array(size);
-	    for (i = 0; i < size; i += 1) {
-	      temp[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    m = new Float32Array(temp.buffer);
-	    mat4.copy(matrix, m);
-
-	    return matrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parsePointData
-	   *
-	   * @param geom, ss
-	   * @returns matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parsePointData = function (geom, ss) {
-	    var numberOfPoints, points, indices, temp, size,
-	        matrix = mat4.create(), vglpoints = null,
-	        vglcolors = null, vglVertexes = null, m,
-	        p = null, idx = 0;
-
-	    numberOfPoints = this.readNumber(ss);
-	    p = new Array(numberOfPoints * 3);
-
-	    //Getting Points and creating 1:1 connectivity
-	    vglpoints = new vgl.sourceDataP3fv();
-	    points = this.readF3Array(numberOfPoints, ss);
-
-	    indices = new Uint16Array(numberOfPoints);
-
-	    //jshint plusplus: false
-	    for (i = 0; i < numberOfPoints; i += 1) {
-	      indices[i] = i;
-	      p[idx++] = points[i * 3];
-	      p[idx++] = points[i * 3 + 1];
-	      p[idx++] = points[i * 3 + 2];
-	    }
-	    //jshint plusplus: true
-	    vglpoints.insert(p);
-	    geom.addSource(vglpoints);
-
-	    //Getting Colors
-	    vglcolors = new vgl.sourceDataC3fv();
-	    this.readColorArray(numberOfPoints, ss, vglcolors);
-	    geom.addSource(vglcolors);
-
-	    //Getting connectivity
-	    vglVertexes = new vgl.points();
-	    vglVertexes.setIndices(indices);
-	    geom.addPrimitive(vglVertexes);
-
-	    //Getting matrix
-	    size = 16 * 4;
-	    temp = new Int8Array(size);
-	    for (i = 0; i < size; i += 1) {
-	      temp[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-
-	    m = new Float32Array(temp.buffer);
-	    mat4.copy(matrix, m);
-
-	    return matrix;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parseColorMapData
-	   *
-	   * @param geom, ss
-	   * @returns matrix
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parseColorMapData = function (geom, ss, numColors) {
-
-	    var tmpArray, size, xrgb, i, c, obj = {};
-
-	    // Set number of colors
-	    obj.numOfColors = numColors;
-
-	    // Getting Position
-	    size = 8;
-	    tmpArray = new Int8Array(size);
-	    for (i = 0; i < size; i += 1) {
-	      tmpArray[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-	    obj.position = new Float32Array(tmpArray.buffer);
-
-	    // Getting Size
-	    size = 8;
-	    tmpArray = new Int8Array(size);
-	    for (i = 0; i < size; i += 1) {
-	      tmpArray[i] = ss[m_pos];
-	      m_pos += 1;
-	    }
-	    obj.size = new Float32Array(tmpArray.buffer);
-
-	    //Getting Colors
-	    obj.colors = [];
-	    //jshint plusplus: false
-	    for (c = 0; c < obj.numOfColors; c += 1) {
-	      tmpArray = new Int8Array(4);
-	      for (i = 0; i < 4; i += 1) {
-	        tmpArray[i] = ss[m_pos];
-	        m_pos += 1;
-	      }
-
-	      xrgb = [
-	        new Float32Array(tmpArray.buffer)[0],
-	        ss[m_pos++],
-	        ss[m_pos++],
-	        ss[m_pos++]
-	      ];
-	      obj.colors[c] = xrgb;
-	    }
-
-	    obj.orientation = ss[m_pos++];
-	    obj.numOfLabels = ss[m_pos++];
-	    obj.title = '';
-	    while (m_pos < ss.length) {
-	      obj.title += String.fromCharCode(ss[m_pos++]);
-	    }
-	    //jshint plusplus: true
-
-	    return obj;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * parseSceneMetadata
-	   *
-	   * @param data
-	   * @returns renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.parseSceneMetadata = function (renderer, layer) {
-
-	    var sceneRenderer = m_vtkScene.Renderers[layer],
-	        camera = renderer.camera(), bgc, localWidth, localHeight;
-
-	    localWidth = (sceneRenderer.size[0] - sceneRenderer.origin[0]) * m_node.width;
-	    localHeight = (sceneRenderer.size[1] - sceneRenderer.origin[1]) * m_node.height;
-	    renderer.resize(localWidth, localHeight);
-
-	    /// We are setting the center to the focal point because of
-	    /// a possible paraview web bug. The center of rotation isn't
-	    /// getting updated correctly on resetCamera.
-	    camera.setCenterOfRotation(
-	      [sceneRenderer.LookAt[1], sceneRenderer.LookAt[2],
-	       sceneRenderer.LookAt[3]]);
-	    camera.setViewAngleDegrees(sceneRenderer.LookAt[0]);
-	    camera.setPosition(
-	      sceneRenderer.LookAt[7], sceneRenderer.LookAt[8],
-	      sceneRenderer.LookAt[9]);
-	    camera.setFocalPoint(
-	      sceneRenderer.LookAt[1], sceneRenderer.LookAt[2],
-	      sceneRenderer.LookAt[3]);
-	    camera.setViewUpDirection(
-	      sceneRenderer.LookAt[4], sceneRenderer.LookAt[5],
-	      sceneRenderer.LookAt[6]);
-
-	    if (layer === 0) {
-	      bgc = sceneRenderer.Background1;
-	      renderer.setBackgroundColor(bgc[0], bgc[1], bgc[2], 1);
-	    } else {
-	      renderer.setResizable(false);
-	    }
-	    renderer.setLayer(layer);
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * initScene
-	   *
-	   * @returns viewer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.initScene = function () {
-	    var renderer, layer;
-
-	    if (m_vtkScene === null) {
-	      return m_viewer;
-	    }
-	    for (layer = m_vtkScene.Renderers.length - 1; layer >= 0; layer -= 1) {
-
-	      renderer = this.getRenderer(layer);
-	      this.parseSceneMetadata(renderer, layer);
-	    }
-
-	    return m_viewer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * createViewer - Creates a viewer object.
-	   *
-	   * @param
-	   *
-	   * @returns viewer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.createViewer = function (node) {
-	    var interactorStyle;
-
-	    if (m_viewer === null) {
-	      m_node = node;
-	      m_viewer = vgl.viewer(node);
-	      m_viewer.init();
-	      m_viewer.renderWindow().removeRenderer(m_viewer.renderWindow().activeRenderer());
-	      m_viewer.renderWindow().addRenderer(new vgl.depthPeelRenderer());
-	      m_vtkRenderedList[0] = m_viewer.renderWindow().activeRenderer();
-	      m_viewer.renderWindow().resize(node.width, node.height);
-	      interactorStyle = vgl.pvwInteractorStyle();
-	      m_viewer.setInteractorStyle(interactorStyle);
-	    }
-
-	    return m_viewer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * deleteViewer - Deletes the viewer object associated with the reader.
-	   *
-	   * @returns void
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.deleteViewer = function () {
-	    m_vtkRenderedList = {};
-	    m_viewer = null;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * updateCanvas -
-	   *
-	   * @param
-	   *
-	   * @returns void
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.updateCanvas = function (node) {
-	    m_node = node;
-	    m_viewer.renderWindow().resize(node.width, node.height);
-
-	    return m_viewer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * clearVtkObjectData - Clear out the list of VTK geometry data.
-	   *
-	   * @param void
-	   * @returns void
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.numObjects = function () {
-	    return m_vtkObjectCount;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * getRenderer - Gets (or creates) the renderer for a layer.
-	   *
-	   * @param layer
-	   * @returns renderer
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.getRenderer = function (layer) {
-	    var renderer;
-
-	    renderer = m_vtkRenderedList[layer];
-	    if (renderer === null || typeof renderer === 'undefined') {
-	      renderer = new vgl.renderer();
-	      renderer.setResetScene(false);
-	      renderer.setResetClippingRange(false);
-	      m_viewer.renderWindow().addRenderer(renderer);
-
-	      if (layer !== 0) {
-	        renderer.camera().setClearMask(vgl.GL.DepthBufferBit);
-	      }
-
-	      m_vtkRenderedList[layer] = renderer;
-	    }
-
-	    return renderer;
-	  };
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * setVtkScene - Set the VTK scene data for camera initialization.
-	   *
-	   * @param scene
-	   * @returns void
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.setVtkScene = function (scene) {
-	    m_vtkScene = scene;
-	  };
-
-	  return this;
-	};
-
-	vgl.DataBuffers = function (initialSize) {
-	  'use strict';
-	  if (!(this instanceof vgl.DataBuffers)) {
-	    return new vgl.DataBuffers(initialSize);
-	  }
-
-	  var data = {};
-
-	  var size;
-	  if (!initialSize && initialSize !== 0) {
-	    size = 256;
-	  } else {
-	    size = initialSize;
-	  }
-
-	  var current = 0;
-
-	  var copyArray = function (dst, src, start, count) {
-	    if (!dst) {
-	      throw 'No destination';
-	    }
-	    if (!start) {
-	      start = 0;
-	    }
-	    if (!count) {
-	      count = src.length;
-	    }
-	    for (var i = 0; i < count; i += 1) {
-	      dst[start + i] = src[i];
-	    }
-	  };
-
-	  var resize = function (min_expand) {
-	    var new_size = size;
-	    /* If the array would increase substantially, don't just double its
-	     * size.  If the array has been increasing gradually, double it as the
-	     * expectation is that it will increase again. */
-	    if (new_size * 2 < min_expand) {
-	      new_size = min_expand;
-	    }
-	    while (new_size < min_expand) {
-	      new_size *= 2;
-	    }
-	    size = new_size;
-	    for (var name in data) {
-	      if (data.hasOwnProperty(name)) {
-	        var newArray = new Float32Array(new_size * data[name].len);
-	        var oldArray = data[name].array;
-	        copyArray(newArray, oldArray);
-	        data[name].array = newArray;
-	        data[name].dirty = true;
-	      }
-	    }
-	  };
-
-	  /**
-	   * Allocate a buffer with a name and a specific number of components per
-	   * entry.  If a buffer with the specified name already exists, it will be
-	   * overwritten.
-	   *
-	   * @param name: the name of the buffer to create or replace.
-	   * @param len: number of components per entry.  Most be a positive integer.
-	   */
-	  this.create = function (name, len) {
-	    if (!len || len < 0) {
-	      throw 'Length of buffer must be a positive integer';
-	    }
-	    var array = new Float32Array(size * len);
-	    data[name] = {
-	      array: array,
-	      len: len,
-	      dirty: false
-	    };
-	    return data[name].array;
-	  };
-
-	  this.alloc = function (num) {
-	    if ((current + num) >= size) {
-	      resize(current + num);
-	    }
-	    var start = current;
-	    current += num;
-	    return start;
-	  };
-
-	  this.get = function (name) {
-	    return data[name].array;
-	  };
-
-	  this.write = function (name, array, start, count) {
-	    if (start + count > size) {
-	      throw 'Write would exceed buffer size';
-	    }
-	    copyArray(data[name].array, array, start * data[name].len, count * data[name].len);
-	    data[name].dirty = true;
-	  };
-
-	  this.repeat = function (name, elem, start, count) {
-	    if (start + count > size) {
-	      throw 'Repeat would exceed buffer size';
-	    }
-	    for (var i = 0; i < count; i += 1) {
-	      copyArray(data[name].array, elem,
-	                 (start + i) * data[name].len, data[name].len);
-	    }
-	    data[name].dirty = true;
-	  };
-
-	  this.count = function () {
-	    return current;
-	  };
-
-	  this.data = function (name) {
-	    return data[name].array;
-	  };
-	};
-
-	////////////////////////////////////////////////////////////////////////////
-	/**
-	 * Create a new instance of class renderer *
-	 *
-	 * @returns {vgl.renderer}
-	 */
-	////////////////////////////////////////////////////////////////////////////
-
-	vgl.depthPeelRenderer = function () {
-	  'use strict';
-
-	  if (!(this instanceof vgl.depthPeelRenderer)) {
-	    return new vgl.depthPeelRenderer();
-	  }
-	  vgl.renderer.call(this);
-
-	  var m_this = this, fbo = [], texID = [], depthTexID = [],
-	      colorBlenderTexID, colorBlenderFBOID, setupTime = vgl.timestamp(),
-	      fpMaterial = vgl.material(), blMaterial = vgl.material(),
-	      fiMaterial = vgl.material(), frontPeelShader = null, blendShader = null,
-	      finalShader, NUM_PASSES = 6, m_quad = null, fpwidth, fpheight, blwidth, blheight,
-	      fiwidth, fiheight, fpopacity, fibackgroundColor;
-
-	  function drawFullScreenQuad(renderState, material) {
-	    m_quad.setMaterial(material);
-
-	    renderState.m_mapper = m_quad.mapper();
-	    renderState.m_material = material;
-
-	    renderState.m_material.bind(renderState);
-	    renderState.m_mapper.render(renderState);
-	    renderState.m_material.undoBind(renderState);
-
-	    m_quad.setMaterial(null);
-	  }
-
-	  function initScreenQuad(renderState, width, height) {
-	    console.log(width);
-	    console.log(height);
-	    m_quad = vgl.utils.createPlane(0.0, 0.0, 0.0,
-	                                   1.0, 0.0, 0.0,
-	                                   0.0, 1.0, 0.0);
-	  }
-
-	  function initShaders(renderState) {
-	    var fpmv, fpproj, fpvertex, fpcolor, fpdepthTex, fpnormal, fpnr,
-	        blvertex, blColorSamp, blPrevDepthSamp, blCurrDepthSamp,
-	        fivertex, fitempTex;
-
-	    // Load the front to back peeling shader
-	    fpvertex = new vgl.vertexAttribute('vertexPosition');
-	    fpnormal = new vgl.vertexAttribute('vertexNormal');
-	    fpcolor = new vgl.vertexAttribute('vertexColor');
-	    fpmv = new vgl.modelViewUniform('modelViewMatrix');
-	    fpnr = new vgl.modelViewUniform('normalMatrix');
-	    fpproj = new vgl.projectionUniform('projectionMatrix');
-	    fpwidth = new vgl.floatUniform('width');
-	    fpheight = new vgl.floatUniform('height');
-	    fpopacity = new vgl.floatUniform('opacity', 1.0);
-	    fpdepthTex = new vgl.uniform(vgl.GL.INT, 'depthTexture');
-	    fpdepthTex.set(0);
-
-	    frontPeelShader = new vgl.shaderProgram();
-	    frontPeelShader.loadShader(vgl.GL.VERTEX_SHADER, 'front_peel.vert');
-	    frontPeelShader.loadShader(vgl.GL.FRAGMENT_SHADER, 'front_peel.frag');
-
-	    frontPeelShader.addUniform(fpmv);
-	    frontPeelShader.addUniform(fpnr);
-	    frontPeelShader.addUniform(fpproj);
-	    frontPeelShader.addUniform(fpdepthTex);
-	    frontPeelShader.addUniform(fpwidth);
-	    frontPeelShader.addUniform(fpheight);
-	    frontPeelShader.addUniform(fpopacity);
-	    frontPeelShader.addVertexAttribute(fpvertex, vgl.vertexAttributeKeys.Position);
-	    frontPeelShader.addVertexAttribute(fpnormal, vgl.vertexAttributeKeys.Normal);
-	    frontPeelShader.addVertexAttribute(fpcolor, vgl.vertexAttributeKeys.Color);
-
-	    // Compile and link the shader
-	    frontPeelShader.compileAndLink(renderState);
-
-	    fpMaterial.addAttribute(frontPeelShader);
-
-	    //     //add attributes and uniforms
-	    //     frontPeelShader.AddAttribute('vVertex');
-	    //     frontPeelShader.AddUniform('MVP');
-	    //     frontPeelShader.AddUniform('vColor');
-	    //     frontPeelShader.AddUniform('depthTexture');
-	    //     //pass constant uniforms at initialization
-	    //     glUniform1i(frontPeelShader('depthTexture'), 0);
-	    // frontPeelShader.UnUse();
-
-	    // Load the blending shader
-	    blendShader = new vgl.shaderProgram();
-	    blendShader.loadShader(vgl.GL.VERTEX_SHADER, 'blend.vert');
-	    blendShader.loadShader(vgl.GL.FRAGMENT_SHADER, 'blend.frag');
-	    blColorSamp = new vgl.uniform(vgl.GL.INT, 'currColorTexture');
-	    blPrevDepthSamp = new vgl.uniform(vgl.GL.INT, 'prevDepthTexture');
-	    blCurrDepthSamp = new vgl.uniform(vgl.GL.INT, 'currDepthTexture');
-
-	    blwidth = new vgl.floatUniform('width');
-	    blheight = new vgl.floatUniform('height');
-	    blColorSamp.set(0);
-	    blPrevDepthSamp.set(1);
-	    blCurrDepthSamp.set(2);
-
-	    blvertex = new vgl.vertexAttribute('vertexPosition');
-
-	    blendShader.addUniform(blColorSamp);
-	    blendShader.addUniform(blPrevDepthSamp);
-	    blendShader.addUniform(blPrevDepthSamp);
-	    blendShader.addUniform(blwidth);
-	    blendShader.addUniform(blheight);
-	    blendShader.addVertexAttribute(blvertex,
-	      vgl.vertexAttributeKeys.Position);
-
-	    // Compile and link the shader
-	    blendShader.compileAndLink(renderState);
-	    blMaterial.addAttribute(blendShader);
-
-	    //     //add attributes and uniforms
-	    //     blendShader.AddAttribute('vVertex');
-	    //     blendShader.AddUniform('currColorTexture');
-	    //     //pass constant uniforms at initialization
-	    //     glUniform1i(blendShader('currColorTexture'), 0);
-	    // blendShader.UnUse();
-
-	    //Load the final shader
-	    finalShader = new vgl.shaderProgram();
-	    finalShader.loadShader(vgl.GL.VERTEX_SHADER, 'blend.vert');
-	    finalShader.loadShader(vgl.GL.FRAGMENT_SHADER, 'final.frag');
-
-	    //fimv = new vgl.modelViewUniform('modelViewMatrix');
-	    //fiproj = new vgl.projectionUniform('projectionMatrix');
-	    fitempTex = new vgl.uniform(vgl.GL.INT, 'colorTexture');
-	    fiwidth = new vgl.floatUniform('width');
-	    fiheight = new vgl.floatUniform('height');
-	    fibackgroundColor = new vgl.uniform(vgl.GL.FLOAT_VEC3, 'backgroundColor');
-	    fitempTex.set(0);
-	    fivertex = new vgl.vertexAttribute('vertexPosition');
-
-	    //finalShader.addUniform(fimv);
-	    //finalShader.addUniform(fiproj);
-	    finalShader.addUniform(fitempTex);
-	    finalShader.addUniform(fiwidth);
-	    finalShader.addUniform(fiheight);
-	    finalShader.addUniform(fibackgroundColor);
-	    finalShader.addVertexAttribute(fivertex, vgl.vertexAttributeKeys.Position);
-	    finalShader.compileAndLink(renderState);
-	    fiMaterial.addAttribute(finalShader);
-	  }
-
-	  function initFBO(renderState, WIDTH, HEIGHT) {
-	    var i, textureFloatExt, textureFloatLinearExt, depthTextureExt, filtering;
-
-	    // Or browser-appropriate prefix
-	    depthTextureExt = renderState.m_context.getExtension('WEBKIT_WEBGL_depth_texture');
-	    if (!depthTextureExt) {
-	      depthTextureExt = renderState.m_context.getExtension('WEBGL_depth_texture');
-
-	      if (!depthTextureExt) {
-	        console.log('Depth textures are not supported');
-	      }
-	    }
-
-	    var floatTextureExt = renderState.m_context.getExtension('OES_texture_float');
-	    if (!floatTextureExt) {
-	      console.log('float textures are not supported');
-	    }
-
-	    textureFloatExt = renderState.m_context.getExtension('OES_texture_float');
-	    if (!textureFloatExt) {
-	      console.log('Extension Texture Float is not working');
-	      window.alert(':( Sorry, Your browser doesn\'t support texture float extension.');
-	      return;
-	    }
-	    textureFloatLinearExt = renderState.m_context.getExtension(
-	        'OES_texture_float_linear');
-	    depthTextureExt = renderState.m_context.getExtension('WEBGL_depth_texture');
-
-	    if (!depthTextureExt) {
-	      console.log('Extension Depth texture is not working');
-	      window.alert(':( Sorry, Your browser doesn\'t support depth texture extension.');
-	      return;
-	    }
-
-	    filtering = textureFloatLinearExt ? vgl.GL.LINEAR : vgl.GL.NEAREST;
-
-	    //FBO initialization function
-	    // Generate 2 FBO
-	    fbo.push(renderState.m_context.createFramebuffer());
-	    fbo.push(renderState.m_context.createFramebuffer());
-
-	    // Create two textures
-	    texID.push(renderState.m_context.createTexture());
-	    texID.push(renderState.m_context.createTexture());
-
-	    //The FBO has two depth attachments
-	    depthTexID.push(renderState.m_context.createTexture());
-	    depthTexID.push(renderState.m_context.createTexture());
-
-	    // For each attachment
-	    for (i = 0; i < 2; i += 1) {
-	      // First initialize the depth texture
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, depthTexID[i]);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MAG_FILTER, vgl.GL.NEAREST);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MIN_FILTER, vgl.GL.NEAREST);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
-	      renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0,
-	        vgl.GL.DEPTH_COMPONENT, WIDTH, HEIGHT, 0, vgl.GL.DEPTH_COMPONENT,
-	        vgl.GL.UNSIGNED_SHORT, null);
-
-	      // Second initialize the color attachment
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, texID[i]);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MAG_FILTER, filtering);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MIN_FILTER, filtering);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
-	      renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
-	      renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, vgl.GL.RGBA,
-	        WIDTH, HEIGHT, 0, vgl.GL.RGBA, vgl.GL.FLOAT, null);
-
-	      // Bind FBO and attach the depth and color attachments
-	      renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, fbo[i]);
-	      renderState.m_context.framebufferTexture2D(vgl.GL.FRAMEBUFFER,
-	        vgl.GL.DEPTH_ATTACHMENT, vgl.GL.TEXTURE_2D, depthTexID[i], 0);
-	      renderState.m_context.framebufferTexture2D(vgl.GL.FRAMEBUFFER,
-	        vgl.GL.COLOR_ATTACHMENT0, vgl.GL.TEXTURE_2D, texID[i], 0);
-	    }
-
-	    // Now setup the color attachment for color blend FBO
-	    colorBlenderTexID = renderState.m_context.createTexture();
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, colorBlenderTexID);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MIN_FILTER, vgl.GL.NEAREST);
-	    renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
-	        vgl.GL.TEXTURE_MAG_FILTER, vgl.GL.NEAREST);
-	    renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, vgl.GL.RGBA,
-	        WIDTH, HEIGHT, 0, vgl.GL.RGBA, vgl.GL.FLOAT, null);
-
-	    // Generate the color blend FBO ID
-	    colorBlenderFBOID = renderState.m_context.createFramebuffer();
-	    renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, colorBlenderFBOID);
-
-	    // Set the depth attachment of previous FBO as depth attachment for this FBO
-	    renderState.m_context.framebufferTexture2D(vgl.GL.FRAMEBUFFER,
-	        vgl.GL.DEPTH_ATTACHMENT, vgl.GL.TEXTURE_2D, depthTexID[0], 0);
-
-	    // Set the color blender texture as the FBO color attachment
-	    renderState.m_context.framebufferTexture2D(vgl.GL.FRAMEBUFFER,
-	        vgl.GL.COLOR_ATTACHMENT0, vgl.GL.TEXTURE_2D, colorBlenderTexID, 0);
-
-	    // Check the FBO completeness status
-	    var status = renderState.m_context.checkFramebufferStatus(
-	        vgl.GL.FRAMEBUFFER);
-	    if (status === vgl.GL.FRAMEBUFFER_COMPLETE) {
-	      console.log('FBO setup successful !!! \n');
-	    } else {
-	      console.log('Problem with FBO setup');
-	    }
-	    // Unbind FBO
-	    renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, null);
-	  }
-
-	  function setup(renderState) {
-	    if (setupTime.getMTime() < m_this.getMTime()) {
-	      initScreenQuad(renderState, m_this.width(), m_this.height());
-	      initShaders(renderState, m_this.width(), m_this.height());
-	      initFBO(renderState, m_this.width(), m_this.height());
-	      setupTime.modified();
-	    }
-	  }
-
-	  function drawScene(renderState, sortedActors, material) {
-
-	    var i, actor, mvMatrixInv = mat4.create();
-
-	    // // Enable alpha blending with over compositing
-	    // gl.enable(vgl.GL.BLEND);
-	    // gl.blendFunc(vgl.GL.SRC_ALPHA, vgl.GL.ONE_MINUS_SRC_ALPHA);
-
-	    for (i = 0; i < sortedActors.length; i += 1) {
-	      actor = sortedActors[i][2];
-
-	      if (actor.referenceFrame() ===
-	          vgl.boundingObject.ReferenceFrame.Relative) {
-	        mat4.multiply(renderState.m_modelViewMatrix, m_this.m_camera.viewMatrix(),
-	          actor.matrix());
-	        renderState.m_projectionMatrix = m_this.m_camera.projectionMatrix();
-	      } else {
-	        renderState.m_modelViewMatrix = actor.matrix();
-	        renderState.m_projectionMatrix = mat4.create();
-	        mat4.ortho(renderState.m_projectionMatrix, 0,
-	                   m_this.m_width, 0, m_this.m_height, -1, 1);
-	      }
-
-	      mat4.invert(mvMatrixInv, renderState.m_modelViewMatrix);
-	      mat4.transpose(renderState.m_normalMatrix, mvMatrixInv);
-	      renderState.m_mapper = actor.mapper();
-
-	      // TODO Fix this shortcut
-	      if (!material) {
-	        renderState.m_material = actor.material();
-	        renderState.m_material.bind(renderState);
-	        renderState.m_mapper.render(renderState);
-	        renderState.m_material.undoBind(renderState);
-	      } else {
-
-	        var ou = actor.material().shaderProgram().uniform('opacity');
-	        if (ou) {
-	          fpopacity.set(ou.get()[0]);
-	        } else {
-	          fpopacity.set(1.0);
-	        }
-	        renderState.m_material = material;
-	        renderState.m_material.bind(renderState);
-	        renderState.m_mapper.render(renderState);
-	        renderState.m_material.undoBind(renderState);
-	      }
-	    }
-	  }
-
-	  function depthPeelRender(renderState, actors) {
-	    var layer;
-
-	    fpwidth.set(m_this.width());
-	    fpheight.set(m_this.height());
-	    blwidth.set(m_this.width());
-	    blheight.set(m_this.height());
-	    fiwidth.set(m_this.width());
-	    fiheight.set(m_this.height());
-
-	    // Clear color and depth buffer
-	    renderState.m_context.clearColor(0.0, 0.0, 0.0, 0.0);
-	    /*jshint bitwise: false */
-	    renderState.m_context.clear(vgl.GL.OLOR_BUFFER_BIT | vgl.GL.DEPTH_BUFFER_BIT);
-	    /*jshint bitwise: true */
-
-	    // Bind the color blending FBO
-	    renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, colorBlenderFBOID);
-
-	    // 1. In the first pass, we render normally with depth test enabled to
-	    // get the nearest surface
-	    renderState.m_context.enable(vgl.GL.DEPTH_TEST);
-	    renderState.m_context.disable(vgl.GL.BLEND);
-
-	    var clearColor = m_this.m_camera.clearColor();
-	    renderState.m_context.clearColor(clearColor[0], clearColor[1], clearColor[2], 0.0);
-	    /*jshint bitwise: false */
-	    renderState.m_context.clear(vgl.GL.COLOR_BUFFER_BIT | vgl.GL.DEPTH_BUFFER_BIT);
-	    /*jshint bitwise: true */
-
-	    drawScene(renderState, actors);
-
-	    // 2. Depth peeling + blending pass
-	    var numLayers = (NUM_PASSES - 1) * 2;
-
-	    // For each pass
-	    for (layer = 1; layer < numLayers; layer += 1) {
-	      var currId = layer % 2;
-	      var prevId = 1 - currId;
-
-	      // Bind the current FBO
-	      renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, fbo[currId]);
-
-	      // Disbale blending and enable depth testing
-	      renderState.m_context.disable(vgl.GL.BLEND);
-	      renderState.m_context.enable(vgl.GL.DEPTH_TEST);
-
-	      // Bind the depth texture from the previous step
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, depthTexID[prevId]);
-
-	      // Set clear color to black
-	      renderState.m_context.clearColor(0.0, 0.0, 0.0, 0.0);
-
-	      // Clear the color and depth buffers
-	      /*jshint bitwise: false */
-	      renderState.m_context.clear(vgl.GL.COLOR_BUFFER_BIT | vgl.GL.DEPTH_BUFFER_BIT);
-	      /*jshint bitwise: true */
-
-	      // Render scene with the front to back peeling shader
-	      drawScene(renderState, actors, fpMaterial);
-
-	      // Bind the color blender FBO
-	      renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, colorBlenderFBOID);
-
-	      // Enable blending but disable depth testing
-	      renderState.m_context.disable(vgl.GL.DEPTH_TEST);
-	      renderState.m_context.enable(vgl.GL.BLEND);
-
-	      // Change the blending equation to add
-	      renderState.m_context.blendEquation(vgl.GL.FUNC_ADD);
-
-	      // Use separate blending function
-	      renderState.m_context.blendFuncSeparate(vgl.GL.DST_ALPHA, vgl.GL.ONE,
-	          vgl.GL.ZERO, vgl.GL.ONE_MINUS_SRC_ALPHA);
-
-	      // Bind the result from the previous iteration as texture
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE0);
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, texID[currId]);
-
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE1);
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, depthTexID[prevId]);
-
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE2);
-	      renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, depthTexID[currId]);
-
-	      drawFullScreenQuad(renderState, blMaterial);
-
-	      renderState.m_context.activeTexture(vgl.GL.TEXTURE0);
-
-	      // Disable blending
-	      renderState.m_context.disable(vgl.GL.BLEND);
-	    }
-
-	    // 3. Final render pass
-	    //remove the FBO
-	    renderState.m_context.bindFramebuffer(vgl.GL.FRAMEBUFFER, null);
-
-	    // Disable depth testing and blending
-	    renderState.m_context.disable(vgl.GL.DEPTH_TEST);
-	    renderState.m_context.disable(vgl.GL.BLEND);
-
-	    // Bind the color blender texture
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, colorBlenderTexID);
-
-	    fibackgroundColor.set(m_this.m_camera.clearColor());
-
-	    // Draw full screen quad
-	    drawFullScreenQuad(renderState, fiMaterial);
-
-	    renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
-	  }
-
-	  ////////////////////////////////////////////////////////////////////////////
-	  /**
-	   * Render the scene
-	   */
-	  ////////////////////////////////////////////////////////////////////////////
-	  this.render = function () {
-	    var i, renSt, children, actor = null, sortedActors = [];
-
-	    renSt = new vgl.renderState();
-	    renSt.m_renderer = m_this;
-	    renSt.m_context = m_this.renderWindow().context();
-	    renSt.m_contextChanged = m_this.m_contextChanged;
-
-	    // Set the viewport for this renderer
-	    renSt.m_context.viewport(m_this.m_x, m_this.m_y, m_this.m_width, m_this.m_height);
-
-	    // Check if we have initialized
-	    setup(renSt);
-
-	    children = m_this.m_sceneRoot.children();
-
-	    if (children.length > 0 && m_this.m_resetScene) {
-	      this.resetCamera();
-	      m_this.m_resetScene = false;
-	    }
-
-	    for (i = 0; i < children.length; i += 1) {
-	      actor = children[i];
-	      actor.computeBounds();
-	      if (actor.visible()) {
-	        sortedActors.push([actor.material().binNumber(),
-	          actor.material().shaderProgram().uniform('opacity').get()[0],
-	          actor]);
-	      }
-	    }
-
-	    // Now perform sorting
-	    sortedActors.sort(function (a, b) { return a[0] - b[0]; });
-	    sortedActors.sort(function (a, b) { return b[1] - a[1]; });
-
-	    depthPeelRender(renSt, sortedActors);
-	  };
-	};
-
-	inherit(vgl.depthPeelRenderer, vgl.renderer);
-
-	return vgl;
-
-	}));
-
-
-
-/***/ }),
-/* 88 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = {
-	  create: __webpack_require__(89)
-	  , clone: __webpack_require__(90)
-	  , copy: __webpack_require__(91)
-	  , identity: __webpack_require__(92)
-	  , transpose: __webpack_require__(93)
-	  , invert: __webpack_require__(94)
-	  , adjoint: __webpack_require__(95)
-	  , determinant: __webpack_require__(96)
-	  , multiply: __webpack_require__(97)
-	  , translate: __webpack_require__(98)
-	  , scale: __webpack_require__(99)
-	  , rotate: __webpack_require__(100)
-	  , rotateX: __webpack_require__(101)
-	  , rotateY: __webpack_require__(102)
-	  , rotateZ: __webpack_require__(103)
-	  , fromRotationTranslation: __webpack_require__(104)
-	  , fromQuat: __webpack_require__(105)
-	  , frustum: __webpack_require__(106)
-	  , perspective: __webpack_require__(107)
-	  , perspectiveFromFieldOfView: __webpack_require__(108)
-	  , ortho: __webpack_require__(109)
-	  , lookAt: __webpack_require__(110)
-	  , str: __webpack_require__(111)
-	}
-
-/***/ }),
-/* 89 */
-/***/ (function(module, exports) {
-
-	module.exports = create;
-
-	/**
-	 * Creates a new identity mat4
-	 *
-	 * @returns {mat4} a new 4x4 matrix
-	 */
-	function create() {
-	    var out = new Float32Array(16);
-	    out[0] = 1;
-	    out[1] = 0;
-	    out[2] = 0;
-	    out[3] = 0;
-	    out[4] = 0;
-	    out[5] = 1;
-	    out[6] = 0;
-	    out[7] = 0;
-	    out[8] = 0;
-	    out[9] = 0;
-	    out[10] = 1;
-	    out[11] = 0;
-	    out[12] = 0;
-	    out[13] = 0;
-	    out[14] = 0;
-	    out[15] = 1;
-	    return out;
-	};
-
-/***/ }),
-/* 90 */
-/***/ (function(module, exports) {
-
-	module.exports = clone;
-
-	/**
-	 * Creates a new mat4 initialized with values from an existing matrix
-	 *
-	 * @param {mat4} a matrix to clone
-	 * @returns {mat4} a new 4x4 matrix
-	 */
-	function clone(a) {
-	    var out = new Float32Array(16);
-	    out[0] = a[0];
-	    out[1] = a[1];
-	    out[2] = a[2];
-	    out[3] = a[3];
-	    out[4] = a[4];
-	    out[5] = a[5];
-	    out[6] = a[6];
-	    out[7] = a[7];
-	    out[8] = a[8];
-	    out[9] = a[9];
-	    out[10] = a[10];
-	    out[11] = a[11];
-	    out[12] = a[12];
-	    out[13] = a[13];
-	    out[14] = a[14];
-	    out[15] = a[15];
-	    return out;
-	};
-
-/***/ }),
-/* 91 */
-/***/ (function(module, exports) {
-
-	module.exports = copy;
-
-	/**
-	 * Copy the values from one mat4 to another
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the source matrix
-	 * @returns {mat4} out
-	 */
-	function copy(out, a) {
-	    out[0] = a[0];
-	    out[1] = a[1];
-	    out[2] = a[2];
-	    out[3] = a[3];
-	    out[4] = a[4];
-	    out[5] = a[5];
-	    out[6] = a[6];
-	    out[7] = a[7];
-	    out[8] = a[8];
-	    out[9] = a[9];
-	    out[10] = a[10];
-	    out[11] = a[11];
-	    out[12] = a[12];
-	    out[13] = a[13];
-	    out[14] = a[14];
-	    out[15] = a[15];
-	    return out;
-	};
-
-/***/ }),
-/* 92 */
-/***/ (function(module, exports) {
-
-	module.exports = identity;
-
-	/**
-	 * Set a mat4 to the identity matrix
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @returns {mat4} out
-	 */
-	function identity(out) {
-	    out[0] = 1;
-	    out[1] = 0;
-	    out[2] = 0;
-	    out[3] = 0;
-	    out[4] = 0;
-	    out[5] = 1;
-	    out[6] = 0;
-	    out[7] = 0;
-	    out[8] = 0;
-	    out[9] = 0;
-	    out[10] = 1;
-	    out[11] = 0;
-	    out[12] = 0;
-	    out[13] = 0;
-	    out[14] = 0;
-	    out[15] = 1;
-	    return out;
-	};
-
-/***/ }),
-/* 93 */
-/***/ (function(module, exports) {
-
-	module.exports = transpose;
-
-	/**
-	 * Transpose the values of a mat4
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the source matrix
-	 * @returns {mat4} out
-	 */
-	function transpose(out, a) {
-	    // If we are transposing ourselves we can skip a few steps but have to cache some values
-	    if (out === a) {
-	        var a01 = a[1], a02 = a[2], a03 = a[3],
-	            a12 = a[6], a13 = a[7],
-	            a23 = a[11];
-
-	        out[1] = a[4];
-	        out[2] = a[8];
-	        out[3] = a[12];
-	        out[4] = a01;
-	        out[6] = a[9];
-	        out[7] = a[13];
-	        out[8] = a02;
-	        out[9] = a12;
-	        out[11] = a[14];
-	        out[12] = a03;
-	        out[13] = a13;
-	        out[14] = a23;
-	    } else {
-	        out[0] = a[0];
-	        out[1] = a[4];
-	        out[2] = a[8];
-	        out[3] = a[12];
-	        out[4] = a[1];
-	        out[5] = a[5];
-	        out[6] = a[9];
-	        out[7] = a[13];
-	        out[8] = a[2];
-	        out[9] = a[6];
-	        out[10] = a[10];
-	        out[11] = a[14];
-	        out[12] = a[3];
-	        out[13] = a[7];
-	        out[14] = a[11];
-	        out[15] = a[15];
-	    }
-	    
-	    return out;
-	};
-
-/***/ }),
-/* 94 */
-/***/ (function(module, exports) {
-
-	module.exports = invert;
-
-	/**
-	 * Inverts a mat4
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the source matrix
-	 * @returns {mat4} out
-	 */
-	function invert(out, a) {
-	    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
-	        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
-	        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
-	        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
-
-	        b00 = a00 * a11 - a01 * a10,
-	        b01 = a00 * a12 - a02 * a10,
-	        b02 = a00 * a13 - a03 * a10,
-	        b03 = a01 * a12 - a02 * a11,
-	        b04 = a01 * a13 - a03 * a11,
-	        b05 = a02 * a13 - a03 * a12,
-	        b06 = a20 * a31 - a21 * a30,
-	        b07 = a20 * a32 - a22 * a30,
-	        b08 = a20 * a33 - a23 * a30,
-	        b09 = a21 * a32 - a22 * a31,
-	        b10 = a21 * a33 - a23 * a31,
-	        b11 = a22 * a33 - a23 * a32,
-
-	        // Calculate the determinant
-	        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
-
-	    if (!det) { 
-	        return null; 
-	    }
-	    det = 1.0 / det;
-
-	    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
-	    out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
-	    out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
-	    out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
-	    out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
-	    out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
-	    out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
-	    out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
-	    out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
-	    out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
-	    out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
-	    out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
-	    out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
-	    out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
-	    out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
-	    out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
-
-	    return out;
-	};
-
-/***/ }),
-/* 95 */
-/***/ (function(module, exports) {
-
-	module.exports = adjoint;
-
-	/**
-	 * Calculates the adjugate of a mat4
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the source matrix
-	 * @returns {mat4} out
-	 */
-	function adjoint(out, a) {
-	    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
-	        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
-	        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
-	        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
-
-	    out[0]  =  (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22));
-	    out[1]  = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22));
-	    out[2]  =  (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12));
-	    out[3]  = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12));
-	    out[4]  = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22));
-	    out[5]  =  (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22));
-	    out[6]  = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12));
-	    out[7]  =  (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12));
-	    out[8]  =  (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21));
-	    out[9]  = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21));
-	    out[10] =  (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11));
-	    out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11));
-	    out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21));
-	    out[13] =  (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21));
-	    out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11));
-	    out[15] =  (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11));
-	    return out;
-	};
-
-/***/ }),
-/* 96 */
-/***/ (function(module, exports) {
-
-	module.exports = determinant;
-
-	/**
-	 * Calculates the determinant of a mat4
-	 *
-	 * @param {mat4} a the source matrix
-	 * @returns {Number} determinant of a
-	 */
-	function determinant(a) {
-	    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
-	        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
-	        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
-	        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
-
-	        b00 = a00 * a11 - a01 * a10,
-	        b01 = a00 * a12 - a02 * a10,
-	        b02 = a00 * a13 - a03 * a10,
-	        b03 = a01 * a12 - a02 * a11,
-	        b04 = a01 * a13 - a03 * a11,
-	        b05 = a02 * a13 - a03 * a12,
-	        b06 = a20 * a31 - a21 * a30,
-	        b07 = a20 * a32 - a22 * a30,
-	        b08 = a20 * a33 - a23 * a30,
-	        b09 = a21 * a32 - a22 * a31,
-	        b10 = a21 * a33 - a23 * a31,
-	        b11 = a22 * a33 - a23 * a32;
-
-	    // Calculate the determinant
-	    return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
-	};
-
-/***/ }),
-/* 97 */
-/***/ (function(module, exports) {
-
-	module.exports = multiply;
-
-	/**
-	 * Multiplies two mat4's
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the first operand
-	 * @param {mat4} b the second operand
-	 * @returns {mat4} out
-	 */
-	function multiply(out, a, b) {
-	    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
-	        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
-	        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
-	        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
-
-	    // Cache only the current line of the second matrix
-	    var b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];  
-	    out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
-	    out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
-	    out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
-	    out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
-
-	    b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
-	    out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
-	    out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
-	    out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
-	    out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
-
-	    b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
-	    out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
-	    out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
-	    out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
-	    out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
-
-	    b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
-	    out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
-	    out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
-	    out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
-	    out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
-	    return out;
-	};
-
-/***/ }),
-/* 98 */
-/***/ (function(module, exports) {
-
-	module.exports = translate;
-
-	/**
-	 * Translate a mat4 by the given vector
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to translate
-	 * @param {vec3} v vector to translate by
-	 * @returns {mat4} out
-	 */
-	function translate(out, a, v) {
-	    var x = v[0], y = v[1], z = v[2],
-	        a00, a01, a02, a03,
-	        a10, a11, a12, a13,
-	        a20, a21, a22, a23;
-
-	    if (a === out) {
-	        out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
-	        out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
-	        out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
-	        out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
-	    } else {
-	        a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
-	        a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
-	        a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
-
-	        out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
-	        out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
-	        out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
-
-	        out[12] = a00 * x + a10 * y + a20 * z + a[12];
-	        out[13] = a01 * x + a11 * y + a21 * z + a[13];
-	        out[14] = a02 * x + a12 * y + a22 * z + a[14];
-	        out[15] = a03 * x + a13 * y + a23 * z + a[15];
-	    }
-
-	    return out;
-	};
-
-/***/ }),
-/* 99 */
-/***/ (function(module, exports) {
-
-	module.exports = scale;
-
-	/**
-	 * Scales the mat4 by the dimensions in the given vec3
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to scale
-	 * @param {vec3} v the vec3 to scale the matrix by
-	 * @returns {mat4} out
-	 **/
-	function scale(out, a, v) {
-	    var x = v[0], y = v[1], z = v[2];
-
-	    out[0] = a[0] * x;
-	    out[1] = a[1] * x;
-	    out[2] = a[2] * x;
-	    out[3] = a[3] * x;
-	    out[4] = a[4] * y;
-	    out[5] = a[5] * y;
-	    out[6] = a[6] * y;
-	    out[7] = a[7] * y;
-	    out[8] = a[8] * z;
-	    out[9] = a[9] * z;
-	    out[10] = a[10] * z;
-	    out[11] = a[11] * z;
-	    out[12] = a[12];
-	    out[13] = a[13];
-	    out[14] = a[14];
-	    out[15] = a[15];
-	    return out;
-	};
-
-/***/ }),
-/* 100 */
-/***/ (function(module, exports) {
-
-	module.exports = rotate;
-
-	/**
-	 * Rotates a mat4 by the given angle
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to rotate
-	 * @param {Number} rad the angle to rotate the matrix by
-	 * @param {vec3} axis the axis to rotate around
-	 * @returns {mat4} out
-	 */
-	function rotate(out, a, rad, axis) {
-	    var x = axis[0], y = axis[1], z = axis[2],
-	        len = Math.sqrt(x * x + y * y + z * z),
-	        s, c, t,
-	        a00, a01, a02, a03,
-	        a10, a11, a12, a13,
-	        a20, a21, a22, a23,
-	        b00, b01, b02,
-	        b10, b11, b12,
-	        b20, b21, b22;
-
-	    if (Math.abs(len) < 0.000001) { return null; }
-	    
-	    len = 1 / len;
-	    x *= len;
-	    y *= len;
-	    z *= len;
-
-	    s = Math.sin(rad);
-	    c = Math.cos(rad);
-	    t = 1 - c;
-
-	    a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
-	    a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
-	    a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
-
-	    // Construct the elements of the rotation matrix
-	    b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s;
-	    b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s;
-	    b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c;
-
-	    // Perform rotation-specific matrix multiplication
-	    out[0] = a00 * b00 + a10 * b01 + a20 * b02;
-	    out[1] = a01 * b00 + a11 * b01 + a21 * b02;
-	    out[2] = a02 * b00 + a12 * b01 + a22 * b02;
-	    out[3] = a03 * b00 + a13 * b01 + a23 * b02;
-	    out[4] = a00 * b10 + a10 * b11 + a20 * b12;
-	    out[5] = a01 * b10 + a11 * b11 + a21 * b12;
-	    out[6] = a02 * b10 + a12 * b11 + a22 * b12;
-	    out[7] = a03 * b10 + a13 * b11 + a23 * b12;
-	    out[8] = a00 * b20 + a10 * b21 + a20 * b22;
-	    out[9] = a01 * b20 + a11 * b21 + a21 * b22;
-	    out[10] = a02 * b20 + a12 * b21 + a22 * b22;
-	    out[11] = a03 * b20 + a13 * b21 + a23 * b22;
-
-	    if (a !== out) { // If the source and destination differ, copy the unchanged last row
-	        out[12] = a[12];
-	        out[13] = a[13];
-	        out[14] = a[14];
-	        out[15] = a[15];
-	    }
-	    return out;
-	};
-
-/***/ }),
-/* 101 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateX;
-
-	/**
-	 * Rotates a matrix by the given angle around the X axis
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to rotate
-	 * @param {Number} rad the angle to rotate the matrix by
-	 * @returns {mat4} out
-	 */
-	function rotateX(out, a, rad) {
-	    var s = Math.sin(rad),
-	        c = Math.cos(rad),
-	        a10 = a[4],
-	        a11 = a[5],
-	        a12 = a[6],
-	        a13 = a[7],
-	        a20 = a[8],
-	        a21 = a[9],
-	        a22 = a[10],
-	        a23 = a[11];
-
-	    if (a !== out) { // If the source and destination differ, copy the unchanged rows
-	        out[0]  = a[0];
-	        out[1]  = a[1];
-	        out[2]  = a[2];
-	        out[3]  = a[3];
-	        out[12] = a[12];
-	        out[13] = a[13];
-	        out[14] = a[14];
-	        out[15] = a[15];
-	    }
-
-	    // Perform axis-specific matrix multiplication
-	    out[4] = a10 * c + a20 * s;
-	    out[5] = a11 * c + a21 * s;
-	    out[6] = a12 * c + a22 * s;
-	    out[7] = a13 * c + a23 * s;
-	    out[8] = a20 * c - a10 * s;
-	    out[9] = a21 * c - a11 * s;
-	    out[10] = a22 * c - a12 * s;
-	    out[11] = a23 * c - a13 * s;
-	    return out;
-	};
-
-/***/ }),
-/* 102 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateY;
-
-	/**
-	 * Rotates a matrix by the given angle around the Y axis
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to rotate
-	 * @param {Number} rad the angle to rotate the matrix by
-	 * @returns {mat4} out
-	 */
-	function rotateY(out, a, rad) {
-	    var s = Math.sin(rad),
-	        c = Math.cos(rad),
-	        a00 = a[0],
-	        a01 = a[1],
-	        a02 = a[2],
-	        a03 = a[3],
-	        a20 = a[8],
-	        a21 = a[9],
-	        a22 = a[10],
-	        a23 = a[11];
-
-	    if (a !== out) { // If the source and destination differ, copy the unchanged rows
-	        out[4]  = a[4];
-	        out[5]  = a[5];
-	        out[6]  = a[6];
-	        out[7]  = a[7];
-	        out[12] = a[12];
-	        out[13] = a[13];
-	        out[14] = a[14];
-	        out[15] = a[15];
-	    }
-
-	    // Perform axis-specific matrix multiplication
-	    out[0] = a00 * c - a20 * s;
-	    out[1] = a01 * c - a21 * s;
-	    out[2] = a02 * c - a22 * s;
-	    out[3] = a03 * c - a23 * s;
-	    out[8] = a00 * s + a20 * c;
-	    out[9] = a01 * s + a21 * c;
-	    out[10] = a02 * s + a22 * c;
-	    out[11] = a03 * s + a23 * c;
-	    return out;
-	};
-
-/***/ }),
-/* 103 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateZ;
-
-	/**
-	 * Rotates a matrix by the given angle around the Z axis
-	 *
-	 * @param {mat4} out the receiving matrix
-	 * @param {mat4} a the matrix to rotate
-	 * @param {Number} rad the angle to rotate the matrix by
-	 * @returns {mat4} out
-	 */
-	function rotateZ(out, a, rad) {
-	    var s = Math.sin(rad),
-	        c = Math.cos(rad),
-	        a00 = a[0],
-	        a01 = a[1],
-	        a02 = a[2],
-	        a03 = a[3],
-	        a10 = a[4],
-	        a11 = a[5],
-	        a12 = a[6],
-	        a13 = a[7];
-
-	    if (a !== out) { // If the source and destination differ, copy the unchanged last row
-	        out[8]  = a[8];
-	        out[9]  = a[9];
-	        out[10] = a[10];
-	        out[11] = a[11];
-	        out[12] = a[12];
-	        out[13] = a[13];
-	        out[14] = a[14];
-	        out[15] = a[15];
-	    }
-
-	    // Perform axis-specific matrix multiplication
-	    out[0] = a00 * c + a10 * s;
-	    out[1] = a01 * c + a11 * s;
-	    out[2] = a02 * c + a12 * s;
-	    out[3] = a03 * c + a13 * s;
-	    out[4] = a10 * c - a00 * s;
-	    out[5] = a11 * c - a01 * s;
-	    out[6] = a12 * c - a02 * s;
-	    out[7] = a13 * c - a03 * s;
-	    return out;
-	};
-
-/***/ }),
-/* 104 */
-/***/ (function(module, exports) {
-
-	module.exports = fromRotationTranslation;
-
-	/**
-	 * Creates a matrix from a quaternion rotation and vector translation
-	 * This is equivalent to (but much faster than):
-	 *
-	 *     mat4.identity(dest);
-	 *     mat4.translate(dest, vec);
-	 *     var quatMat = mat4.create();
-	 *     quat4.toMat4(quat, quatMat);
-	 *     mat4.multiply(dest, quatMat);
-	 *
-	 * @param {mat4} out mat4 receiving operation result
-	 * @param {quat4} q Rotation quaternion
-	 * @param {vec3} v Translation vector
-	 * @returns {mat4} out
-	 */
-	function fromRotationTranslation(out, q, v) {
-	    // Quaternion math
-	    var x = q[0], y = q[1], z = q[2], w = q[3],
-	        x2 = x + x,
-	        y2 = y + y,
-	        z2 = z + z,
-
-	        xx = x * x2,
-	        xy = x * y2,
-	        xz = x * z2,
-	        yy = y * y2,
-	        yz = y * z2,
-	        zz = z * z2,
-	        wx = w * x2,
-	        wy = w * y2,
-	        wz = w * z2;
-
-	    out[0] = 1 - (yy + zz);
-	    out[1] = xy + wz;
-	    out[2] = xz - wy;
-	    out[3] = 0;
-	    out[4] = xy - wz;
-	    out[5] = 1 - (xx + zz);
-	    out[6] = yz + wx;
-	    out[7] = 0;
-	    out[8] = xz + wy;
-	    out[9] = yz - wx;
-	    out[10] = 1 - (xx + yy);
-	    out[11] = 0;
-	    out[12] = v[0];
-	    out[13] = v[1];
-	    out[14] = v[2];
-	    out[15] = 1;
-	    
-	    return out;
-	};
-
-/***/ }),
-/* 105 */
-/***/ (function(module, exports) {
-
-	module.exports = fromQuat;
-
-	/**
-	 * Creates a matrix from a quaternion rotation.
-	 *
-	 * @param {mat4} out mat4 receiving operation result
-	 * @param {quat4} q Rotation quaternion
-	 * @returns {mat4} out
-	 */
-	function fromQuat(out, q) {
-	    var x = q[0], y = q[1], z = q[2], w = q[3],
-	        x2 = x + x,
-	        y2 = y + y,
-	        z2 = z + z,
-
-	        xx = x * x2,
-	        yx = y * x2,
-	        yy = y * y2,
-	        zx = z * x2,
-	        zy = z * y2,
-	        zz = z * z2,
-	        wx = w * x2,
-	        wy = w * y2,
-	        wz = w * z2;
-
-	    out[0] = 1 - yy - zz;
-	    out[1] = yx + wz;
-	    out[2] = zx - wy;
-	    out[3] = 0;
-
-	    out[4] = yx - wz;
-	    out[5] = 1 - xx - zz;
-	    out[6] = zy + wx;
-	    out[7] = 0;
-
-	    out[8] = zx + wy;
-	    out[9] = zy - wx;
-	    out[10] = 1 - xx - yy;
-	    out[11] = 0;
-
-	    out[12] = 0;
-	    out[13] = 0;
-	    out[14] = 0;
-	    out[15] = 1;
-
-	    return out;
-	};
-
-/***/ }),
-/* 106 */
-/***/ (function(module, exports) {
-
-	module.exports = frustum;
-
-	/**
-	 * Generates a frustum matrix with the given bounds
-	 *
-	 * @param {mat4} out mat4 frustum matrix will be written into
-	 * @param {Number} left Left bound of the frustum
-	 * @param {Number} right Right bound of the frustum
-	 * @param {Number} bottom Bottom bound of the frustum
-	 * @param {Number} top Top bound of the frustum
-	 * @param {Number} near Near bound of the frustum
-	 * @param {Number} far Far bound of the frustum
-	 * @returns {mat4} out
-	 */
-	function frustum(out, left, right, bottom, top, near, far) {
-	    var rl = 1 / (right - left),
-	        tb = 1 / (top - bottom),
-	        nf = 1 / (near - far);
-	    out[0] = (near * 2) * rl;
-	    out[1] = 0;
-	    out[2] = 0;
-	    out[3] = 0;
-	    out[4] = 0;
-	    out[5] = (near * 2) * tb;
-	    out[6] = 0;
-	    out[7] = 0;
-	    out[8] = (right + left) * rl;
-	    out[9] = (top + bottom) * tb;
-	    out[10] = (far + near) * nf;
-	    out[11] = -1;
-	    out[12] = 0;
-	    out[13] = 0;
-	    out[14] = (far * near * 2) * nf;
-	    out[15] = 0;
-	    return out;
-	};
-
-/***/ }),
-/* 107 */
-/***/ (function(module, exports) {
-
-	module.exports = perspective;
-
-	/**
-	 * Generates a perspective projection matrix with the given bounds
-	 *
-	 * @param {mat4} out mat4 frustum matrix will be written into
-	 * @param {number} fovy Vertical field of view in radians
-	 * @param {number} aspect Aspect ratio. typically viewport width/height
-	 * @param {number} near Near bound of the frustum
-	 * @param {number} far Far bound of the frustum
-	 * @returns {mat4} out
-	 */
-	function perspective(out, fovy, aspect, near, far) {
-	    var f = 1.0 / Math.tan(fovy / 2),
-	        nf = 1 / (near - far);
-	    out[0] = f / aspect;
-	    out[1] = 0;
-	    out[2] = 0;
-	    out[3] = 0;
-	    out[4] = 0;
-	    out[5] = f;
-	    out[6] = 0;
-	    out[7] = 0;
-	    out[8] = 0;
-	    out[9] = 0;
-	    out[10] = (far + near) * nf;
-	    out[11] = -1;
-	    out[12] = 0;
-	    out[13] = 0;
-	    out[14] = (2 * far * near) * nf;
-	    out[15] = 0;
-	    return out;
-	};
-
-/***/ }),
-/* 108 */
-/***/ (function(module, exports) {
-
-	module.exports = perspectiveFromFieldOfView;
-
-	/**
-	 * Generates a perspective projection matrix with the given field of view.
-	 * This is primarily useful for generating projection matrices to be used
-	 * with the still experiemental WebVR API.
-	 *
-	 * @param {mat4} out mat4 frustum matrix will be written into
-	 * @param {number} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees
-	 * @param {number} near Near bound of the frustum
-	 * @param {number} far Far bound of the frustum
-	 * @returns {mat4} out
-	 */
-	function perspectiveFromFieldOfView(out, fov, near, far) {
-	    var upTan = Math.tan(fov.upDegrees * Math.PI/180.0),
-	        downTan = Math.tan(fov.downDegrees * Math.PI/180.0),
-	        leftTan = Math.tan(fov.leftDegrees * Math.PI/180.0),
-	        rightTan = Math.tan(fov.rightDegrees * Math.PI/180.0),
-	        xScale = 2.0 / (leftTan + rightTan),
-	        yScale = 2.0 / (upTan + downTan);
-
-	    out[0] = xScale;
-	    out[1] = 0.0;
-	    out[2] = 0.0;
-	    out[3] = 0.0;
-	    out[4] = 0.0;
-	    out[5] = yScale;
-	    out[6] = 0.0;
-	    out[7] = 0.0;
-	    out[8] = -((leftTan - rightTan) * xScale * 0.5);
-	    out[9] = ((upTan - downTan) * yScale * 0.5);
-	    out[10] = far / (near - far);
-	    out[11] = -1.0;
-	    out[12] = 0.0;
-	    out[13] = 0.0;
-	    out[14] = (far * near) / (near - far);
-	    out[15] = 0.0;
-	    return out;
-	}
-
-
-
-/***/ }),
-/* 109 */
-/***/ (function(module, exports) {
-
-	module.exports = ortho;
-
-	/**
-	 * Generates a orthogonal projection matrix with the given bounds
-	 *
-	 * @param {mat4} out mat4 frustum matrix will be written into
-	 * @param {number} left Left bound of the frustum
-	 * @param {number} right Right bound of the frustum
-	 * @param {number} bottom Bottom bound of the frustum
-	 * @param {number} top Top bound of the frustum
-	 * @param {number} near Near bound of the frustum
-	 * @param {number} far Far bound of the frustum
-	 * @returns {mat4} out
-	 */
-	function ortho(out, left, right, bottom, top, near, far) {
-	    var lr = 1 / (left - right),
-	        bt = 1 / (bottom - top),
-	        nf = 1 / (near - far);
-	    out[0] = -2 * lr;
-	    out[1] = 0;
-	    out[2] = 0;
-	    out[3] = 0;
-	    out[4] = 0;
-	    out[5] = -2 * bt;
-	    out[6] = 0;
-	    out[7] = 0;
-	    out[8] = 0;
-	    out[9] = 0;
-	    out[10] = 2 * nf;
-	    out[11] = 0;
-	    out[12] = (left + right) * lr;
-	    out[13] = (top + bottom) * bt;
-	    out[14] = (far + near) * nf;
-	    out[15] = 1;
-	    return out;
-	};
-
-/***/ }),
-/* 110 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var identity = __webpack_require__(92);
-
-	module.exports = lookAt;
-
-	/**
-	 * Generates a look-at matrix with the given eye position, focal point, and up axis
-	 *
-	 * @param {mat4} out mat4 frustum matrix will be written into
-	 * @param {vec3} eye Position of the viewer
-	 * @param {vec3} center Point the viewer is looking at
-	 * @param {vec3} up vec3 pointing up
-	 * @returns {mat4} out
-	 */
-	function lookAt(out, eye, center, up) {
-	    var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
-	        eyex = eye[0],
-	        eyey = eye[1],
-	        eyez = eye[2],
-	        upx = up[0],
-	        upy = up[1],
-	        upz = up[2],
-	        centerx = center[0],
-	        centery = center[1],
-	        centerz = center[2];
-
-	    if (Math.abs(eyex - centerx) < 0.000001 &&
-	        Math.abs(eyey - centery) < 0.000001 &&
-	        Math.abs(eyez - centerz) < 0.000001) {
-	        return identity(out);
-	    }
-
-	    z0 = eyex - centerx;
-	    z1 = eyey - centery;
-	    z2 = eyez - centerz;
-
-	    len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
-	    z0 *= len;
-	    z1 *= len;
-	    z2 *= len;
-
-	    x0 = upy * z2 - upz * z1;
-	    x1 = upz * z0 - upx * z2;
-	    x2 = upx * z1 - upy * z0;
-	    len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
-	    if (!len) {
-	        x0 = 0;
-	        x1 = 0;
-	        x2 = 0;
-	    } else {
-	        len = 1 / len;
-	        x0 *= len;
-	        x1 *= len;
-	        x2 *= len;
-	    }
-
-	    y0 = z1 * x2 - z2 * x1;
-	    y1 = z2 * x0 - z0 * x2;
-	    y2 = z0 * x1 - z1 * x0;
-
-	    len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
-	    if (!len) {
-	        y0 = 0;
-	        y1 = 0;
-	        y2 = 0;
-	    } else {
-	        len = 1 / len;
-	        y0 *= len;
-	        y1 *= len;
-	        y2 *= len;
-	    }
-
-	    out[0] = x0;
-	    out[1] = y0;
-	    out[2] = z0;
-	    out[3] = 0;
-	    out[4] = x1;
-	    out[5] = y1;
-	    out[6] = z1;
-	    out[7] = 0;
-	    out[8] = x2;
-	    out[9] = y2;
-	    out[10] = z2;
-	    out[11] = 0;
-	    out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
-	    out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
-	    out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
-	    out[15] = 1;
-
-	    return out;
-	};
-
-/***/ }),
-/* 111 */
-/***/ (function(module, exports) {
-
-	module.exports = str;
-
-	/**
-	 * Returns a string representation of a mat4
-	 *
-	 * @param {mat4} mat matrix to represent as a string
-	 * @returns {String} string representation of the matrix
-	 */
-	function str(a) {
-	    return 'mat4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ', ' +
-	                    a[4] + ', ' + a[5] + ', ' + a[6] + ', ' + a[7] + ', ' +
-	                    a[8] + ', ' + a[9] + ', ' + a[10] + ', ' + a[11] + ', ' + 
-	                    a[12] + ', ' + a[13] + ', ' + a[14] + ', ' + a[15] + ')';
-	};
-
-/***/ }),
-/* 112 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = {
-	  create: __webpack_require__(113),
-	  clone: __webpack_require__(114),
-	  fromValues: __webpack_require__(115),
-	  copy: __webpack_require__(116),
-	  set: __webpack_require__(117),
-	  add: __webpack_require__(118),
-	  subtract: __webpack_require__(119),
-	  multiply: __webpack_require__(120),
-	  divide: __webpack_require__(121),
-	  min: __webpack_require__(122),
-	  max: __webpack_require__(123),
-	  scale: __webpack_require__(124),
-	  scaleAndAdd: __webpack_require__(125),
-	  distance: __webpack_require__(126),
-	  squaredDistance: __webpack_require__(127),
-	  length: __webpack_require__(128),
-	  squaredLength: __webpack_require__(129),
-	  negate: __webpack_require__(130),
-	  inverse: __webpack_require__(131),
-	  normalize: __webpack_require__(132),
-	  dot: __webpack_require__(133),
-	  lerp: __webpack_require__(134),
-	  random: __webpack_require__(135),
-	  transformMat4: __webpack_require__(136),
-	  transformQuat: __webpack_require__(137)
-	}
-
-
-/***/ }),
-/* 113 */
-/***/ (function(module, exports) {
-
-	module.exports = create
-
-	/**
-	 * Creates a new, empty vec4
-	 *
-	 * @returns {vec4} a new 4D vector
-	 */
-	function create () {
-	  var out = new Float32Array(4)
-	  out[0] = 0
-	  out[1] = 0
-	  out[2] = 0
-	  out[3] = 0
-	  return out
-	}
-
-
-/***/ }),
-/* 114 */
-/***/ (function(module, exports) {
-
-	module.exports = clone
-
-	/**
-	 * Creates a new vec4 initialized with values from an existing vector
-	 *
-	 * @param {vec4} a vector to clone
-	 * @returns {vec4} a new 4D vector
-	 */
-	function clone (a) {
-	  var out = new Float32Array(4)
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = a[2]
-	  out[3] = a[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 115 */
-/***/ (function(module, exports) {
-
-	module.exports = fromValues
-
-	/**
-	 * Creates a new vec4 initialized with the given values
-	 *
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @param {Number} z Z component
-	 * @param {Number} w W component
-	 * @returns {vec4} a new 4D vector
-	 */
-	function fromValues (x, y, z, w) {
-	  var out = new Float32Array(4)
-	  out[0] = x
-	  out[1] = y
-	  out[2] = z
-	  out[3] = w
-	  return out
-	}
-
-
-/***/ }),
-/* 116 */
-/***/ (function(module, exports) {
-
-	module.exports = copy
-
-	/**
-	 * Copy the values from one vec4 to another
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the source vector
-	 * @returns {vec4} out
-	 */
-	function copy (out, a) {
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = a[2]
-	  out[3] = a[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 117 */
-/***/ (function(module, exports) {
-
-	module.exports = set
-
-	/**
-	 * Set the components of a vec4 to the given values
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @param {Number} z Z component
-	 * @param {Number} w W component
-	 * @returns {vec4} out
-	 */
-	function set (out, x, y, z, w) {
-	  out[0] = x
-	  out[1] = y
-	  out[2] = z
-	  out[3] = w
-	  return out
-	}
-
-
-/***/ }),
-/* 118 */
-/***/ (function(module, exports) {
-
-	module.exports = add
-
-	/**
-	 * Adds two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function add (out, a, b) {
-	  out[0] = a[0] + b[0]
-	  out[1] = a[1] + b[1]
-	  out[2] = a[2] + b[2]
-	  out[3] = a[3] + b[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 119 */
-/***/ (function(module, exports) {
-
-	module.exports = subtract
-
-	/**
-	 * Subtracts vector b from vector a
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function subtract (out, a, b) {
-	  out[0] = a[0] - b[0]
-	  out[1] = a[1] - b[1]
-	  out[2] = a[2] - b[2]
-	  out[3] = a[3] - b[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 120 */
-/***/ (function(module, exports) {
-
-	module.exports = multiply
-
-	/**
-	 * Multiplies two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function multiply (out, a, b) {
-	  out[0] = a[0] * b[0]
-	  out[1] = a[1] * b[1]
-	  out[2] = a[2] * b[2]
-	  out[3] = a[3] * b[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 121 */
-/***/ (function(module, exports) {
-
-	module.exports = divide
-
-	/**
-	 * Divides two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function divide (out, a, b) {
-	  out[0] = a[0] / b[0]
-	  out[1] = a[1] / b[1]
-	  out[2] = a[2] / b[2]
-	  out[3] = a[3] / b[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 122 */
-/***/ (function(module, exports) {
-
-	module.exports = min
-
-	/**
-	 * Returns the minimum of two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function min (out, a, b) {
-	  out[0] = Math.min(a[0], b[0])
-	  out[1] = Math.min(a[1], b[1])
-	  out[2] = Math.min(a[2], b[2])
-	  out[3] = Math.min(a[3], b[3])
-	  return out
-	}
-
-
-/***/ }),
-/* 123 */
-/***/ (function(module, exports) {
-
-	module.exports = max
-
-	/**
-	 * Returns the maximum of two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {vec4} out
-	 */
-	function max (out, a, b) {
-	  out[0] = Math.max(a[0], b[0])
-	  out[1] = Math.max(a[1], b[1])
-	  out[2] = Math.max(a[2], b[2])
-	  out[3] = Math.max(a[3], b[3])
-	  return out
-	}
-
-
-/***/ }),
-/* 124 */
-/***/ (function(module, exports) {
-
-	module.exports = scale
-
-	/**
-	 * Scales a vec4 by a scalar number
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the vector to scale
-	 * @param {Number} b amount to scale the vector by
-	 * @returns {vec4} out
-	 */
-	function scale (out, a, b) {
-	  out[0] = a[0] * b
-	  out[1] = a[1] * b
-	  out[2] = a[2] * b
-	  out[3] = a[3] * b
-	  return out
-	}
-
-
-/***/ }),
-/* 125 */
-/***/ (function(module, exports) {
-
-	module.exports = scaleAndAdd
-
-	/**
-	 * Adds two vec4's after scaling the second operand by a scalar value
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @param {Number} scale the amount to scale b by before adding
-	 * @returns {vec4} out
-	 */
-	function scaleAndAdd (out, a, b, scale) {
-	  out[0] = a[0] + (b[0] * scale)
-	  out[1] = a[1] + (b[1] * scale)
-	  out[2] = a[2] + (b[2] * scale)
-	  out[3] = a[3] + (b[3] * scale)
-	  return out
-	}
-
-
-/***/ }),
-/* 126 */
-/***/ (function(module, exports) {
-
-	module.exports = distance
-
-	/**
-	 * Calculates the euclidian distance between two vec4's
-	 *
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {Number} distance between a and b
-	 */
-	function distance (a, b) {
-	  var x = b[0] - a[0],
-	    y = b[1] - a[1],
-	    z = b[2] - a[2],
-	    w = b[3] - a[3]
-	  return Math.sqrt(x * x + y * y + z * z + w * w)
-	}
-
-
-/***/ }),
-/* 127 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredDistance
-
-	/**
-	 * Calculates the squared euclidian distance between two vec4's
-	 *
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {Number} squared distance between a and b
-	 */
-	function squaredDistance (a, b) {
-	  var x = b[0] - a[0],
-	    y = b[1] - a[1],
-	    z = b[2] - a[2],
-	    w = b[3] - a[3]
-	  return x * x + y * y + z * z + w * w
-	}
-
-
-/***/ }),
-/* 128 */
-/***/ (function(module, exports) {
-
-	module.exports = length
-
-	/**
-	 * Calculates the length of a vec4
-	 *
-	 * @param {vec4} a vector to calculate length of
-	 * @returns {Number} length of a
-	 */
-	function length (a) {
-	  var x = a[0],
-	    y = a[1],
-	    z = a[2],
-	    w = a[3]
-	  return Math.sqrt(x * x + y * y + z * z + w * w)
-	}
-
-
-/***/ }),
-/* 129 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredLength
-
-	/**
-	 * Calculates the squared length of a vec4
-	 *
-	 * @param {vec4} a vector to calculate squared length of
-	 * @returns {Number} squared length of a
-	 */
-	function squaredLength (a) {
-	  var x = a[0],
-	    y = a[1],
-	    z = a[2],
-	    w = a[3]
-	  return x * x + y * y + z * z + w * w
-	}
-
-
-/***/ }),
-/* 130 */
-/***/ (function(module, exports) {
-
-	module.exports = negate
-
-	/**
-	 * Negates the components of a vec4
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a vector to negate
-	 * @returns {vec4} out
-	 */
-	function negate (out, a) {
-	  out[0] = -a[0]
-	  out[1] = -a[1]
-	  out[2] = -a[2]
-	  out[3] = -a[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 131 */
-/***/ (function(module, exports) {
-
-	module.exports = inverse
-
-	/**
-	 * Returns the inverse of the components of a vec4
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a vector to invert
-	 * @returns {vec4} out
-	 */
-	function inverse (out, a) {
-	  out[0] = 1.0 / a[0]
-	  out[1] = 1.0 / a[1]
-	  out[2] = 1.0 / a[2]
-	  out[3] = 1.0 / a[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 132 */
-/***/ (function(module, exports) {
-
-	module.exports = normalize
-
-	/**
-	 * Normalize a vec4
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a vector to normalize
-	 * @returns {vec4} out
-	 */
-	function normalize (out, a) {
-	  var x = a[0],
-	    y = a[1],
-	    z = a[2],
-	    w = a[3]
-	  var len = x * x + y * y + z * z + w * w
-	  if (len > 0) {
-	    len = 1 / Math.sqrt(len)
-	    out[0] = x * len
-	    out[1] = y * len
-	    out[2] = z * len
-	    out[3] = w * len
-	  }
-	  return out
-	}
-
-
-/***/ }),
-/* 133 */
-/***/ (function(module, exports) {
-
-	module.exports = dot
-
-	/**
-	 * Calculates the dot product of two vec4's
-	 *
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @returns {Number} dot product of a and b
-	 */
-	function dot (a, b) {
-	  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
-	}
-
-
-/***/ }),
-/* 134 */
-/***/ (function(module, exports) {
-
-	module.exports = lerp
-
-	/**
-	 * Performs a linear interpolation between two vec4's
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the first operand
-	 * @param {vec4} b the second operand
-	 * @param {Number} t interpolation amount between the two inputs
-	 * @returns {vec4} out
-	 */
-	function lerp (out, a, b, t) {
-	  var ax = a[0],
-	    ay = a[1],
-	    az = a[2],
-	    aw = a[3]
-	  out[0] = ax + t * (b[0] - ax)
-	  out[1] = ay + t * (b[1] - ay)
-	  out[2] = az + t * (b[2] - az)
-	  out[3] = aw + t * (b[3] - aw)
-	  return out
-	}
-
-
-/***/ }),
-/* 135 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var vecNormalize = __webpack_require__(132)
-	var vecScale = __webpack_require__(124)
-
-	module.exports = random
-
-	/**
-	 * Generates a random vector with the given scale
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
-	 * @returns {vec4} out
-	 */
-	function random (out, scale) {
-	  scale = scale || 1.0
-
-	  // TODO: This is a pretty awful way of doing this. Find something better.
-	  out[0] = Math.random()
-	  out[1] = Math.random()
-	  out[2] = Math.random()
-	  out[3] = Math.random()
-	  vecNormalize(out, out)
-	  vecScale(out, out, scale)
-	  return out
-	}
-
-
-/***/ }),
-/* 136 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat4
-
-	/**
-	 * Transforms the vec4 with a mat4.
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the vector to transform
-	 * @param {mat4} m matrix to transform with
-	 * @returns {vec4} out
-	 */
-	function transformMat4 (out, a, m) {
-	  var x = a[0], y = a[1], z = a[2], w = a[3]
-	  out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w
-	  out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w
-	  out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w
-	  out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w
-	  return out
-	}
-
-
-/***/ }),
-/* 137 */
-/***/ (function(module, exports) {
-
-	module.exports = transformQuat
-
-	/**
-	 * Transforms the vec4 with a quat
-	 *
-	 * @param {vec4} out the receiving vector
-	 * @param {vec4} a the vector to transform
-	 * @param {quat} q quaternion to transform with
-	 * @returns {vec4} out
-	 */
-	function transformQuat (out, a, q) {
-	  var x = a[0], y = a[1], z = a[2],
-	    qx = q[0], qy = q[1], qz = q[2], qw = q[3],
-
-	    // calculate quat * vec
-	    ix = qw * x + qy * z - qz * y,
-	    iy = qw * y + qz * x - qx * z,
-	    iz = qw * z + qx * y - qy * x,
-	    iw = -qx * x - qy * y - qz * z
-
-	  // calculate result * inverse quat
-	  out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy
-	  out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz
-	  out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx
-	  out[3] = a[3]
-	  return out
-	}
-
-
-/***/ }),
-/* 138 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = {
-	  create: __webpack_require__(139)
-	  , clone: __webpack_require__(140)
-	  , angle: __webpack_require__(141)
-	  , fromValues: __webpack_require__(142)
-	  , copy: __webpack_require__(145)
-	  , set: __webpack_require__(146)
-	  , add: __webpack_require__(147)
-	  , subtract: __webpack_require__(148)
-	  , multiply: __webpack_require__(149)
-	  , divide: __webpack_require__(150)
-	  , min: __webpack_require__(151)
-	  , max: __webpack_require__(152)
-	  , scale: __webpack_require__(153)
-	  , scaleAndAdd: __webpack_require__(154)
-	  , distance: __webpack_require__(155)
-	  , squaredDistance: __webpack_require__(156)
-	  , length: __webpack_require__(157)
-	  , squaredLength: __webpack_require__(158)
-	  , negate: __webpack_require__(159)
-	  , inverse: __webpack_require__(160)
-	  , normalize: __webpack_require__(143)
-	  , dot: __webpack_require__(144)
-	  , cross: __webpack_require__(161)
-	  , lerp: __webpack_require__(162)
-	  , random: __webpack_require__(163)
-	  , transformMat4: __webpack_require__(164)
-	  , transformMat3: __webpack_require__(165)
-	  , transformQuat: __webpack_require__(166)
-	  , rotateX: __webpack_require__(167)
-	  , rotateY: __webpack_require__(168)
-	  , rotateZ: __webpack_require__(169)
-	  , forEach: __webpack_require__(170)
-	}
-
-/***/ }),
-/* 139 */
-/***/ (function(module, exports) {
-
-	module.exports = create;
-
-	/**
-	 * Creates a new, empty vec3
-	 *
-	 * @returns {vec3} a new 3D vector
-	 */
-	function create() {
-	    var out = new Float32Array(3)
-	    out[0] = 0
-	    out[1] = 0
-	    out[2] = 0
-	    return out
-	}
-
-/***/ }),
-/* 140 */
-/***/ (function(module, exports) {
-
-	module.exports = clone;
-
-	/**
-	 * Creates a new vec3 initialized with values from an existing vector
-	 *
-	 * @param {vec3} a vector to clone
-	 * @returns {vec3} a new 3D vector
-	 */
-	function clone(a) {
-	    var out = new Float32Array(3)
-	    out[0] = a[0]
-	    out[1] = a[1]
-	    out[2] = a[2]
-	    return out
-	}
-
-/***/ }),
-/* 141 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = angle
-
-	var fromValues = __webpack_require__(142)
-	var normalize = __webpack_require__(143)
-	var dot = __webpack_require__(144)
-
-	/**
-	 * Get the angle between two 3D vectors
-	 * @param {vec3} a The first operand
-	 * @param {vec3} b The second operand
-	 * @returns {Number} The angle in radians
-	 */
-	function angle(a, b) {
-	    var tempA = fromValues(a[0], a[1], a[2])
-	    var tempB = fromValues(b[0], b[1], b[2])
-	 
-	    normalize(tempA, tempA)
-	    normalize(tempB, tempB)
-	 
-	    var cosine = dot(tempA, tempB)
-
-	    if(cosine > 1.0){
-	        return 0
-	    } else {
-	        return Math.acos(cosine)
-	    }     
-	}
-
-
-/***/ }),
-/* 142 */
-/***/ (function(module, exports) {
-
-	module.exports = fromValues;
-
-	/**
-	 * Creates a new vec3 initialized with the given values
-	 *
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @param {Number} z Z component
-	 * @returns {vec3} a new 3D vector
-	 */
-	function fromValues(x, y, z) {
-	    var out = new Float32Array(3)
-	    out[0] = x
-	    out[1] = y
-	    out[2] = z
-	    return out
-	}
-
-/***/ }),
-/* 143 */
-/***/ (function(module, exports) {
-
-	module.exports = normalize;
-
-	/**
-	 * Normalize a vec3
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a vector to normalize
-	 * @returns {vec3} out
-	 */
-	function normalize(out, a) {
-	    var x = a[0],
-	        y = a[1],
-	        z = a[2]
-	    var len = x*x + y*y + z*z
-	    if (len > 0) {
-	        //TODO: evaluate use of glm_invsqrt here?
-	        len = 1 / Math.sqrt(len)
-	        out[0] = a[0] * len
-	        out[1] = a[1] * len
-	        out[2] = a[2] * len
-	    }
-	    return out
-	}
-
-/***/ }),
-/* 144 */
-/***/ (function(module, exports) {
-
-	module.exports = dot;
-
-	/**
-	 * Calculates the dot product of two vec3's
-	 *
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {Number} dot product of a and b
-	 */
-	function dot(a, b) {
-	    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
-	}
-
-/***/ }),
-/* 145 */
-/***/ (function(module, exports) {
-
-	module.exports = copy;
-
-	/**
-	 * Copy the values from one vec3 to another
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the source vector
-	 * @returns {vec3} out
-	 */
-	function copy(out, a) {
-	    out[0] = a[0]
-	    out[1] = a[1]
-	    out[2] = a[2]
-	    return out
-	}
-
-/***/ }),
-/* 146 */
-/***/ (function(module, exports) {
-
-	module.exports = set;
-
-	/**
-	 * Set the components of a vec3 to the given values
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @param {Number} z Z component
-	 * @returns {vec3} out
-	 */
-	function set(out, x, y, z) {
-	    out[0] = x
-	    out[1] = y
-	    out[2] = z
-	    return out
-	}
-
-/***/ }),
-/* 147 */
-/***/ (function(module, exports) {
-
-	module.exports = add;
-
-	/**
-	 * Adds two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function add(out, a, b) {
-	    out[0] = a[0] + b[0]
-	    out[1] = a[1] + b[1]
-	    out[2] = a[2] + b[2]
-	    return out
-	}
-
-/***/ }),
-/* 148 */
-/***/ (function(module, exports) {
-
-	module.exports = subtract;
-
-	/**
-	 * Subtracts vector b from vector a
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function subtract(out, a, b) {
-	    out[0] = a[0] - b[0]
-	    out[1] = a[1] - b[1]
-	    out[2] = a[2] - b[2]
-	    return out
-	}
-
-/***/ }),
-/* 149 */
-/***/ (function(module, exports) {
-
-	module.exports = multiply;
-
-	/**
-	 * Multiplies two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function multiply(out, a, b) {
-	    out[0] = a[0] * b[0]
-	    out[1] = a[1] * b[1]
-	    out[2] = a[2] * b[2]
-	    return out
-	}
-
-/***/ }),
-/* 150 */
-/***/ (function(module, exports) {
-
-	module.exports = divide;
-
-	/**
-	 * Divides two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function divide(out, a, b) {
-	    out[0] = a[0] / b[0]
-	    out[1] = a[1] / b[1]
-	    out[2] = a[2] / b[2]
-	    return out
-	}
-
-/***/ }),
-/* 151 */
-/***/ (function(module, exports) {
-
-	module.exports = min;
-
-	/**
-	 * Returns the minimum of two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function min(out, a, b) {
-	    out[0] = Math.min(a[0], b[0])
-	    out[1] = Math.min(a[1], b[1])
-	    out[2] = Math.min(a[2], b[2])
-	    return out
-	}
-
-/***/ }),
-/* 152 */
-/***/ (function(module, exports) {
-
-	module.exports = max;
-
-	/**
-	 * Returns the maximum of two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function max(out, a, b) {
-	    out[0] = Math.max(a[0], b[0])
-	    out[1] = Math.max(a[1], b[1])
-	    out[2] = Math.max(a[2], b[2])
-	    return out
-	}
-
-/***/ }),
-/* 153 */
-/***/ (function(module, exports) {
-
-	module.exports = scale;
-
-	/**
-	 * Scales a vec3 by a scalar number
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the vector to scale
-	 * @param {Number} b amount to scale the vector by
-	 * @returns {vec3} out
-	 */
-	function scale(out, a, b) {
-	    out[0] = a[0] * b
-	    out[1] = a[1] * b
-	    out[2] = a[2] * b
-	    return out
-	}
-
-/***/ }),
-/* 154 */
-/***/ (function(module, exports) {
-
-	module.exports = scaleAndAdd;
-
-	/**
-	 * Adds two vec3's after scaling the second operand by a scalar value
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @param {Number} scale the amount to scale b by before adding
-	 * @returns {vec3} out
-	 */
-	function scaleAndAdd(out, a, b, scale) {
-	    out[0] = a[0] + (b[0] * scale)
-	    out[1] = a[1] + (b[1] * scale)
-	    out[2] = a[2] + (b[2] * scale)
-	    return out
-	}
-
-/***/ }),
-/* 155 */
-/***/ (function(module, exports) {
-
-	module.exports = distance;
-
-	/**
-	 * Calculates the euclidian distance between two vec3's
-	 *
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {Number} distance between a and b
-	 */
-	function distance(a, b) {
-	    var x = b[0] - a[0],
-	        y = b[1] - a[1],
-	        z = b[2] - a[2]
-	    return Math.sqrt(x*x + y*y + z*z)
-	}
-
-/***/ }),
-/* 156 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredDistance;
-
-	/**
-	 * Calculates the squared euclidian distance between two vec3's
-	 *
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {Number} squared distance between a and b
-	 */
-	function squaredDistance(a, b) {
-	    var x = b[0] - a[0],
-	        y = b[1] - a[1],
-	        z = b[2] - a[2]
-	    return x*x + y*y + z*z
-	}
-
-/***/ }),
-/* 157 */
-/***/ (function(module, exports) {
-
-	module.exports = length;
-
-	/**
-	 * Calculates the length of a vec3
-	 *
-	 * @param {vec3} a vector to calculate length of
-	 * @returns {Number} length of a
-	 */
-	function length(a) {
-	    var x = a[0],
-	        y = a[1],
-	        z = a[2]
-	    return Math.sqrt(x*x + y*y + z*z)
-	}
-
-/***/ }),
-/* 158 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredLength;
-
-	/**
-	 * Calculates the squared length of a vec3
-	 *
-	 * @param {vec3} a vector to calculate squared length of
-	 * @returns {Number} squared length of a
-	 */
-	function squaredLength(a) {
-	    var x = a[0],
-	        y = a[1],
-	        z = a[2]
-	    return x*x + y*y + z*z
-	}
-
-/***/ }),
-/* 159 */
-/***/ (function(module, exports) {
-
-	module.exports = negate;
-
-	/**
-	 * Negates the components of a vec3
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a vector to negate
-	 * @returns {vec3} out
-	 */
-	function negate(out, a) {
-	    out[0] = -a[0]
-	    out[1] = -a[1]
-	    out[2] = -a[2]
-	    return out
-	}
-
-/***/ }),
-/* 160 */
-/***/ (function(module, exports) {
-
-	module.exports = inverse;
-
-	/**
-	 * Returns the inverse of the components of a vec3
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a vector to invert
-	 * @returns {vec3} out
-	 */
-	function inverse(out, a) {
-	  out[0] = 1.0 / a[0]
-	  out[1] = 1.0 / a[1]
-	  out[2] = 1.0 / a[2]
-	  return out
-	}
-
-/***/ }),
-/* 161 */
-/***/ (function(module, exports) {
-
-	module.exports = cross;
-
-	/**
-	 * Computes the cross product of two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @returns {vec3} out
-	 */
-	function cross(out, a, b) {
-	    var ax = a[0], ay = a[1], az = a[2],
-	        bx = b[0], by = b[1], bz = b[2]
-
-	    out[0] = ay * bz - az * by
-	    out[1] = az * bx - ax * bz
-	    out[2] = ax * by - ay * bx
-	    return out
-	}
-
-/***/ }),
-/* 162 */
-/***/ (function(module, exports) {
-
-	module.exports = lerp;
-
-	/**
-	 * Performs a linear interpolation between two vec3's
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the first operand
-	 * @param {vec3} b the second operand
-	 * @param {Number} t interpolation amount between the two inputs
-	 * @returns {vec3} out
-	 */
-	function lerp(out, a, b, t) {
-	    var ax = a[0],
-	        ay = a[1],
-	        az = a[2]
-	    out[0] = ax + t * (b[0] - ax)
-	    out[1] = ay + t * (b[1] - ay)
-	    out[2] = az + t * (b[2] - az)
-	    return out
-	}
-
-/***/ }),
-/* 163 */
-/***/ (function(module, exports) {
-
-	module.exports = random;
-
-	/**
-	 * Generates a random vector with the given scale
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
-	 * @returns {vec3} out
-	 */
-	function random(out, scale) {
-	    scale = scale || 1.0
-
-	    var r = Math.random() * 2.0 * Math.PI
-	    var z = (Math.random() * 2.0) - 1.0
-	    var zScale = Math.sqrt(1.0-z*z) * scale
-
-	    out[0] = Math.cos(r) * zScale
-	    out[1] = Math.sin(r) * zScale
-	    out[2] = z * scale
-	    return out
-	}
-
-/***/ }),
-/* 164 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat4;
-
-	/**
-	 * Transforms the vec3 with a mat4.
-	 * 4th vector component is implicitly '1'
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the vector to transform
-	 * @param {mat4} m matrix to transform with
-	 * @returns {vec3} out
-	 */
-	function transformMat4(out, a, m) {
-	    var x = a[0], y = a[1], z = a[2],
-	        w = m[3] * x + m[7] * y + m[11] * z + m[15]
-	    w = w || 1.0
-	    out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w
-	    out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w
-	    out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w
-	    return out
-	}
-
-/***/ }),
-/* 165 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat3;
-
-	/**
-	 * Transforms the vec3 with a mat3.
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the vector to transform
-	 * @param {mat4} m the 3x3 matrix to transform with
-	 * @returns {vec3} out
-	 */
-	function transformMat3(out, a, m) {
-	    var x = a[0], y = a[1], z = a[2]
-	    out[0] = x * m[0] + y * m[3] + z * m[6]
-	    out[1] = x * m[1] + y * m[4] + z * m[7]
-	    out[2] = x * m[2] + y * m[5] + z * m[8]
-	    return out
-	}
-
-/***/ }),
-/* 166 */
-/***/ (function(module, exports) {
-
-	module.exports = transformQuat;
-
-	/**
-	 * Transforms the vec3 with a quat
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec3} a the vector to transform
-	 * @param {quat} q quaternion to transform with
-	 * @returns {vec3} out
-	 */
-	function transformQuat(out, a, q) {
-	    // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations
-
-	    var x = a[0], y = a[1], z = a[2],
-	        qx = q[0], qy = q[1], qz = q[2], qw = q[3],
-
-	        // calculate quat * vec
-	        ix = qw * x + qy * z - qz * y,
-	        iy = qw * y + qz * x - qx * z,
-	        iz = qw * z + qx * y - qy * x,
-	        iw = -qx * x - qy * y - qz * z
-
-	    // calculate result * inverse quat
-	    out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy
-	    out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz
-	    out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx
-	    return out
-	}
-
-/***/ }),
-/* 167 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateX;
-
-	/**
-	 * Rotate a 3D vector around the x-axis
-	 * @param {vec3} out The receiving vec3
-	 * @param {vec3} a The vec3 point to rotate
-	 * @param {vec3} b The origin of the rotation
-	 * @param {Number} c The angle of rotation
-	 * @returns {vec3} out
-	 */
-	function rotateX(out, a, b, c){
-	    var p = [], r=[]
-	    //Translate point to the origin
-	    p[0] = a[0] - b[0]
-	    p[1] = a[1] - b[1]
-	    p[2] = a[2] - b[2]
-
-	    //perform rotation
-	    r[0] = p[0]
-	    r[1] = p[1]*Math.cos(c) - p[2]*Math.sin(c)
-	    r[2] = p[1]*Math.sin(c) + p[2]*Math.cos(c)
-
-	    //translate to correct position
-	    out[0] = r[0] + b[0]
-	    out[1] = r[1] + b[1]
-	    out[2] = r[2] + b[2]
-
-	    return out
-	}
-
-/***/ }),
-/* 168 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateY;
-
-	/**
-	 * Rotate a 3D vector around the y-axis
-	 * @param {vec3} out The receiving vec3
-	 * @param {vec3} a The vec3 point to rotate
-	 * @param {vec3} b The origin of the rotation
-	 * @param {Number} c The angle of rotation
-	 * @returns {vec3} out
-	 */
-	function rotateY(out, a, b, c){
-	    var p = [], r=[]
-	    //Translate point to the origin
-	    p[0] = a[0] - b[0]
-	    p[1] = a[1] - b[1]
-	    p[2] = a[2] - b[2]
-	  
-	    //perform rotation
-	    r[0] = p[2]*Math.sin(c) + p[0]*Math.cos(c)
-	    r[1] = p[1]
-	    r[2] = p[2]*Math.cos(c) - p[0]*Math.sin(c)
-	  
-	    //translate to correct position
-	    out[0] = r[0] + b[0]
-	    out[1] = r[1] + b[1]
-	    out[2] = r[2] + b[2]
-	  
-	    return out
-	}
-
-/***/ }),
-/* 169 */
-/***/ (function(module, exports) {
-
-	module.exports = rotateZ;
-
-	/**
-	 * Rotate a 3D vector around the z-axis
-	 * @param {vec3} out The receiving vec3
-	 * @param {vec3} a The vec3 point to rotate
-	 * @param {vec3} b The origin of the rotation
-	 * @param {Number} c The angle of rotation
-	 * @returns {vec3} out
-	 */
-	function rotateZ(out, a, b, c){
-	    var p = [], r=[]
-	    //Translate point to the origin
-	    p[0] = a[0] - b[0]
-	    p[1] = a[1] - b[1]
-	    p[2] = a[2] - b[2]
-	  
-	    //perform rotation
-	    r[0] = p[0]*Math.cos(c) - p[1]*Math.sin(c)
-	    r[1] = p[0]*Math.sin(c) + p[1]*Math.cos(c)
-	    r[2] = p[2]
-	  
-	    //translate to correct position
-	    out[0] = r[0] + b[0]
-	    out[1] = r[1] + b[1]
-	    out[2] = r[2] + b[2]
-	  
-	    return out
-	}
-
-/***/ }),
-/* 170 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = forEach;
-
-	var vec = __webpack_require__(139)()
-
-	/**
-	 * Perform some operation over an array of vec3s.
-	 *
-	 * @param {Array} a the array of vectors to iterate over
-	 * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
-	 * @param {Number} offset Number of elements to skip at the beginning of the array
-	 * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
-	 * @param {Function} fn Function to call for each vector in the array
-	 * @param {Object} [arg] additional argument to pass to fn
-	 * @returns {Array} a
-	 * @function
-	 */
-	function forEach(a, stride, offset, count, fn, arg) {
-	        var i, l
-	        if(!stride) {
-	            stride = 3
-	        }
-
-	        if(!offset) {
-	            offset = 0
-	        }
-	        
-	        if(count) {
-	            l = Math.min((count * stride) + offset, a.length)
-	        } else {
-	            l = a.length
-	        }
-
-	        for(i = offset; i < l; i += stride) {
-	            vec[0] = a[i] 
-	            vec[1] = a[i+1] 
-	            vec[2] = a[i+2]
-	            fn(vec, vec, arg)
-	            a[i] = vec[0] 
-	            a[i+1] = vec[1] 
-	            a[i+2] = vec[2]
-	        }
-	        
-	        return a
-	}
-
-/***/ }),
-/* 171 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = {
-	  create: __webpack_require__(172)
-	  , clone: __webpack_require__(173)
-	  , fromValues: __webpack_require__(174)
-	  , copy: __webpack_require__(175)
-	  , set: __webpack_require__(176)
-	  , add: __webpack_require__(177)
-	  , subtract: __webpack_require__(178)
-	  , multiply: __webpack_require__(179)
-	  , divide: __webpack_require__(180)
-	  , min: __webpack_require__(181)
-	  , max: __webpack_require__(182)
-	  , scale: __webpack_require__(183)
-	  , scaleAndAdd: __webpack_require__(184)
-	  , distance: __webpack_require__(185)
-	  , squaredDistance: __webpack_require__(186)
-	  , length: __webpack_require__(187)
-	  , squaredLength: __webpack_require__(188)
-	  , negate: __webpack_require__(189)
-	  , normalize: __webpack_require__(190)
-	  , dot: __webpack_require__(191)
-	  , cross: __webpack_require__(192)
-	  , lerp: __webpack_require__(193)
-	  , random: __webpack_require__(194)
-	  , transformMat2: __webpack_require__(195)
-	  , transformMat2d: __webpack_require__(196)
-	  , transformMat3: __webpack_require__(197)
-	  , transformMat4: __webpack_require__(198)
-	  , forEach: __webpack_require__(199)
-	}
-
-/***/ }),
-/* 172 */
-/***/ (function(module, exports) {
-
-	module.exports = create
-
-	/**
-	 * Creates a new, empty vec2
-	 *
-	 * @returns {vec2} a new 2D vector
-	 */
-	function create() {
-	    var out = new Float32Array(2)
-	    out[0] = 0
-	    out[1] = 0
-	    return out
-	}
-
-/***/ }),
-/* 173 */
-/***/ (function(module, exports) {
-
-	module.exports = clone
-
-	/**
-	 * Creates a new vec2 initialized with values from an existing vector
-	 *
-	 * @param {vec2} a vector to clone
-	 * @returns {vec2} a new 2D vector
-	 */
-	function clone(a) {
-	    var out = new Float32Array(2)
-	    out[0] = a[0]
-	    out[1] = a[1]
-	    return out
-	}
-
-/***/ }),
-/* 174 */
-/***/ (function(module, exports) {
-
-	module.exports = fromValues
-
-	/**
-	 * Creates a new vec2 initialized with the given values
-	 *
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @returns {vec2} a new 2D vector
-	 */
-	function fromValues(x, y) {
-	    var out = new Float32Array(2)
-	    out[0] = x
-	    out[1] = y
-	    return out
-	}
-
-/***/ }),
-/* 175 */
-/***/ (function(module, exports) {
-
-	module.exports = copy
-
-	/**
-	 * Copy the values from one vec2 to another
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the source vector
-	 * @returns {vec2} out
-	 */
-	function copy(out, a) {
-	    out[0] = a[0]
-	    out[1] = a[1]
-	    return out
-	}
-
-/***/ }),
-/* 176 */
-/***/ (function(module, exports) {
-
-	module.exports = set
-
-	/**
-	 * Set the components of a vec2 to the given values
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {Number} x X component
-	 * @param {Number} y Y component
-	 * @returns {vec2} out
-	 */
-	function set(out, x, y) {
-	    out[0] = x
-	    out[1] = y
-	    return out
-	}
-
-/***/ }),
-/* 177 */
-/***/ (function(module, exports) {
-
-	module.exports = add
-
-	/**
-	 * Adds two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function add(out, a, b) {
-	    out[0] = a[0] + b[0]
-	    out[1] = a[1] + b[1]
-	    return out
-	}
-
-/***/ }),
-/* 178 */
-/***/ (function(module, exports) {
-
-	module.exports = subtract
-
-	/**
-	 * Subtracts vector b from vector a
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function subtract(out, a, b) {
-	    out[0] = a[0] - b[0]
-	    out[1] = a[1] - b[1]
-	    return out
-	}
-
-/***/ }),
-/* 179 */
-/***/ (function(module, exports) {
-
-	module.exports = multiply
-
-	/**
-	 * Multiplies two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function multiply(out, a, b) {
-	    out[0] = a[0] * b[0]
-	    out[1] = a[1] * b[1]
-	    return out
-	}
-
-/***/ }),
-/* 180 */
-/***/ (function(module, exports) {
-
-	module.exports = divide
-
-	/**
-	 * Divides two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function divide(out, a, b) {
-	    out[0] = a[0] / b[0]
-	    out[1] = a[1] / b[1]
-	    return out
-	}
-
-/***/ }),
-/* 181 */
-/***/ (function(module, exports) {
-
-	module.exports = min
-
-	/**
-	 * Returns the minimum of two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function min(out, a, b) {
-	    out[0] = Math.min(a[0], b[0])
-	    out[1] = Math.min(a[1], b[1])
-	    return out
-	}
-
-/***/ }),
-/* 182 */
-/***/ (function(module, exports) {
-
-	module.exports = max
-
-	/**
-	 * Returns the maximum of two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec2} out
-	 */
-	function max(out, a, b) {
-	    out[0] = Math.max(a[0], b[0])
-	    out[1] = Math.max(a[1], b[1])
-	    return out
-	}
-
-/***/ }),
-/* 183 */
-/***/ (function(module, exports) {
-
-	module.exports = scale
-
-	/**
-	 * Scales a vec2 by a scalar number
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the vector to scale
-	 * @param {Number} b amount to scale the vector by
-	 * @returns {vec2} out
-	 */
-	function scale(out, a, b) {
-	    out[0] = a[0] * b
-	    out[1] = a[1] * b
-	    return out
-	}
-
-/***/ }),
-/* 184 */
-/***/ (function(module, exports) {
-
-	module.exports = scaleAndAdd
-
-	/**
-	 * Adds two vec2's after scaling the second operand by a scalar value
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @param {Number} scale the amount to scale b by before adding
-	 * @returns {vec2} out
-	 */
-	function scaleAndAdd(out, a, b, scale) {
-	    out[0] = a[0] + (b[0] * scale)
-	    out[1] = a[1] + (b[1] * scale)
-	    return out
-	}
-
-/***/ }),
-/* 185 */
-/***/ (function(module, exports) {
-
-	module.exports = distance
-
-	/**
-	 * Calculates the euclidian distance between two vec2's
-	 *
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {Number} distance between a and b
-	 */
-	function distance(a, b) {
-	    var x = b[0] - a[0],
-	        y = b[1] - a[1]
-	    return Math.sqrt(x*x + y*y)
-	}
-
-/***/ }),
-/* 186 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredDistance
-
-	/**
-	 * Calculates the squared euclidian distance between two vec2's
-	 *
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {Number} squared distance between a and b
-	 */
-	function squaredDistance(a, b) {
-	    var x = b[0] - a[0],
-	        y = b[1] - a[1]
-	    return x*x + y*y
-	}
-
-/***/ }),
-/* 187 */
-/***/ (function(module, exports) {
-
-	module.exports = length
-
-	/**
-	 * Calculates the length of a vec2
-	 *
-	 * @param {vec2} a vector to calculate length of
-	 * @returns {Number} length of a
-	 */
-	function length(a) {
-	    var x = a[0],
-	        y = a[1]
-	    return Math.sqrt(x*x + y*y)
-	}
-
-/***/ }),
-/* 188 */
-/***/ (function(module, exports) {
-
-	module.exports = squaredLength
-
-	/**
-	 * Calculates the squared length of a vec2
-	 *
-	 * @param {vec2} a vector to calculate squared length of
-	 * @returns {Number} squared length of a
-	 */
-	function squaredLength(a) {
-	    var x = a[0],
-	        y = a[1]
-	    return x*x + y*y
-	}
-
-/***/ }),
-/* 189 */
-/***/ (function(module, exports) {
-
-	module.exports = negate
-
-	/**
-	 * Negates the components of a vec2
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a vector to negate
-	 * @returns {vec2} out
-	 */
-	function negate(out, a) {
-	    out[0] = -a[0]
-	    out[1] = -a[1]
-	    return out
-	}
-
-/***/ }),
-/* 190 */
-/***/ (function(module, exports) {
-
-	module.exports = normalize
-
-	/**
-	 * Normalize a vec2
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a vector to normalize
-	 * @returns {vec2} out
-	 */
-	function normalize(out, a) {
-	    var x = a[0],
-	        y = a[1]
-	    var len = x*x + y*y
-	    if (len > 0) {
-	        //TODO: evaluate use of glm_invsqrt here?
-	        len = 1 / Math.sqrt(len)
-	        out[0] = a[0] * len
-	        out[1] = a[1] * len
-	    }
-	    return out
-	}
-
-/***/ }),
-/* 191 */
-/***/ (function(module, exports) {
-
-	module.exports = dot
-
-	/**
-	 * Calculates the dot product of two vec2's
-	 *
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {Number} dot product of a and b
-	 */
-	function dot(a, b) {
-	    return a[0] * b[0] + a[1] * b[1]
-	}
-
-/***/ }),
-/* 192 */
-/***/ (function(module, exports) {
-
-	module.exports = cross
-
-	/**
-	 * Computes the cross product of two vec2's
-	 * Note that the cross product must by definition produce a 3D vector
-	 *
-	 * @param {vec3} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @returns {vec3} out
-	 */
-	function cross(out, a, b) {
-	    var z = a[0] * b[1] - a[1] * b[0]
-	    out[0] = out[1] = 0
-	    out[2] = z
-	    return out
-	}
-
-/***/ }),
-/* 193 */
-/***/ (function(module, exports) {
-
-	module.exports = lerp
-
-	/**
-	 * Performs a linear interpolation between two vec2's
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the first operand
-	 * @param {vec2} b the second operand
-	 * @param {Number} t interpolation amount between the two inputs
-	 * @returns {vec2} out
-	 */
-	function lerp(out, a, b, t) {
-	    var ax = a[0],
-	        ay = a[1]
-	    out[0] = ax + t * (b[0] - ax)
-	    out[1] = ay + t * (b[1] - ay)
-	    return out
-	}
-
-/***/ }),
-/* 194 */
-/***/ (function(module, exports) {
-
-	module.exports = random
-
-	/**
-	 * Generates a random vector with the given scale
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
-	 * @returns {vec2} out
-	 */
-	function random(out, scale) {
-	    scale = scale || 1.0
-	    var r = Math.random() * 2.0 * Math.PI
-	    out[0] = Math.cos(r) * scale
-	    out[1] = Math.sin(r) * scale
-	    return out
-	}
-
-/***/ }),
-/* 195 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat2
-
-	/**
-	 * Transforms the vec2 with a mat2
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the vector to transform
-	 * @param {mat2} m matrix to transform with
-	 * @returns {vec2} out
-	 */
-	function transformMat2(out, a, m) {
-	    var x = a[0],
-	        y = a[1]
-	    out[0] = m[0] * x + m[2] * y
-	    out[1] = m[1] * x + m[3] * y
-	    return out
-	}
-
-/***/ }),
-/* 196 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat2d
-
-	/**
-	 * Transforms the vec2 with a mat2d
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the vector to transform
-	 * @param {mat2d} m matrix to transform with
-	 * @returns {vec2} out
-	 */
-	function transformMat2d(out, a, m) {
-	    var x = a[0],
-	        y = a[1]
-	    out[0] = m[0] * x + m[2] * y + m[4]
-	    out[1] = m[1] * x + m[3] * y + m[5]
-	    return out
-	}
-
-/***/ }),
-/* 197 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat3
-
-	/**
-	 * Transforms the vec2 with a mat3
-	 * 3rd vector component is implicitly '1'
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the vector to transform
-	 * @param {mat3} m matrix to transform with
-	 * @returns {vec2} out
-	 */
-	function transformMat3(out, a, m) {
-	    var x = a[0],
-	        y = a[1]
-	    out[0] = m[0] * x + m[3] * y + m[6]
-	    out[1] = m[1] * x + m[4] * y + m[7]
-	    return out
-	}
-
-/***/ }),
-/* 198 */
-/***/ (function(module, exports) {
-
-	module.exports = transformMat4
-
-	/**
-	 * Transforms the vec2 with a mat4
-	 * 3rd vector component is implicitly '0'
-	 * 4th vector component is implicitly '1'
-	 *
-	 * @param {vec2} out the receiving vector
-	 * @param {vec2} a the vector to transform
-	 * @param {mat4} m matrix to transform with
-	 * @returns {vec2} out
-	 */
-	function transformMat4(out, a, m) {
-	    var x = a[0], 
-	        y = a[1]
-	    out[0] = m[0] * x + m[4] * y + m[12]
-	    out[1] = m[1] * x + m[5] * y + m[13]
-	    return out
-	}
-
-/***/ }),
-/* 199 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = forEach
-
-	var vec = __webpack_require__(172)()
-
-	/**
-	 * Perform some operation over an array of vec2s.
-	 *
-	 * @param {Array} a the array of vectors to iterate over
-	 * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
-	 * @param {Number} offset Number of elements to skip at the beginning of the array
-	 * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
-	 * @param {Function} fn Function to call for each vector in the array
-	 * @param {Object} [arg] additional argument to pass to fn
-	 * @returns {Array} a
-	 * @function
-	 */
-	function forEach(a, stride, offset, count, fn, arg) {
-	    var i, l
-	    if(!stride) {
-	        stride = 2
-	    }
-
-	    if(!offset) {
-	        offset = 0
-	    }
-	    
-	    if(count) {
-	        l = Math.min((count * stride) + offset, a.length)
-	    } else {
-	        l = a.length
-	    }
-
-	    for(i = offset; i < l; i += stride) {
-	        vec[0] = a[i]
-	        vec[1] = a[i+1]
-	        fn(vec, vec, arg)
-	        a[i] = vec[0]
-	        a[i+1] = vec[1]
-	    }
-	    
-	    return a
-	}
-
-/***/ }),
-/* 200 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerRenderer = __webpack_require__(201).registerRenderer;
-	var renderer = __webpack_require__(202);
-
-	/**
-	 * Create a new instance of class vglRenderer.
-	 *
-	 * @class geo.gl.vglRenderer
-	 * @extends geo.renderer
-	 * @param {object} arg Options for the renderer.
-	 * @param {geo.layer} [arg.layer] Layer associated with the renderer.
-	 * @param {HTMLElement} [arg.canvas] Canvas element associated with the
-	 *   renderer.
-	 * @param {object} [arg.options] Additional options for the vgl renderer.
-	 * @returns {geo.gl.vglRenderer}
-	 */
-	var vglRenderer = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof vglRenderer)) {
-	    return new vglRenderer(arg);
-	  }
-	  arg = arg || {};
-	  renderer.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var vgl = __webpack_require__(86);
-	  var mat4 = __webpack_require__(88);
-	  var util = __webpack_require__(83);
-	  var geo_event = __webpack_require__(9);
-
-	  var m_this = this,
-	      m_contextRenderer = null,
-	      m_viewer = null,
-	      m_width = 0,
-	      m_height = 0,
-	      m_lastZoom,
-	      m_updateCamera = false,
-	      s_init = this._init,
-	      s_exit = this._exit;
-
-	  // TODO: Move this API to the base class
-	  /**
-	   * Return width of the renderer.
-	   *
-	   * @returns {number} The width of the current canvas.
-	   */
-	  this.width = function () {
-	    return m_width;
-	  };
-
-	  /**
-	   * Return height of the renderer.
-	   *
-	   * @returns {number} The height of the current canvas.
-	   */
-	  this.height = function () {
-	    return m_height;
-	  };
-
-	  /**
-	   * Get context specific renderer.
-	   *
-	   * @returns {object} The vgl context renderer.
-	   */
-	  this.contextRenderer = function () {
-	    return m_contextRenderer;
-	  };
-
-	  /**
-	   * Get API used by the renderer.
-	   *
-	   * @returns {string} `vgl`.
-	   */
-	  this.api = function () {
-	    return 'vgl';
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    if (m_this.initialized()) {
-	      return m_this;
-	    }
-
-	    s_init.call(m_this);
-
-	    var canvas = $(document.createElement('canvas'));
-	    canvas.addClass('webgl-canvas');
-	    $(m_this.layer().node().get(0)).append(canvas);
-
-	    if (window.overrideContextAttributes) {
-	      var elem = canvas.get(0);
-	      var getContext = elem.getContext;
-	      elem.getContext = function (contextType, contextAttributes) {
-	        contextAttributes = contextAttributes || {};
-	        if (window.overrideContextAttributes) {
-	          for (var key in window.overrideContextAttributes) {
-	            if (window.overrideContextAttributes.hasOwnProperty(key)) {
-	              contextAttributes[key] = window.overrideContextAttributes[key];
-	            }
-	          }
-	        }
-	        return getContext.call(elem, contextType, contextAttributes);
-	      };
-	    }
-
-	    m_viewer = vgl.viewer(canvas.get(0), arg.options);
-	    m_viewer.init();
-	    m_contextRenderer = m_viewer.renderWindow().activeRenderer();
-	    m_contextRenderer.setResetScene(false);
-
-	    if (m_viewer.renderWindow().renderers().length > 0) {
-	      m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length);
-	    }
-	    m_this.canvas(canvas);
-	    /* Initialize the size of the renderer */
-	    var map = m_this.layer().map(),
-	        mapSize = map.size();
-	    m_this._resize(0, 0, mapSize.width, mapSize.height);
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Handle resize event.
-	   *
-	   * @param {number} x The left coordinate.
-	   * @param {number} y The top coordinate.
-	   * @param {number} w The width in pixels.
-	   * @param {number} h The height in pixels.
-	   * @returns {this}
-	   */
-	  this._resize = function (x, y, w, h) {
-	    var renderWindow = m_viewer.renderWindow();
-
-	    m_width = w;
-	    m_height = h;
-	    m_this.canvas().attr('width', w);
-	    m_this.canvas().attr('height', h);
-	    renderWindow.positionAndResize(x, y, w, h);
-
-	    m_updateCamera = true;
-	    m_this._render();
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Render.  This actually schedules rendering for the next animation frame.
-	   *
-	   * @returns {this}
-	   */
-	  this._render = function () {
-	    /* If we are already scheduled to render, don't schedule again.  Rather,
-	     * mark that we should render after other animation frame requests occur.
-	     * It would be nice if we could just reschedule the call by removing and
-	     * readding the animation frame request, but this doesn't work for if the
-	     * reschedule occurs during another animation frame callback (it then waits
-	     * until a subsequent frame). */
-	    m_this.layer().map().scheduleAnimationFrame(this._renderFrame, true);
-	    return m_this;
-	  };
-
-	  /**
-	   * This clears the render timer and actually renders.
-	   */
-	  this._renderFrame = function () {
-	    if (m_viewer) {
-	      if (m_updateCamera) {
-	        m_updateCamera = false;
-	        m_this._updateRendererCamera();
-	      }
-	      m_viewer.render();
-	    }
-	  };
-
-	  /**
-	   * Get the GL context for this renderer.
-	   *
-	   * @returns {WebGLRenderingContext} The current context.  If unavailable,
-	   *    falls back to the vgl generic context.
-	   */
-	  this._glContext = function () {
-	    if (m_viewer && m_viewer.renderWindow()) {
-	      return m_viewer.renderWindow().context();
-	    }
-	    return vgl.GL;
-	  };
-
-	  /**
-	   * Exit.
-	   */
-	  this._exit = function () {
-	    m_this.layer().map().scheduleAnimationFrame(this._renderFrame, 'remove');
-	    m_this.canvas().remove();
-	    if (m_viewer) {
-	      var renderState = new vgl.renderState();
-	      renderState.m_renderer = m_viewer;
-	      renderState.m_context = this._glContext();
-	      m_viewer.exit(renderState);
-	      if (this._glContext() !== vgl.GL && this._glContext().getExtension('WEBGL_lose_context') && this._glContext().getExtension('WEBGL_lose_context').loseContext) {
-	        this._glContext().getExtension('WEBGL_lose_context').loseContext();
-	      }
-	    }
-	    // make sure we clear shaders associated with the generate context, too
-	    vgl.clearCachedShaders(vgl.GL);
-	    m_viewer = null;
-	    s_exit();
-	  };
-
-	  /**
-	   * Update the vgl renderer's camera based on the map's camera class.
-	   */
-	  this._updateRendererCamera = function () {
-	    var renderWindow = m_viewer.renderWindow(),
-	        map = m_this.layer().map(),
-	        camera = map.camera(),
-	        rotation = map.rotation() || 0,
-	        view = camera.view,
-	        proj = camera.projectionMatrix;
-	    if (proj[15]) {
-	      /* we want positive z to be closer to the camera, but webGL does the
-	       * converse, so reverse the z coordinates. */
-	      proj = mat4.scale(util.mat4AsArray(), proj, [1, 1, -1]);
-	    }
-	    /* A similar kluge as in the base camera class worldToDisplay4.  With this,
-	     * we can show z values from 0 to 1. */
-	    proj = mat4.translate(util.mat4AsArray(), proj,
-	                          [0, 0, camera.constructor.bounds.far]);
-	    /* Check if the rotation is a multiple of 90 */
-	    var basis = Math.PI / 2,
-	        angle = rotation % basis,  // move to range (-pi/2, pi/2)
-	        ortho = (Math.min(Math.abs(angle), Math.abs(angle - basis)) < 0.00001);
-	    renderWindow.renderers().forEach(function (renderer) {
-	      var cam = renderer.camera();
-	      if (util.compareArrays(view, cam.viewMatrix()) &&
-	          util.compareArrays(proj, cam.projectionMatrix()) &&
-	          m_lastZoom === map.zoom()) {
-	        return;
-	      }
-	      m_lastZoom = map.zoom();
-	      cam.setViewMatrix(view, true);
-	      cam.setProjectionMatrix(proj);
-	      var viewport = camera.viewport;
-	      /* Test if we should align texels.  We won't if the projection matrix
-	       * is not simple, if there is a rotation that isn't a multiple of 90
-	       * degrees, if the viewport is not at an integer location, or if the zoom
-	       * level is not close to an integer.
-	       *   Note that the test for the viewport is strict (val % 1 is non-zero
-	       * if the value is not an integer), as, in general, the alignment is only
-	       * non-integral if a percent offset or calculation was used in css
-	       * somewhere.  The test for zoom level always has some allowance for
-	       * precision, as it is often the result of repeated computations. */
-	      if (proj[1] || proj[2] || proj[3] || proj[4] || proj[6] || proj[7] ||
-	          proj[8] || proj[9] || proj[11] || proj[15] !== 1 || !ortho ||
-	          (viewport.left && viewport.left % 1) ||
-	          (viewport.top && viewport.top % 1) ||
-	          (parseFloat(m_lastZoom.toFixed(6)) !==
-	           parseFloat(m_lastZoom.toFixed(0)))) {
-	        /* Don't align texels */
-	        cam.viewAlignment = function () {
-	          return null;
-	        };
-	      } else {
-	        /* Set information for texel alignment.  The rounding factors should
-	         * probably be divided by window.devicePixelRatio. */
-	        cam.viewAlignment = function () {
-	          var align = {
-	            roundx: 2.0 / viewport.width,
-	            roundy: 2.0 / viewport.height
-	          };
-	          align.dx = (viewport.width % 2) ? align.roundx * 0.5 : 0;
-	          align.dy = (viewport.height % 2) ? align.roundy * 0.5 : 0;
-	          return align;
-	        };
-	      }
-	    });
-	  };
-
-	  // Connect to pan event.  This is sufficient, as all zooms and rotations also
-	  // produce a pan
-	  m_this.layer().geoOn(geo_event.pan, function (evt) {
-	    void (evt);
-	    m_updateCamera = true;
-	  });
-
-	  // Connect to parallelprojection event
-	  m_this.layer().geoOn(geo_event.parallelprojection, function (evt) {
-	    var vglRenderer = m_this.contextRenderer(),
-	        camera,
-	        layer = m_this.layer();
-
-	    if (evt.geo && evt.geo._triggeredBy !== layer) {
-	      if (!vglRenderer || !vglRenderer.camera()) {
-	        console.log('Parallel projection event triggered on unconnected VGL ' +
-	                    'renderer.');
-	        return;
-	      }
-	      camera = vglRenderer.camera();
-	      camera.setEnableParallelProjection(evt.parallelProjection);
-	      m_updateCamera = true;
-	    }
-	  });
-
-	  return this;
-	};
-
-	inherit(vglRenderer, renderer);
-
-	registerRenderer('vgl', vglRenderer);
-
-	(function () {
-	  'use strict';
-
-	  var checkedWebGL;
-
-	  /**
-	   * Report if the vgl renderer is supported.  This is just a check if webGL is
-	   * supported and available.
-	   *
-	   * @returns {boolean} true if available.
-	   */
-	  vglRenderer.supported = function () {
-	    if (checkedWebGL === undefined) {
-	      /* This is extracted from what Modernizr uses. */
-	      var canvas, ctx, exts; // eslint-disable-line no-unused-vars
-	      try {
-	        canvas = document.createElement('canvas');
-	        ctx = (canvas.getContext('webgl') ||
-	               canvas.getContext('experimental-webgl'));
-	        exts = ctx.getSupportedExtensions();
-	        checkedWebGL = true;
-	      } catch (e) {
-	        console.warn('No webGL support');
-	        checkedWebGL = false;
-	      }
-	      canvas = undefined;
-	      ctx = undefined;
-	      exts = undefined;
-	    }
-	    return checkedWebGL;
-	  };
-
-	  /**
-	   * If the vgl renderer is not supported, supply the name of a renderer that
-	   * should be used instead.  This asks for the null renderer.
-	   *
-	   * @returns {null} null for the null renderer.
-	   */
-	  vglRenderer.fallback = function () {
-	    return null;
-	  };
-
-	})();
-
-	module.exports = vglRenderer;
-
-
-/***/ }),
-/* 201 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var widgets = {
-	  dom: {}
-	};
-	var layers = {};
-	var layerDefaultFeatures = {};
-	var renderers = {};
-	var features = {};
-	var featureCapabilities = {};
-	var fileReaders = {};
-	var rendererLayerAdjustments = {};
-	var annotations = {};
-	var util = {};
-
-	/**
-	 * Register a new file reader type.
-	 *
-	 * @param {string} name Name of the reader to register.  If the name already
-	 *      exists, the class creation function is replaced.
-	 * @param {function} func Class creation function.
-	 */
-	util.registerFileReader = function (name, func) {
-	  fileReaders[name] = func;
-	};
-
-	/**
-	 * Create a new file reader.
-	 *
-	 * @param {string} name Name of the reader to create.
-	 * @param {object} opts Options for the new reader.
-	 * @returns {geo.fileReader|null} The new reader or null if no such name is
-	 *      registered.
-	 */
-	util.createFileReader = function (name, opts) {
-	  if (fileReaders.hasOwnProperty(name)) {
-	    return fileReaders[name](opts);
-	  }
-	  return null;
-	};
-
-	/**
-	 * Register a new renderer type.
-	 *
-	 * @param {string} name Name of the renderer to register.  If the name already
-	 *      exists, the class creation function is replaced.
-	 * @param {function} func Class creation function.
-	 */
-	util.registerRenderer = function (name, func) {
-	  renderers[name] = func;
-	};
-
-	/**
-	 * Create new instance of the renderer.
-	 *
-	 * @param {string} name Name of the renderer to create.
-	 * @param {geo.layer} layer The layer associated with the renderer.
-	 * @param {HTMLCanvasElement} [canvas] A canvas object to share between
-	 *      renderers.
-	 * @param {object} options Options for the new renderer.
-	 * @returns {geo.renderer|null} The new renderer or null if no such name is
-	 *      registered.
-	 */
-	util.createRenderer = function (name, layer, canvas, options) {
-	  if (renderers.hasOwnProperty(name)) {
-	    var ren = renderers[name](
-	      {layer: layer, canvas: canvas, options: options}
-	    );
-	    ren._init();
-	    return ren;
-	  }
-	  return null;
-	};
-
-	/**
-	 * Check if the named renderer is supported.  If not, display a warning and get
-	 * the name of a fallback renderer.  Ideally, we would pass a list of desired
-	 * features, and, if the renderer is unavailable, this would choose a fallback
-	 * that would support those features.
-	 *
-	 * @param {string|null} name Name of the desired renderer.
-	 * @param {boolean} noFallback If truthy, don't recommend a fallback.
-	 * @returns {string|null|false} The name of the renderer that should be used
-	 *      or false if no valid renderer can be determined.
-	 */
-	util.checkRenderer = function (name, noFallback) {
-	  if (name === null) {
-	    return name;
-	  }
-	  if (renderers.hasOwnProperty(name)) {
-	    var ren = renderers[name];
-	    if (!ren.supported || ren.supported()) {
-	      return name;
-	    }
-	    if (!ren.fallback || noFallback) {
-	      return false;
-	    }
-	    var fallback = util.checkRenderer(ren.fallback(), true);
-	    if (fallback !== false) {
-	      console.warn(name + ' renderer is unavailable, using ' + fallback +
-	                   ' renderer instead');
-	    }
-	    return fallback;
-	  }
-	  return false;
-	};
-
-	/**
-	 * Check if there is a renderer that is supported and supports a list of
-	 * features.  If not, display a warning.  This picks the first renderer that
-	 * supports all of the listed features.
-	 *
-	 * @param {array|undefined} featureList A list of features that will be used
-	 *      with this renderer.  Features are the basic feature names (e.g.,
-	 *      `'quad'`), or the feature name followed by a required capability (e.g.,
-	 *      `'quad.image'`).  If more than one feature or more than one capability
-	 *      of a feature is required, include each feature and capability
-	 *      combination in the list (e.g., `['quad.image', 'plane']`).  If no
-	 *      capability is specified for a feature (or that feature was registered
-	 *      without a capability object), then the feature will match regardless of
-	 *      capabilities.
-	 * @returns {string|null|false} The name of the renderer that should be used
-	 *      or false if no valid renderer can be determined.
-	 */
-	util.rendererForFeatures = function (featureList) {
-	  var preferredRenderers = ['vgl', 'canvas', 'd3', null];
-
-	  var renderer, ridx, feature, fidx, capability, available;
-	  for (ridx = 0; ridx < preferredRenderers.length; ridx += 1) {
-	    renderer = preferredRenderers[ridx];
-	    if (util.checkRenderer(renderer, true) === false) {
-	      continue;
-	    }
-	    if (!featureList) {
-	      return renderer;
-	    }
-	    if (!features[renderer]) {
-	      continue;
-	    }
-	    available = true;
-	    for (fidx = 0; fidx < featureList.length; fidx += 1) {
-	      feature = featureList[fidx];
-	      capability = null;
-	      if (feature.indexOf('.') >= 0) {
-	        capability = feature;
-	        feature = feature.substr(0, feature.indexOf('.'));
-	      }
-	      if (features[renderer][feature] === undefined) {
-	        available = false;
-	        break;
-	      }
-	      if (capability && featureCapabilities[renderer][feature] &&
-	          !featureCapabilities[renderer][feature][capability]) {
-	        available = false;
-	        break;
-	      }
-	    }
-	    if (available) {
-	      return renderer;
-	    }
-	  }
-	  console.warn('There is no renderer available for the feature list "' +
-	               (featureList || []).join(', ') + '".');
-	  return false;
-	};
-
-	/**
-	 * Register a new feature type.
-	 *
-	 * @param {string} category The feature category -- this is the renderer name.
-	 * @param {string} name The feature name.
-	 * @param {function} func A function to call to create the feature.
-	 * @param {object|undefined} capabilities A map of capabilities that this
-	 *      feature supports.  If the feature is implemented with different
-	 *      capabilities in multiple categories (renderers), then the feature
-	 *      should expose a simple dictionary of supported and unsupported
-	 *      features.  For instance, the quad feature has color quads, image quads,
-	 *      and image quads that support full transformations.  The capabilities
-	 *      should be defined in the base feature in a capabilities object so that
-	 *      they can be referenced by that rather than an explicit string.
-	 * @returns {object} if this feature replaces an existing one, this was the
-	 *      feature that was replaced.  In this case, a warning is issued.
-	 */
-	util.registerFeature = function (category, name, func, capabilities) {
-	  if (!(category in features)) {
-	    features[category] = {};
-	    featureCapabilities[category] = {};
-	  }
-
-	  var old = features[category][name];
-	  if (old) {
-	    console.warn('The ' + category + '.' + name + ' feature is already registered');
-	  }
-	  features[category][name] = func;
-	  featureCapabilities[category][name] = capabilities;
-	  return old;
-	};
-
-	/**
-	 * Create new instance of a feature.
-	 *
-	 * @param {string} name Name of the feature to create.
-	 * @param {geo.layer} layer The layer associated with the feature.
-	 * @param {geo.renderer} renderer The renderer associated with the feature.
-	 *      This is usually `layer.renderer()`.
-	 * @param {object} arg Options for the new feature.
-	 * @returns {geo.feature|null} The new feature or null if no such name is
-	 *      registered.
-	 */
-	util.createFeature = function (name, layer, renderer, arg) {
-	  var category = renderer.api(),
-	      options = {'layer': layer, 'renderer': renderer};
-	  if (category in features && name in features[category]) {
-	    if (arg !== undefined) {
-	      $.extend(true, options, arg);
-	    }
-	    var feature = features[category][name](options);
-	    if (layer.gcs === undefined) {
-	      layer.gcs = function () {
-	        return layer.map().gcs();
-	      };
-	    }
-	    return feature;
-	  }
-	  return null;
-	};
-
-	/**
-	 * Register a layer adjustment.  Layer adjustments are appiled to specified
-	 * layers when they are created as a method to mixin specific changes,
-	 * usually based on the renderer used for that layer.
-	 *
-	 * @param {string} category The category for the adjustment; this is commonly
-	 *      the renderer name.
-	 * @param {string} name The name of the adjustement.
-	 * @param {function} func The function to call when the adjustment is used.
-	 * @returns {object} if this layer adjustment replaces an existing one, this
-	 *      was the value that was replaced.  In this case, a warning is issued.
-	 */
-	util.registerLayerAdjustment = function (category, name, func) {
-	  if (!(category in rendererLayerAdjustments)) {
-	    rendererLayerAdjustments[category] = {};
-	  }
-
-	  var old = rendererLayerAdjustments[category][name];
-	  if (old) {
-	    console.warn('The ' + category + '.' + name + ' layer adjustment is already registered');
-	  }
-	  rendererLayerAdjustments[category][name] = func;
-	  return old;
-	};
-
-	/**
-	 * If a layer needs to be adjusted based on the renderer, call the function
-	 * that adjusts it.
-	 *
-	 * @param {string} name Name of the layer.
-	 * @param {object} layer Instantiated layer object.
-	 */
-	util.adjustLayerForRenderer = function (name, layer) {
-	  var rendererName = layer.rendererName();
-	  if (rendererName) {
-	    if (rendererLayerAdjustments &&
-	        rendererLayerAdjustments[rendererName] &&
-	        rendererLayerAdjustments[rendererName][name]) {
-	      rendererLayerAdjustments[rendererName][name].apply(layer);
-	    }
-	  }
-	};
-
-	/**
-	 * Register a new layer type.
-	 *
-	 * @param {string} name Name of the layer to register.  If the name already
-	 *      exists, the class creation function is replaced.
-	 * @param {function} func Class creation function.
-	 * @param {array} [defaultFeatures] An optional list of feature capabailities
-	 *      that are required to use this layer.
-	 */
-	util.registerLayer = function (name, func, defaultFeatures) {
-	  layers[name] = func;
-	  layerDefaultFeatures[name] = defaultFeatures;
-	};
-
-	/**
-	 * Create new instance of the layer.
-	 *
-	 * @param {string} name Name of the layer to create.
-	 * @param {geo.map} map The map class instance that owns the layer.
-	 * @param {object} arg Options for the new layer.
-	 * @returns {geo.layer|null} The new layer or null if no such name is
-	 *      registered.
-	 */
-	util.createLayer = function (name, map, arg) {
-	  // Default renderer is vgl
-	  var options = {map: map},
-	      layer = null;
-
-	  if (name in layers) {
-	    if (!arg.renderer && !arg.features && layerDefaultFeatures) {
-	      options.features = layerDefaultFeatures[name];
-	    }
-	    if (arg !== undefined) {
-	      $.extend(true, options, arg);
-	    }
-	    layer = layers[name](options);
-	    layer.layerName = name;
-	    layer._init();
-	    return layer;
-	  } else {
-	    return null;
-	  }
-	};
-
-	/**
-	 * Register a new widget type.
-	 *
-	 * @param {string} category A category for this widget.  This is usually
-	 *      `'dom'`.
-	 * @param {string} name The name of the widget to register.
-	 * @param {function} func Class creation function.
-	 * @returns {object} If this widget replaces an existing one, this was the
-	 *      value that was replaced.  In this case, a warning is issued.
-	 */
-	util.registerWidget = function (category, name, func) {
-	  if (!(category in widgets)) {
-	    widgets[category] = {};
-	  }
-
-	  var old = widgets[category][name];
-	  if (old) {
-	    console.warn('The ' + category + '.' + name + ' widget is already registered');
-	  }
-	  widgets[category][name] = func;
-	  return old;
-	};
-
-	/**
-	 * Create new instance of a dom widget.
-	 *
-	 * @param {string} name Name of the widget to create.
-	 * @param {geo.layer} layer The layer associated with the widget.
-	 * @param {object} arg Options for the new widget.
-	 * @returns {geo.widget} The new widget.
-	 */
-	util.createWidget = function (name, layer, arg) {
-	  var options = {
-	    layer: layer
-	  };
-
-	  if (name in widgets.dom) {
-	    if (arg !== undefined) {
-	      $.extend(true, options, arg);
-	    }
-
-	    return widgets.dom[name](options);
-	  }
-
-	  throw new Error('Cannot create unknown widget ' + name);
-	};
-
-	/**
-	 * Register a new annotation type.
-	 *
-	 * @param {string} name The annotation name.
-	 * @param {function} func A function to call to create the annotation.
-	 * @param {object|undefined} features A map of features that are used by this
-	 *      annotation.  Each key is a feature that is used.  If the value is true,
-	 *      the that feature is always needed.  If a list, then it is the set of
-	 *      annotation states for which that feature is required.  These can be
-	 *      used to pick an appropriate renderer when creating an annotation layer.
-	 * @returns {object} if this annotation replaces an existing one, this was the
-	 *      value that was replaced.  In this case, a warning is issued.
-	 */
-	util.registerAnnotation = function (name, func, features) {
-	  var old = annotations[name];
-	  if (old) {
-	    console.warn('The ' + name + ' annotation is already registered');
-	  }
-	  annotations[name] = {func: func, features: features || {}};
-	  return old;
-	};
-
-	/**
-	 * Create an annotation based on a registered type.
-	 *
-	 * @param {string} name The annotation name
-	 * @param {object} options The options for the annotation.
-	 * @returns {object} the new annotation.
-	 */
-	util.createAnnotation = function (name, options) {
-	  if (!annotations[name]) {
-	    console.warn('The ' + name + ' annotation is not registered');
-	    return;
-	  }
-	  var annotation = annotations[name].func(options);
-	  return annotation;
-	};
-
-	/**
-	 * Get a list of registered annotation types.
-	 *
-	 * @returns {array} A list of registered annotations.
-	 */
-	util.listAnnotations = function () {
-	  return Object.keys(annotations);
-	};
-
-	/**
-	 * Get a list of required features for a set of annotations.
-	 *
-	 * @param {array|object|undefined} annotationList A list of annotations that
-	 *   will be used.  Instead of a list, if this is an object, the keys are the
-	 *   annotation names, and the values are each a list of modes that will be
-	 *   used with that annotation.  For example, ['polygon', 'rectangle'] lists
-	 *   features required to show those annotations in any mode,  whereas
-	 *   {polygon: [annotationState.done], rectangle: [annotationState.done]} only
-	 *   lists features that are needed to show the completed annotations.
-	 * @returns {array} a list of features needed for the specified annotations.
-	 *   There may be duplicates in the list.
-	 */
-	util.featuresForAnnotations = function (annotationList) {
-	  var features = [];
-
-	  var annList = Array.isArray(annotationList) ? annotationList : Object.keys(annotationList);
-	  annList.forEach(function (ann) {
-	    if (!annotations[ann]) {
-	      return;
-	    }
-	    Object.keys(annotations[ann].features).forEach(function (feature) {
-	      if (Array.isArray(annotationList) || annotationList[ann] === true ||
-	          !Array.isArray(annotations[ann].features[feature])) {
-	        features.push(feature);
-	      } else {
-	        annotationList[ann].forEach(function (state) {
-	          if ($.inArray(state, annotations[ann].features[feature]) >= 0) {
-	            features.push(feature);
-	          }
-	        });
-	      }
-	    });
-	  });
-	  return features;
-	};
-
-	/**
-	 * Check if there is a renderer that is supported and supports a list of
-	 * annotations.  If not, display a warning.  This generates a list of required
-	 * features, then picks the first renderer that supports all of these features.
-	 *
-	 * @param {array|object|undefined} annotationList A list of annotations that
-	 *   will be used with this renderer.  Instead of a list, if this is an object,
-	 *   the keys are the annotation names, and the values are each a list of modes
-	 *   that will be used with that annotation.  See featuresForAnnotations for
-	 *   more details.
-	 * @returns {string|null|false} the name of the renderer that should be used or
-	 *   false if no valid renderer can be determined.
-	 */
-	util.rendererForAnnotations = function (annotationList) {
-	  return util.rendererForFeatures(util.featuresForAnnotations(annotationList));
-	};
-
-	module.exports = util;
-
-
-/***/ }),
-/* 202 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var object = __webpack_require__(203);
-
-	/**
-	 * Create a new instance of class renderer.
-	 *
-	 * @class geo.renderer
-	 * @extends geo.object
-	 * @param {object} arg Options for the renderer.
-	 * @param {geo.layer} [arg.layer] Layer associated with the renderer.
-	 * @param {HTMLElement} [arg.canvas] Canvas element associated with the
-	 *   renderer.
-	 * @returns {geo.renderer}
-	 */
-	var renderer = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof renderer)) {
-	    return new renderer(arg);
-	  }
-	  object.call(this);
-
-	  arg = arg || {};
-	  var m_this = this,
-	      m_layer = arg.layer === undefined ? null : arg.layer,
-	      m_canvas = arg.canvas === undefined ? null : arg.canvas,
-	      m_initialized = false;
-
-	  /**
-	   * Get layer of the renderer.
-	   *
-	   * @returns {geo.layer}
-	   */
-	  this.layer = function () {
-	    return m_layer;
-	  };
-
-	  /**
-	   * Get/set canvas for the renderer.
-	   *
-	   * @param {HTMLElement} [val] If `undefined`, return the current canvas
-	   *    element, otherwise set the canvas element and mark the renderer as
-	   *    modified.
-	   * @returns {HTMLElement|this} The current canvas element or the renderer
-	   *    instance.
-	   */
-	  this.canvas = function (val) {
-	    if (val === undefined) {
-	      return m_canvas;
-	    }
-	    m_canvas = val;
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the map associated with the renderer's layer.
-	   *
-	   * @returns {geo.map|null} The map associated with the renderer's layer or
-	   *    `null` if there is no layer.
-	   */
-	  this.map = function () {
-	    if (m_layer) {
-	      return m_layer.map();
-	    } else {
-	      return null;
-	    }
-	  };
-
-	  /**
-	   * Get/set if renderer has been initialized.
-	   *
-	   * @param {boolean} [val] If `undefined` return the initialization state,
-	   *    otherwise set it.
-	   * @returns {boolean|this} The initialization state or this renderer
-	   *    instance.
-	   */
-	  this.initialized = function (val) {
-	    if (val === undefined) {
-	      return m_initialized;
-	    }
-	    m_initialized = val;
-	    return m_this;
-	  };
-
-	  /**
-	   * Get render API used by the renderer.
-	   *
-	   * This must be subclassed, returning a string describing the renderer
-	   * interface.
-	   */
-	  this.api = function () {
-	    throw new Error('Should be implemented by derived classes');
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    return m_this;
-	  };
-
-	  /**
-	   * Handle resize event.
-	   *
-	   * @param {number} x Ignored.
-	   * @param {number} y Ignored.
-	   * @param {number} w New width in pixels.
-	   * @param {number} h New height in pixels.
-	   * @returns {this}
-	   */
-	  this._resize = function (x, y, w, h) {
-	    return m_this;
-	  };
-
-	  /**
-	   * Render.
-	   *
-	   * @returns {this}
-	   */
-	  this._render = function () {
-	    return m_this;
-	  };
-
-	  return this;
-	};
-
-	inherit(renderer, object);
-	module.exports = renderer;
-
-
-/***/ }),
-/* 203 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var vgl = __webpack_require__(86);
-	var inherit = __webpack_require__(8);
-
-	/**
-	 * Create a new instance of class object.
-	 *
-	 * @class
-	 * @alias geo.object
-	 * @extends vgl.object
-	 * @returns {geo.object}
-	 */
-	var object = function () {
-	  'use strict';
-	  if (!(this instanceof object)) {
-	    return new object();
-	  }
-
-	  var util = __webpack_require__(83);
-
-	  var m_this = this,
-	      m_eventHandlers = {},
-	      m_idleHandlers = [],
-	      m_promiseCount = 0;
-
-	  /**
-	   * Bind a handler that will be called once when all internal promises are
-	   * resolved.
-	   *
-	   * @param {function} handler A function taking no arguments.
-	   * @returns {this}
-	   */
-	  this.onIdle = function (handler) {
-	    if (m_promiseCount) {
-	      m_idleHandlers.push(handler);
-	    } else {
-	      handler();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Add a new promise object preventing idle event handlers from being called
-	   * until it is resolved.
-	   *
-	   * @param {Promise} promise A promise object.
-	   * @returns {this}
-	   */
-	  this.addPromise = function (promise) {
-	    // called on any resolution of the promise
-	    function onDone() {
-	      m_promiseCount -= 1;
-	      if (!m_promiseCount) {
-	        m_idleHandlers.splice(0, m_idleHandlers.length)
-	          .forEach(function (handler) {
-	            handler();
-	          });
-	      }
-	    }
-	    m_promiseCount += 1;
-	    if (promise.always) {
-	      promise.always(onDone);
-	    } else {
-	      promise.then(onDone, onDone);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Bind an event handler to this object.
-	   *
-	   * @param {string} event An event from {@link geo.event} or a user-defined
-	   *   value.
-	   * @param {function} handler A function that is called when `event` is
-	   *   triggered.  The function is passed a {@link geo.event} object.
-	   * @returns {this}
-	   */
-	  this.geoOn = function (event, handler) {
-	    if (Array.isArray(event)) {
-	      event.forEach(function (e) {
-	        m_this.geoOn(e, handler);
-	      });
-	      return m_this;
-	    }
-	    if (!util.isFunction(handler)) {
-	      console.warn('Handler for ' + event + ' is not a function', handler, m_this);
-	      return m_this;
-	    }
-	    if (!m_eventHandlers.hasOwnProperty(event)) {
-	      m_eventHandlers[event] = [];
-	    }
-	    m_eventHandlers[event].push(handler);
-	    return m_this;
-	  };
-
-	  /**
-	   * Report if an event handler is bound to this object.
-	   *
-	   * @param {string|string[]} event An event or list of events to check.
-	   * @param {function} [handler] A function that might be bound.  If
-	   *   `undefined`, this will report `true` if there is any handler for the
-	   *   specified event.
-	   * @returns {boolean} true if any of the specified events are bound to the
-	   *   specified handler.
-	   */
-	  this.geoIsOn = function (event, handler) {
-	    if (Array.isArray(event)) {
-	      return event.some(function (e) {
-	        return m_this.geoIsOn(e, handler);
-	      });
-	    }
-	    return (m_eventHandlers[event] || []).some(function (h) {
-	      return h === handler || handler === undefined;
-	    });
-	  };
-
-	  /**
-	   * Trigger an event (or events) on this object and call all handlers.
-	   *
-	   * @param {string|string[]} event An event or list of events from
-	   *       {@link geo.event} or defined by the user.
-	   * @param {object} [args] Additional information to add to the
-	   *       {@link geo.event} object passed to the handlers.
-	   * @returns {this}
-	   */
-	  this.geoTrigger = function (event, args) {
-
-	    // if we have an array of events, recall with single events
-	    if (Array.isArray(event)) {
-	      event.forEach(function (e) {
-	        m_this.geoTrigger(e, args);
-	      });
-	      return m_this;
-	    }
-
-	    // append the event type to the argument object
-	    args = args || {};
-	    args.event = event;
-
-	    if (m_eventHandlers.hasOwnProperty(event)) {
-	      m_eventHandlers[event].forEach(function (handler) {
-	        try {
-	          handler.call(m_this, args);
-	        } catch (err) {
-	          console.warn('Event handler for ' + event + ' threw an error', err);
-	        }
-	      });
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Remove handlers from one event or an array of events.  If no event is
-	   * provided all handlers will be removed.
-	   *
-	   * @param {string|string[]} [event] An event or a list of events from
-	   *       {@link geo.event} or defined by the user, or `undefined` to remove
-	   *       all events (in which case `arg` is ignored).
-	   * @param {(function|function[])?} [arg] A function or array of functions to
-	   *       remove from the events or a falsey value to remove all handlers
-	   *       from the events.
-	   * @returns {this}
-	   */
-	  this.geoOff = function (event, arg) {
-	    if (event === undefined) {
-	      m_eventHandlers = {};
-	      m_idleHandlers = [];
-	      m_promiseCount = 0;
-	    }
-	    if (Array.isArray(event)) {
-	      event.forEach(function (e) {
-	        m_this.geoOff(e, arg);
-	      });
-	      return m_this;
-	    }
-	    if (!arg) {
-	      m_eventHandlers[event] = [];
-	    } else if (Array.isArray(arg)) {
-	      arg.forEach(function (handler) {
-	        m_this.geoOff(event, handler);
-	      });
-	      return m_this;
-	    }
-	    if (m_eventHandlers.hasOwnProperty(event)) {
-	      m_eventHandlers[event] = m_eventHandlers[event].filter(function (f) {
-	        return f !== arg;
-	      }
-	      );
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Free all resources and destroy the object.
-	   */
-	  this._exit = function () {
-	    m_this.geoOff();
-	  };
-
-	  vgl.object.call(this);
-
-	  return this;
-	};
-
-	inherit(object, vgl.object);
-	module.exports = object;
-
-
-/***/ }),
-/* 204 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/*
-	markercluster plugin:
-
-	Copyright 2012 David Leaver
-
-	Permission is hereby granted, free of charge, to any person obtaining
-	a copy of this software and associated documentation files (the
-	"Software"), to deal in the Software without restriction, including
-	without limitation the rights to use, copy, modify, merge, publish,
-	distribute, sublicense, and/or sell copies of the Software, and to
-	permit persons to whom the Software is furnished to do so, subject to
-	the following conditions:
-
-	The above copyright notice and this permission notice shall be
-	included in all copies or substantial portions of the Software.
-
-	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-	Leaflet utilities:
-
-	Copyright (c) 2010-2015, Vladimir Agafonkin
-	Copyright (c) 2010-2011, CloudMade
-	All rights reserved.
-
-	Redistribution and use in source and binary forms, with or without modification, are
-	permitted provided that the following conditions are met:
-
-	   1. Redistributions of source code must retain the above copyright notice, this list of
-	      conditions and the following disclaimer.
-
-	   2. Redistributions in binary form must reproduce the above copyright notice, this list
-	      of conditions and the following disclaimer in the documentation and/or other
-	      materials provided with the distribution.
-
-	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-	COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-	TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-	*/
-
-	/**
-	 * Code taken from https://github.com/Leaflet/Leaflet.markercluster
-	 * to support faster hierarchical clustering of features.
-	 * @copyright 2012, David Leaver
-	 */
-
-	var $ = __webpack_require__(1);
-	var L = {};
-	L.Util = {
-	    // return unique ID of an object
-	    stamp: function (obj) {
-	        obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
-	        return obj._leaflet_id;
-	    },
-	    lastId: 0
-	};
-
-	/**
-	 * @class
-	 * @alias geo.util.DistanceGrid
-	 */
-	var DistanceGrid = function (cellSize) {
-	    this._cellSize = cellSize;
-	    this._sqCellSize = cellSize * cellSize;
-	    this._grid = {};
-	    this._objectPoint = {};
-	};
-
-	DistanceGrid.prototype = {
-
-	    addObject: function (obj, point) {
-	        var x = this._getCoord(point.x),
-	            y = this._getCoord(point.y),
-	            grid = this._grid,
-	            row = grid[y] = grid[y] || {},
-	            cell = row[x] = row[x] || [],
-	            stamp = L.Util.stamp(obj);
-
-	        point.obj = obj;
-	        this._objectPoint[stamp] = point;
-
-	        cell.push(obj);
-	    },
-
-	    updateObject: function (obj, point) {
-	        this.removeObject(obj);
-	        this.addObject(obj, point);
-	    },
-
-	    //Returns true if the object was found
-	    removeObject: function (obj, point) {
-	        var x = this._getCoord(point.x),
-	            y = this._getCoord(point.y),
-	            grid = this._grid,
-	            row = grid[y] = grid[y] || {},
-	            cell = row[x] = row[x] || [],
-	            i, len;
-
-	        delete this._objectPoint[L.Util.stamp(obj)];
-
-	        for (i = 0, len = cell.length; i < len; i++) {
-	            if (cell[i] === obj) {
-
-	                cell.splice(i, 1);
-
-	                if (len === 1) {
-	                    delete row[x];
-	                }
-
-	                return true;
-	            }
-	        }
-
-	    },
-
-	    eachObject: function (fn, context) {
-	        var i, j, k, len, row, cell, removed,
-	            grid = this._grid;
-
-	        for (i in grid) {
-	            row = grid[i];
-
-	            for (j in row) {
-	                cell = row[j];
-
-	                for (k = 0, len = cell.length; k < len; k++) {
-	                    removed = fn.call(context, cell[k]);
-	                    if (removed) {
-	                        k--;
-	                        len--;
-	                    }
-	                }
-	            }
-	        }
-	    },
-
-	    getNearObject: function (point) {
-	        var x = this._getCoord(point.x),
-	            y = this._getCoord(point.y),
-	            i, j, k, row, cell, len, obj, dist,
-	            objectPoint = this._objectPoint,
-	            closestDistSq = this._sqCellSize,
-	            closest = null;
-
-	        for (i = y - 1; i <= y + 1; i++) {
-	            row = this._grid[i];
-	            if (row) {
-
-	                for (j = x - 1; j <= x + 1; j++) {
-	                    cell = row[j];
-	                    if (cell) {
-
-	                        for (k = 0, len = cell.length; k < len; k++) {
-	                            obj = cell[k];
-	                            dist = this._sqDist(
-	                                objectPoint[L.Util.stamp(obj)],
-	                                point
-	                            );
-	                            if (dist < closestDistSq) {
-	                                closestDistSq = dist;
-	                                closest = obj;
-	                            }
-	                        }
-	                    }
-	                }
-	            }
-	        }
-	        return closest;
-	    },
-
-	    /* return the point coordinates contained in the structure */
-	    contents: function () {
-	        return $.map(this._objectPoint, function (val) { return val; });
-	    },
-
-	    _getCoord: function (x) {
-	        return Math.floor(x / this._cellSize);
-	    },
-
-	    _sqDist: function (p, p2) {
-	        var dx = p2.x - p.x,
-	            dy = p2.y - p.y;
-	        return dx * dx + dy * dy;
-	    }
-	};
-
-	module.exports = DistanceGrid;
-
-
-/***/ }),
-/* 205 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/**
-	 * Using methods adapted from leaflet to cluster an array of positions
-	 * hierarchically given an array of length scales (zoom levels).
-	 */
-
-	var $ = __webpack_require__(1);
-	var vgl = __webpack_require__(86);
-
-	/**
-	 * This class manages a group of nearby points that are clustered as a
-	 * single object for display purposes.  The class constructor is private
-	 * and only meant to be created by the ClusterGroup object.
-	 *
-	 * This is a tree-like data structure.  Each node in the tree is a
-	 * cluster containing child clusters and unclustered points.
-	 *
-	 * @class
-	 * @private
-	 *
-	 * @param {geo.util.ClusterGroup} group The source cluster group
-	 * @param {number} zoom The zoom level of the current node
-	 * @param {object[]} children An array of ClusterTrees or point objects
-	 */
-	function ClusterTree(group, zoom, children) {
-	  this._group = group;
-	  this._zoom = zoom;
-	  this._points = [];     // Unclustered points
-	  this._clusters = [];   // Child clusters
-	  this._count = 0;       // Total number of points
-	  this._parent = null;
-	  this._coord = null;    // The cached coordinates
-	  var that = this;
-
-	  // add the children provided in the constructor call
-	  (children || []).forEach(function (c) {
-	    that._add(c);
-	  });
-	}
-
-	/**
-	 * Add a point or cluster as a child to the current cluster.
-	 * @param {object} pt A ClusterTree or point object
-	 * @private
-	 */
-	ClusterTree.prototype._add = function (pt) {
-	  var inc = 1;
-
-	  if (pt instanceof ClusterTree) {
-	    // add a child cluster
-	    this._clusters.push(pt);
-	    inc = pt._count;
-	  } else {
-	    this._points.push(pt);
-	  }
-	  pt._parent = this;
-
-	  // increment the counter
-	  this._increment(inc);
-	};
-
-	/**
-	 * Increment the child counter for this and the parent.
-	 * @param {number} inc The value to increment by
-	 * @private
-	 */
-	ClusterTree.prototype._increment = function (inc) {
-	  this._coord = null;
-	  this._count += inc;
-	  if (this._parent) {
-	    this._parent._increment(inc);
-	  }
-	};
-
-	/**
-	 * Return the total number of child points contained in the cluster.
-	 * @returns {number} Total points contained
-	 */
-	ClusterTree.prototype.count = function () {
-	  return this._count;
-	};
-
-	/**
-	 * Recursively call a function on all points contained in the cluster.
-	 * Calls the function with `this` as the current ClusterTree object, and
-	 * arguments to arguments the point object and the zoom level:
-	 *   func.call(this, point, zoom)
-	 */
-	ClusterTree.prototype.each = function (func) {
-	  var i;
-	  for (i = 0; i < this._points.length; i += 1) {
-	    func.call(this, this._points[i], this._zoom);
-	  }
-	  for (i = 0; i < this._clusters.length; i += 1) {
-	    this._clusters[i].each.call(
-	      this._clusters[i],
-	      func
-	    );
-	  }
-	};
-
-	/**
-	 * Get the coordinates of the cluster (the mean position of all the points
-	 * contained).  This is lazily calculated and cached.
-	 */
-	ClusterTree.prototype.coords = function () {
-	  var i, center = {x: 0, y: 0};
-	  if (this._coord) {
-	    return this._coord;
-	  }
-	  // first add up the points at the node
-	  for (i = 0; i < this._points.length; i += 1) {
-	    center.x += this._points[i].x;
-	    center.y += this._points[i].y;
-	  }
-
-	  // add up the contribution from the clusters
-	  for (i = 0; i < this._clusters.length; i += 1) {
-	    center.x += this._clusters[i].coords().x * this._clusters[i].count();
-	    center.y += this._clusters[i].coords().y * this._clusters[i].count();
-	  }
-
-	  return {
-	    x: center.x / this.count(),
-	    y: center.y / this.count()
-	  };
-	};
-
-	/**
-	 * This class manages clustering of an array of positions hierarchically.
-	 * The algorithm and code was adapted from the Leaflet marker cluster
-	 * plugin by David Leaver: https://github.com/Leaflet/Leaflet.markercluster
-	 *
-	 * @class
-	 * @alias geo.util.ClusterGroup
-	 * @param {object} opts An options object
-	 * @param {number} width The width of the window; used for scaling.
-	 * @param {number} height The height of the window; used for scaling.
-	 * @param {number} maxZoom The maximimum zoom level to calculate
-	 * @param {number} radius Proportional to the clustering radius in pixels
-	 */
-	function C(opts, width, height) {
-
-	  var DistanceGrid = __webpack_require__(204);
-
-	  // store the options
-	  this._opts = $.extend({
-	    maxZoom: 18,
-	    radius: 0.05
-	  }, opts);
-	  this._opts.width = this._opts.width || width || 256;
-	  this._opts.height = this._opts.height || height || 256;
-
-	  // generate the initial datastructures
-	  this._clusters = {}; // clusters at each zoom level
-	  this._points = {};   // unclustered points at each zoom level
-
-	  var zoom, scl;
-	  for (zoom = this._opts.maxZoom; zoom >= 0; zoom -= 1) {
-	    scl = this._scaleAtLevel(zoom, this._opts.width, this._opts.height);
-	    this._clusters[zoom] = new DistanceGrid(scl);
-	    this._points[zoom] = new DistanceGrid(scl);
-	  }
-	  this._topClusterLevel = new ClusterTree(this, -1);
-	}
-
-	/**
-	 * Returns a characteristic distance scale at a particular zoom level.  This
-	 * scale is used to control the clustering radius.  When the renderer supports
-	 * it, this call should be replaced by a calculation involving the view port
-	 * size in point coordinates at a particular zoom level.
-	 * @private
-	 */
-	C.prototype._scaleAtLevel = function (zoom, width, height) {
-	  return vgl.zoomToHeight(zoom, width, height) / 2 * this._opts.radius;
-	};
-
-	/**
-	 * Add a position to the cluster group.
-	 * @protected
-	 */
-	C.prototype.addPoint = function (point) {
-	  var zoom, closest, parent, newCluster, lastParent, z;
-	  /*
-	   * start at the maximum zoom level and search for nearby
-	   *
-	   * 1.  existing clusters
-	   * 2.  unclustered points
-	   *
-	   * otherwise add the point as a new unclustered point
-	   */
-	  for (zoom = this._opts.maxZoom; zoom >= 0; zoom -= 1) {
-
-	    // find near cluster
-	    closest = this._clusters[zoom].getNearObject(point);
-	    if (closest) {
-	      // add the point to the cluster and return
-	      closest._add(point);
-	      return;
-	    }
-
-	    // find near point
-	    closest = this._points[zoom].getNearObject(point);
-	    if (closest) {
-	      parent = closest._parent;
-	      if (parent) {
-	        // remove the point from the parent
-	        for (z = parent._points.length - 1; z >= 0; z -= 1) {
-	          if (parent._points[z] === closest) {
-	            parent._points.splice(z, 1);
-	            parent._increment(-1);
-	            break;
-	          }
-	        }
-	      }
-
-	      if (!parent) {
-	        $.noop();
-	      }
-	      // create a new cluster with these two points
-	      newCluster = new ClusterTree(this, zoom, [closest, point]);
-	      this._clusters[zoom].addObject(newCluster, newCluster.coords());
-
-	      // create intermediate parent clusters that don't exist
-	      lastParent = newCluster;
-	      for (z = zoom - 1; z > parent._zoom; z -= 1) {
-	        lastParent = new ClusterTree(this, z, [lastParent]);
-	        this._clusters[z].addObject(lastParent, lastParent.coords());
-	      }
-	      parent._add(lastParent);
-
-	      // remove closest from this zoom level and any above (replace with newCluster)
-	      for (z = zoom; z >= 0; z -= 1) {
-	        if (!this._points[z].removeObject(closest, closest)) {
-	          break;
-	        }
-	      }
-
-	      return;
-	    }
-
-	    // add an unclustered point
-	    this._points[zoom].addObject(point, point);
-	  }
-
-	  // otherwise add to the top
-	  this._topClusterLevel._add(point);
-	};
-
-	/**
-	 * Return the unclustered points contained at a given zoom level.
-	 * @param {number} zoom The zoom level
-	 * @return {object[]} The array of unclustered points
-	 */
-	C.prototype.points = function (zoom) {
-	  zoom = Math.min(Math.max(Math.floor(zoom), 0), this._opts.maxZoom - 1);
-	  return this._points[Math.floor(zoom)].contents();
-	};
-
-	/**
-	 * Return the clusters contained at a given zoom level.
-	 * @param {number} zoom The zoom level
-	 * @return {ClusterTree[]} The array of clusters
-	 */
-	C.prototype.clusters = function (zoom) {
-	  zoom = Math.min(Math.max(Math.floor(zoom), 0), this._opts.maxZoom - 1);
-	  return this._clusters[Math.floor(zoom)].contents();
-	};
-
-	module.exports = C;
-
-
-/***/ }),
-/* 206 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-	var timestamp = __webpack_require__(209);
-	var transform = __webpack_require__(11);
-	var util = __webpack_require__(83);
-
-	/**
-	 * Line feature specification.
-	 *
-	 * @typedef {geo.feature.spec} geo.lineFeature.spec
-	 * @param {object|Function} [position] Position of the data.  Default is
-	 *   (data).
-	 * @param {object|Function} [line] Lines from the data.  Default is (data).
-	 *   Typically, the data is an array of lines, each of which is an array of
-	 *   points.  Only lines that have at least two points are rendered.  The
-	 *   position function is called for each point as `position(linePoint,
-	 *   pointIndex, lineEntry, lineEntryIndex)`.
-	 * @param {object} [style] Style object with default style options.
-	 * @param {geo.geoColor|Function} [style.strokeColor] Color to stroke each
-	 *   line.  The color can vary by point.
-	 * @param {number|Function} [style.strokeOpacity] Opacity for each line
-	 *   stroke.  The opacity can vary by point.  Opacity is on a [0-1] scale.
-	 * @param {number|Function} [style.strokeWidth] The weight of the line
-	 *   stroke in pixels.  The width can vary by point.
-	 * @param {number|Function} [style.strokeOffset] This is a value from -1
-	 *   (left) to 1 (right), with 0 being centered.  This can vary by point.
-	 * @param {string|Function} [style.lineCap='butt'] One of 'butt', 'square', or
-	 *   'round'.  This can vary by point.
-	 * @param {string|Function} [style.lineJoin='miter'] One of 'miter', 'bevel',
-	 *   'round', or 'miter-clip'.  This can vary by point.
-	 * @param {boolean|Function} [style.closed=false] If true and the renderer
-	 *   supports it, connect the first and last points of a line if the line has
-	 *   more than two points.  This applies per line (if a function, it is called
-	 *   with `(lineEntry, lineEntryIndex)`.
-	 * @param {number|Function} [style.miterLimit=10] For lines of more than two
-	 *   segments that are mitered, if the miter length exceeds the `strokeWidth`
-	 *   divided by the sine of half the angle between segments, then a bevel join
-	 *   is used instead.  This is a single value that applies to all lines.  If a
-	 *   function, it is called with `(data)`.
-	 * @param {number|Function} [style.antialiasing] Antialiasing distance in
-	 *   pixels.  Values must be non-negative.  A value greater than 1 will produce
-	 *   a visible gradient.  This is a single value that applies to all lines.
-	 * @param {string|Function} [style.debug] If 'debug', render lines in debug
-	 *   mode.  This is a single value that applies to all lines.
-	 */
-
-	/**
-	 * Create a new instance of class lineFeature.
-	 *
-	 * @class geo.lineFeature
-	 * @extends geo.feature
-	 * @param {geo.lineFeature.spec} arg
-	 * @returns {geo.lineFeature}
-	 */
-	var lineFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof lineFeature)) {
-	    return new lineFeature(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      m_pointSearchTime = timestamp(),
-	      m_pointSearchInfo;
-
-	  this.featureType = 'line';
-	  this._subfeatureStyles = {
-	    lineCap: true,
-	    lineJoin: true,
-	    strokeColor: true,
-	    strokeOffset: true,
-	    strokeOpacity: true,
-	    strokeWidth: true
-	  };
-
-	  /**
-	   * Get/set lineaccessor.
-	   *
-	   * @param {object} [val] if specified, use this for the line accessor
-	   *    and return the feature.  If not specified, return the current line.
-	   * @returns {object|this} The current line or this feature.
-	   */
-	  this.line = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('line');
-	    } else {
-	      m_this.style('line', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set position accessor.
-	   *
-	   * @param {object} [val] if specified, use this for the position accessor
-	   *    and return the feature.  If not specified, return the current
-	   *    position.
-	   * @returns {object|this} The current position or this feature.
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else {
-	      m_this.style('position', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Cache information needed for point searches.  The point search
-	   * information record is an array with one entry per line, each entry of
-	   * which is an array with one entry per line segment.  These each contain
-	   * an object with the end coordinates (`u`, `v`) of the segment in map gcs
-	   * coordinates and the square of the maximum half-width that needs to be
-	   * considered for the line (`r2`).
-	   *
-	   * @returns {object} The point search information record.
-	   */
-	  this._updatePointSearchInfo = function () {
-	    if (m_pointSearchTime.getMTime() >= m_this.dataTime().getMTime() &&
-	        m_pointSearchTime.getMTime() >= m_this.getMTime()) {
-	      return m_pointSearchInfo;
-	    }
-	    m_pointSearchTime.modified();
-	    m_pointSearchInfo = [];
-	    var data = m_this.data(),
-	        line = m_this.line(),
-	        widthFunc = m_this.style.get('strokeWidth'),
-	        posFunc = m_this.position(),
-	        closedFunc = m_this.style.get('closed'),
-	        gcs = m_this.gcs(),
-	        mapgcs = m_this.layer().map().gcs();
-
-	    data.forEach(function (d, index) {
-	      var closed = closedFunc(d, index),
-	          last, lastr2, first, record = [];
-
-	      line(d, index).forEach(function (current, j) {
-	        var p = posFunc(current, j, d, index);
-	        if (gcs !== mapgcs) {
-	          p = transform.transformCoordinates(gcs, mapgcs, p);
-	        }
-	        var r = Math.ceil(widthFunc(current, j, d, index) / 2) + 2;
-	        var r2 = r * r;
-	        if (last) {
-	          record.push({u: p, v: last, r2: lastr2 > r2 ? lastr2 : r2});
-	        }
-	        last = p;
-	        lastr2 = r2;
-	        if (!first && closed) {
-	          first = {p: p, r2: r2};
-	        }
-	      });
-	      if (closed && first && (last.x !== first.p.x || last.y !== first.p.y)) {
-	        record.push({u: last, v: first.p, r2: lastr2 > first.r2 ? lastr2 : first.r2});
-	      }
-	      m_pointSearchInfo.push(record);
-	    });
-	    return m_pointSearchInfo;
-	  };
-
-	  /**
-	   * Returns an array of datum indices that contain the given point.  This is a
-	   * slow implementation with runtime order of the number of vertices.  A point
-	   * is considered on a line segment if it is close to the line or either end
-	   * point.  Closeness is based on the maximum width of the line segement, and
-	   * is `ceil(maxwidth / 2) + 2` pixels.  This means that corner extensions
-	   * due to mitering may be outside of the selection area and that variable-
-	   * width lines will have a greater selection region than their visual size at
-	   * the narrow end.
-	   *
-	   * @param {geo.geoPosition} p point to search for in map interface gcs.
-	   * @returns {object} An object with `index`: a list of line indices, and
-	   *    `found`: a list of quads that contain the specified coordinate.
-	   */
-	  this.pointSearch = function (p) {
-	    var data = m_this.data(), indices = [], found = [];
-	    if (!data || !data.length || !m_this.layer()) {
-	      return {
-	        found: found,
-	        index: indices
-	      };
-	    }
-	    this._updatePointSearchInfo();
-	    var map = m_this.layer().map(),
-	        scale = map.unitsPerPixel(map.zoom()),
-	        scale2 = scale * scale,
-	        pt = transform.transformCoordinates(map.ingcs(), map.gcs(), p),
-	        i, j, record;
-
-	    for (i = 0; i < m_pointSearchInfo.length; i += 1) {
-	      record = m_pointSearchInfo[i];
-	      for (j = 0; j < record.length; j += 1) {
-	        if (util.distance2dToLineSquared(pt, record[j].u, record[j].v) <= record[j].r2 * scale2) {
-	          found.push(data[i]);
-	          indices.push(i);
-	          break;
-	        }
-	      }
-	    }
-	    return {
-	      found: found,
-	      index: indices
-	    };
-	  };
-
-	  /**
-	   * Search for lines contained within a rectangilar region.
-	   *
-	   * @param {geo.geoPosition} lowerLeft Lower-left corner in gcs coordinates.
-	   * @param {geo.geoPosition} upperRight Upper-right corner in gcs coordinates.
-	   * @param {object} [opts] Additional search options.
-	   * @param {boolean} [opts.partial=false] If truthy, include lines that are
-	   *    partially in the box, otherwise only include lines that are fully
-	   *    within the region.
-	   * @returns {number[]} A list of line indices that are in the box region.
-	   */
-	  this.boxSearch = function (lowerLeft, upperRight, opts) {
-	    var pos = m_this.position(),
-	        idx = [],
-	        line = m_this.line();
-
-	    opts = opts || {};
-	    opts.partial = opts.partial || false;
-	    if (opts.partial) {
-	      throw new Error('Unimplemented query method.');
-	    }
-
-	    m_this.data().forEach(function (d, i) {
-	      var inside = true;
-	      line(d, i).forEach(function (e, j) {
-	        if (!inside) { return; }
-	        var p = pos(e, j, d, i);
-	        if (!(p.x >= lowerLeft.x &&
-	              p.x <= upperRight.x &&
-	              p.y >= lowerLeft.y &&
-	              p.y <= upperRight.y)
-	        ) {
-	          inside = false;
-	        }
-	      });
-	      if (inside) {
-	        idx.push(i);
-	      }
-	    });
-	    return idx;
-	  };
-
-	  /**
-	   * Take a set of data, reduce the number of vertices per linen using the
-	   * Ramer–Douglas–Peucker algorithm, and use the result as the new data.
-	   * This changes the instance's data, the position accessor, and the line
-	   * accessor.
-	   *
-	   * @param {array} data A new data array.
-	   * @param {number} [tolerance] The maximum variation allowed in map.gcs
-	   *    units.  A value of zero will only remove perfectly colinear points.  If
-	   *    not specified, this is set to a half display pixel at the map's current
-	   *    zoom level.
-	   * @param {function} [posFunc=this.style.get('position')] The function to
-	   *    get the position of each vertex.
-	   * @param {function} [lineFunc=this.style.get('line')] The function to get
-	   *    each line.
-	   * @returns {this}
-	   */
-	  this.rdpSimplifyData = function (data, tolerance, posFunc, lineFunc) {
-	    data = data || m_this.data();
-	    posFunc = posFunc || m_this.style.get('position');
-	    lineFunc = lineFunc || m_this.style.get('line');
-	    var map = m_this.layer().map(),
-	        mapgcs = map.gcs(),
-	        featuregcs = m_this.gcs(),
-	        closedFunc = m_this.style.get('closed');
-	    if (tolerance === undefined) {
-	      tolerance = map.unitsPerPixel(map.zoom()) * 0.5;
-	    }
-
-	    /* transform the coordinates to the map gcs */
-	    data = data.map(function (d, idx) {
-	      var lineItem = lineFunc(d, idx),
-	          pts = transform.transformCoordinates(featuregcs, mapgcs, lineItem.map(function (ld, lidx) {
-	            return posFunc(ld, lidx, d, idx);
-	          })),
-	          elem = util.rdpLineSimplify(pts, tolerance, closedFunc(d, idx), []);
-	      if (elem.length < 2 || (elem.length === 2 && util.distance2dSquared(elem[0], elem[1]) < tolerance * tolerance)) {
-	        elem = [];
-	      }
-	      elem = transform.transformCoordinates(mapgcs, featuregcs, elem);
-	      /* Copy element properties, as they might be used by styles */
-	      for (var key in d) {
-	        if (d.hasOwnProperty(key) && !(Array.isArray(d) && key >= 0 && key < d.length)) {
-	          elem[key] = d[key];
-	        }
-	      }
-	      return elem;
-	    });
-
-	    /* Set the reduced lines as the data and use simple accessors. */
-	    m_this.style('position', function (d) { return d; });
-	    m_this.style('line', function (d) { return d; });
-	    m_this.data(data);
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.lineFeature.spec} arg The feature specification.
-	   * @returns {this}
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        strokeWidth: 1.0,
-	        // Default to gold color for lines
-	        strokeColor: { r: 1.0, g: 0.8431372549, b: 0.0 },
-	        strokeStyle: 'solid',
-	        strokeOpacity: 1.0,
-	        // Values of 2 and above appear smoothest.
-	        antialiasing: 2.0,
-	        closed: false,
-	        line: function (d) { return d; },
-	        position: function (d) { return d; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.line !== undefined) {
-	      defaultStyle.line = arg.line;
-	    }
-
-	    if (arg.position !== undefined) {
-	      defaultStyle.position = arg.position;
-	    }
-
-	    m_this.style(defaultStyle);
-
-	    m_this.dataTime().modified();
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	/**
-	 * Create a lineFeature.
-	 *
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to
-	 * @param {geo.lineFeature.spec} spec The feature specification
-	 * @returns {geo.lineFeature|null} The created feature or `null` for failure.
-	 */
-	lineFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'line';
-	  return feature.create(layer, spec);
-	};
-
-	lineFeature.capabilities = {
-	  /* core feature name -- support in any manner */
-	  feature: 'line',
-	  /* support for solid-colored, constant-width lines */
-	  basic: 'line.basic',
-	  /* support for lines that vary in width and color */
-	  multicolor: 'line.multicolor'
-	};
-
-	inherit(lineFeature, feature);
-	module.exports = lineFeature;
-
-
-/***/ }),
-/* 207 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-	var timestamp = __webpack_require__(209);
-	var geo_event = __webpack_require__(9);
-
-	/**
-	 * General specification for features.
-	 *
-	 * @typedef {object} geo.feature.spec
-	 * @property {geo.layer} [layer] the parent layer associated with the feature.
-	 * @property {boolean} [selectionAPI=false] If truthy, enable selection events
-	 *      on the feature.  Selection events are those in `geo.event.feature`.
-	 *      They can be bound via a call like
-	 *      <pre><code>
-	 *      feature.geoOn(geo.event.feature.mousemove, function (evt) {
-	 *        // do something with the feature
-	 *      });
-	 *      </code></pre>
-	 *      where the handler is passed a `geo.feature.event` object.
-	 * @property {boolean} [visible=true] If truthy, show the feature.  If falsy,
-	 *      hide the feature and do not allow interaction with it.
-	 * @property {string} [gcs] The interface gcs for this feature.  If `undefined`
-	 *      or `null`, this uses the layer's interface gcs.  This is a string used
-	 *      by {@linkcode geo.transform}.
-	 * @property {number} [bin=0] The bin number is used to determine the order
-	 *      of multiple features on the same layer.  It has no effect except on the
-	 *      vgl renderer.  A negative value hides the feature without stopping
-	 *      interaction with it.  Otherwise, more features with higher bin numbers
-	 *      are drawn above those with lower bin numbers.  If two features have the
-	 *      same bin number, their order relative to one another is indeterminate
-	 *      and may be unstable.
-	 * @property {geo.renderer?} [renderer] A reference to the renderer used for
-	 *      the feature.
-	 * @property {object} [style] An object that contains style values for the
-	 *      feature.
-	 * @property {function|number} [style.opacity=1] The opacity on a scale of 0 to
-	 *      1.
-	 */
-
-	/**
-	 * @typedef {geo.feature.spec} geo.feature.createSpec
-	 * @property {string} type A supported feature type.
-	 * @property {object[]} [data=[]] An array of arbitrary objects used to
-	 *  construct the feature.  These objects (and their associated indices in the
-	 *  array) will be passed back to style and attribute accessors provided by the
-	 *  user.
-	 */
-
-	/**
-	 * @typedef {geo.event} geo.feature.event
-	 * @property {number} index The index of the feature within the data array.
-	 * @property {object} data The data element associated with the indexed
-	 *      feature.
-	 * @property {geo.mouseState} mouse The mouse information during the event.
-	 * @property {object} [extra] Additional information about the feature.  This
-	 *      is sometimes used to identify a subsection of the feature.
-	 * @property {number} [eventID] A monotonically increasing number identifying
-	 *      this feature event loop.  This is provided on
-	 *      `geo.event.feature.mousemove`, `geo.event.feature.mouseclick`,
-	 *      `geo.event.feature.mouseover`, `geo.event.feature.mouseout`,
-	 *      `geo.event.feature.brush`, and `geo.event.feature.brushend`
-	 *      events, since each of those can trigger multiple events for one mouse
-	 *      action (all events triggered by the same mouse action will have the
-	 *      same `eventID`).
-	 * @property {boolean} [top] `true` if this is the top-most feature that the
-	 *      mouse is over.  Only the top-most feature gets
-	 *      `geo.event.feature.mouseon` events, whereas multiple features can get
-	 *      other events.
-	 */
-
-	/**
-	 * @typedef {object} geo.feature.searchResult
-	 * @property {object[]} found A list of elements from the data array that were
-	 *      found by the search.
-	 * @property {number[]} index A list of the indices of the elements that were
-	 *      found by the search.
-	 * @property {object[]} [extra] A list of additional information per found
-	 *      element.  The information is passed to events without change.
-	 */
-
-	/**
-	 * Create a new instance of class feature.
-	 *
-	 * @class
-	 * @alias geo.feature
-	 * @extends geo.sceneObject
-	 * @param {geo.feature.spec} [arg] A feature specification.
-	 * @returns {geo.feature}
-	 */
-	var feature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof feature)) {
-	    return new feature(arg);
-	  }
-	  sceneObject.call(this);
-
-	  var util = __webpack_require__(83);
-
-	  /**
-	   * @private
-	   */
-	  arg = arg || {};
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_selectionAPI = arg.selectionAPI === undefined ? false : !!arg.selectionAPI,
-	      m_style = {},
-	      m_layer = arg.layer === undefined ? null : arg.layer,
-	      m_gcs = arg.gcs,
-	      m_visible = arg.visible === undefined ? true : arg.visible,
-	      m_bin = arg.bin === undefined ? 0 : arg.bin,
-	      m_renderer = arg.renderer === undefined ? null : arg.renderer,
-	      m_dataTime = timestamp(),
-	      m_buildTime = timestamp(),
-	      m_updateTime = timestamp(),
-	      m_dependentFeatures = [],
-	      m_selectedFeatures = [];
-
-	  // subclasses can add keys to this for styles that apply to subcomponents of
-	  // data items, such as individual vertices on lines or polygons.
-	  this._subfeatureStyles = {};
-
-	  /**
-	   * Private method to bind mouse handlers on the map element.  This does
-	   * nothing if the selectionAPI is turned off.  Otherwise, it first unbinds
-	   * any existing handlers and then binds handlers.
-	   */
-	  this._bindMouseHandlers = function () {
-
-	    // Don't bind handlers for improved performance on features that don't
-	    // require it.
-	    if (!this.selectionAPI()) {
-	      return;
-	    }
-
-	    // First unbind to be sure that the handlers aren't bound twice.
-	    m_this._unbindMouseHandlers();
-
-	    m_this.geoOn(geo_event.mousemove, m_this._handleMousemove);
-	    m_this.geoOn(geo_event.mouseclick, m_this._handleMouseclick);
-	    m_this.geoOn(geo_event.brushend, m_this._handleBrushend);
-	    m_this.geoOn(geo_event.brush, m_this._handleBrush);
-	  };
-
-	  /**
-	   * Private method to unbind mouse handlers on the map element.
-	   */
-	  this._unbindMouseHandlers = function () {
-	    m_this.geoOff(geo_event.mousemove, m_this._handleMousemove);
-	    m_this.geoOff(geo_event.mouseclick, m_this._handleMouseclick);
-	    m_this.geoOff(geo_event.brushend, m_this._handleBrushend);
-	    m_this.geoOff(geo_event.brush, m_this._handleBrush);
-	  };
-
-	  /**
-	   * Search for features containing the given point.  This should be defined in
-	   * relevant subclasses.
-	   *
-	   * @param {geo.geoPosition} geo Coordinate in interface gcs.
-	   * @returns {geo.feature.searchResult} An object with a list of features and
-	   *    feature indices that are located at the specified point.
-	   */
-	  this.pointSearch = function (geo) {
-	    // base class method does nothing
-	    return {
-	      index: [],
-	      found: []
-	    };
-	  };
-
-	  /**
-	   * Search for features contained within a rectangilar region.  This should be
-	   * defined in relevant subclasses.
-	   *
-	   * @param {geo.geoPosition} lowerLeft Lower-left corner in gcs coordinates.
-	   * @param {geo.geoPosition} upperRight Upper-right corner in gcs coordinates.
-	   * @param {object} [opts] Additional search options.
-	   * @param {boolean} [opts.partial=false] If truthy, include features that are
-	   *    partially in the box, otherwise only include features that are fully
-	   *    within the region.
-	   * @returns {number[]} A list of features indices that are in the box region.
-	   */
-	  this.boxSearch = function (lowerLeft, upperRight, opts) {
-	    // base class method does nothing
-	    return [];
-	  };
-
-	  /**
-	   * Private mousemove handler.  This uses `pointSearch` to determine which
-	   * features the mouse is over, then fires appropriate events.
-	   *
-	   * @fires geo.event.feature.mouseover
-	   * @fires geo.event.feature.mouseout
-	   * @fires geo.event.feature.mousemove
-	   * @fires geo.event.feature.mouseoff
-	   * @fires geo.event.feature.mouseon
-	   */
-	  this._handleMousemove = function () {
-	    var mouse = m_this.layer().map().interactor().mouse(),
-	        data = m_this.data(),
-	        over = m_this.pointSearch(mouse.geo),
-	        newFeatures = [], oldFeatures = [], lastTop = -1, top = -1, extra;
-
-	    // exit if we have no old or new found entries
-	    if (!m_selectedFeatures.length && !over.index.length) {
-	      return;
-	    }
-
-	    extra = over.extra || {};
-
-	    // if we are over more than one item, trigger an event that is allowed to
-	    // reorder the values in evt.over.index.  Event handlers don't have to
-	    // maintain evt.over.found.  Handlers should not modify evt.over.extra or
-	    // evt.previous.
-	    if (over.index.length > 1) {
-	      m_this.geoTrigger(geo_event.feature.mouseover_order, {
-	        feature: this,
-	        mouse: mouse,
-	        previous: m_selectedFeatures,
-	        over: over
-	      });
-	    }
-
-	    // Get the index of the element that was previously on top
-	    if (m_selectedFeatures.length) {
-	      lastTop = m_selectedFeatures[m_selectedFeatures.length - 1];
-	    }
-
-	    // There are probably faster ways of doing this:
-	    newFeatures = over.index.filter(function (i) {
-	      return m_selectedFeatures.indexOf(i) < 0;
-	    });
-	    oldFeatures = m_selectedFeatures.filter(function (i) {
-	      return over.index.indexOf(i) < 0;
-	    });
-
-	    feature.eventID += 1;
-	    // Fire events for mouse in first.
-	    newFeatures.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.mouseover, {
-	        data: data[i],
-	        index: i,
-	        extra: extra[i],
-	        mouse: mouse,
-	        eventID: feature.eventID,
-	        top: idx === newFeatures.length - 1
-	      }, true);
-	    });
-
-	    feature.eventID += 1;
-	    // Fire events for mouse out next
-	    oldFeatures.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.mouseout, {
-	        data: data[i],
-	        index: i,
-	        mouse: mouse,
-	        eventID: feature.eventID,
-	        top: idx === oldFeatures.length - 1
-	      }, true);
-	    });
-
-	    feature.eventID += 1;
-	    // Fire events for mouse move last
-	    over.index.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.mousemove, {
-	        data: data[i],
-	        index: i,
-	        extra: extra[i],
-	        mouse: mouse,
-	        eventID: feature.eventID,
-	        top: idx === over.index.length - 1
-	      }, true);
-	    });
-
-	    // Replace the selected features array
-	    m_selectedFeatures = over.index;
-
-	    // Get the index of the element that is now on top
-	    if (m_selectedFeatures.length) {
-	      top = m_selectedFeatures[m_selectedFeatures.length - 1];
-	    }
-
-	    if (lastTop !== top) {
-	      // The element on top changed so we need to fire mouseon/mouseoff
-	      if (lastTop !== -1) {
-	        m_this.geoTrigger(geo_event.feature.mouseoff, {
-	          data: data[lastTop],
-	          index: lastTop,
-	          mouse: mouse
-	        }, true);
-	      }
-
-	      if (top !== -1) {
-	        m_this.geoTrigger(geo_event.feature.mouseon, {
-	          data: data[top],
-	          index: top,
-	          extra: extra[top],
-	          mouse: mouse
-	        }, true);
-	      }
-	    }
-	  };
-
-	  /**
-	   * Clear our tracked selected features.
-	   *
-	   * @returns {this}
-	   */
-	  this._clearSelectedFeatures = function () {
-	    m_selectedFeatures = [];
-	    return m_this;
-	  };
-
-	  /**
-	   * Private mouseclick handler.  This uses `pointSearch` to determine which
-	   * features the mouse is over, then fires a click event for each such
-	   * feature.
-	   *
-	   * @param {geo.event} evt The event that triggered this handler.
-	   * @fires geo.event.feature.mouseclick
-	   */
-	  this._handleMouseclick = function (evt) {
-	    var mouse = m_this.layer().map().interactor().mouse(),
-	        data = m_this.data(),
-	        over = m_this.pointSearch(mouse.geo),
-	        extra = over.extra || {};
-
-	    // if we are over more than one item, trigger an event that is allowed to
-	    // reorder the values in evt.over.index.  Event handlers don't have to
-	    // maintain evt.over.found.  Handlers should not modify evt.over.extra.
-	    if (over.index.length > 1) {
-	      m_this.geoTrigger(geo_event.feature.mouseclick_order, {
-	        feature: this,
-	        mouse: mouse,
-	        over: over
-	      });
-	    }
-	    mouse.buttonsDown = evt.buttonsDown;
-	    feature.eventID += 1;
-	    over.index.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.mouseclick, {
-	        data: data[i],
-	        index: i,
-	        extra: extra[i],
-	        mouse: mouse,
-	        eventID: feature.eventID,
-	        top: idx === over.index.length - 1
-	      }, true);
-	    });
-	  };
-
-	  /**
-	   * Private brush handler.  This uses `boxSearch` to determine which features
-	   * the brush includes, then fires appropriate events.
-	   *
-	   * @param {geo.brushSelection} brush The current brush selection.
-	   * @fires geo.event.feature.brush
-	   */
-	  this._handleBrush = function (brush) {
-	    var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
-	        data = m_this.data();
-
-	    feature.eventID += 1;
-	    idx.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.brush, {
-	        data: data[i],
-	        index: i,
-	        mouse: brush.mouse,
-	        brush: brush,
-	        eventID: feature.eventID,
-	        top: idx === idx.length - 1
-	      }, true);
-	    });
-	  };
-
-	  /**
-	   * Private brushend handler.  This uses `boxSearch` to determine which
-	   * features the brush includes, then fires appropriate events.
-	   *
-	   * @param {geo.brushSelection} brush The current brush selection.
-	   * @fires geo.event.feature.brushend
-	   */
-	  this._handleBrushend = function (brush) {
-	    var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
-	        data = m_this.data();
-
-	    feature.eventID += 1;
-	    idx.forEach(function (i, idx) {
-	      m_this.geoTrigger(geo_event.feature.brushend, {
-	        data: data[i],
-	        index: i,
-	        mouse: brush.mouse,
-	        brush: brush,
-	        eventID: feature.eventID,
-	        top: idx === idx.length - 1
-	      }, true);
-	    });
-	  };
-
-	  /**
-	   * Get/Set style used by the feature.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the current style
-	   *    object.  If a string and `arg2` is undefined, return the style
-	   *    associated with the specified key.  If a string and `arg2` is defined,
-	   *    set the named style to the specified value.  Otherwise, extend the
-	   *    current style with the values in the specified object.
-	   * @param {*} [arg2] If `arg1` is a string, the new value for that style.
-	   * @returns {object|this} Either the entire style object, the value of a
-	   *    specific style, or the current class instance.
-	   */
-	  this.style = function (arg1, arg2) {
-	    if (arg1 === undefined) {
-	      return m_style;
-	    } else if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return m_style[arg1];
-	    } else if (arg2 === undefined) {
-	      m_style = $.extend({}, m_style, arg1);
-	      m_this.modified();
-	      return m_this;
-	    } else {
-	      m_style[arg1] = arg2;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * A uniform getter that always returns a function even for constant styles.
-	   * This can also return all defined styles as functions in a single object.
-	   *
-	   * @param {string} [key] If defined, return a function for the named style.
-	   *    Otherwise, return an object with a function for all defined styles.
-	   * @returns {function|object} Either a function for the named style or an
-	   *    object with functions for all defined styles.
-	   */
-	  this.style.get = function (key) {
-	    var out;
-	    if (key === undefined) {
-	      var all = {}, k;
-	      for (k in m_style) {
-	        if (m_style.hasOwnProperty(k)) {
-	          all[k] = m_this.style.get(k);
-	        }
-	      }
-	      return all;
-	    }
-	    if (key.toLowerCase().match(/color$/)) {
-	      if (util.isFunction(m_style[key])) {
-	        out = function () {
-	          return util.convertColor(
-	            m_style[key].apply(this, arguments)
-	          );
-	        };
-	      } else {
-	        // if the color is not a function, only convert it once
-	        out = util.ensureFunction(util.convertColor(m_style[key]));
-	      }
-	    } else {
-	      out = util.ensureFunction(m_style[key]);
-	    }
-	    return out;
-	  };
-
-	  /**
-	   * Set style(s) from array(s).  For each style, the array should have one
-	   * value per data item.  The values are not converted or validated.  Color
-	   * values should be `geo.geoColorObject`s.  If invalid values are given the
-	   * behavior is undefined.
-	   *   For some feature styles, if the first entry of an array is itself an
-	   * array, then each entry of the array is expected to be an array, and values
-	   * are used from these subarrays.  This allows a style to apply, for
-	   * instance, per vertex of a data item rather than per data item.
-	   *
-	   * @param {string|object} keyOrObject Either the name of a single style or
-	   *    an object where the keys are the names of styles and the values are
-	   *    each arrays.
-	   * @param {array} styleArray If keyOrObject is a string, an array of values
-	   *    for the style.  If keyOrObject is an object, this parameter is ignored.
-	   * @param {boolean} [refresh=false] `true` to redraw the feature when it has
-	   *    been updated.  If an object with styles is passed, the redraw is only
-	   *    done once.
-	   * @returns {this} The feature instance.
-	   */
-	  this.updateStyleFromArray = function (keyOrObject, styleArray, refresh) {
-	    if (typeof keyOrObject !== 'string') {
-	      $.each(keyOrObject, function (key, value) {
-	        m_this.updateStyleFromArray(key, value);
-	      });
-	    } else {
-	      /* colors are always expected to be objects with r, g, b values, so for
-	       * any color, make sure we don't have undefined entries. */
-	      var fallback;
-	      if (keyOrObject.toLowerCase().match(/color$/)) {
-	        fallback = {r: 0, g: 0, b: 0};
-	      }
-	      if (!Array.isArray(styleArray)) {
-	        return m_this;
-	      }
-	      if (m_this._subfeatureStyles[keyOrObject]) {
-	        if (styleArray.length && Array.isArray(styleArray[0])) {
-	          m_this.style(keyOrObject, function (v, j, d, i) {
-	            var val = (styleArray[i] || [])[j];
-	            return val !== undefined ? val : fallback;
-	          });
-	        } else {
-	          m_this.style(keyOrObject, function (v, j, d, i) {
-	            var val = styleArray[i];
-	            return val !== undefined ? val : fallback;
-	          });
-	        }
-	      } else {
-	        m_this.style(keyOrObject, function (d, i) {
-	          var val = styleArray[i];
-	          return val !== undefined ? val : fallback;
-	        });
-	      }
-	    }
-	    if (refresh && m_this.visible()) {
-	      m_this.draw();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the layer referenced by the feature.
-	   *
-	   * @returns {geo.layer} The layer associated with the feature.
-	   */
-	  this.layer = function () {
-	    return m_layer;
-	  };
-
-	  /**
-	   * Get the renderer used by the feature.
-	   *
-	   * @returns {geo.renderer} The renderer used to render the feature.
-	   */
-	  this.renderer = function () {
-	    return m_renderer;
-	  };
-
-	  /**
-	   * Get/Set the projection of the feature.
-	   *
-	   * @param {string?} [val] If `undefined`, return the current gcs.  If
-	   *    `null`, use the map's interface gcs.  Otherwise, set a new value for
-	   *    the gcs.
-	   * @returns {string|this} A string used by {@linkcode geo.transform}.  If the
-	   *    map interface gcs is in use, that value will be returned.  If the gcs
-	   *    is set, return the current class instance.
-	   */
-	  this.gcs = function (val) {
-	    if (val === undefined) {
-	      if ((m_gcs === undefined || m_gcs === null) && m_layer) {
-	        return m_layer.map().ingcs();
-	      }
-	      return m_gcs;
-	    } else {
-	      m_gcs = val;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Convert from the feature's gcs coordinates to display coordinates.
-	   *
-	   * @param {geo.geoPosition} c The input coordinate to convert.
-	   * @returns {geo.screenPosition} Display space coordinates.
-	   */
-	  this.featureGcsToDisplay = function (c) {
-	    var map = m_layer.map();
-	    c = map.gcsToWorld(c, m_this.gcs());
-	    c = map.worldToDisplay(c);
-	    if (m_renderer.baseToLocal) {
-	      c = m_renderer.baseToLocal(c);
-	    }
-	    return c;
-	  };
-
-	  /**
-	   * Get/Set the visibility of the feature.
-	   *
-	   * @param {boolean} [val] A boolean to change the visibility, or `undefined`
-	   *    to return the visibility.
-	   * @param {boolean} [direct] If `true`, when getting the visibility,
-	   *    disregard the visibility of the parent layer, and when setting, refresh
-	   *    the state regardless of whether it has changed or not.  Otherwise, the
-	   *    functional visibility is returned, where both the feature and the layer
-	   *    must be visible for a `true` result.
-	   * @returns {boolean|this} Either the visibility (if getting) or the feature
-	   *    (if setting).
-	   */
-	  this.visible = function (val, direct) {
-	    if (val === undefined) {
-	      if (!direct && m_layer && m_layer.visible && !m_layer.visible()) {
-	        return false;
-	      }
-	      return m_visible;
-	    }
-	    if (m_visible !== val || direct) {
-	      m_visible = val;
-	      m_this.modified();
-	      if (m_layer && m_layer.visible && !m_layer.visible()) {
-	        val = false;
-	      }
-	      // bind or unbind mouse handlers on visibility change
-	      if (val) {
-	        m_this._bindMouseHandlers();
-	      } else {
-	        m_this._unbindMouseHandlers();
-	      }
-	      for (var i = 0; i < m_dependentFeatures.length; i += 1) {
-	        m_dependentFeatures[i].visible(m_visible, direct);
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set a list of dependent features.  Dependent features have their
-	   * visibility changed at the same time as the feature.
-	   *
-	   * @param {geo.feature[]} [arg] If specified, the new list of dependent
-	   *    features.  Otherwise, return the current list of dependent features.
-	   * @returns {geo.feature[]|this} The current list of dependent features or
-	   *    a reference to `this`.
-	   */
-	  this.dependentFeatures = function (arg) {
-	    if (arg === undefined) {
-	      return m_dependentFeatures.slice();
-	    }
-	    m_dependentFeatures = arg.slice();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set bin of the feature.  The bin number is used to determine the order
-	   * of multiple features on the same layer.  It has no effect except on the
-	   * vgl renderer.  A negative value hides the feature without stopping
-	   * interaction with it.  Otherwise, more features with higher bin numbers are
-	   * drawn above those with lower bin numbers.  If two features have the same
-	   * bin number, their order relative to one another is indeterminate and may
-	   * be unstable.
-	   *
-	   * @param {number} [val] The new bin number.  If `undefined`, return the
-	   *    current bin number.
-	   * @returns {number|this} The current bin number or a reference to `this`.
-	   */
-	  this.bin = function (val) {
-	    if (val === undefined) {
-	      return m_bin;
-	    } else {
-	      m_bin = val;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Get/Set timestamp of data change.
-	   *
-	   * @param {geo.timestamp} [val] The new data timestamp object or `undefined`
-	   *    to get the current data timestamp object.
-	   * @returns {geo.timestamp|this}
-	   */
-	  this.dataTime = function (val) {
-	    if (val === undefined) {
-	      return m_dataTime;
-	    } else {
-	      m_dataTime = val;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Get/Set timestamp of last time a build happened.
-	   *
-	   * @param {geo.timestamp} [val] The new build timestamp object or `undefined`
-	   *    to get the current build timestamp object.
-	   * @returns {geo.timestamp|this}
-	   */
-	  this.buildTime = function (val) {
-	    if (val === undefined) {
-	      return m_buildTime;
-	    } else {
-	      m_buildTime = val;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Get/Set timestamp of last time an update happened.
-	   *
-	   * @param {geo.timestamp} [val] The new update timestamp object or
-	   *    `undefined` to get the current update timestamp object.
-	   * @returns {geo.timestamp|this}
-	   */
-	  this.updateTime = function (val) {
-	    if (val === undefined) {
-	      return m_updateTime;
-	    } else {
-	      m_updateTime = val;
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Get/Set the data array for the feature.  This is equivalent to getting or
-	   * setting the `data` style, except that setting the data array via this
-	   * method updates the data timestamp, whereas setting it via the style does
-	   * not.
-	   *
-	   * @param {array} [data] A new data array or `undefined` to return the
-	   *    existing array.
-	   * @returns {array|this}
-	   */
-	  this.data = function (data) {
-	    if (data === undefined) {
-	      return m_this.style('data') || [];
-	    } else {
-	      m_this.style('data', data);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Get/Set if the selection API is enabled for this feature.
-	   *
-	   * @param {boolean} [arg] `undefined` to return the selectionAPI state, or a
-	   *    boolean to change the state.
-	   * @param {boolean} [direct] If `true`, when getting the selectionAPI state,
-	   *    disregard the state of the parent layer, and when setting, refresh the
-	   *    state regardless of whether it has changed or not.
-	   * @returns {boolean|this} Either the selectionAPI state (if getting) or the
-	   *    feature (if setting).
-	   */
-	  this.selectionAPI = function (arg, direct) {
-	    if (arg === undefined) {
-	      if (!direct && m_layer && m_layer.selectionAPI && !m_layer.selectionAPI()) {
-	        return false;
-	      }
-	      return m_selectionAPI;
-	    }
-	    arg = !!arg;
-	    if (arg !== m_selectionAPI || direct) {
-	      m_selectionAPI = arg;
-	      this._unbindMouseHandlers();
-	      this._bindMouseHandlers();
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * If the selectionAPI is on, then setting
-	   * `this.geoOn(geo.event.feature.mouseover_order, this.mouseOverOrderHighestIndex)`
-	   * will make it so that the mouseon events prefer the highest index feature.
-	   *
-	   * @param {geo.event} evt The event; this should be triggered from
-	   *    `geo.event.feature.mouseover_order`.
-	   */
-	  this.mouseOverOrderHighestIndex = function (evt) {
-	    // sort the found indices.  The last one is the one "on top".
-	    evt.over.index.sort();
-	    // this isn't necessary, but ensures that other event handlers have
-	    // consistent information
-	    var data = evt.feature.data();
-	    evt.over.index.forEach(function (di, idx) {
-	      evt.over.found[idx] = data[di];
-	    });
-	  };
-
-	  /**
-	   * Initialize the class instance.  Derived classes should implement this.
-	   *
-	   * @param {geo.feature.spec} arg The feature specification.
-	   */
-	  this._init = function (arg) {
-	    if (!m_layer) {
-	      throw new Error('Feature requires a valid layer');
-	    }
-	    m_style = $.extend({},
-	                {'opacity': 1.0}, arg.style === undefined ? {} :
-	                arg.style);
-	    m_this._bindMouseHandlers();
-	  };
-
-	  /**
-	   * Build.
-	   *
-	   * Derived classes should implement this.
-	   */
-	  this._build = function () {
-	  };
-
-	  /**
-	   * Update.
-	   *
-	   * Derived classes should implement this.
-	   */
-	  this._update = function () {
-	  };
-
-	  /**
-	   * Destroy.  Unbind mouse handlers, clear internal variables, and call the
-	   * parent destroy method.
-	   *
-	   * Derived classes should implement this.
-	   */
-	  this._exit = function () {
-	    m_this._unbindMouseHandlers();
-	    m_selectedFeatures = [];
-	    m_style = {};
-	    s_exit();
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	/**
-	 * The most recent `geo.feature.event` triggered.
-	 * @type {number}
-	 */
-	feature.eventID = 0;
-
-	/**
-	 * Create a feature.  This defines a general interface; see individual feature
-	 * types for specific details.
-	 *
-	 * @param {geo.layer} layer The layer to add the feature to.
-	 * @param {geo.feature.spec} spec The feature specification.  At least the
-	 *      `type` must be specified.
-	 * @returns {geo.feature|null} The created feature or `null` for a failure.
-	 */
-	feature.create = function (layer, spec) {
-	  'use strict';
-
-	  // Check arguments
-	  if (!(layer instanceof __webpack_require__(210))) {
-	    console.warn('Invalid layer');
-	    return null;
-	  }
-	  if (typeof spec !== 'object') {
-	    console.warn('Invalid spec');
-	    return null;
-	  }
-	  var type = spec.type;
-	  var feature = layer.createFeature(type, spec);
-	  if (!feature) {
-	    console.warn('Could not create feature type "' + type + '"');
-	    return null;
-	  }
-
-	  spec.data = spec.data || [];
-	  return feature.style(spec);
-	};
-
-	inherit(feature, sceneObject);
-	module.exports = feature;
-
-
-/***/ }),
-/* 208 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var object = __webpack_require__(203);
-
-	/**
-	 * Create a new instance of class sceneObject, which extends the object's
-	 * event handling with a tree-based event propagation.
-	 *
-	 * @class
-	 * @alias geo.sceneObject
-	 * @extends geo.object
-	 * @param {object} arg Options for the object.
-	 * @returns {geo.sceneObject}
-	 */
-	var sceneObject = function (arg) {
-	  'use strict';
-	  if (!(this instanceof sceneObject)) {
-	    return new sceneObject();
-	  }
-	  object.call(this, arg);
-
-	  var m_this = this,
-	      m_parent = null,
-	      m_children = [],
-	      s_exit = this._exit,
-	      s_trigger = this.geoTrigger,
-	      s_addPromise = this.addPromise,
-	      s_onIdle = this.onIdle;
-
-	  /**
-	   * Override object.addPromise to propagate up the scene tree.
-	   *
-	   * @param {Promise} promise A promise object.
-	   * @returns {this}
-	   */
-	  this.addPromise = function (promise) {
-	    if (m_parent) {
-	      m_parent.addPromise(promise);
-	    } else {
-	      s_addPromise(promise);
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * Override object.onIdle to propagate up the scene tree.
-	   *
-	   * @param {function} handler A function taking no arguments.
-	   * @returns {this}
-	   */
-	  this.onIdle = function (handler) {
-	    if (m_parent) {
-	      m_parent.onIdle(handler);
-	    } else {
-	      s_onIdle(handler);
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * Get/set parent of the object.
-	   *
-	   * @param {geo.sceneObject} [arg] The new parant or `undefined` to get the
-	   *    current parent.
-	   * @returns {this|geo.sceneObject}
-	   */
-	  this.parent = function (arg) {
-	    if (arg === undefined) {
-	      return m_parent;
-	    }
-	    m_parent = arg;
-	    return m_this;
-	  };
-
-	  /**
-	   * Add a child (or an array of children) to the object.
-	   *
-	   * @param {geo.object|geo.object[]} child A child object or array of child
-	   *    objects.
-	   * @returns {this}
-	   */
-	  this.addChild = function (child) {
-	    if (Array.isArray(child)) {
-	      child.forEach(m_this.addChild);
-	      return m_this;
-	    }
-	    child.parent(m_this);
-	    m_children.push(child);
-	    return m_this;
-	  };
-
-	  /**
-	   * Remove a child (or array of children) from the object.
-	   *
-	   * @param {geo.object|geo.object[]} child A child object or array of child
-	   *    objects.
-	   * @returns {this}
-	   */
-	  this.removeChild = function (child) {
-	    if (Array.isArray(child)) {
-	      child.forEach(m_this.removeChild);
-	      return m_this;
-	    }
-	    m_children = m_children.filter(function (c) { return c !== child; });
-	    return m_this;
-	  };
-
-	  /**
-	   * Get an array of the child objects.
-	   *
-	   * @returns {geo.object[]} A copy of the array of child objects.
-	   */
-	  this.children = function () {
-	    return m_children.slice();
-	  };
-
-	  /**
-	   * Force redraw of a scene object, to be implemented by subclasses.
-	   * Base class just calls draw of child objects.
-	   *
-	   * @param {object} arg Options to pass to the child draw functions.
-	   * @returns {this}
-	   */
-	  this.draw = function (arg) {
-	    m_this.children().forEach(function (child) {
-	      child.draw(arg);
-	    });
-	    return m_this;
-	  };
-
-	  /**
-	   * Trigger an event (or events) on this object and call all handlers.
-	   *
-	   * @param {string} event The event to trigger.
-	   * @param {object} args Arbitrary argument to pass to the handler.
-	   * @param {boolean} [childrenOnly] If truthy, only propagate down the tree.
-	   * @returns {this}
-	   */
-	  this.geoTrigger = function (event, args, childrenOnly) {
-
-	    var geoArgs;
-
-	    args = args || {};
-	    geoArgs = args.geo || {};
-	    args.geo = geoArgs;
-
-	    // stop propagation if requested by the handler
-	    if (geoArgs.stopPropagation) {
-	      return m_this;
-	    }
-
-	    // If the event was not triggered by the parent, just propagate up the tree
-	    if (!childrenOnly && m_parent && geoArgs._triggeredBy !== m_parent) {
-	      geoArgs._triggeredBy = m_this;
-	      m_parent.geoTrigger(event, args);
-	      return m_this;
-	    }
-
-	    // call the object's own handlers
-	    s_trigger.call(m_this, event, args);
-
-	    // stop propagation if requested by the handler
-	    if (geoArgs.stopPropagation) {
-	      return m_this;
-	    }
-
-	    // trigger the event on the children
-	    m_children.forEach(function (child) {
-	      if (child.geoTrigger) {
-	        geoArgs._triggeredBy = m_this;
-	        child.geoTrigger(event, args);
-	      }
-	    });
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Free all resources and destroy the object.
-	   */
-	  this._exit = function () {
-	    m_children = [];
-	    m_parent = null;
-	    s_exit();
-	  };
-
-	  return this;
-	};
-
-	inherit(sceneObject, object);
-	module.exports = sceneObject;
-
-
-/***/ }),
-/* 209 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var vgl = __webpack_require__(86);
-	var inherit = __webpack_require__(8);
-
-	/**
-	 * Create a new instance of class timestamp.
-	 *
-	 * @class geo.timestamp
-	 * @extends vgl.timestamp
-	 * @returns {geo.timestamp}
-	 */
-	var timestamp = function () {
-	  'use strict';
-	  if (!(this instanceof timestamp)) {
-	    return new timestamp();
-	  }
-	  vgl.timestamp.call(this);
-	};
-
-	inherit(timestamp, vgl.timestamp);
-	module.exports = timestamp;
-
-
-/***/ }),
-/* 210 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-	var feature = __webpack_require__(207);
-	var checkRenderer = __webpack_require__(201).checkRenderer;
-	var rendererForFeatures = __webpack_require__(201).rendererForFeatures;
-	var rendererForAnnotations = __webpack_require__(201).rendererForAnnotations;
-
-	/**
-	 * Create a new layer.
-	 *
-	 * @class
-	 * @alias geo.layer
-	 * @extends geo.sceneObject
-	 * @param {object} [arg] Options for the new layer.
-	 * @param {string} arg.attribution An attribution string to display
-	 * @param {number} arg.zIndex The z-index to assign to the layer (defaults
-	 *   to the index of the layer inside the map)
-	 * @returns {geo.layer}
-	 */
-	var layer = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof layer)) {
-	    return new layer(arg);
-	  }
-	  arg = arg || {};
-	  sceneObject.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var timestamp = __webpack_require__(209);
-	  var createRenderer = __webpack_require__(201).createRenderer;
-	  var newLayerId = __webpack_require__(83).newLayerId;
-	  var geo_event = __webpack_require__(9);
-	  var camera = __webpack_require__(211);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_id = arg.id === undefined ? layer.newLayerId() : arg.id,
-	      m_name = '',
-	      m_map = arg.map === undefined ? null : arg.map,
-	      m_node = null,
-	      m_canvas = null,
-	      m_renderer = null,
-	      m_initialized = false,
-	      m_rendererName = arg.renderer !== undefined ? arg.renderer : (
-	        arg.annotations ? rendererForAnnotations(arg.annotations) :
-	        rendererForFeatures(arg.features)),
-	      m_dataTime = timestamp(),
-	      m_updateTime = timestamp(),
-	      m_sticky = arg.sticky === undefined ? true : arg.sticky,
-	      m_active = arg.active === undefined ? true : arg.active,
-	      m_opacity = arg.opacity === undefined ? 1 : arg.opacity,
-	      m_attribution = arg.attribution || null,
-	      m_visible = arg.visible === undefined ? true : arg.visible,
-	      m_selectionAPI = arg.selectionAPI === undefined ? true : arg.selectionAPI,
-	      m_zIndex;
-
-	  m_rendererName = checkRenderer(m_rendererName);
-
-	  if (!m_map) {
-	    throw new Error('Layers must be initialized on a map.');
-	  }
-
-	  /**
-	   * Get the name of the renderer.
-	   *
-	   * @returns {string}
-	   */
-	  this.rendererName = function () {
-	    return m_rendererName;
-	  };
-
-	  /**
-	   * Get or set the z-index of the layer.  The z-index controls the display
-	   * order of the layers in much the same way as the CSS z-index property.
-	   *
-	   * @param {number} [zIndex] The new z-index, or undefined to return the
-	   *    current z-index.
-	   * @param {boolean} [allowDuplicate] When setting the z index, if this is
-	   *    truthy, allow other layers to have the same z-index.  Otherwise,
-	   *    ensure that other layers have distinct z-indices from this one.
-	   * @returns {number|this}
-	   */
-	  this.zIndex = function (zIndex, allowDuplicate) {
-	    if (zIndex === undefined) {
-	      return m_zIndex;
-	    }
-	    if (!allowDuplicate) {
-	      // if any extant layer has the same index, then we move all of those
-	      // layers up.  We do this in reverse order since, if two layers above
-	      // this one share a z-index, they will resolve to the layer insert order.
-	      m_map.children().reverse().forEach(function (child) {
-	        if (child.zIndex && child !== this && child.zIndex() === zIndex) {
-	          child.zIndex(zIndex + 1);
-	        }
-	      });
-	    }
-	    m_zIndex = zIndex;
-	    m_node.css('z-index', m_zIndex);
-	    return m_this;
-	  };
-
-	  /**
-	   * Bring the layer above the given number of layers.  This will rotate the
-	   * current z-indices for this and the next `n` layers.
-	   *
-	   * @param {number} [n=1] The number of positions to move.
-	   * @returns {this}
-	   */
-	  this.moveUp = function (n) {
-	    var order, i, me = null, tmp, sign;
-
-	    // set the default
-	    if (n === undefined) {
-	      n = 1;
-	    }
-
-	    // set the sort direction that controls if we are moving up
-	    // or down the z-index
-	    sign = 1;
-	    if (n < 0) {
-	      sign = -1;
-	      n = -n;
-	    }
-
-	    // get a sorted list of layers
-	    order = m_this.map().layers().sort(
-	      function (a, b) { return sign * (a.zIndex() - b.zIndex()); }
-	    );
-
-	    for (i = 0; i < order.length; i += 1) {
-	      if (me === null) {
-	        // loop until we get to the current layer
-	        if (order[i] === m_this) {
-	          me = i;
-	        }
-	      } else if (i - me <= n) {
-	        // swap the next n layers
-	        tmp = m_this.zIndex();
-	        m_this.zIndex(order[i].zIndex(), true);
-	        order[i].zIndex(tmp, true);
-	      } else {
-	        // all the swaps are done now
-	        break;
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Bring the layer below the given number of layers.  This will rotate the
-	   * current z-indices for this and the previous `n` layers.
-	   *
-	   * @param {number} [n=1] The number of positions to move.
-	   * @returns {this}
-	   */
-	  this.moveDown = function (n) {
-	    if (n === undefined) {
-	      n = 1;
-	    }
-	    return m_this.moveUp(-n);
-	  };
-
-	  /**
-	   * Bring the layer to the top of the map layers.
-	   *
-	   * @returns {this}
-	   */
-	  this.moveToTop = function () {
-	    return m_this.moveUp(m_this.map().children().length - 1);
-	  };
-
-	  /**
-	   * Bring the layer to the bottom of the map layers.
-	   *
-	   * @returns {this}
-	   */
-	  this.moveToBottom = function () {
-	    return m_this.moveDown(m_this.map().children().length - 1);
-	  };
-
-	  /**
-	   * Get whether or not the layer is sticky (navigates with the map).
-	   *
-	   * @returns {boolean}
-	   */
-	  this.sticky = function () {
-	    return m_sticky;
-	  };
-
-	  /**
-	   * Get/Set whether or not the layer is active.  An active layer will receive
-	   * native mouse when the layer is on top.  Non-active layers will never
-	   * receive native mouse events.
-	   *
-	   * @param {boolean} [arg] If specified, the new `active` value.
-	   * @returns {boolean|object}
-	   */
-	  this.active = function (arg) {
-	    if (arg === undefined) {
-	      return m_active;
-	    }
-	    if (m_active !== arg) {
-	      m_active = arg;
-	      m_node.toggleClass('active', m_active);
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * Get root node of the layer.
-	   *
-	   * @returns {div}
-	   */
-	  this.node = function () {
-	    return m_node;
-	  };
-
-	  /**
-	   * Get/Set id of the layer.
-	   *
-	   * @param {string} [val] If specified, the new id of the layer.
-	   * @returns {string|this}
-	   */
-	  this.id = function (val) {
-	    if (val === undefined) {
-	      return m_id;
-	    }
-	    m_id = newLayerId();
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set name of the layer.
-	   *
-	   * @param {string} [val] If specified, the new name of the layer.
-	   * @returns {string|this}
-	   */
-	  this.name = function (val) {
-	    if (val === undefined) {
-	      return m_name;
-	    }
-	    m_name = val;
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the map associated with this layer.
-	   *
-	   * @returns {geo.map} The map associated with the layer.
-	   */
-	  this.map = function () {
-	    return m_map;
-	  };
-
-	  /**
-	   * Get renderer for the layer.
-	   *
-	   * @returns {geo.renderer} The renderer associated with the layer or `null`
-	   *    if there is no renderer.
-	   */
-	  this.renderer = function () {
-	    return m_renderer;
-	  };
-
-	  /**
-	   * Get canvas of the layer.
-	   *
-	   * @returns {HTMLCanvasElement} The canvas element associated with the
-	   *    layer.
-	   */
-	  this.canvas = function () {
-	    return m_canvas;
-	  };
-
-	  /**
-	   * Return last time data got changed.
-	   *
-	   * @returns {geo.timestamp} The data time.
-	   */
-	  this.dataTime = function () {
-	    return m_dataTime;
-	  };
-
-	  /**
-	   * Return the modified time for the last update that did something.
-	   *
-	   * @returns {geo.timestamp} The update time.
-	   */
-	  this.updateTime = function () {
-	    return m_updateTime;
-	  };
-
-	  /**
-	   * Get/Set if the layer has been initialized.
-	   *
-	   * @param {boolean} [val] If specified, update the intialized value.
-	   *    Otherwise, return it.
-	   * @returns {boolean|this} Either the initialized value or this.
-	   */
-	  this.initialized = function (val) {
-	    if (val !== undefined) {
-	      m_initialized = val;
-	      return m_this;
-	    }
-	    return m_initialized;
-	  };
-
-	  /**
-	   * Transform coordinates from world coordinates into a local coordinate
-	   * system specific to the underlying renderer.  This method is exposed
-	   * to allow direct access the rendering context, but otherwise should
-	   * not be called directly.  The default implementation is the identity
-	   * operator.
-	   *
-	   * @param {geo.geoPosition} input World coordinates.
-	   * @returns {geo.geoPosition} Renderer coordinates.
-	   */
-	  this.toLocal = function (input) {
-	    if (m_this._toLocalMatrix) {
-	      camera.applyTransform(m_this._toLocalMatrix, input);
-	    }
-	    return input;
-	  };
-
-	  /**
-	   * Transform coordinates from a local coordinate system to world coordinates.
-	   *
-	   * @param {geo.geoPosition} input Renderer coordinates.
-	   * @returns {geo.geoPosition} World coordinates.
-	   */
-	  this.fromLocal = function (input) {
-	    if (m_this._fromLocalMatrix) {
-	      camera.applyTransform(m_this._fromLocalMatrix, input);
-	    }
-	    return input;
-	  };
-
-	  /**
-	   * Get or set the attribution html content that will displayed with the
-	   * layer.  By default, nothing will be displayed.  Note, this content
-	   * is **not** html escaped, so care should be taken when renderering
-	   * user provided content.
-	   *
-	   * @param {string?} arg An html fragment
-	   * @returns {string|this} Chainable as a setter
-	   */
-	  this.attribution = function (arg) {
-	    if (arg !== undefined) {
-	      m_attribution = arg;
-	      m_this.map().updateAttribution();
-	      return m_this;
-	    }
-	    return m_attribution;
-	  };
-
-	  /**
-	   * Get/Set visibility of the layer.
-	   *
-	   * @param {boolean} [val] If specified, change the visibility.  Otherwise,
-	   *    get it.
-	   * @returns {boolean|this} either the visibility (if getting) or the layer
-	   *    (if setting).
-	   */
-	  this.visible = function (val) {
-	    if (val === undefined) {
-	      return m_visible;
-	    }
-	    if (m_visible !== val) {
-	      m_visible = val;
-	      m_node.css('display', m_visible ? '' : 'none');
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set selectionAPI of the layer.
-	   *
-	   * @param {boolean} [val] If specified, set the selectionAPI state, otherwise
-	   *    return it.
-	   * @returns {boolean|this} Either the selectionAPI state or the layer.
-	   */
-	  this.selectionAPI = function (val) {
-	    if (val === undefined) {
-	      return m_selectionAPI;
-	    }
-	    if (m_selectionAPI !== val) {
-	      m_selectionAPI = val;
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Init layer.
-	   *
-	   * @param {boolean} noEvents If a subclass of this intends to bind the
-	   *    resize, pan, and zoom events itself, set this flag to true to avoid
-	   *    binding them here.
-	   * @returns {this}
-	   */
-	  this._init = function (noEvents) {
-	    if (m_initialized) {
-	      return m_this;
-	    }
-
-	    m_map.node().append(m_node);
-
-	    /* Pass along the arguments, but not the map reference */
-	    var options = $.extend({}, arg);
-	    delete options.map;
-
-	    if (m_rendererName === null) {
-	      // if given a "null" renderer, then pass the map element as the
-	      // canvas
-	      m_renderer = null;
-	      m_canvas = m_node;
-	    } else if (m_canvas) { // Share context if we have valid one
-	      m_renderer = createRenderer(m_rendererName, m_this, m_canvas, options);
-	    } else {
-	      m_renderer = createRenderer(m_rendererName, m_this, undefined, options);
-	      m_canvas = m_renderer.canvas();
-	    }
-
-	    m_node.toggleClass('active', m_this.active());
-
-	    m_initialized = true;
-
-	    if (!noEvents) {
-	      // Bind events to handlers
-	      m_this.geoOn(geo_event.resize, function (event) {
-	        m_this._update({event: event});
-	      });
-
-	      m_this.geoOn(geo_event.pan, function (event) {
-	        m_this._update({event: event});
-	      });
-
-	      m_this.geoOn(geo_event.rotate, function (event) {
-	        m_this._update({event: event});
-	      });
-
-	      m_this.geoOn(geo_event.zoom, function (event) {
-	        m_this._update({event: event});
-	      });
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Clean up resources.
-	   */
-	  this._exit = function () {
-	    m_this.geoOff();
-	    if (m_renderer) {
-	      m_renderer._exit();
-	    }
-	    m_node.off();
-	    m_node.remove();
-	    arg = {};
-	    m_canvas = null;
-	    m_renderer = null;
-	    s_exit();
-	  };
-
-	  /**
-	   * Update layer.
-	   *
-	   * This is a stub that should be subclasses.
-	   */
-	  this._update = function () {
-	  };
-
-	  /**
-	   * Return the width of the layer in pixels.
-	   *
-	   * @returns {number} The width of the parent map in pixels.
-	   */
-	  this.width = function () {
-	    return m_this.map().size().width;
-	  };
-
-	  /**
-	   * Return the height of the layer in pixels.
-	   *
-	   * @returns {number} The height of the parent map in pixels.
-	   */
-	  this.height = function () {
-	    return m_this.map().size().height;
-	  };
-
-	  /**
-	   * Get or set the current layer opacity.  The opacity is in the range [0-1].
-	   *
-	   * @param {number} [opac] If specified, set the opacity.  Otherwise, return
-	   *    the opacity.
-	   * @returns {number|this} The current opacity or the current layer.
-	   */
-	  this.opacity = function (opac) {
-	    if (opac !== undefined) {
-	      m_opacity = opac;
-	      m_node.css('opacity', m_opacity);
-	      return m_this;
-	    }
-	    return m_opacity;
-	  };
-
-	  if (arg.zIndex === undefined) {
-	    var maxZ = -1;
-	    m_map.children().forEach(function (child) {
-	      if (child.zIndex) {
-	        maxZ = Math.max(maxZ, child.zIndex());
-	      }
-	    });
-	    arg.zIndex = maxZ + 1;
-	  }
-	  m_zIndex = arg.zIndex;
-
-	  // Create top level div for the layer
-	  m_node = $(document.createElement('div'));
-	  m_node.addClass('geojs-layer');
-	  m_node.attr('id', m_name);
-	  m_this.opacity(m_opacity);
-
-	  // set the z-index
-	  m_this.zIndex(m_zIndex);
-
-	  return m_this;
-	};
-
-	/**
-	 * Gets a new id number for a layer.
-	 * @protected
-	 * @instance
-	 * @returns {number}
-	 */
-	layer.newLayerId = (function () {
-	  'use strict';
-	  var currentId = 1;
-	  return function () {
-	    var id = currentId;
-	    currentId += 1;
-	    return id;
-	  };
-	}());
-
-	/**
-	 * General object specification for feature types.
-	 * @typedef geo.layer.spec
-	 * @type {object}
-	 * @property {string} [type='feature'] For feature compatibility with more than
-	 *    one kind of creatable layer
-	 * @property {object[]} [data=[]] The default data array to apply to each
-	 *    feature if none exists
-	 * @property {string} [renderer='vgl'] The renderer to use
-	 * @property {geo.feature.spec[]} [features=[]] Features to add to the layer.
-	 */
-
-	/**
-	 * Create a layer from an object.  Any errors in the creation
-	 * of the layer will result in returning null.
-	 * @param {geo.map} map The map to add the layer to
-	 * @param {geo.layer.spec} spec The object specification
-	 * @returns {geo.layer|null}
-	 */
-	layer.create = function (map, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-
-	  // add osmLayer later
-	  spec.type = 'feature';
-	  if (spec.type !== 'feature') {
-	    console.warn('Unsupported layer type');
-	    return null;
-	  }
-
-	  spec.renderer = spec.renderer || 'vgl';
-	  spec.renderer = checkRenderer(spec.renderer);
-
-	  if (!spec.renderer) {
-	    console.warn('Invalid renderer');
-	    return null;
-	  }
-
-	  var layer = map.createLayer(spec.type, spec);
-	  if (!layer) {
-	    console.warn('Unable to create a layer');
-	    return null;
-	  }
-
-	  // probably move this down to featureLayer eventually
-	  spec.features.forEach(function (f) {
-	    f.data = f.data || spec.data;
-	    f.feature = feature.create(layer, f);
-	  });
-
-	  return layer;
-	};
-
-	inherit(layer, sceneObject);
-	module.exports = layer;
-
-
-/***/ }),
-/* 211 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	(function () {
-	  'use strict';
-
-	  var inherit = __webpack_require__(8);
-	  var object = __webpack_require__(203);
-	  var util = __webpack_require__(83);
-	  var mat4 = __webpack_require__(88);
-	  var vec4 = __webpack_require__(112);
-
-	  /**
-	   * This class defines the raw interface for a camera.  At a low level, the
-	   * camera provides a methods for converting between a map's coordinate system
-	   * to display pixel coordinates.
-	   *
-	   * For the moment, all camera transforms are assumed to be expressible as
-	   * 4x4 matrices.  More general cameras may follow that break this assumption.
-	   *
-	   * The interface for the camera is relatively stable for "map-like" views,
-	   * e.g. when the camera is pointing in the direction [0, 0, -1], and placed
-	   * above the z=0 plane.  More general view changes and events have not yet
-	   * been defined.
-	   *
-	   * The camera emits the following events when the view changes:
-	   *
-	   *   * {@link geo.event.camera.pan} when the camera is translated in the
-	   *       x/y plane
-	   *   * {@link geo.event.camera.zoom} when the camera is changed in a way
-	   *       that modifies the current zoom level
-	   *   * {@link geo.event.camera.view} when the visible bounds change for
-	   *       any reason
-	   *   * {@link geo.event.camera.projection} when the projection type changes
-	   *   * {@link geo.event.camera.viewport} when the viewport changes
-	   *
-	   * By convention, protected methods do not update the internal matrix state,
-	   * public methods do.  There are a few primary methods that are intended to
-	   * be used by external classes to mutate the internal state:
-	   *
-	   *   * bounds: Set the visible bounds (for initialization and zooming)
-	   *   * pan: Translate the camera in x/y by an offset (for panning)
-	   *   * viewFromCenterSizeRotation: set the camera view based on a center
-	   *        point, boundary size, and rotation angle.
-	   *
-	   * @class
-	   * @alias geo.camera
-	   * @extends geo.object
-	   * @param {object?} spec Options argument
-	   * @param {string} spec.projection One of the supported geo.camera.projection
-	   * @param {object} spec.viewport The initial camera viewport
-	   * @param {object} spec.viewport.width
-	   * @param {object} spec.viewport.height
-	   * @returns {geo.camera}
-	   */
-	  var camera = function (spec) {
-	    if (!(this instanceof camera)) {
-	      return new camera(spec);
-	    }
-
-	    var geo_event = __webpack_require__(9);
-
-	    spec = spec || {};
-	    object.call(this, spec);
-
-	    /**
-	     * The view matrix
-	     * @protected
-	     */
-	    this._view = util.mat4AsArray();
-
-	    /**
-	     * The projection matrix
-	     * @protected
-	     */
-	    this._proj = util.mat4AsArray();
-
-	    /**
-	     * The projection type (one of `this.constructor.projection`)
-	     * @protected
-	     */
-	    this._projection = null;
-
-	    /**
-	     * The transform matrix (view * proj)
-	     * @protected
-	     */
-	    this._transform = util.mat4AsArray();
-
-	    /**
-	     * The inverse transform matrix (view * proj)^-1
-	     * @protected
-	     */
-	    this._inverse = util.mat4AsArray();
-
-	    /**
-	     * Cached bounds object recomputed on demand.
-	     * @protected
-	     */
-	    this._bounds = null;
-
-	    /**
-	     * Cached "display" matrix recomputed on demand.
-	     * @see {@link geo.camera.display}
-	     * @protected
-	     */
-	    this._display = null;
-
-	    /**
-	     * Cached "world" matrix recomputed on demand.
-	     * @see {@link geo.camera.world}
-	     * @protected
-	     */
-	    this._world = null;
-
-	    /**
-	     * The viewport parameters size and offset.
-	     * @property {number} height Viewport height in pixels
-	     * @property {number} width Viewport width in pixels
-	     * @protected
-	     */
-	    this._viewport = {width: 1, height: 1};
-
-	    /**
-	     * Set up the projection matrix for the current projection type.
-	     * @protected
-	     */
-	    this._createProj = function () {
-	      var s = this.constructor.bounds.near / this.constructor.bounds.far;
-
-	      // call mat4.frustum or mat4.ortho here
-	      if (this._projection === 'perspective') {
-	        mat4.frustum(
-	          this._proj,
-	          this.constructor.bounds.left * s,
-	          this.constructor.bounds.right * s,
-	          this.constructor.bounds.bottom * s,
-	          this.constructor.bounds.top * s,
-	          -this.constructor.bounds.near,
-	          -this.constructor.bounds.far
-	        );
-	      } else if (this._projection === 'parallel') {
-	        mat4.ortho(
-	          this._proj,
-	          this.constructor.bounds.left,
-	          this.constructor.bounds.right,
-	          this.constructor.bounds.bottom,
-	          this.constructor.bounds.top,
-	          this.constructor.bounds.near,
-	          this.constructor.bounds.far
-	        );
-	      }
-	    };
-
-	    /**
-	     * Update the internal state of the camera on change to camera
-	     * parameters.
-	     * @protected
-	     */
-	    this._update = function () {
-	      this._bounds = null;
-	      this._display = null;
-	      this._world = null;
-	      this._transform = camera.combine(this._proj, this._view);
-	      mat4.invert(this._inverse, this._transform);
-	      this.geoTrigger(geo_event.camera.view, {
-	        camera: this
-	      });
-	    };
-
-	    /**
-	     * Getter/setter for the view matrix.
-	     * @note copies the matrix value on set.
-	     */
-	    Object.defineProperty(this, 'view', {
-	      get: function () {
-	        return this._view;
-	      },
-	      set: function (view) {
-	        mat4.copy(this._view, view);
-	        this._update();
-	      }
-	    });
-
-	    /**
-	     * Getter/setter for the view bounds.
-	     *
-	     * If not provided, near and far bounds will be set to [-1, 1] by
-	     * default.  We will probably want to change this to a unit specific
-	     * value initialized by the map when drawing true 3D objects or
-	     * tilting the camera.
-	     *
-	     * Returned near/far bounds are also -1, 1 for the moment.
-	     */
-	    Object.defineProperty(this, 'bounds', {
-	      get: function () {
-	        if (this._bounds === null) {
-	          this._bounds = this._getBounds();
-	        }
-	        return this._bounds;
-	      },
-	      set: function (bounds) {
-	        this._setBounds(bounds);
-	        this._update();
-	      }
-	    });
-
-	    /**
-	     * Getter for the "display" matrix.  This matrix converts from
-	     * world coordinates into display coordinates.  This matrix exists to
-	     * generate matrix3d css transforms that can be used in layers that
-	     * render on the DOM.
-	     */
-	    Object.defineProperty(this, 'display', {
-	      get: function () {
-	        var mat;
-	        if (this._display === null) {
-	          mat = camera.affine(
-	            {x: 1, y: 1}, // translate to: [0, 2] x [0, 2]
-	            {
-	              x: this.viewport.width / 2,
-	              y: this.viewport.height / -2
-	            }             // scale to: [0, width] x [-height, 0]
-	          );
-
-	          // applies mat to the transform (world -> normalized)
-	          this._display = camera.combine(
-	            mat,
-	            this._transform
-	          );
-	        }
-	        return this._display;
-	      }
-	    });
-
-	    /**
-	     * Getter for the "world" matrix.  This matrix converts from
-	     * display coordinates into world coordinates.  This is constructed
-	     * by inverting the "display" matrix.
-	     */
-	    Object.defineProperty(this, 'world', {
-	      get: function () {
-	        if (this._world === null) {
-	          this._world = mat4.invert(
-	            util.mat4AsArray(),
-	            this.display
-	          );
-	        }
-	        return this._world;
-	      }
-	    });
-
-	    /**
-	     * Getter/setter for the projection type.
-	     */
-	    Object.defineProperty(this, 'projection', {
-	      get: function () {
-	        return this._projection;
-	      },
-	      set: function (type) {
-	        if (!this.constructor.projection[type]) {
-	          throw new Error('Unsupported projection type: ' + type);
-	        }
-	        if (type !== this._projection) {
-	          this._projection = type;
-	          this._createProj();
-	          this._update();
-	          this.geoTrigger(geo_event.camera.projection, {
-	            camera: this,
-	            projection: type
-	          });
-	        }
-	      }
-	    });
-
-	    /**
-	     * Getter for the projection matrix (when applicable).
-	     * This generally shouldn't be modified directly because
-	     * the rest of the code assumes that the clipping bounds
-	     * are [-1, -1, -1] to [1, 1, 1] in camera coordinates.
-	     */
-	    Object.defineProperty(this, 'projectionMatrix', {
-	      get: function () {
-	        return this._proj;
-	      }
-	    });
-
-	    /**
-	     * Getter for the transform matrix.
-	     */
-	    Object.defineProperty(this, 'transform', {
-	      get: function () {
-	        return this._transform;
-	      }
-	    });
-
-	    /**
-	     * Getter for the inverse transform matrix.
-	     */
-	    Object.defineProperty(this, 'inverse', {
-	      get: function () {
-	        return this._inverse;
-	      }
-	    });
-
-	    /**
-	     * Getter/setter for the viewport.
-	     *
-	     * The viewport consists of a width and height in pixels, plus a left and
-	     * top offset in pixels.  The offsets are only used to determine if pixel
-	     * alignment is possible.
-	     */
-	    Object.defineProperty(this, 'viewport', {
-	      get: function () {
-	        return {
-	          width: this._viewport.width,
-	          height: this._viewport.height,
-	          left: this._viewport.left,
-	          top: this._viewport.top
-	        };
-	      },
-	      set: function (viewport) {
-	        if (!(viewport.width > 0 &&
-	              viewport.height > 0)) {
-	          throw new Error('Invalid viewport dimensions');
-	        }
-	        if (viewport.width === this._viewport.width &&
-	            viewport.height === this._viewport.height) {
-	          return;
-	        }
-
-	        // apply scaling to the view matrix to account for the new aspect ratio
-	        // without changing the apparent zoom level
-	        if (this._viewport.width && this._viewport.height) {
-	          this._scale([
-	            this._viewport.width / viewport.width,
-	            this._viewport.height / viewport.height,
-	            1
-	          ]);
-
-	          // translate by half the difference to keep the center the same
-	          this._translate([
-	            (viewport.width - this._viewport.width) / 2,
-	            (viewport.height - this._viewport.height) / 2,
-	            0
-	          ]);
-	        }
-
-	        this._viewport = {
-	          width: viewport.width,
-	          height: viewport.height,
-	          left: viewport.left,
-	          top: viewport.top
-	        };
-	        this._update();
-	        this.geoTrigger(geo_event.camera.viewport, {
-	          camera: this,
-	          viewport: this.viewport
-	        });
-	      }
-	    });
-
-	    /**
-	     * Reset the view matrix to its initial (identity) state.
-	     * @protected
-	     * @returns {this} Chainable.
-	     */
-	    this._resetView = function () {
-	      mat4.identity(this._view);
-	      return this;
-	    };
-
-	    /**
-	     * Uses `mat4.translate` to translate the camera by the given vector amount.
-	     * @protected
-	     * @param {vec3|Array} offset The camera translation vector.
-	     * @returns {this} Chainable.
-	     */
-	    this._translate = function (offset) {
-	      mat4.translate(this._view, this._view, offset);
-	      return this;
-	    };
-
-	    /**
-	     * Uses `mat4.scale` to scale the camera by the given vector amount.
-	     * @protected
-	     * @param {vec3|Array} scale The scaling vector.
-	     * @returns {this} Chainable.
-	     */
-	    this._scale = function (scale) {
-	      mat4.scale(this._view, this._view, scale);
-	      return this;
-	    };
-
-	    /**
-	     * Project a vec4 from world space into clipped space [-1, 1] in place.
-	     * @protected
-	     * @param {vec4} point The point in world coordinates (mutated).
-	     * @returns {vec4} The point in clip space coordinates.
-	     */
-	    this._worldToClip4 = function (point) {
-	      return camera.applyTransform(this._transform, point);
-	    };
-
-	    /**
-	     * Project a vec4 from clipped space into world space in place.
-	     * @protected
-	     * @param {vec4} point The point in clipped coordinates (mutated).
-	     * @returns {vec4} The point in world space coordinates.
-	     */
-	    this._clipToWorld4 = function (point) {
-	      return camera.applyTransform(this._inverse, point);
-	    };
-
-	    /**
-	     * Apply the camera's projection transform to the given point.
-	     * @param {vec4} pt a point in clipped coordinates.
-	     * @returns {vec4} the point in normalized coordinates.
-	     */
-	    this.applyProjection = function (pt) {
-	      var w;
-	      if (this._projection === 'perspective') {
-	        w = 1 / (pt[3] || 1);
-	        pt[0] = w * pt[0];
-	        pt[1] = w * pt[1];
-	        pt[2] = w * pt[2];
-	        pt[3] = w;
-	      } else {
-	        pt[3] = 1;
-	      }
-	      return pt;
-	    };
-
-	    /**
-	     * Unapply the camera's projection transform from the given point.
-	     * @param {vec4} pt a point in normalized coordinates.
-	     * @returns {vec4} the point in clipped coordinates.
-	     */
-	    this.unapplyProjection = function (pt) {
-	      var w;
-	      if (this._projection === 'perspective') {
-	        w = pt[3] || 1;
-	        pt[0] = w * pt[0];
-	        pt[1] = w * pt[1];
-	        pt[2] = w * pt[2];
-	        pt[3] = w;
-	      } else {
-	        pt[3] = 1;
-	      }
-	      return pt;
-	    };
-
-	    /**
-	     * Project a vec4 from world space into viewport space.
-	     * @param {vec4} point The point in world coordinates (mutated).
-	     * @returns {vec4} The point in display coordinates.
-	     *
-	     * @note For the moment, this computation assumes the following:
-	     *   * point[3] > 0
-	     *   * depth range [0, 1]
-	     *
-	     * The clip space z and w coordinates are returned with the window
-	     * x/y coordinates.
-	     */
-	    this.worldToDisplay4 = function (point) {
-	      // This is because z = 0 is the far plane exposed to the user, but
-	      // internally the far plane is at -2.
-	      point[2] -= 2;
-
-	      // convert to clip space
-	      this._worldToClip4(point);
-
-	      // apply projection specific transformation
-	      point = this.applyProjection(point);
-
-	      // convert to display space
-	      point[0] = this._viewport.width * (1 + point[0]) / 2.0;
-	      point[1] = this._viewport.height * (1 - point[1]) / 2.0;
-	      point[2] = (1 + point[2]) / 2.0;
-	      return point;
-	    };
-
-	    /**
-	     * Project a vec4 from display space into world space in place.
-	     * @param {vec4} point The point in display coordinates (mutated).
-	     * @returns {vec4} The point in world space coordinates.
-	     *
-	     * @note For the moment, this computation assumes the following:
-	     *   * point[3] > 0
-	     *   * depth range [0, 1]
-	     */
-	    this.displayToWorld4 = function (point) {
-	      // convert to clip space
-	      point[0] = 2 * point[0] / this._viewport.width - 1;
-	      point[1] = -2 * point[1] / this._viewport.height + 1;
-	      point[2] = 2 * point[2] - 1;
-
-	      // invert projection transform
-	      point = this.unapplyProjection(point);
-
-	      // convert to world coordinates
-	      this._clipToWorld4(point);
-
-	      // move far surface to z = 0
-	      point[2] += 2;
-	      return point;
-	    };
-
-	    /**
-	     * Project a point object from world space into viewport space.
-	     * @param {object} point The point in world coordinates.
-	     * @param {number} point.x
-	     * @param {number} point.y
-	     * @returns {object} The point in display coordinates.
-	     */
-	    this.worldToDisplay = function (point) {
-	      // define some magic numbers:
-	      var z = 0, // z coordinate of the surface in world coordinates
-	          w = 1; // enables perspective divide (i.e. for point conversion)
-	      point = this.worldToDisplay4(
-	        [point.x, point.y, z, w]
-	      );
-	      return {x: point[0], y: point[1]};
-	    };
-
-	    /**
-	     * Project a point object from viewport space into world space.
-	     * @param {object} point The point in display coordinates.
-	     * @param {number} point.x
-	     * @param {number} point.y
-	     * @returns {object} The point in world coordinates.
-	     */
-	    this.displayToWorld = function (point) {
-	      // define some magic numbers:
-	      var z = 1, // the z coordinate of the surface
-	          w = 2; // perspective divide at z = 1
-	      point = this.displayToWorld4(
-	        [point.x, point.y, z, w]
-	      );
-	      return {x: point[0], y: point[1]};
-	    };
-
-	    /**
-	     * Calculate the current bounds in world coordinates from the
-	     * current view matrix.  This computes a matrix vector multiplication
-	     * so the result is cached for public facing methods.
-	     *
-	     * @protected
-	     * @returns {object} bounds object.
-	     */
-	    this._getBounds = function () {
-	      var ul, ur, ll, lr, bds = {};
-
-	      // get corners
-	      ul = this.displayToWorld({x: 0, y: 0});
-	      ur = this.displayToWorld({x: this._viewport.width, y: 0});
-	      ll = this.displayToWorld({x: 0, y: this._viewport.height});
-	      lr = this.displayToWorld({
-	        x: this._viewport.width,
-	        y: this._viewport.height
-	      });
-
-	      bds.left = Math.min(ul.x, ur.x, ll.x, lr.x);
-	      bds.bottom = Math.min(ul.y, ur.y, ll.y, lr.y);
-	      bds.right = Math.max(ul.x, ur.x, ll.x, lr.x);
-	      bds.top = Math.max(ul.y, ur.y, ll.y, lr.y);
-
-	      return bds;
-	    };
-
-	    /**
-	     * Sets the view matrix so that the given world bounds
-	     * are in view.  To account for the viewport aspect ratio,
-	     * the resulting bounds may be larger in width or height than
-	     * the requested bound, but should be centered in the frame.
-	     *
-	     * @protected
-	     * @param {object} bounds
-	     * @param {number} bounds.left
-	     * @param {number} bounds.right
-	     * @param {number} bounds.bottom
-	     * @param {number} bounds.top
-	     * @param {number?} bounds.near Currently ignored.
-	     * @param {number?} bounds.far Currently ignored.
-	     * @returns {this} Chainable.
-	     */
-	    this._setBounds = function (bounds) {
-	      var size = {
-	        width: bounds.right - bounds.left,
-	        height: bounds.top - bounds.bottom
-	      };
-	      var center = {
-	        x: (bounds.left + bounds.right) / 2,
-	        y: (bounds.bottom + bounds.top) / 2
-	      };
-
-	      this._viewFromCenterSizeRotation(center, size, 0);
-	      return this;
-	    };
-
-	    /**
-	     * Sets the view matrix so that the given world center is centered, at
-	     * least a certain width and height are visible, and a rotation is applied.
-	     * The resulting bounds may be larger in width or height than the values if
-	     * the viewport is a different aspect ratio.
-	     *
-	     * @protected
-	     * @param {object} center Center of the view in gcs coordinates.
-	     * @param {number} center.x
-	     * @param {number} center.y
-	     * @param {object} size Minimum size of the view in gcs units.
-	     * @param {number} size.width
-	     * @param {number} size.height
-	     * @param {number} rotation in clockwise radians.  Optional.
-	     * @returns {this} Chainable.
-	     */
-	    this._viewFromCenterSizeRotation = function (center, size, rotation) {
-	      var translate = util.vec3AsArray(),
-	          scale = util.vec3AsArray(),
-	          c_ar, v_ar, w, h;
-
-	      // reset view to the identity
-	      this._resetView();
-
-	      w = Math.abs(size.width);
-	      h = Math.abs(size.height);
-	      c_ar = w / h;
-	      v_ar = this._viewport.width / this._viewport.height;
-
-	      if (c_ar >= v_ar) {
-	        // grow camera bounds vertically
-	        h = w / v_ar;
-	        scale[0] = 2 / w;
-	        scale[1] = 2 / h;
-	      } else {
-	        // grow bounds horizontally
-	        w = h * v_ar;
-	        scale[0] = 2 / w;
-	        scale[1] = 2 / h;
-	      }
-
-	      scale[2] = 1;
-	      this._scale(scale);
-
-	      if (rotation) {
-	        this._rotate(rotation);
-	      }
-
-	      // translate to the new center.
-	      translate[0] = -center.x;
-	      translate[1] = -center.y;
-	      translate[2] = 0;
-
-	      this._translate(translate);
-
-	      return this;
-	    };
-
-	    /**
-	     * Sets the view matrix so that the given world center is centered, at
-	     * least a certain width and height are visible, and a rotation is applied.
-	     * The resulting bounds may be larger in width or height than the values if
-	     * the viewport is a different aspect ratio.
-	     *
-	     * @param {object} center Center of the view in gcs coordinates.
-	     * @param {number} center.x
-	     * @param {number} center.y
-	     * @param {object} size Minimum size of the view in gcs units.
-	     * @param {number} size.width
-	     * @param {number} size.height
-	     * @param {number} rotation in clockwise radians.  Optional.
-	     * @returns {this} Chainable.
-	     */
-	    this.viewFromCenterSizeRotation = function (center, size, rotation) {
-	      this._viewFromCenterSizeRotation(center, size, rotation);
-	      this._update();
-	      return this;
-	    };
-
-	    /**
-	     * Pans the view matrix by the given amount.
-	     *
-	     * @param {object} offset The delta in world space coordinates.
-	     * @param {number} offset.x
-	     * @param {number} offset.y
-	     * @param {number} [offset.z=0]
-	     * @returns {this} Chainable.
-	     */
-	    this.pan = function (offset) {
-	      if (!offset.x && !offset.y && !offset.z) {
-	        return;
-	      }
-	      this._translate([
-	        offset.x,
-	        offset.y,
-	        offset.z || 0
-	      ]);
-	      this._update();
-	      return this;
-	    };
-
-	    /**
-	     * Zooms the view matrix by the given amount.
-	     *
-	     * @param {number} zoom The zoom scale to apply
-	     * @returns {this} Chainable.
-	     */
-	    this.zoom = function (zoom) {
-	      if (zoom === 1) {
-	        return;
-	      }
-	      mat4.scale(this._view, this._view, [
-	        zoom,
-	        zoom,
-	        zoom
-	      ]);
-	      this._update();
-	      return this;
-	    };
-
-	    /**
-	     * Rotate the view matrix by the given amount.
-	     *
-	     * @param {number} rotation Counter-clockwise rotation angle in radians.
-	     * @param {object} center Center of rotation in world space coordinates.
-	     * @param {vec3} [axis=[0, 0, -1]] axis of rotation.
-	     * @returns {this} Chainable.
-	     */
-	    this._rotate = function (rotation, center, axis) {
-	      if (!rotation) {
-	        return;
-	      }
-	      axis = axis || [0, 0, -1];
-	      if (!center) {
-	        center = [0, 0, 0];
-	      } else if (center.x !== undefined) {
-	        center = [center.x || 0, center.y || 0, center.z || 0];
-	      }
-	      var invcenter = [-center[0], -center[1], -center[2]];
-	      mat4.translate(this._view, this._view, center);
-	      mat4.rotate(this._view, this._view, rotation, axis);
-	      mat4.translate(this._view, this._view, invcenter);
-	      return this;
-	    };
-
-	    /**
-	     * Returns a CSS transform that converts (by default) from world coordinates
-	     * into display coordinates.  This allows users of this module to
-	     * position elements using world coordinates directly inside DOM
-	     * elements.
-	     *
-	     * @note This transform will not take into account projection specific
-	     * transforms.  For perspective projections, one can use the properties
-	     * `perspective` and `perspective-origin` to apply the projection
-	     * in css directly.
-	     *
-	     * @param {string} transform The transform to return
-	     *   * display
-	     *   * world
-	     * @returns {string} The css transform string
-	     */
-	    this.css = function (transform) {
-	      var m;
-	      switch ((transform || '').toLowerCase()) {
-	        case 'display':
-	        case '':
-	          m = this.display;
-	          break;
-	        case 'world':
-	          m = this.world;
-	          break;
-	        default:
-	          throw new Error('Unknown transform ' + transform);
-	      }
-	      return camera.css(m);
-	    };
-
-	    /**
-	     * Represent a glmatrix as a pretty-printed string.
-	     * @param {mat4} mat A 4 x 4 matrix.
-	     * @param {number} prec The number of decimal places.
-	     * @returns {string}
-	     */
-	    this.ppMatrix = function (mat, prec) {
-	      var t = mat;
-	      prec = prec || 2;
-	      function f(i) {
-	        var d = t[i], s = d.toExponential(prec);
-	        if (d >= 0) {
-	          s = ' ' + s;
-	        }
-	        return s;
-	      }
-	      return [
-	        [f(0), f(4), f(8), f(12)].join(' '),
-	        [f(1), f(5), f(9), f(13)].join(' '),
-	        [f(2), f(6), f(10), f(14)].join(' '),
-	        [f(3), f(7), f(11), f(15)].join(' ')
-	      ].join('\n');
-	    };
-
-	    /**
-	     * Pretty print the transform matrix.
-	     * @returns {string} A string representation of the matrix.
-	     */
-	    this.toString = function () {
-	      return this.ppMatrix(this._transform);
-	    };
-
-	    /**
-	     * Return a debugging string of the current camera state.
-	     * @returns {string} A string with the camera state.
-	     */
-	    this.debug = function () {
-	      return [
-	        'bounds',
-	        JSON.stringify(this.bounds),
-	        'view:',
-	        this.ppMatrix(this._view),
-	        'projection:',
-	        this.ppMatrix(this._proj),
-	        'transform:',
-	        this.ppMatrix(this._transform)
-	      ].join('\n');
-	    };
-
-	    /**
-	     * Represent the value of the camera as its transform matrix.
-	     * @returns {mat4} The transform matrix.
-	     */
-	    this.valueOf = function () {
-	      return this._transform;
-	    };
-
-	    // initialize the view matrix
-	    this._resetView();
-
-	    // set up the projection matrix
-	    this.projection = spec.projection || 'parallel';
-
-	    // initialize the viewport
-	    if (spec.viewport) {
-	      this.viewport = spec.viewport;
-	    }
-
-	    // trigger an initial update to set up the camera state
-	    this._update();
-
-	    return this;
-	  };
-
-	  /**
-	   * Supported projection types.
-	   */
-	  camera.projection = {
-	    perspective: true,
-	    parallel: true
-	  };
-
-	  /**
-	   * Camera clipping bounds, probably shouldn't be modified.
-	   */
-	  camera.bounds = {
-	    left: -1,
-	    right: 1,
-	    top: 1,
-	    bottom: -1,
-	    far: -2,
-	    near: -1
-	  };
-
-	  /**
-	   * Output a mat4 as a css transform.
-	   * @param {mat4} t A matrix transform.
-	   * @returns {string} A css transform string.
-	   */
-	  camera.css = function (t) {
-	    return (
-	      'matrix3d(' +
-	        [
-	          t[0].toFixed(20),
-	          t[1].toFixed(20),
-	          t[2].toFixed(20),
-	          t[3].toFixed(20),
-	          t[4].toFixed(20),
-	          t[5].toFixed(20),
-	          t[6].toFixed(20),
-	          t[7].toFixed(20),
-	          t[8].toFixed(20),
-	          t[9].toFixed(20),
-	          t[10].toFixed(20),
-	          t[11].toFixed(20),
-	          t[12].toFixed(20),
-	          t[13].toFixed(20),
-	          t[14].toFixed(20),
-	          t[15].toFixed(20)
-	        ].join(',') +
-	      ')'
-	    );
-	  };
-
-	  /**
-	   * Generate a mat4 representing an affine coordinate transformation.
-	   *
-	   * For the following affine transform:
-	   *
-	   *    x |-> m * (x + a) + b
-	   *
-	   * applies the css transform:
-	   *
-	   *    translate(b) scale(m) translate(a) .
-	   *
-	   * If a parameter is `null` or `undefined`, that component is skipped.
-	   *
-	   * @param {object?} pre Coordinate offset **before** scaling.
-	   * @param {object?} scale Coordinate scaling.
-	   * @param {object?} post Coordinate offset **after** scaling.
-	   * @returns {mat4} The new transform matrix.
-	   */
-	  camera.affine = function (pre, scale, post) {
-	    var mat = util.mat4AsArray();
-
-	    // Note: mat4 operations are applied to the right side of the current
-	    // transform, so the first applied here is the last applied to the
-	    // coordinate.
-	    if (post) {
-	      mat4.translate(mat, mat, [post.x || 0, post.y || 0, post.z || 0]);
-	    }
-	    if (scale) {
-	      mat4.scale(mat, mat, [scale.x || 1, scale.y || 1, scale.z || 1]);
-	    }
-	    if (pre) {
-	      mat4.translate(mat, mat, [pre.x || 0, pre.y || 0, pre.z || 0]);
-	    }
-	    return mat;
-	  };
-
-	  /**
-	   * Apply the given transform matrix to a point in place.
-	   * @param {mat4} t
-	   * @param {vec4} pt
-	   * @returns {vec4}
-	   */
-	  camera.applyTransform = function (t, pt) {
-	    return vec4.transformMat4(pt, pt, t);
-	  };
-
-	  /**
-	   * Combine two transforms by multiplying their matrix representations.
-	   * @note The second transform provided will be the first applied in the
-	   * coordinate transform.
-	   * @param {mat4} A
-	   * @param {mat4} B
-	   * @returns {mat4} A * B
-	   */
-	  camera.combine = function (A, B) {
-	    return mat4.multiply(util.mat4AsArray(), A, B);
-	  };
-
-	  inherit(camera, object);
-	  module.exports = camera;
-	})();
-
-
-/***/ }),
-/* 212 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Create a new instance of class pointFeature
-	 *
-	 * @class geo.pointFeature
-	 * @param {object} arg Options object
-	 * @param {boolean} arg.clustering Enable point clustering
-	 * @extends geo.feature
-	 * @returns {geo.pointFeature}
-	 */
-	var pointFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof pointFeature)) {
-	    return new pointFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var timestamp = __webpack_require__(209);
-	  var ClusterGroup = __webpack_require__(205);
-	  var geo_event = __webpack_require__(9);
-	  var util = __webpack_require__(83);
-	  var kdbush = __webpack_require__(213);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      m_rangeTree = null,
-	      m_rangeTreeTime = timestamp(),
-	      s_data = this.data,
-	      m_maxRadius = 0,
-	      m_clustering = arg.clustering,
-	      m_clusterTree = null,
-	      m_allData = [],
-	      m_lastZoom = null,
-	      m_ignoreData = false; // flag to ignore data() calls made locally
-
-	  this.featureType = 'point';
-
-	  /**
-	   * Get/Set clustering option
-	   *
-	   * @returns {geo.pointFeature|boolean}
-	   */
-	  this.clustering = function (val) {
-	    if (val === undefined) {
-	      return m_clustering;
-	    }
-	    if (m_clustering && !val) {
-	      // Throw out the cluster tree and reset the data
-	      m_clusterTree = null;
-	      m_clustering = false;
-	      s_data(m_allData);
-	    } else if (val && m_clustering !== val) {
-	      // Generate the cluster tree
-	      m_clustering = val;
-	      m_this._clusterData();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Generate the clustering tree from positions.  This might be async in the
-	   * future.
-	   */
-	  this._clusterData = function () {
-	    if (!m_clustering) {
-	      // clustering is not enabled, so this is a no-op
-	      return;
-	    }
-
-	    // set clustering options to default if an options argument wasn't supplied
-	    var opts = m_clustering === true ? {radius: 0.01} : m_clustering;
-
-	    // generate the cluster tree from the raw data
-	    var position = m_this.position();
-	    m_clusterTree = new ClusterGroup(
-	        opts, m_this.layer().width(), m_this.layer().height());
-
-	    m_allData.forEach(function (d, i) {
-
-	      // for each point in the data set normalize the coordinate
-	      // representation and add the point to the cluster treee
-	      var pt = util.normalizeCoordinates(position(d, i));
-	      pt.index = i;
-	      m_clusterTree.addPoint(pt);
-	    });
-
-	    // reset the last zoom state and trigger a redraw at the current zoom level
-	    m_lastZoom = null;
-	    m_this._handleZoom(m_this.layer().map().zoom());
-	  };
-
-	  /**
-	   * Handle zoom events for clustering.  This keeps track of the last
-	   * clustering level, and only regenerates the displayed points when the
-	   * zoom level changes.
-	   */
-	  this._handleZoom = function (zoom) {
-	    // get the current zoom level rounded down
-	    var z = Math.floor(zoom);
-
-	    if (!m_clustering || z === m_lastZoom) {
-	      // short cut when there is nothing to do
-	      return;
-	    }
-
-	    // store the current zoom level privately
-	    m_lastZoom = z;
-
-	    // get the raw data elements for the points at the current level
-	    var data = m_clusterTree.points(z).map(function (d) {
-	      return m_allData[d.index];
-	    });
-
-	    // append the clusters at the current level
-	    m_clusterTree.clusters(z).forEach(function (d) {
-	      // mark the datum as a cluster for accessor methods
-	      d.__cluster = true;
-
-	      // store all of the data objects for each point in the cluster as __data
-	      d.__data = [];
-	      d.obj.each(function (e) {
-	        d.__data.push(m_allData[e.index]);
-	      });
-	      data.push(d);
-	    });
-
-	    // prevent recomputing the clustering and set the new data array
-	    m_ignoreData = true;
-	    m_this.data(data);
-	  };
-
-	  /**
-	   * Get/Set position
-	   *
-	   * @returns {geo.pointFeature}
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else {
-	      var isFunc = util.isFunction(val);
-	      m_this.style('position', function (d, i) {
-	        if (d.__cluster) {
-	          return d;
-	        } else if (isFunc) {
-	          return val(d, i);
-	        } else {
-	          return val;
-	        }
-	      });
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Update the current range tree object.  Should be called whenever the
-	   * data changes.
-	   */
-	  this._updateRangeTree = function () {
-	    if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) {
-	      return;
-	    }
-	    var pts, position,
-	        radius = m_this.style.get('radius'),
-	        stroke = m_this.style.get('stroke'),
-	        strokeWidth = m_this.style.get('strokeWidth');
-
-	    position = m_this.position();
-
-	    m_maxRadius = 0;
-
-	    // create an array of positions in geo coordinates
-	    pts = m_this.data().map(function (d, i) {
-	      var pt = position(d);
-
-	      // store the maximum point radius
-	      m_maxRadius = Math.max(
-	        m_maxRadius,
-	        radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0)
-	      );
-
-	      return [pt.x, pt.y];
-	    });
-
-	    m_rangeTree = kdbush(pts);
-	    m_rangeTreeTime.modified();
-	  };
-
-	  /**
-	   * Returns an array of datum indices that contain the given point.
-	   * Largely adapted from wigglemaps pointQuerier:
-	   *
-	   * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js
-	   */
-	  this.pointSearch = function (p) {
-	    var min, max, data, idx = [], found = [], ifound = [], map, pt,
-	        fgcs = m_this.gcs(), // this feature's gcs
-	        corners,
-	        stroke = m_this.style.get('stroke'),
-	        strokeWidth = m_this.style.get('strokeWidth'),
-	        radius = m_this.style.get('radius');
-
-	    data = m_this.data();
-	    if (!data || !data.length) {
-	      return {
-	        found: [],
-	        index: []
-	      };
-	    }
-
-	    // We need to do this before we find corners, since the max radius is
-	    // determined then
-	    m_this._updateRangeTree();
-
-	    map = m_this.layer().map();
-	    pt = map.gcsToDisplay(p);
-	    // check all corners to make sure we handle rotations
-	    corners = [
-	      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y - m_maxRadius}, fgcs),
-	      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y - m_maxRadius}, fgcs),
-	      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y + m_maxRadius}, fgcs),
-	      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y + m_maxRadius}, fgcs)
-	    ];
-	    min = {
-	      x: Math.min(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
-	      y: Math.min(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
-	    };
-	    max = {
-	      x: Math.max(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
-	      y: Math.max(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
-	    };
-
-	    // Find points inside the bounding box
-	    idx = m_rangeTree.range(min.x, min.y, max.x, max.y);
-
-	    // Filter by circular region
-	    idx.forEach(function (i) {
-	      var d = data[i],
-	          p = m_this.position()(d, i),
-	          dx, dy, rad, rad2;
-
-	      rad = radius(data[i], i);
-	      rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0;
-	      rad2 = rad * rad;
-	      p = map.gcsToDisplay(p, fgcs);
-	      dx = p.x - pt.x;
-	      dy = p.y - pt.y;
-	      if (dx * dx + dy * dy <= rad2) {
-	        found.push(d);
-	        ifound.push(i);
-	      }
-	    });
-
-	    return {
-	      found: found,
-	      index: ifound
-	    };
-	  };
-
-	  /**
-	   * Returns an array of datum indices that are contained in the given box.
-	   */
-	  this.boxSearch = function (lowerLeft, upperRight) {
-	    var pos = m_this.position(),
-	        idx = [];
-	    // TODO: use the range tree
-	    m_this.data().forEach(function (d, i) {
-	      var p = pos(d);
-	      if (p.x >= lowerLeft.x &&
-	          p.x <= upperRight.x &&
-	          p.y >= lowerLeft.y &&
-	          p.y <= upperRight.y
-	      ) {
-	        idx.push(i);
-	      }
-	    });
-	    return idx;
-	  };
-
-	  /**
-	   * Overloaded data method that updates the internal range tree on write.
-	   */
-	  this.data = function (data) {
-	    if (data === undefined) {
-	      return s_data();
-	    }
-	    if (!m_ignoreData) {
-	      m_allData = data;
-	    }
-	    if (m_clustering && !m_ignoreData) {
-	      m_this._clusterData();
-	    } else {
-	      s_data(data);
-	    }
-	    m_ignoreData = false;
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        radius: 5.0,
-	        stroke: true,
-	        strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
-	        strokeWidth: 1.25,
-	        strokeOpacity: 1.0,
-	        fillColor: { r: 1.0, g: 0.839, b: 0.439 },
-	        fill: true,
-	        fillOpacity: 0.8,
-	        sprites: false,
-	        sprites_image: null,
-	        position: function (d) { return d; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.position !== undefined) {
-	      defaultStyle.position = arg.position;
-	    }
-
-	    m_this.style(defaultStyle);
-	    if (defaultStyle.position) {
-	      m_this.position(defaultStyle.position);
-	    }
-	    m_this.dataTime().modified();
-
-	    // bind to the zoom handler for point clustering
-	    m_this.geoOn(geo_event.zoom, function (evt) {
-	      m_this._handleZoom(evt.zoomLevel);
-	    });
-	  };
-
-	  return m_this;
-	};
-
-	/**
-	 * Object specification for a point feature.
-	 *
-	 * @extends geo.feature.spec // need to make a jsdoc plugin for this to work
-	 * @typedef geo.pointFeature.spec
-	 * @type {object}
-	 */
-
-	/**
-	 * Create a pointFeature from an object.
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to
-	 * @param {geo.pointFeature.spec} spec The object specification
-	 * @returns {geo.pointFeature|null}
-	 */
-	pointFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'point';
-	  return feature.create(layer, spec);
-	};
-
-	pointFeature.capabilities = {
-	  /* core feature name -- support in any manner */
-	  feature: 'point'
-	};
-
-	inherit(pointFeature, feature);
-	module.exports = pointFeature;
-
-
-/***/ }),
-/* 213 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	'use strict';
-
-	var sort = __webpack_require__(214);
-	var range = __webpack_require__(215);
-	var within = __webpack_require__(216);
-
-	module.exports = kdbush;
-
-	function kdbush(points, getX, getY, nodeSize, ArrayType) {
-	    return new KDBush(points, getX, getY, nodeSize, ArrayType);
-	}
-
-	function KDBush(points, getX, getY, nodeSize, ArrayType) {
-	    getX = getX || defaultGetX;
-	    getY = getY || defaultGetY;
-	    ArrayType = ArrayType || Array;
-
-	    this.nodeSize = nodeSize || 64;
-	    this.points = points;
-
-	    this.ids = new ArrayType(points.length);
-	    this.coords = new ArrayType(points.length * 2);
-
-	    for (var i = 0; i < points.length; i++) {
-	        this.ids[i] = i;
-	        this.coords[2 * i] = getX(points[i]);
-	        this.coords[2 * i + 1] = getY(points[i]);
-	    }
-
-	    sort(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0);
-	}
-
-	KDBush.prototype = {
-	    range: function (minX, minY, maxX, maxY) {
-	        return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize);
-	    },
-
-	    within: function (x, y, r) {
-	        return within(this.ids, this.coords, x, y, r, this.nodeSize);
-	    }
-	};
-
-	function defaultGetX(p) { return p[0]; }
-	function defaultGetY(p) { return p[1]; }
-
-
-/***/ }),
-/* 214 */
-/***/ (function(module, exports) {
-
-	'use strict';
-
-	module.exports = sortKD;
-
-	function sortKD(ids, coords, nodeSize, left, right, depth) {
-	    if (right - left <= nodeSize) return;
-
-	    var m = Math.floor((left + right) / 2);
-
-	    select(ids, coords, m, left, right, depth % 2);
-
-	    sortKD(ids, coords, nodeSize, left, m - 1, depth + 1);
-	    sortKD(ids, coords, nodeSize, m + 1, right, depth + 1);
-	}
-
-	function select(ids, coords, k, left, right, inc) {
-
-	    while (right > left) {
-	        if (right - left > 600) {
-	            var n = right - left + 1;
-	            var m = k - left + 1;
-	            var z = Math.log(n);
-	            var s = 0.5 * Math.exp(2 * z / 3);
-	            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
-	            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
-	            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
-	            select(ids, coords, k, newLeft, newRight, inc);
-	        }
-
-	        var t = coords[2 * k + inc];
-	        var i = left;
-	        var j = right;
-
-	        swapItem(ids, coords, left, k);
-	        if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right);
-
-	        while (i < j) {
-	            swapItem(ids, coords, i, j);
-	            i++;
-	            j--;
-	            while (coords[2 * i + inc] < t) i++;
-	            while (coords[2 * j + inc] > t) j--;
-	        }
-
-	        if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j);
-	        else {
-	            j++;
-	            swapItem(ids, coords, j, right);
-	        }
-
-	        if (j <= k) left = j + 1;
-	        if (k <= j) right = j - 1;
-	    }
-	}
-
-	function swapItem(ids, coords, i, j) {
-	    swap(ids, i, j);
-	    swap(coords, 2 * i, 2 * j);
-	    swap(coords, 2 * i + 1, 2 * j + 1);
-	}
-
-	function swap(arr, i, j) {
-	    var tmp = arr[i];
-	    arr[i] = arr[j];
-	    arr[j] = tmp;
-	}
-
-
-/***/ }),
-/* 215 */
-/***/ (function(module, exports) {
-
-	'use strict';
-
-	module.exports = range;
-
-	function range(ids, coords, minX, minY, maxX, maxY, nodeSize) {
-	    var stack = [0, ids.length - 1, 0];
-	    var result = [];
-	    var x, y;
-
-	    while (stack.length) {
-	        var axis = stack.pop();
-	        var right = stack.pop();
-	        var left = stack.pop();
-
-	        if (right - left <= nodeSize) {
-	            for (var i = left; i <= right; i++) {
-	                x = coords[2 * i];
-	                y = coords[2 * i + 1];
-	                if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
-	            }
-	            continue;
-	        }
-
-	        var m = Math.floor((left + right) / 2);
-
-	        x = coords[2 * m];
-	        y = coords[2 * m + 1];
-
-	        if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);
-
-	        var nextAxis = (axis + 1) % 2;
-
-	        if (axis === 0 ? minX <= x : minY <= y) {
-	            stack.push(left);
-	            stack.push(m - 1);
-	            stack.push(nextAxis);
-	        }
-	        if (axis === 0 ? maxX >= x : maxY >= y) {
-	            stack.push(m + 1);
-	            stack.push(right);
-	            stack.push(nextAxis);
-	        }
-	    }
-
-	    return result;
-	}
-
-
-/***/ }),
-/* 216 */
-/***/ (function(module, exports) {
-
-	'use strict';
-
-	module.exports = within;
-
-	function within(ids, coords, qx, qy, r, nodeSize) {
-	    var stack = [0, ids.length - 1, 0];
-	    var result = [];
-	    var r2 = r * r;
-
-	    while (stack.length) {
-	        var axis = stack.pop();
-	        var right = stack.pop();
-	        var left = stack.pop();
-
-	        if (right - left <= nodeSize) {
-	            for (var i = left; i <= right; i++) {
-	                if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
-	            }
-	            continue;
-	        }
-
-	        var m = Math.floor((left + right) / 2);
-
-	        var x = coords[2 * m];
-	        var y = coords[2 * m + 1];
-
-	        if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);
-
-	        var nextAxis = (axis + 1) % 2;
-
-	        if (axis === 0 ? qx - r <= x : qy - r <= y) {
-	            stack.push(left);
-	            stack.push(m - 1);
-	            stack.push(nextAxis);
-	        }
-	        if (axis === 0 ? qx + r >= x : qy + r >= y) {
-	            stack.push(m + 1);
-	            stack.push(right);
-	            stack.push(nextAxis);
-	        }
-	    }
-
-	    return result;
-	}
-
-	function sqDist(ax, ay, bx, by) {
-	    var dx = ax - bx;
-	    var dy = ay - by;
-	    return dx * dx + dy * dy;
-	}
-
-
-/***/ }),
-/* 217 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-	var transform = __webpack_require__(11);
-
-	/**
-	 * Polygon feature specification.
-	 *
-	 * @typedef {geo.feature.spec} geo.polygonFeature.spec
-	 * @param {object|Function} [position] Position of the data.  Default is
-	 *   (data).
-	 * @param {object|Function} [polygon] Polygons from the data.  Default is
-	 *   (data).  Typically, the data is an array of polygons, each of which is of
-	 *   the form {outer: [(coordinates)], inner: [[(coordinates of first hole)],
-	 *   [(coordinates of second hole)], ...]}.  The inner record is optional.
-	 *   Alternately, if there are no holes, a polygon can just be an array of
-	 *   coordinates in the form of geo.geoPosition.  The first and last point of
-	 *   each polygon may be the same.
-	 * @param {object} [style] Style object with default style options.
-	 * @param {boolean|Function} [style.fill] True to fill polygon.  Defaults to
-	 *   true.
-	 * @param {geo.geoColor|Function} [style.fillColor] Color to fill each polygon.
-	 *   The color can vary by vertex.
-	 * @param {number|Function} [style.fillOpacity] Opacity for each polygon.  The
-	 *   opacity can vary by vertex.  Opacity is on a [0-1] scale.
-	 * @param {boolean|Function} [style.stroke] True to stroke polygon.  Defaults
-	 *   to false.
-	 * @param {geo.geoColor|Function} [style.strokeColor] Color to stroke each
-	 *   polygon.  The color can vary by vertex.
-	 * @param {number|Function} [style.strokeOpacity] Opacity for each polygon
-	 *   stroke.  The opacity can vary by vertex.  Opacity is on a [0-1] scale.
-	 * @param {number|Function} [style.strokeWidth] The weight of the polygon
-	 *   stroke in pixels.  The width can vary by vertex.
-	 * @param {boolean|Function} [style.uniformPolygon] Boolean indicating if each
-	 *   polygon has a uniform style (uniform fill color, fill opacity, stroke
-	 *   color, and stroke opacity).   Defaults to false.  Can vary by polygon.
-	 */
-
-	/**
-	 * Create a new instance of class polygonFeature.
-	 *
-	 * @class
-	 * @alias geo.polygonFeature
-	 * @extends geo.feature
-	 * @param {geo.polygonFeature.spec} arg
-	 * @returns {geo.polygonFeature}
-	 */
-	var polygonFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof polygonFeature)) {
-	    return new polygonFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  var util = __webpack_require__(83);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_lineFeature,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      s_data = this.data,
-	      s_draw = this.draw,
-	      s_modified = this.modified,
-	      s_style = this.style,
-	      m_coordinates = [];
-
-	  this.featureType = 'polygon';
-	  this._subfeatureStyles = {
-	    fillColor: true,
-	    fillOpacity: true,
-	    lineCap: true,
-	    lineJoin: true,
-	    strokeColor: true,
-	    strokeOffset: true,
-	    strokeOpacity: true,
-	    strokeWidth: true
-	  };
-
-	  /**
-	   * Get/set data.
-	   *
-	   * @param {object} [arg] if specified, use this for the data and return the
-	   *    feature.  If not specified, return the current data.
-	   * @returns {geo.polygonFeature|object}
-	   */
-	  this.data = function (arg) {
-	    var ret = s_data(arg);
-	    if (arg !== undefined) {
-	      m_coordinates = getCoordinates();
-	      this._checkForStroke();
-	    }
-	    return ret;
-	  };
-
-	  /**
-	   * Get the internal coordinates whenever the data changes.  Also compute the
-	   * extents of the outside of each polygon for faster checking if points are
-	   * in the polygon.
-	   *
-	   * @private
-	   * @param {object[]} [data=this.data()] The data to process.
-	   * @param {function} [posFunc=this.style.get('position')] The function to
-	   *    get the position of each vertex.
-	   * @param {function} [polyFunc=this.style.get('polygon')] The function to
-	   *    get each polygon.
-	   * @returns {object[]} An array of polygon positions.  Each has `outer` and
-	   *    `inner` if it has any coordinates, or is undefined.
-	   */
-	  function getCoordinates(data, posFunc, polyFunc) {
-	    data = data || m_this.data();
-	    posFunc = posFunc || m_this.style.get('position');
-	    polyFunc = polyFunc || m_this.style.get('polygon');
-	    var coordinates = data.map(function (d, i) {
-	      var poly = polyFunc(d, i);
-	      if (!poly) {
-	        return;
-	      }
-	      var outer, inner, range, coord, j, x, y;
-
-	      coord = poly.outer || (Array.isArray(poly) ? poly : []);
-	      outer = new Array(coord.length);
-	      for (j = 0; j < coord.length; j += 1) {
-	        outer[j] = posFunc.call(m_this, coord[j], j, d, i);
-	        x = outer[j].x || outer[j][0] || 0;
-	        y = outer[j].y || outer[j][1] || 0;
-	        if (!j) {
-	          range = {min: {x: x, y: y}, max: {x: x, y: y}};
-	        } else {
-	          if (x < range.min.x) { range.min.x = x; }
-	          if (y < range.min.y) { range.min.y = y; }
-	          if (x > range.max.x) { range.max.x = x; }
-	          if (y > range.max.y) { range.max.y = y; }
-	        }
-	      }
-	      inner = (poly.inner || []).map(function (hole) {
-	        coord = hole || [];
-	        var trans = new Array(coord.length);
-	        for (j = 0; j < coord.length; j += 1) {
-	          trans[j] = posFunc.call(m_this, coord[j], j, d, i);
-	        }
-	        return trans;
-	      });
-	      return {
-	        outer: outer,
-	        inner: inner,
-	        range: range
-	      };
-	    });
-	    return coordinates;
-	  }
-
-	  /**
-	   * Get the set of normalized polygon coordinates.
-	   *
-	   * @returns {object[]} An array of polygon positions.  Each has `outer` and
-	   *    `inner` if it has any coordinates, or is undefined.
-	   */
-	  this.polygonCoordinates = function () {
-	    return m_coordinates;
-	  };
-
-	  /**
-	   * Get the style for the stroke of the polygon.  Since polygons can have
-	   * holes, the number of stroke lines may not be the same as the number of
-	   * polygons.  If the style for a stroke is a function, this calls the
-	   * appropriate value for the polygon.  Any style set for a stroke line should
-	   * be wrapped in this function.
-	   *
-	   * @param {(object|function)?} styleValue The polygon's style value used for
-	   *    the stroke.  This should be m_this.style(<name of style>) and not
-	   *    m_this.style.get(<name of style>), as the result is more efficient if
-	   *    the style is not a function.
-	   * @returns {object|function} A style that can be used for the stroke.
-	   * @private
-	   */
-	  function linePolyStyle(styleValue) {
-	    if (util.isFunction(styleValue)) {
-	      return function (d) {
-	        return styleValue(d[0], d[1], d[2], d[3]);
-	      };
-	    } else {
-	      return styleValue;
-	    }
-	  }
-
-	  /**
-	   * Get/set polygon accessor.
-	   *
-	   * @param {object} [val] if specified, use this for the polygon accessor
-	   *    and return the feature.  If not specified, return the current polygon.
-	   * @returns {object|this} The current polygon or this feature.
-	   */
-	  this.polygon = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('polygon');
-	    } else {
-	      m_this.style('polygon', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	      m_coordinates = getCoordinates();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set position accessor.
-	   *
-	   * @param {object} [val] if specified, use this for the position accessor
-	   *    and return the feature.  If not specified, return the current
-	   *    position.
-	   * @returns {object|this} The current position or this feature.
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else {
-	      m_this.style('position', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	      m_coordinates = getCoordinates();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Point search method for selection api.  Returns markers containing the
-	   * given point.
-	   *
-	   * @param {geo.geoPosition} coordinate point to search for in map interface
-	   *    gcs.
-	   * @returns {object} An object with `index`: a list of polygon indices, and
-	   *    `found`: a list of polygons that contain the specified coordinate.
-	   */
-	  this.pointSearch = function (coordinate) {
-	    var found = [], indices = [], irecord = {}, data = m_this.data(),
-	        map = m_this.layer().map(),
-	        pt = transform.transformCoordinates(map.ingcs(), m_this.gcs(), coordinate);
-	    m_coordinates.forEach(function (coord, i) {
-	      var inside = util.pointInPolygon(
-	        pt,
-	        coord.outer,
-	        coord.inner,
-	        coord.range
-	      );
-	      if (inside) {
-	        indices.push(i);
-	        irecord[i] = true;
-	        found.push(data[i]);
-	      }
-	    });
-	    if (m_lineFeature) {
-	      var lineFound = m_lineFeature.pointSearch(coordinate);
-	      lineFound.found.forEach(function (lineData) {
-	        if (lineData.length && lineData[0].length === 4 && !irecord[lineData[0][3]]) {
-	          indices.push(lineData[0][3]);
-	          irecord[lineData[0][3]] = true;
-	          found.push(data[lineData[0][3]]);
-	        }
-	      });
-	    }
-	    return {
-	      index: indices,
-	      found: found
-	    };
-	  };
-
-	  /**
-	   * Get/Set style used by the feature.  This calls the super function, then
-	   * checks if strokes are required.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the current style
-	   *    object.  If a string and `arg2` is undefined, return the style
-	   *    associated with the specified key.  If a string and `arg2` is defined,
-	   *    set the named style to the specified value.  Otherwise, extend the
-	   *    current style with the values in the specified object.
-	   * @param {*} [arg2] If `arg1` is a string, the new value for that style.
-	   * @returns {object|this} Either the entire style object, the value of a
-	   *    specific style, or the current class instance.
-	   */
-	  this.style = function (arg1, arg2) {
-	    var result = s_style.apply(this, arguments);
-	    if (arg1 !== undefined && (typeof arg1 !== 'string' || arg2 !== undefined)) {
-	      this._checkForStroke();
-	    }
-	    return result;
-	  };
-
-	  this.style.get = s_style.get;
-
-	  /**
-	   * Get an outer or inner loop of a polygon and return the necessary data to
-	   * use it for a closed polyline.
-	   *
-	   * @param {object} item: the polygon.
-	   * @param {number} itemIndex: the index of the polygon
-	   * @param {Array} loop: the inner or outer loop.
-	   * @returns {Array} the loop with the data necessary to send to the position
-	   *    function for each vertex.
-	   */
-	  this._getLoopData = function (item, itemIndex, loop) {
-	    var line = [], i;
-
-	    for (i = 0; i < loop.length; i += 1) {
-	      line.push([loop[i], i, item, itemIndex]);
-	    }
-	    return line;
-	  };
-
-	  /**
-	   * Check if we need to add a line feature to the layer, and update it as
-	   * necessary.
-	   */
-	  this._checkForStroke = function () {
-	    if (s_style('stroke') === false) {
-	      if (m_lineFeature && m_this.layer()) {
-	        m_this.layer().deleteFeature(m_lineFeature);
-	        m_lineFeature = null;
-	        m_this.dependentFeatures([]);
-	      }
-	      return;
-	    }
-	    if (!m_this.layer()) {
-	      return;
-	    }
-	    if (!m_lineFeature) {
-	      m_lineFeature = m_this.layer().createFeature('line', {
-	        selectionAPI: false,
-	        gcs: m_this.gcs(),
-	        visible: m_this.visible(undefined, true)
-	      });
-	      m_this.dependentFeatures([m_lineFeature]);
-	    }
-	    var polyStyle = m_this.style();
-	    m_lineFeature.style({
-	      antialiasing: linePolyStyle(polyStyle.antialiasing),
-	      closed: true,
-	      lineCap: linePolyStyle(polyStyle.lineCap),
-	      lineJoin: linePolyStyle(polyStyle.lineJoin),
-	      miterLimit: linePolyStyle(polyStyle.miterLimit),
-	      strokeWidth: linePolyStyle(polyStyle.strokeWidth),
-	      strokeStyle: linePolyStyle(polyStyle.strokeStyle),
-	      strokeColor: linePolyStyle(polyStyle.strokeColor),
-	      strokeOffset: linePolyStyle(polyStyle.strokeOffset),
-	      strokeOpacity: util.isFunction(polyStyle.stroke) || !polyStyle.stroke ?
-	        function (d) {
-	          return m_this.style.get('stroke')(d[2], d[3]) ? m_this.style.get('strokeOpacity')(d[0], d[1], d[2], d[3]) : 0;
-	        } :
-	        linePolyStyle(polyStyle.strokeOpacity)
-	    });
-	    var data = this.data(),
-	        posVal = this.style('position');
-	    if (data !== m_lineFeature._lastData || posVal !== m_lineFeature._lastPosVal) {
-	      var lineData = [], i, polygon, loop,
-	          posFunc = this.style.get('position'),
-	          polyFunc = this.style.get('polygon');
-
-	      for (i = 0; i < data.length; i += 1) {
-	        polygon = polyFunc(data[i], i);
-	        if (!polygon) {
-	          continue;
-	        }
-	        loop = polygon.outer || (Array.isArray(polygon) ? polygon : []);
-	        if (loop.length >= 2) {
-	          lineData.push(m_this._getLoopData(data[i], i, loop));
-	          if (polygon.inner) {
-	            polygon.inner.forEach(function (loop) {
-	              if (loop.length >= 2) {
-	                lineData.push(m_this._getLoopData(data[i], i, loop));
-	              }
-	            });
-	          }
-	        }
-	      }
-	      m_lineFeature.position(function (d, i, item, itemIndex) {
-	        return posFunc(d[0], d[1], d[2], d[3]);
-	      });
-	      m_lineFeature.data(lineData);
-	      m_lineFeature._lastData = data;
-	      m_lineFeature._lastPosVal = posVal;
-	    }
-	  };
-
-	  /**
-	   * Redraw the object.
-	   *
-	   * @returns {object} The results of the superclass draw function.
-	   */
-	  this.draw = function () {
-	    var result = s_draw();
-	    if (m_lineFeature) {
-	      m_lineFeature.draw();
-	    }
-	    return result;
-	  };
-
-	  /**
-	   * When the feature is marked as modified, mark our sub-feature as
-	   * modified, too.
-	   *
-	   * @returns {object} The results of the superclass modified function.
-	   */
-	  this.modified = function () {
-	    var result = s_modified();
-	    if (m_lineFeature) {
-	      m_lineFeature.modified();
-	    }
-	    return result;
-	  };
-
-	  /**
-	   * Take a set of data, reduce the number of vertices per polygon using the
-	   * Ramer–Douglas–Peucker algorithm, and use the result as the new data.
-	   * This changes the instance's data, the position accessor, and the polygon
-	   * accessor.
-	   *
-	   * @param {array} data A new data array.
-	   * @param {number} [tolerance] The maximum variation allowed in map.gcs
-	   *    units.  A value of zero will only remove perfectly colinear points.  If
-	   *    not specified, this is set to a half display pixel at the map's current
-	   *    zoom level.
-	   * @param {function} [posFunc=this.style.get('position')] The function to
-	   *    get the position of each vertex.
-	   * @param {function} [polyFunc=this.style.get('polygon')] The function to
-	   *    get each polygon.
-	   * @returns {this}
-	   */
-	  this.rdpSimplifyData = function (data, tolerance, posFunc, polyFunc) {
-	    var map = m_this.layer().map(),
-	        mapgcs = map.gcs(),
-	        featuregcs = m_this.gcs(),
-	        coordinates = getCoordinates(data, posFunc, polyFunc);
-	    if (tolerance === undefined) {
-	      tolerance = map.unitsPerPixel(map.zoom()) * 0.5;
-	    }
-
-	    /* transform the coordinates to the map gcs */
-	    coordinates = coordinates.map(function (poly) {
-	      return {
-	        outer: transform.transformCoordinates(featuregcs, mapgcs, poly.outer),
-	        inner: poly.inner.map(function (hole) {
-	          return transform.transformCoordinates(featuregcs, mapgcs, hole);
-	        })
-	      };
-	    });
-	    data = data.map(function (d, idx) {
-	      var poly = coordinates[idx],
-	          elem = {};
-	      /* Copy element properties, as they might be used by styles */
-	      for (var key in d) {
-	        if (d.hasOwnProperty(key) && !(Array.isArray(d) && key >= 0 && key < d.length)) {
-	          elem[key] = d[key];
-	        }
-	      }
-	      if (poly && poly.outer.length >= 3) {
-	        // discard degenerate holes before anything else
-	        elem.inner = poly.inner.filter(function (hole) {
-	          return hole.length >= 3;
-	        });
-	        // simplify the outside of the polygon without letting it cross holes
-	        elem.outer = util.rdpLineSimplify(poly.outer, tolerance, true, elem.inner);
-	        if (elem.outer.length >= 3) {
-	          var allButSelf = elem.inner.slice();
-	          // simplify holes without crossing other holes or the outside
-	          elem.inner.map(function (hole, idx) {
-	            allButSelf[idx] = elem.outer;
-	            var result = util.rdpLineSimplify(hole, tolerance, true, allButSelf);
-	            allButSelf[idx] = result;
-	            return result;
-	          }).filter(function (hole) {
-	            return hole.length >= 3;
-	          });
-	          // transform coordinates back to the feature gcs
-	          elem.outer = transform.transformCoordinates(mapgcs, featuregcs, elem.outer);
-	          elem.inner = elem.inner.map(function (hole) {
-	            return transform.transformCoordinates(mapgcs, featuregcs, hole);
-	          });
-	        } else {
-	          elem.outer = elem.inner = [];
-	        }
-	      } else {
-	        elem.outer = [];
-	      }
-	      return elem;
-	    });
-
-	    /* Set the reduced polgons as the data and use simple accessors. */
-	    m_this.style('position', function (d) { return d; });
-	    m_this.style('polygon', function (d) { return d; });
-	    m_this.data(data);
-	    return m_this;
-	  };
-
-	  /**
-	   * If the selectionAPI is on, then setting
-	   * `this.geoOn(geo.event.feature.mouseover_order, this.mouseOverOrderClosestBorder)`
-	   * will make it so that the mouseon events prefer the polygon with the
-	   * closet border, including hole edges.
-	   *
-	   * @param {geo.event} evt The event; this should be triggered from
-	   *    `geo.event.feature.mouseover_order`.
-	   */
-	  this.mouseOverOrderClosestBorder = function (evt) {
-	    var data = evt.feature.data(),
-	        map = evt.feature.layer().map(),
-	        pt = transform.transformCoordinates(map.ingcs(), evt.feature.gcs(), evt.mouse.geo),
-	        coor = evt.feature.polygonCoordinates(),
-	        dist = {};
-	    evt.over.index.forEach(function (di, idx) {
-	      var poly = coor[di], mindist;
-	      poly.outer.forEach(function (line1, pidx) {
-	        var line2 = poly.outer[(pidx + 1) % poly.outer.length];
-	        var dist = util.distance2dToLineSquared(pt, line1, line2);
-	        if (mindist === undefined || dist < mindist) {
-	          mindist = dist;
-	        }
-	      });
-	      poly.inner.forEach(function (inner) {
-	        inner.forEach(function (line1, pidx) {
-	          var line2 = inner[(pidx + 1) % inner.length];
-	          var dist = util.distance2dToLineSquared(pt, line1, line2);
-	          if (mindist === undefined || dist < mindist) {
-	            mindist = dist;
-	          }
-	        });
-	      });
-	      dist[di] = mindist;
-	    });
-	    evt.over.index.sort(function (i1, i2) {
-	      return dist[i1] - dist[i2];
-	    }).reverse();
-	    // this isn't necessary, but ensures that other event handlers have
-	    // consistent information
-	    evt.over.index.forEach(function (di, idx) {
-	      evt.over.found[idx] = data[di];
-	    });
-	  };
-
-	  /**
-	   * Destroy.
-	   */
-	  this._exit = function () {
-	    if (m_lineFeature && m_this.layer()) {
-	      m_this.layer().deleteFeature(m_lineFeature);
-	      m_lineFeature = null;
-	      m_this.dependentFeatures([]);
-	    }
-	    s_exit();
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.polygonFeature.spec} arg An object with options for the
-	   *    feature.
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    var style = $.extend(
-	      {},
-	      {
-	        // default style
-	        fill: true,
-	        fillColor: {r: 0.0, g: 0.5, b: 0.5},
-	        fillOpacity: 1.0,
-	        stroke: false,
-	        strokeWidth: 1.0,
-	        strokeStyle: 'solid',
-	        strokeColor: {r: 0.0, g: 1.0, b: 1.0},
-	        strokeOpacity: 1.0,
-	        polygon: function (d) { return d; },
-	        position: function (d) { return d; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.polygon !== undefined) {
-	      style.polygon = arg.polygon;
-	    }
-	    if (arg.position !== undefined) {
-	      style.position = arg.position;
-	    }
-	    m_this.style(style);
-
-	    this._checkForStroke();
-	  };
-
-	  /* Don't call _init here -- let subclasses call it */
-	  return this;
-	};
-
-	/**
-	 * Create a polygonFeature from an object.
-	 *
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to
-	 * @param {geo.polygonFeature.spec} spec The object specification
-	 * @returns {geo.polygonFeature|null}
-	 */
-	polygonFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'polygon';
-	  return feature.create(layer, spec);
-	};
-
-	polygonFeature.capabilities = {
-	  /* core feature name -- support in any manner */
-	  feature: 'polygon'
-	};
-
-	inherit(polygonFeature, feature);
-	module.exports = polygonFeature;
-
-
-/***/ }),
-/* 218 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Object specification for a text feature.
-	 *
-	 * @typedef {geo.feature.spec} geo.textFeature.spec
-	 * @property {geo.geoPosition[]|function} [position] The position of each data
-	 *      element.  Defaults to the `x`, `y`, and `z` properties of the data
-	 *      element.
-	 * @property {string[]|function} [text] The text of each data element.
-	 *      Defaults to the `text` property of the data element.
-	 * @property {object} [style] The style to apply to each data element.
-	 * @property {boolean|function} [style.visible=true] If falsy, don't show this
-	 *      data element.
-	 * @property {string|function} [style.font] A css font specification.  This
-	 *      is of the form `[style] [variant] [weight] [stretch] size[/line-height]
-	 *      family`.  Individual font styles override this value if a style is
-	 *      specified in each.  See the individual font styles for details.
-	 * @property {string|function} [style.fontStyle='normal'] The font style.  One
-	 *      of `normal`, `italic`, or `oblique`.
-	 * @property {string|function} [style.fontVariant='normal'] The font variant.
-	 *      This can have values such as `small-caps` or `slashed-zero`.
-	 * @property {string|function} [style.fontWeight='normal'] The font weight.
-	 *      This may be a numeric value where 400 is normal and 700 is bold, or a
-	 *      string such as `bold` or `lighter`.
-	 * @property {string|function} [style.fontStretch='normal'] The font stretch,
-	 *      such as `condensed`.
-	 * @property {string|function} [style.fontSize='medium'] The font size.
-	 * @property {string|function} [style.lineHeight='normal'] The font line
-	 *      height.
-	 * @property {string|function} [style.fontFamily] The font family.
-	 * @property {string|function} [style.textAlign='center'] The horizontal text
-	 *      alignment.  One of `start`, `end`, `left`, `right`, or `center`.
-	 * @property {string|function} [style.textBaseline='middle'] The vertical text
-	 *      alignment.  One of `top`, `hanging`, `middle`, `alphabetic`,
-	 *      `ideographic`, or `bottom`.
-	 * @property {geo.geoColor|function} [style.color='black'] Text color.  May
-	 *      include opacity.
-	 * @property {number|function} [style.textOpacity=1] The opacity of the text.
-	 *      If the color includes opacity, this is combined with that value.
-	 * @property {number|function} [style.rotation=0] Text rotation in radians.
-	 * @property {boolean|function} [style.rotateWithMap=false] If truthy, rotate
-	 *      the text when the map rotates.  Otherwise, the text is always in the
-	 *      same orientation.
-	 * @property {number|function} [style.scale=4] The zoom basis value used when
-	 *      `scaleWithMap` is truthy.
-	 * @property {boolean|function} [style.scaleWithMap=false] If truthy, use the
-	 *      `scale` style as the basis of the map zoom value for the font size.
-	 *      The size is scaled from this point.
-	 * @property {geo.screenPosition|function} [style.offset] Offset from the
-	 *      default position for the text.  This is applied before rotation.
-	 * @property {geo.geoColor|function} [style.shadowColor='black'] Text shadow
-	 *      color.  May include opacity.
-	 * @property {geo.screenPosition|function} [style.shadowOffset] Offset for a
-	 *      text shadow.  This is applied before rotation.
-	 * @property {number|null|function} [style.shadowBlur] If not null, add a text
-	 *      shadow with this much blur.
-	 * @property {boolean|function} [style.shadowRotate=false] If truthy, rotate
-	 *      the shadow offset based on the text rotation (the `shadowOffset` is
-	 *      the offset if the text has a 0 rotation).
-	 * @property {geo.geoColor|function} [style.textStrokeColor='transparent'] Text
-	 *      stroke color.  May include opacity.
-	 * @property {geo.geoColor|function} [style.textStrokeWidth=0] Text stroke
-	 *      width in pixels.
-	 */
-
-	/**
-	 * Create a new instance of class textFeature.
-	 *
-	 * @class
-	 * @alias geo.textFeature
-	 * @extends geo.feature
-	 *
-	 * @param {geo.textFeature.spec} [arg] Options for the feature.
-	 * @returns {geo.textFeature} The created feature.
-	 */
-	var textFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof textFeature)) {
-	    return new textFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init;
-
-	  this.featureType = 'text';
-
-	  /**
-	   * Get/Set position.
-	   *
-	   * @param {array|function} [val] If `undefined`, return the current position
-	   *    setting.  Otherwise, modify the current position setting.
-	   * @returns {array|function|this} The current position or this feature.
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else if (val !== m_this.style('position')) {
-	      m_this.style('position', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set text.
-	   *
-	   * @param {array|function} [val] If `undefined`, return the current text
-	   *    setting.  Otherwise, modify the current text setting.
-	   * @returns {array|function|this} The current text or this feature.
-	   */
-	  this.text = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('text');
-	    } else if (val !== m_this.style('text')) {
-	      m_this.style('text', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.textFeature.spec} [arg] The feature specification.
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    var style = $.extend(
-	      {},
-	      {
-	        font: 'bold 16px sans-serif',
-	        textAlign: 'center',
-	        textBaseline: 'middle',
-	        color: { r: 0, g: 0, b: 0 },
-	        rotation: 0,  /* in radians */
-	        rotateWithMap: false,
-	        textScaled: false,
-	        position: function (d) { return d; },
-	        text: function (d) { return d.text; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.position !== undefined) {
-	      style.position = arg.position;
-	    }
-	    if (arg.text !== undefined) {
-	      style.text = arg.text;
-	    }
-
-	    m_this.style(style);
-	    if (style.position) {
-	      m_this.position(style.position);
-	    }
-	    if (style.text) {
-	      m_this.text(style.text);
-	    }
-	    m_this.dataTime().modified();
-	  };
-
-	  this._init(arg);
-	  return m_this;
-	};
-
-	textFeature.usedStyles = [
-	  'visible', 'font', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch',
-	  'fontSize', 'lineHeight', 'fontFamily', 'textAlign', 'textBaseline', 'color',
-	  'textOpacity', 'rotation', 'rotateWithMap', 'textScaled', 'offset',
-	  'shadowColor', 'shadowOffset', 'shadowBlur', 'shadowRotate',
-	  'textStrokeColor', 'textStrokeWidth'
-	];
-
-	/**
-	 * Create a textFeature from an object.
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to
-	 * @param {geo.textFeature.spec} spec The object specification
-	 * @returns {geo.textFeature|null}
-	 */
-	textFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'text';
-	  return feature.create(layer, spec);
-	};
-
-	textFeature.capabilities = {
-	  /* core feature name -- support in any manner */
-	  feature: 'text'
-	};
-
-	inherit(textFeature, feature);
-	module.exports = textFeature;
-
-
-/***/ }),
-/* 219 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var featureLayer = __webpack_require__(220);
-	var geo_annotation = __webpack_require__(7);
-	var geo_event = __webpack_require__(9);
-	var registry = __webpack_require__(201);
-	var transform = __webpack_require__(11);
-	var $ = __webpack_require__(1);
-	var Mousetrap = __webpack_require__(221);
-	var textFeature = __webpack_require__(218);
-
-	/**
-	 * @typedef {object} geo.annotationLayer.labelRecord
-	 * @property {string} text The text of the label
-	 * @property {geo.geoPosition} position The position of the label in map gcs
-	 *      coordinates.
-	 * @property {object} [style] A `geo.textFeature` style object.
-	 */
-
-	/**
-	 * Layer to handle direct interactions with different features.  Annotations
-	 * (features) can be created by calling mode(<name of feature>) or cancelled
-	 * with mode(null).  There is also an "edit" mode which is used when modifying
-	 * an annotation.
-	 *
-	 * @class
-	 * @alias geo.annotationLayer
-	 * @extends geo.featureLayer
-	 * @param {object} [args] Layer options.
-	 * @param {number} [args.dblClickTime=300] The delay in milliseconds that is
-	 *    treated as a double-click when working with annotations.
-	 * @param {number} [args.adjacentPointProximity=5] The minimum distance in
-	 *    display coordinates (pixels) between two adjacent points when creating a
-	 *    polygon or line.  A value of 0 requires an exact match.
-	 * @param {number} [args.continuousPointProximity=5] The minimum distance in
-	 *    display coordinates (pixels) between two adjacent points when dragging
-	 *    to create an annotation.  `false` disables continuous drawing mode.
-	 * @param {number} [args.continuousPointColinearity=1.0deg] The minimum
-	 *    angle between a series of three points when dragging to not interpret
-	 *    them as colinear.  Only applies if `continuousPointProximity` is not
-	 *    `false`.
-	 * @param {number} [args.finalPointProximity=10] The maximum distance in
-	 *    display coordinates (pixels) between the starting point and the mouse
-	 *    coordinates to signal closing a polygon.  A value of 0 requires an exact
-	 *    match.  A negative value disables closing a polygon by clicking on the
-	 *    start point.
-	 * @param {boolean} [args.showLabels=true] Truthy to show feature labels that
-	 *    are allowed by the associated feature to be shown.
-	 * @param {boolean} [args.clickToEdit=false] Truthy to allow clicking an
-	 *    annotation to place it in edit mode.
-	 * @param {object} [args.defaultLabelStyle] Default styles for labels.
-	 * @returns {geo.annotationLayer}
-	 */
-	var annotationLayer = function (args) {
-	  'use strict';
-	  if (!(this instanceof annotationLayer)) {
-	    return new annotationLayer(args);
-	  }
-	  featureLayer.call(this, args);
-
-	  var mapInteractor = __webpack_require__(222);
-	  var timestamp = __webpack_require__(209);
-	  var util = __webpack_require__(83);
-
-	  var m_this = this,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      s_draw = this.draw,
-	      s_update = this._update,
-	      m_buildTime = timestamp(),
-	      m_options,
-	      m_mode = null,
-	      m_annotations = [],
-	      m_features = [],
-	      m_labelFeature,
-	      m_labelLayer;
-
-	  var geojsonStyleProperties = {
-	    'closed': {dataType: 'boolean', keys: ['closed', 'close']},
-	    'fill': {dataType: 'boolean', keys: ['fill']},
-	    'fillColor': {dataType: 'color', keys: ['fillColor', 'fill-color', 'marker-color', 'fill']},
-	    'fillOpacity': {dataType: 'opacity', keys: ['fillOpacity', 'fill-opacity']},
-	    'lineCap': {dataType: 'text', keys: ['lineCap', 'line-cap']},
-	    'lineJoin': {dataType: 'text', keys: ['lineJoin', 'line-join']},
-	    'radius': {dataType: 'positive', keys: ['radius']},
-	    'scaled': {dataType: 'booleanOrNumber', keys: ['scaled']},
-	    'stroke': {dataType: 'boolean', keys: ['stroke']},
-	    'strokeColor': {dataType: 'color', keys: ['strokeColor', 'stroke-color', 'stroke']},
-	    'strokeOffset': {dataType: 'number', keys: ['strokeOffset', 'stroke-offset']},
-	    'strokeOpacity': {dataType: 'opacity', keys: ['strokeOpacity', 'stroke-opacity']},
-	    'strokeWidth': {dataType: 'positive', keys: ['strokeWidth', 'stroke-width']}
-	  };
-	  textFeature.usedStyles.forEach(function (key) {
-	    geojsonStyleProperties[key] = {
-	      option: 'labelStyle',
-	      dataType: ['visible', 'rotateWithMap', 'scaleWithMap'].indexOf(key) >= 0 ? 'boolean' : (
-	        ['scale'].indexOf(key) >= 0 ? 'booleanOrNumber' : (
-	        ['rotation'].indexOf(key) >= 0 ? 'angle' : (
-	        ['offset', 'shadowOffset'].indexOf(key) >= 0 ? 'coordinate2' : (
-	        ['shadowBlur, strokeWidth'].indexOf(key) >= 0 ? 'numberOrBlank' :
-	        'text')))),
-	      keys: [
-	        key,
-	        'label' + key.charAt(0).toUpperCase() + key.slice(1),
-	        key.replace(/([A-Z])/g, '-$1').toLowerCase(),
-	        'label-' + key.replace(/([A-Z])/g, '-$1').toLowerCase()]
-	    };
-	  });
-
-	  m_options = $.extend(true, {}, {
-	    dblClickTime: 300,
-	    adjacentPointProximity: 5,  // in pixels, 0 is exact
-	    // in pixels; set to continuousPointProximity to false to disable
-	    // continuous drawing modes.
-	    continuousPointProximity: 5,
-	    // in radians, minimum angle between continuous points to interpret them as
-	    // being coliner
-	    continuousPointColinearity: 1.0 * Math.PI / 180,
-	    finalPointProximity: 10,  // in pixels, 0 is exact
-	    showLabels: true,
-	    clickToEdit: false
-	  }, args);
-
-	  /**
-	   * Process an action event.  If we are in rectangle-creation mode, this
-	   * creates a rectangle.
-	   *
-	   * @param {geo.event} evt The selection event.
-	   */
-	  this._processAction = function (evt) {
-	    var update;
-	    if (evt.state && evt.state.actionRecord &&
-	        evt.state.actionRecord.owner === geo_annotation.actionOwner &&
-	        m_this.currentAnnotation) {
-	      switch (m_this.mode()) {
-	        case m_this.modes.edit:
-	          update = m_this.currentAnnotation.processEditAction(evt);
-	          break;
-	        default:
-	          update = m_this.currentAnnotation.processAction(evt);
-	          break;
-	      }
-	    }
-	    m_this._updateFromEvent(update);
-	  };
-
-	  /**
-	   * Handle updating the current annotation based on an update state.
-	   *
-	   * @param {string|undefined} update Truthy to update.  `'done'` if the
-	   *    annotation was completed and the mode should return to `null`.
-	   *    `'remove'` to remove the current annotation and set the mode to `null`.
-	   *    Falsy to do nothing.
-	   */
-	  this._updateFromEvent = function (update) {
-	    switch (update) {
-	      case 'remove':
-	        m_this.removeAnnotation(m_this.currentAnnotation, false);
-	        m_this.mode(null);
-	        break;
-	      case 'done':
-	        m_this.mode(null);
-	        break;
-	    }
-	    if (update) {
-	      m_this.modified();
-	      m_this.draw();
-	    }
-	  };
-
-	  /**
-	   * Handle mouse movement.  If there is a current annotation, the movement
-	   * event is sent to it.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   */
-	  this._handleMouseMove = function (evt) {
-	    if (m_this.mode() && m_this.currentAnnotation) {
-	      var update = m_this.currentAnnotation.mouseMove(evt);
-	      if (update) {
-	        m_this.modified();
-	        m_this.draw();
-	      }
-	    }
-	  };
-
-	  /**
-	   * Select or deselect an edit handle.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   * @param {boolean} enable Truthy to select the handle, falsy to deselect it.
-	   * @returns {this}
-	   */
-	  this._selectEditHandle = function (evt, enable) {
-	    if (!evt.data || !evt.data.editHandle) {
-	      return;
-	    }
-	    $.each(m_features[geo_annotation._editHandleFeatureLevel], function (type, feature) {
-	      feature.feature.modified();
-	    });
-	    m_this.currentAnnotation.selectEditHandle(evt.data, enable);
-	    m_this.draw();
-	    m_this.map().node().toggleClass('annotation-input', !!enable);
-	    m_this.map().interactor().removeAction(
-	      undefined, undefined, geo_annotation.actionOwner);
-	    if (enable) {
-	      var actions = m_this.currentAnnotation.actions(geo_annotation.state.edit);
-	      $.each(actions, function (idx, action) {
-	        m_this.map().interactor().addAction(action);
-	      });
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Handle mouse on events.  If there is no current annotation and
-	   * clickToEdit is enabled, any hovered annotation is highlighted.
-	   * event is sent to it.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   */
-	  this._handleMouseOn = function (evt) {
-	    if (!evt.data || !evt.data.annotation) {
-	      return;
-	    }
-	    if (m_this.mode() === m_this.modes.edit && m_this.currentAnnotation) {
-	      m_this._selectEditHandle(evt, true);
-	      return;
-	    }
-	    if (m_this.mode() || m_this.currentAnnotation || !m_this.options('clickToEdit')) {
-	      return;
-	    }
-	    evt.data.annotation.state(geo_annotation.state.highlight);
-	    m_this.modified();
-	    m_this.draw();
-	  };
-
-	  /**
-	   * Handle mouse off events.  If the specific annotation is in the highlight
-	   * state, move it back to the done state.
-	   *
-	   * @param {geo.event} evt The mouse move event.
-	   */
-	  this._handleMouseOff = function (evt) {
-	    if (!evt.data || !evt.data.annotation) {
-	      return;
-	    }
-	    if (m_this.mode() === m_this.modes.edit && evt.data.editHandle && evt.data.selected) {
-	      m_this._selectEditHandle(evt, false);
-	      return;
-	    }
-	    if (evt.data.annotation.state() === geo_annotation.state.highlight) {
-	      evt.data.annotation.state(geo_annotation.state.done);
-	      m_this.modified();
-	      m_this.draw();
-	    }
-	  };
-
-	  /**
-	   * Handle mouse clicks.  If there is a current annotation, the click event is
-	   * sent to it.
-	   *
-	   * @param {geo.event} evt The mouse click event.
-	   */
-	  this._handleMouseClick = function (evt) {
-	    var retrigger = false, update;
-	    if (m_this.mode() === m_this.modes.edit) {
-	      if (m_this.map().interactor().hasAction(undefined, undefined, geo_annotation.actionOwner)) {
-	        update = m_this.currentAnnotation.mouseClickEdit(evt);
-	        m_this._updateFromEvent(update);
-	        return;
-	      }
-	      m_this.mode(null);
-	      m_this.draw();
-	      $.each(m_features, function (idx, featureLevel) {
-	        $.each(featureLevel, function (type, feature) {
-	          feature.feature._clearSelectedFeatures();
-	        });
-	      });
-	      retrigger = true;
-	    } else if (m_this.mode() && m_this.currentAnnotation) {
-	      update = m_this.currentAnnotation.mouseClick(evt);
-	      m_this._updateFromEvent(update);
-	      retrigger = !m_this.mode();
-	    } else if (!m_this.mode() && !m_this.currentAnnotation && m_this.options('clickToEdit')) {
-	      var highlighted = m_this.annotations().filter(function (ann) {
-	        return ann.state() === geo_annotation.state.highlight;
-	      });
-	      if (highlighted.length !== 1) {
-	        return;
-	      }
-	      m_this.mode(m_this.modes.edit, highlighted[0]);
-	      m_this.draw();
-	      retrigger = true;
-	    }
-	    if (retrigger) {
-	      // retrigger mouse move to ensure the correct events are attached
-	      m_this.map().interactor().retriggerMouseMove();
-	    }
-	  };
-
-	  /**
-	   * Set or get options.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the options object.
-	   *    If a string, either set or return the option of that name.  If an
-	   *    object, update the options with the object's values.
-	   * @param {object} [arg2] If `arg1` is a string and this is defined, set
-	   *    the option to this value.
-	   * @returns {object|this} If options are set, return the annotation,
-	   *    otherwise return the requested option or the set of options.
-	   */
-	  this.options = function (arg1, arg2) {
-	    if (arg1 === undefined) {
-	      return m_options;
-	    }
-	    if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return m_options[arg1];
-	    }
-	    if (arg2 === undefined) {
-	      m_options = $.extend(true, m_options, arg1);
-	    } else {
-	      m_options[arg1] = arg2;
-	    }
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Calculate the display distance for two coordinate in the current map.
-	   *
-	   * @param {geo.geoPosition|geo.screenPosition} coord1 The first coordinates.
-	   * @param {string|geo.transform|null} gcs1 `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, `'display'` if the coordinates are
-	   *    already in display coordinates, or any other transform.
-	   * @param {geo.geoPosition|geo.screenPosition} coord2 the second coordinates.
-	   * @param {string|geo.transform|null} [gcs2] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, `'display'` if the coordinates are
-	   *    already in display coordinates, or any other transform.
-	   * @returns {number} the Euclidian distance between the two coordinates.
-	   */
-	  this.displayDistance = function (coord1, gcs1, coord2, gcs2) {
-	    var map = m_this.map();
-	    if (gcs1 !== 'display') {
-	      gcs1 = (gcs1 === null ? map.gcs() : (
-	              gcs1 === undefined ? map.ingcs() : gcs1));
-	      coord1 = map.gcsToDisplay(coord1, gcs1);
-	    }
-	    if (gcs2 !== 'display') {
-	      gcs2 = (gcs2 === null ? map.gcs() : (
-	              gcs2 === undefined ? map.ingcs() : gcs2));
-	      coord2 = map.gcsToDisplay(coord2, gcs2);
-	    }
-	    var dist = Math.sqrt(Math.pow(coord1.x - coord2.x, 2) +
-	                         Math.pow(coord1.y - coord2.y, 2));
-	    return dist;
-	  };
-
-	  /**
-	   * Add an annotation to the layer.  The annotation could be in any state.
-	   *
-	   * @param {geo.annotation} annotation Te annotation to add.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {this} The current layer.
-	   */
-	  this.addAnnotation = function (annotation, gcs) {
-	    var pos = $.inArray(annotation, m_annotations);
-	    if (pos < 0) {
-	      m_this.geoTrigger(geo_event.annotation.add_before, {
-	        annotation: annotation
-	      });
-	      m_annotations.push(annotation);
-	      annotation.layer(m_this);
-	      var map = m_this.map();
-	      gcs = (gcs === null ? map.gcs() : (
-	             gcs === undefined ? map.ingcs() : gcs));
-	      if (gcs !== map.gcs()) {
-	        annotation._coordinates(transform.transformCoordinates(
-	            gcs, map.gcs(), annotation._coordinates()));
-	      }
-	      m_this.modified();
-	      m_this.draw();
-	      m_this.geoTrigger(geo_event.annotation.add, {
-	        annotation: annotation
-	      });
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Remove an annotation from the layer.
-	   *
-	   * @param {geo.annoation} annotation The annotation to remove.
-	   * @param {boolean} update If `false`, don't update the layer after removing
-	   *    the annotation.
-	   * @returns {boolean} `true` if an annotation was removed.
-	   */
-	  this.removeAnnotation = function (annotation, update) {
-	    var pos = $.inArray(annotation, m_annotations);
-	    if (pos >= 0) {
-	      if (annotation === m_this.currentAnnotation) {
-	        m_this.currentAnnotation = null;
-	      }
-	      annotation._exit();
-	      m_annotations.splice(pos, 1);
-	      if (update !== false) {
-	        m_this.modified();
-	        m_this.draw();
-	      }
-	      m_this.geoTrigger(geo_event.annotation.remove, {
-	        annotation: annotation
-	      });
-	    }
-	    return pos >= 0;
-	  };
-
-	  /**
-	   * Remove all annotations from the layer.
-	   *
-	   * @param {boolean} [skipCreating] If truthy, don't remove annotations that
-	   *    are in the create state.
-	   * @param {boolean} [update] If `false`, don't update the layer after
-	   *    removing the annotation.
-	   * @returns {number} The number of annotations that were removed.
-	   */
-	  this.removeAllAnnotations = function (skipCreating, update) {
-	    var removed = 0, annotation, pos = 0;
-	    while (pos < m_annotations.length) {
-	      annotation = m_annotations[pos];
-	      if (skipCreating && annotation.state() === geo_annotation.state.create) {
-	        pos += 1;
-	        continue;
-	      }
-	      m_this.removeAnnotation(annotation, false);
-	      removed += 1;
-	    }
-	    if (removed && update !== false) {
-	      m_this.modified();
-	      m_this.draw();
-	    }
-	    return removed;
-	  };
-
-	  /**
-	   * Get the list of annotations on the layer.
-	   *
-	   * @returns {geo.annoation[]} An array of annotations.
-	   */
-	  this.annotations = function () {
-	    return m_annotations.slice();
-	  };
-
-	  /**
-	   * Get an annotation by its id.
-	   *
-	   * @param {number} id The annotation ID.
-	   * @returns {geo.annotation} The selected annotation or `undefined` if none
-	   *    matches the id.
-	   */
-	  this.annotationById = function (id) {
-	    if (id !== undefined && id !== null) {
-	      id = +id;  /* Cast to int */
-	    }
-	    var annotations = m_annotations.filter(function (annotation) {
-	      return annotation.id() === id;
-	    });
-	    if (annotations.length) {
-	      return annotations[0];
-	    }
-	  };
-
-	  /* A list of special modes */
-	  this.modes = {
-	    edit: 'edit'
-	  };
-
-	  /**
-	   * Get or set the current mode.
-	   *
-	   * @param {string|null} [arg] `undefined` to get the current mode, `null` to
-	   *    stop creating/editing, `this.modes.edit` (`'edit'`) plus an annotation
-	   *    to switch to edit mode, or the name of the type of annotation to
-	   *    create.
-	   * @param {geo.annotation} [editAnnotation] If `arg === this.modes.edit`,
-	   *    this is the annotation that should be edited.
-	   * @returns {string|null|this} The current mode or the layer.
-	   */
-	  this.mode = function (arg, editAnnotation) {
-	    if (arg === undefined) {
-	      return m_mode;
-	    }
-	    if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation)) {
-	      var createAnnotation, actions,
-	          mapNode = m_this.map().node(), oldMode = m_mode;
-	      m_mode = arg;
-	      mapNode.toggleClass('annotation-input', !!(m_mode && m_mode !== m_this.modes.edit));
-	      if (m_mode) {
-	        Mousetrap(mapNode[0]).bind('esc', function () { m_this.mode(null); });
-	      } else {
-	        Mousetrap(mapNode[0]).unbind('esc');
-	      }
-	      if (m_this.currentAnnotation) {
-	        switch (m_this.currentAnnotation.state()) {
-	          case geo_annotation.state.create:
-	            m_this.removeAnnotation(m_this.currentAnnotation);
-	            break;
-	          case geo_annotation.state.edit:
-	            m_this.currentAnnotation.state(geo_annotation.state.done);
-	            m_this.modified();
-	            m_this.draw();
-	            break;
-	        }
-	        m_this.currentAnnotation = null;
-	      }
-	      switch (m_mode) {
-	        case m_this.modes.edit:
-	          m_this.currentAnnotation = editAnnotation;
-	          m_this.currentAnnotation.state(geo_annotation.state.edit);
-	          m_this.modified();
-	          break;
-	        case 'line':
-	          createAnnotation = geo_annotation.lineAnnotation;
-	          break;
-	        case 'point':
-	          createAnnotation = geo_annotation.pointAnnotation;
-	          break;
-	        case 'polygon':
-	          createAnnotation = geo_annotation.polygonAnnotation;
-	          break;
-	        case 'rectangle':
-	          createAnnotation = geo_annotation.rectangleAnnotation;
-	          break;
-	      }
-	      m_this.map().interactor().removeAction(
-	        undefined, undefined, geo_annotation.actionOwner);
-	      if (createAnnotation) {
-	        m_this.currentAnnotation = createAnnotation({
-	          state: geo_annotation.state.create,
-	          layer: this
-	        });
-	        m_this.addAnnotation(m_this.currentAnnotation, null);
-	        actions = m_this.currentAnnotation.actions(geo_annotation.state.create);
-	        $.each(actions, function (idx, action) {
-	          m_this.map().interactor().addAction(action);
-	        });
-	      }
-	      m_this.geoTrigger(geo_event.annotation.mode, {
-	        mode: m_mode, oldMode: oldMode});
-	      if (oldMode === m_this.modes.edit) {
-	        m_this.modified();
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Return the current set of annotations as a geojson object.  Alternately,
-	   * add a set of annotations from a geojson object.
-	   *
-	   * @param {string|objectFile} [geojson] If present, add annotations based on
-	   *    the given geojson object.  If `undefined`, return the current
-	   *    annotations as geojson.  This may be a JSON string, a javascript
-	   *    object, or a File object.
-	   * @param {boolean|string} [clear] If `true`, when adding annotations, first
-	   *    remove all existing objects.  If `'update'`, update existing
-	   *    annotations and remove annotations that no longer exist.  If falsy,
-	   *    update existing annotations and leave annotations that have not chaged.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @param {boolean} [includeCrs] If truthy, include the coordinate system in
-	   *    the output.
-	   * @returns {object|number|undefined} If `geojson` was undefined, the current
-	   *    annotations is a javascript object that can be converted to geojson
-	   *    using JSON.stringify.  If `geojson` is specified, either the number of
-	   *    annotations now present upon success, or `undefined` if the value in
-	   *    `geojson` was not able to be parsed.
-	   */
-	  this.geojson = function (geojson, clear, gcs, includeCrs) {
-	    if (geojson !== undefined) {
-	      var reader = registry.createFileReader('jsonReader', {layer: m_this});
-	      if (!reader.canRead(geojson)) {
-	        return;
-	      }
-	      if (clear === true) {
-	        m_this.removeAllAnnotations(true, false);
-	      }
-	      if (clear === 'update') {
-	        $.each(m_this.annotations(), function (idx, annotation) {
-	          annotation.options('updated', false);
-	        });
-	      }
-	      reader.read(geojson, function (features) {
-	        $.each(features.slice(), function (feature_idx, feature) {
-	          m_this._geojsonFeatureToAnnotation(feature, gcs);
-	          m_this.deleteFeature(feature);
-	        });
-	      });
-	      if (clear === 'update') {
-	        $.each(m_this.annotations(), function (idx, annotation) {
-	          if (annotation.options('updated') === false &&
-	              annotation.state() === geo_annotation.state.done) {
-	            m_this.removeAnnotation(annotation, false);
-	          }
-	        });
-	      }
-	      m_this.modified();
-	      m_this.draw();
-	      return m_annotations.length;
-	    }
-	    geojson = null;
-	    var features = [];
-	    $.each(m_annotations, function (annotation_idx, annotation) {
-	      var obj = annotation.geojson(gcs, includeCrs);
-	      if (obj) {
-	        features.push(obj);
-	      }
-	    });
-	    if (features.length) {
-	      geojson = {
-	        type: 'FeatureCollection',
-	        features: features
-	      };
-	    }
-	    return geojson;
-	  };
-
-	  /**
-	   * Convert a feature as parsed by the geojson reader into one or more
-	   * annotations.
-	   *
-	   * @param {geo.feature} feature The feature to convert.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   */
-	  this._geojsonFeatureToAnnotation = function (feature, gcs) {
-	    var dataList = feature.data(),
-	        annotationList = registry.listAnnotations(),
-	        map = m_this.map();
-	    gcs = (gcs === null ? map.gcs() : (
-	        gcs === undefined ? map.ingcs() : gcs));
-	    $.each(dataList, function (data_idx, data) {
-	      var type = (data.properties || {}).annotationType || feature.featureType,
-	          options = $.extend({}, data.properties || {}),
-	          position, datagcs, i, existing;
-	      if ($.inArray(type, annotationList) < 0) {
-	        return;
-	      }
-	      options.style = options.style || {};
-	      options.labelStyle = options.labelStyle || {};
-	      delete options.annotationType;
-	      // the geoJSON reader can only emit line, polygon, and point
-	      switch (feature.featureType) {
-	        case 'line':
-	          position = feature.line()(data, data_idx);
-	          if (!position || position.length < 2) {
-	            return;
-	          }
-	          break;
-	        case 'polygon':
-	          position = feature.polygon()(data, data_idx);
-	          if (!position || !position.outer || position.outer.length < 3) {
-	            return;
-	          }
-	          position = position.outer;
-	          if (position[position.length - 1][0] === position[0][0] &&
-	              position[position.length - 1][1] === position[0][1]) {
-	            position.splice(position.length - 1, 1);
-	            if (position.length < 3) {
-	              return;
-	            }
-	          }
-	          break;
-	        case 'point':
-	          position = [feature.position()(data, data_idx)];
-	          break;
-	      }
-	      for (i = 0; i < position.length; i += 1) {
-	        position[i] = util.normalizeCoordinates(position[i]);
-	      }
-	      datagcs = ((data.crs && data.crs.type === 'name' && data.crs.properties &&
-	                  data.crs.properties.type === 'proj4' &&
-	                  data.crs.properties.name) ? data.crs.properties.name : gcs);
-	      if (datagcs !== map.gcs()) {
-	        position = transform.transformCoordinates(datagcs, map.gcs(), position);
-	      }
-	      options.coordinates = position;
-	      /* For each style listed in the geojsonStyleProperties object, check if
-	       * is given under any of the variety of keys as a valid instance of the
-	       * required data type.  If not, use the property from the feature. */
-	      $.each(geojsonStyleProperties, function (key, prop) {
-	        var value;
-	        $.each(prop.keys, function (idx, altkey) {
-	          if (value === undefined) {
-	            value = m_this.validateAttribute(options[altkey], prop.dataType);
-	          }
-	        });
-	        if (value === undefined) {
-	          value = m_this.validateAttribute(
-	            feature.style.get(key)(data, data_idx), prop.dataType);
-	        }
-	        if (value !== undefined) {
-	          options[prop.option || 'style'][key] = value;
-	        }
-	      });
-	      /* Delete property keys we have used */
-	      $.each(geojsonStyleProperties, function (key, prop) {
-	        $.each(prop.keys, function (idx, altkey) {
-	          delete options[altkey];
-	        });
-	      });
-	      if (options.annotationId !== undefined) {
-	        existing = m_this.annotationById(options.annotationId);
-	        if (existing) {
-	          delete options.annotationId;
-	        }
-	      }
-	      if (existing && existing.type() === type && existing.state() === geo_annotation.state.done && existing.options('updated') === false) {
-	        /* We could change the state of the existing annotation if it differs
-	         * from done. */
-	        delete options.state;
-	        delete options.layer;
-	        options.updated = true;
-	        existing.options(options);
-	        m_this.geoTrigger(geo_event.annotation.update, {
-	          annotation: existing
-	        });
-	      } else {
-	        options.state = geo_annotation.state.done;
-	        options.layer = m_this;
-	        options.updated = 'new';
-	        m_this.addAnnotation(registry.createAnnotation(type, options), null);
-	      }
-	    });
-	  };
-
-	  /**
-	   * Validate a value for an attribute based on a specified data type.  This
-	   * returns a sanitized value or `undefined` if the value was invalid.  Data
-	   * types include:
-	   * - `color`: a css string, `#rrggbb` hex string, `#rgb` hex string, number,
-	   *   or object with r, g, b properties in the range of [0-1].
-	   * - `opacity`: a floating point number in the range [0, 1].
-	   * - `positive`: a floating point number greater than zero.
-	   * - `boolean`: a string whose lowercase value is `'false'`, `'off'`, or
-	   *   `'no'`, and falsy values are false, all else is true.  `null` and
-	   *   `undefined` are still considered invalid values.
-	   * - `booleanOrNumber`: a string whose lowercase value is `'false'`, `'off'`,
-	   *   `'no'`, `'true'`, `'on'`, or `'yes'`, falsy values that aren't 0, and
-	   *   `true` are handled as booleans.  Otherwise, a floating point number that
-	   *   isn't NaN or an infinity.
-	   * - `coordinate2`: either an object with x and y properties that are
-	   *   numbers, or a string of the form <x>[,]<y> with optional whitespace, or
-	   *   a JSON encoded object with x and y values, or a JSON encoded list of at
-	   *   leasst two numbers.
-	   * - `number`: a floating point number that isn't NaN or an infinity.
-	   * - `angle`: a number that represents radians.  If followed by one of `deg`,
-	   *   `grad`, or `turn`, it is converted to radians.  An empty string is also
-	   *   allowed.
-	   * - `text`: any text string.
-	   * @param {number|string|object|boolean} value The value to validate.
-	   * @param {string} dataType The data type for validation.
-	   * @returns {number|string|object|boolean|undefined} The sanitized value or
-	   *    `undefined`.
-	   */
-	  this.validateAttribute = function (value, dataType) {
-	    var parts;
-
-	    if (value === undefined || value === null) {
-	      return;
-	    }
-	    switch (dataType) {
-	      case 'angle':
-	        if (value === '') {
-	          break;
-	        }
-	        parts = /^\s*([-.0-9eE]+)\s*(deg|rad|grad|turn)?\s*$/.exec(('' + value).toLowerCase());
-	        if (!parts || !isFinite(parts[1])) {
-	          return;
-	        }
-	        var factor = (parts[2] === 'grad' ? Math.PI / 200 :
-	            (parts[2] === 'deg' ? Math.PI / 180 :
-	            (parts[2] === 'turn' ? 2 * Math.PI : 1)));
-	        value = +parts[1] * factor;
-	        break;
-	      case 'boolean':
-	        value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
-	        break;
-	      case 'booleanOrNumber':
-	        if ((!value && value !== 0 && value !== '') || ['true', 'false', 'off', 'on', 'no', 'yes'].indexOf(('' + value).toLowerCase()) >= 0) {
-	          value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
-	        } else {
-	          if (!util.isNonNullFinite(value)) {
-	            return;
-	          }
-	          value = +value;
-	        }
-	        break;
-	      case 'coordinate2':
-	        if (value === '') {
-	          break;
-	        }
-	        if (value && util.isNonNullFinite(value.x) && util.isNonNullFinite(value.y)) {
-	          value.x = +value.x;
-	          value.y = +value.y;
-	          break;
-	        }
-	        try { value = JSON.parse(value); } catch (err) { }
-	        if (value && util.isNonNullFinite(value.x) && util.isNonNullFinite(value.y)) {
-	          value.x = +value.x;
-	          value.y = +value.y;
-	          break;
-	        }
-	        if (Array.isArray(value) && util.isNonNullFinite(value[0]) && util.isNonNullFinite(value[1])) {
-	          value = {x: +value[0], y: +value[1]};
-	          break;
-	        }
-	        parts = /^\s*([-.0-9eE]+)(?:\s+|\s*,)\s*([-.0-9eE]+)\s*$/.exec('' + value);
-	        if (!parts || !isFinite(parts[1]) || !isFinite(parts[2])) {
-	          return;
-	        }
-	        value = {x: +parts[1], y: +parts[2]};
-	        break;
-	      case 'color':
-	        value = util.convertColor(value);
-	        if (value === undefined || value.r === undefined) {
-	          return;
-	        }
-	        break;
-	      case 'number':
-	        if (!util.isNonNullFinite(value)) {
-	          return;
-	        }
-	        value = +value;
-	        break;
-	      case 'numberOrBlank':
-	        if (value === '') {
-	          break;
-	        }
-	        if (!util.isNonNullFinite(value)) {
-	          return;
-	        }
-	        value = +value;
-	        break;
-	      case 'opacity':
-	        if (value === undefined || value === null || value === '') {
-	          return;
-	        }
-	        value = +value;
-	        if (isNaN(value) || value < 0 || value > 1) {
-	          return;
-	        }
-	        break;
-	      case 'positive':
-	        value = +value;
-	        if (!isFinite(value) || value <= 0) {
-	          return;
-	        }
-	        break;
-	      case 'text':
-	        value = '' + value;
-	        break;
-	    }
-	    return value;
-	  };
-
-	  /**
-	   * Update layer.
-	   *
-	   * @returns {this} The current layer.
-	   */
-	  this._update = function () {
-	    if (m_this.getMTime() > m_buildTime.getMTime()) {
-	      var labels = m_this.options('showLabels') ? [] : null,
-	          editable = m_this.options('clickToEdit') || m_this.mode() === m_this.modes.edit;
-	      /* Interally, we have a set of feature levels (to provide z-index
-	       * support), each of which can have data from multiple annotations.  We
-	       * clear the data on each of these features, then build it up from each
-	       * annotation.  Eventually, it may be necessary to optimize this and
-	       * only update the features that are changed.
-	       */
-	      $.each(m_features, function (idx, featureLevel) {
-	        $.each(featureLevel, function (type, feature) {
-	          feature.data = [];
-	          delete feature.feature.scaleOnZoom;
-	        });
-	      });
-	      $.each(m_annotations, function (annotation_idx, annotation) {
-	        var features = annotation.features();
-	        if (labels) {
-	          var annotationLabel = annotation.labelRecord();
-	          if (annotationLabel) {
-	            labels.push(annotationLabel);
-	          }
-	        }
-	        $.each(features, function (idx, featureLevel) {
-	          if (m_features[idx] === undefined) {
-	            m_features[idx] = {};
-	          }
-	          $.each(featureLevel, function (type, featureSpec) {
-	            /* Create features as needed */
-	            if (!m_features[idx][type]) {
-	              var feature = m_this.createFeature(type, {
-	                gcs: m_this.map().gcs(),
-	                selectionAPI: editable
-	              });
-	              if (!feature) {
-	                /* We can't create the desired feature, porbably because of the
-	                 * selected renderer.  Issue one warning only. */
-	                var key = 'error_feature_' + type;
-	                if (!m_this[key]) {
-	                  console.warn('Cannot create a ' + type + ' feature for ' +
-	                               'annotations.');
-	                  m_this[key] = true;
-	                }
-	                return;
-	              }
-	              if (editable) {
-	                feature.geoOn(geo_event.feature.mouseon, m_this._handleMouseOn);
-	                feature.geoOn(geo_event.feature.mouseoff, m_this._handleMouseOff);
-	              }
-
-	              /* Since each annotation can have separate styles, the styles are
-	               * combined together with a meta-style function.  Any style that
-	               * could be used should be in this list.  Color styles may be
-	               * restricted to {r, g, b} objects for efficiency, but this
-	               * hasn't been tested.
-	               */
-	              var style = {};
-	              $.each([
-	                'closed', 'fill', 'fillColor', 'fillOpacity', 'line',
-	                'lineCap', 'lineJoin', 'polygon', 'position', 'radius',
-	                'stroke', 'strokeColor', 'strokeOffset', 'strokeOpacity',
-	                'strokeWidth', 'uniformPolygon'
-	              ], function (keyidx, key) {
-	                var origFunc;
-	                if (feature.style()[key] !== undefined) {
-	                  origFunc = feature.style.get(key);
-	                }
-	                style[key] = function (d, i, d2, i2) {
-	                  var style = (
-	                    (d && d.style) ? d.style : (d && d[2] && d[2].style) ?
-	                    d[2].style : d2.style);
-	                  var result = style ? style[key] : d;
-	                  if (util.isFunction(result)) {
-	                    result = result(d, i, d2, i2);
-	                  }
-	                  if (result === undefined && origFunc) {
-	                    result = origFunc(d, i, d2, i2);
-	                  }
-	                  return result;
-	                };
-	              });
-	              feature.style(style);
-	              m_features[idx][type] = {
-	                feature: feature,
-	                style: style,
-	                data: []
-	              };
-	            } else {
-	              feature = m_features[idx][type].feature;
-	              // update whether we check for selections on existing features
-	              if (feature.selectionAPI() !== !!editable) {
-	                feature.selectionAPI(editable);
-	                if (editable) {
-	                  feature.geoOn(geo_event.feature.mouseon, m_this._handleMouseOn);
-	                  feature.geoOn(geo_event.feature.mouseoff, m_this._handleMouseOff);
-	                } else {
-	                  feature.geoOff(geo_event.feature.mouseon, m_this._handleMouseOn);
-	                  feature.geoOff(geo_event.feature.mouseoff, m_this._handleMouseOff);
-	                }
-	              }
-	            }
-	            /* Collect the data for each feature */
-	            var dataEntry = featureSpec.data || featureSpec;
-	            if (!Array.isArray(dataEntry)) {
-	              dataEntry = [dataEntry];
-	            }
-	            dataEntry.forEach(function (dataElement) {
-	              dataElement.annotation = annotation;
-	              m_features[idx][type].data.push(dataElement);
-	              if (featureSpec.scaleOnZoom) {
-	                m_features[idx][type].feature.scaleOnZoom = true;
-	              }
-	            });
-	          });
-	        });
-	      });
-	      /* Update the data for each feature */
-	      $.each(m_features, function (idx, featureLevel) {
-	        $.each(featureLevel, function (type, feature) {
-	          feature.feature.data(feature.data);
-	        });
-	      });
-	      m_this._updateLabels(labels);
-	      m_buildTime.modified();
-	    }
-	    s_update.call(m_this, arguments);
-	    return m_this;
-	  };
-
-	  /**
-	   * Show or hide annotation labels.  Create or destroy a child layer or a
-	   * feature as needed.
-	   *
-	   * @param {object[]|null} labels The list of labels to display of `null` for
-	   *    no labels.
-	   * @returns {this} The class instance.
-	   */
-	  this._updateLabels = function (labels) {
-	    if (!labels || !labels.length) {
-	      m_this._removeLabelFeature();
-	      return m_this;
-	    }
-	    if (!m_labelFeature) {
-	      var renderer = registry.rendererForFeatures(['text']);
-	      if (renderer !== m_this.renderer().api()) {
-	        m_labelLayer = registry.createLayer('feature', m_this.map(), {renderer: renderer});
-	        m_this.addChild(m_labelLayer);
-	        m_labelLayer._update();
-	        m_this.geoTrigger(geo_event.layerAdd, {
-	          target: m_this,
-	          layer: m_labelLayer
-	        });
-	      }
-	      var style = {};
-	      textFeature.usedStyles.forEach(function (key) {
-	        style[key] = function (d, i) {
-	          if (d.style && d.style[key] !== undefined) {
-	            return d.style[key];
-	          }
-	          return (m_this.options('defaultLabelStyle') || {})[key];
-	        };
-	      });
-	      m_labelFeature = (m_labelLayer || m_this).createFeature('text', {
-	        style: style,
-	        gcs: m_this.map().gcs(),
-	        position: function (d) {
-	          return d.position;
-	        }
-	      });
-	    }
-	    m_labelFeature.data(labels);
-	    return m_this;
-	  };
-
-	  /**
-	   * Check if any features are marked that they need to be updated when a zoom
-	   * occurs.  If so, mark that feature as modified.
-	   */
-	  this._handleZoom = function () {
-	    var i, features = m_this.features();
-	    for (i = 0; i < features.length; i += 1) {
-	      if (features[i].scaleOnZoom) {
-	        features[i].modified();
-	      }
-	    }
-	  };
-
-	  /**
-	   * Remove the label feature if it exists.
-	   *
-	   * @returns {this} The current layer.
-	   */
-	  this._removeLabelFeature = function () {
-	    if (m_labelLayer) {
-	      m_labelLayer._exit();
-	      m_this.removeChild(m_labelLayer);
-	      m_this.geoTrigger(geo_event.layerRemove, {
-	        target: m_this,
-	        layer: m_labelLayer
-	      });
-	      m_labelLayer = m_labelFeature = null;
-	    }
-	    if (m_labelFeature) {
-	      m_this.removeFeature(m_labelFeature);
-	      m_labelFeature = null;
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Update if necessary and draw the layer.
-	   *
-	   * @returns {this} The current layer.
-	   */
-	  this.draw = function () {
-	    m_this._update();
-	    s_draw.call(m_this);
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this} The current layer.
-	   */
-	  this._init = function () {
-	    // Call super class init
-	    s_init.call(m_this);
-
-	    if (!m_this.map().interactor()) {
-	      m_this.map().interactor(mapInteractor({actions: []}));
-	    }
-	    m_this.geoOn(geo_event.actionselection, m_this._processAction);
-	    m_this.geoOn(geo_event.actionmove, m_this._processAction);
-	    m_this.geoOn(geo_event.actionup, m_this._processAction);
-
-	    m_this.geoOn(geo_event.mouseclick, m_this._handleMouseClick);
-	    m_this.geoOn(geo_event.mousemove, m_this._handleMouseMove);
-
-	    m_this.geoOn(geo_event.zoom, m_this._handleZoom);
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Free all resources.
-	   *
-	   * @returns {this} The current layer.
-	   */
-	  this._exit = function () {
-	    m_this._removeLabelFeature();
-	    // Call super class exit
-	    s_exit.call(m_this);
-	    m_annotations = [];
-	    m_features = [];
-	    return m_this;
-	  };
-
-	  return m_this;
-	};
-
-	inherit(annotationLayer, featureLayer);
-	registry.registerLayer('annotation', annotationLayer);
-	module.exports = annotationLayer;
-
-
-/***/ }),
-/* 220 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var layer = __webpack_require__(210);
-	var geo_event = __webpack_require__(9);
-	var registry = __webpack_require__(201);
-
-	/**
-	 * Layer to draw points, lines, and polygons on the map The polydata layer
-	 * provide mechanisms to create and draw geometrical shapes such as points,
-	 * lines, and polygons.
-	 * @class
-	 * @alias geo.featureLayer
-	 * @extends geo.layer
-	 * @param {object} arg Options for the new layer.
-	 * @returns {geo.featureLayer}
-	 */
-	var featureLayer = function (arg) {
-	  'use strict';
-	  if (!(this instanceof featureLayer)) {
-	    return new featureLayer(arg);
-	  }
-	  layer.call(this, arg);
-
-	  /**
-	   * private
-	   */
-	  var m_this = this,
-	      m_features = [],
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      s_update = this._update,
-	      s_visible = this.visible,
-	      s_selectionAPI = this.selectionAPI,
-	      s_draw = this.draw;
-
-	  /**
-	   * Create a feature by name.
-	   *
-	   * @param {string} featureName The name of the feature to create.
-	   * @param {object} arg Properties for the new feature.
-	   * @returns {geo.feature} The created feature.
-	   */
-	  this.createFeature = function (featureName, arg) {
-
-	    var newFeature = registry.createFeature(
-	      featureName, m_this, m_this.renderer(), arg);
-	    if (newFeature) {
-	      this.addFeature(newFeature);
-	    }
-	    return newFeature;
-	  };
-
-	  /**
-	   * Add a feature to the layer if it is not already present.
-	   *
-	   * @param {object} feature the feature to add.
-	   * @returns {this}
-	   */
-	  this.addFeature = function (feature) {
-	    /* try to remove the feature first so that we don't have two copies */
-	    this.removeFeature(feature);
-	    m_this.addChild(feature);
-	    m_features.push(feature);
-	    m_this.dataTime().modified();
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Remove a feature without destroying it.
-	   *
-	   * @param {geo.feature} feature The feature to remove.
-	   * @returns {this}
-	   */
-	  this.removeFeature = function (feature) {
-	    var pos;
-
-	    pos = m_features.indexOf(feature);
-	    if (pos >= 0) {
-	      m_features.splice(pos, 1);
-	      m_this.removeChild(feature);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Delete feature.
-	   *
-	   * @param {geo.feature} feature The feature to delete.
-	   * @returns {this}
-	   */
-	  this.deleteFeature = function (feature) {
-
-	    // call _exit first, as destroying the feature affect other features
-	    if (feature) {
-	      if (m_features.indexOf(feature) >= 0) {
-	        feature._exit();
-	      }
-	      this.removeFeature(feature);
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set drawables.
-	   *
-	   * @param {geo.feature[]} val A list of features, or unspecified to return
-	   *    the current feature list.  If a list is provided, features are added or
-	   *    removed as needed.
-	   * @returns {geo.feature[]|this} The current features associated with the
-	   *    layer or the current layer.
-	   */
-	  this.features = function (val) {
-	    if (val === undefined) {
-	      return m_features.slice();
-	    } else {
-	      // delete existing features that aren't in the new array.  Since features
-	      // can affect other features during their exit process, make sure each
-	      // feature still exists as we work through the list.
-	      var existing = m_features.slice();
-	      var i;
-	      for (i = 0; i < existing.length; i += 1) {
-	        if (val.indexOf(existing[i]) < 0 && m_features.indexOf(existing[i]) >= 0) {
-	          this.deleteFeature(existing[i]);
-	        }
-	      }
-	      for (i = 0; i < val.length; i += 1) {
-	        this.addFeature(val[i]);
-	      }
-	      return m_this;
-	    }
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    if (m_this.initialized()) {
-	      return m_this;
-	    }
-
-	    // Call super class init
-	    s_init.call(m_this, true);
-
-	    // Bind events to handlers
-	    m_this.geoOn(geo_event.resize, function (event) {
-	      if (m_this.renderer()) {
-	        m_this.renderer()._resize(event.x, event.y, event.width, event.height);
-	        m_this._update({event: event});
-	        m_this.renderer()._render();
-	      } else {
-	        m_this._update({event: event});
-	      }
-	    });
-
-	    m_this.geoOn(geo_event.pan, function (event) {
-	      m_this._update({event: event});
-	      if (m_this.renderer()) {
-	        m_this.renderer()._render();
-	      }
-	    });
-
-	    m_this.geoOn(geo_event.rotate, function (event) {
-	      m_this._update({event: event});
-	      if (m_this.renderer()) {
-	        m_this.renderer()._render();
-	      }
-	    });
-
-	    m_this.geoOn(geo_event.zoom, function (event) {
-	      m_this._update({event: event});
-	      if (m_this.renderer()) {
-	        m_this.renderer()._render();
-	      }
-	    });
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Update layer.
-	   *
-	   * @param {object} request A value to pass to the parent class.
-	   * @returns {this}
-	   */
-	  this._update = function (request) {
-	    var i;
-
-	    if (!m_features.length) {
-	      return m_this;
-	    }
-
-	    // Call base class update
-	    s_update.call(m_this, request);
-
-	    if (m_this.dataTime().getMTime() > m_this.updateTime().getMTime()) {
-	      for (i = 0; i < m_features.length; i += 1) {
-	        m_features[i].renderer(m_this.renderer());
-	      }
-	    }
-
-	    for (i = 0; i < m_features.length; i += 1) {
-	      m_features[i]._update();
-	    }
-
-	    m_this.updateTime().modified();
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Free all resources.
-	   */
-	  this._exit = function () {
-	    m_this.clear();
-	    s_exit();
-	  };
-
-	  /**
-	   * Draw.  If the layer is visible, call the parent class's draw function and
-	   * the renderer's render function.
-	   *
-	   * @returns {this}
-	   */
-	  this.draw = function () {
-	    if (m_this.visible()) {
-	      // Call sceneObject.draw, which calls draw on all child objects.
-	      s_draw();
-
-	      // Now call render on the renderer. In certain cases it may not do
-	      // anything if the child objects are drawn on the screen already.
-	      if (m_this.renderer()) {
-	        m_this.renderer()._render();
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set visibility of the layer.
-	   *
-	   * @param {boolean} [val] If specified, change the visibility, otherwise
-	   *    return it.
-	   * @returns {boolean|this} The current visibility or the layer.
-	   */
-	  this.visible = function (val) {
-	    if (val === undefined) {
-	      return s_visible();
-	    }
-	    if (m_this.visible() !== val) {
-	      s_visible(val);
-
-	      // take a copy of the features; changing visible could mutate them.
-	      var features = m_features.slice(), i;
-
-	      for (i = 0; i < features.length; i += 1) {
-	        features[i].visible(features[i].visible(undefined, true), true);
-	      }
-	      if (val) {
-	        m_this.draw();
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set selectionAPI of the layer.
-	   *
-	   * @param {boolean} [val] If specified change the selectionAPI state of the
-	   *    layer, otherwise return the current state.
-	   * @returns {boolean|this} The selectionAPI state or the current layer.
-	   */
-	  this.selectionAPI = function (val) {
-	    if (val === undefined) {
-	      return s_selectionAPI();
-	    }
-	    if (m_this.selectionAPI() !== val) {
-	      s_selectionAPI(val);
-
-	      // take a copy of the features; changing selectionAPI could mutate them.
-	      var features = m_features.slice(), i;
-
-	      for (i = 0; i < features.length; i += 1) {
-	        features[i].selectionAPI(features[i].selectionAPI(undefined, true), true);
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Clear all features in layer.
-	   *
-	   * @returns {this}
-	   */
-	  this.clear = function () {
-	    while (m_features.length) {
-	      m_this.deleteFeature(m_features[0]);
-	    }
-	    return m_this;
-	  };
-
-	  return m_this;
-	};
-
-	inherit(featureLayer, layer);
-	registry.registerLayer('feature', featureLayer);
-	module.exports = featureLayer;
-
-
-/***/ }),
-/* 221 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var __WEBPACK_AMD_DEFINE_RESULT__;/*global define:false */
-	/**
-	 * Copyright 2012-2017 Craig Campbell
-	 *
-	 * Licensed under the Apache License, Version 2.0 (the "License");
-	 * you may not use this file except in compliance with the License.
-	 * You may obtain a copy of the License at
-	 *
-	 * http://www.apache.org/licenses/LICENSE-2.0
-	 *
-	 * Unless required by applicable law or agreed to in writing, software
-	 * distributed under the License is distributed on an "AS IS" BASIS,
-	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-	 * See the License for the specific language governing permissions and
-	 * limitations under the License.
-	 *
-	 * Mousetrap is a simple keyboard shortcut library for Javascript with
-	 * no external dependencies
-	 *
-	 * @version 1.6.1
-	 * @url craig.is/killing/mice
-	 */
-	(function(window, document, undefined) {
-
-	    // Check if mousetrap is used inside browser, if not, return
-	    if (!window) {
-	        return;
-	    }
-
-	    /**
-	     * mapping of special keycodes to their corresponding keys
-	     *
-	     * everything in this dictionary cannot use keypress events
-	     * so it has to be here to map to the correct keycodes for
-	     * keyup/keydown events
-	     *
-	     * @type {Object}
-	     */
-	    var _MAP = {
-	        8: 'backspace',
-	        9: 'tab',
-	        13: 'enter',
-	        16: 'shift',
-	        17: 'ctrl',
-	        18: 'alt',
-	        20: 'capslock',
-	        27: 'esc',
-	        32: 'space',
-	        33: 'pageup',
-	        34: 'pagedown',
-	        35: 'end',
-	        36: 'home',
-	        37: 'left',
-	        38: 'up',
-	        39: 'right',
-	        40: 'down',
-	        45: 'ins',
-	        46: 'del',
-	        91: 'meta',
-	        93: 'meta',
-	        224: 'meta'
-	    };
-
-	    /**
-	     * mapping for special characters so they can support
-	     *
-	     * this dictionary is only used incase you want to bind a
-	     * keyup or keydown event to one of these keys
-	     *
-	     * @type {Object}
-	     */
-	    var _KEYCODE_MAP = {
-	        106: '*',
-	        107: '+',
-	        109: '-',
-	        110: '.',
-	        111 : '/',
-	        186: ';',
-	        187: '=',
-	        188: ',',
-	        189: '-',
-	        190: '.',
-	        191: '/',
-	        192: '`',
-	        219: '[',
-	        220: '\\',
-	        221: ']',
-	        222: '\''
-	    };
-
-	    /**
-	     * this is a mapping of keys that require shift on a US keypad
-	     * back to the non shift equivelents
-	     *
-	     * this is so you can use keyup events with these keys
-	     *
-	     * note that this will only work reliably on US keyboards
-	     *
-	     * @type {Object}
-	     */
-	    var _SHIFT_MAP = {
-	        '~': '`',
-	        '!': '1',
-	        '@': '2',
-	        '#': '3',
-	        '$': '4',
-	        '%': '5',
-	        '^': '6',
-	        '&': '7',
-	        '*': '8',
-	        '(': '9',
-	        ')': '0',
-	        '_': '-',
-	        '+': '=',
-	        ':': ';',
-	        '\"': '\'',
-	        '<': ',',
-	        '>': '.',
-	        '?': '/',
-	        '|': '\\'
-	    };
-
-	    /**
-	     * this is a list of special strings you can use to map
-	     * to modifier keys when you specify your keyboard shortcuts
-	     *
-	     * @type {Object}
-	     */
-	    var _SPECIAL_ALIASES = {
-	        'option': 'alt',
-	        'command': 'meta',
-	        'return': 'enter',
-	        'escape': 'esc',
-	        'plus': '+',
-	        'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
-	    };
-
-	    /**
-	     * variable to store the flipped version of _MAP from above
-	     * needed to check if we should use keypress or not when no action
-	     * is specified
-	     *
-	     * @type {Object|undefined}
-	     */
-	    var _REVERSE_MAP;
-
-	    /**
-	     * loop through the f keys, f1 to f19 and add them to the map
-	     * programatically
-	     */
-	    for (var i = 1; i < 20; ++i) {
-	        _MAP[111 + i] = 'f' + i;
-	    }
-
-	    /**
-	     * loop through to map numbers on the numeric keypad
-	     */
-	    for (i = 0; i <= 9; ++i) {
-
-	        // This needs to use a string cause otherwise since 0 is falsey
-	        // mousetrap will never fire for numpad 0 pressed as part of a keydown
-	        // event.
-	        //
-	        // @see https://github.com/ccampbell/mousetrap/pull/258
-	        _MAP[i + 96] = i.toString();
-	    }
-
-	    /**
-	     * cross browser add event method
-	     *
-	     * @param {Element|HTMLDocument} object
-	     * @param {string} type
-	     * @param {Function} callback
-	     * @returns void
-	     */
-	    function _addEvent(object, type, callback) {
-	        if (object.addEventListener) {
-	            object.addEventListener(type, callback, false);
-	            return;
-	        }
-
-	        object.attachEvent('on' + type, callback);
-	    }
-
-	    /**
-	     * takes the event and returns the key character
-	     *
-	     * @param {Event} e
-	     * @return {string}
-	     */
-	    function _characterFromEvent(e) {
-
-	        // for keypress events we should return the character as is
-	        if (e.type == 'keypress') {
-	            var character = String.fromCharCode(e.which);
-
-	            // if the shift key is not pressed then it is safe to assume
-	            // that we want the character to be lowercase.  this means if
-	            // you accidentally have caps lock on then your key bindings
-	            // will continue to work
-	            //
-	            // the only side effect that might not be desired is if you
-	            // bind something like 'A' cause you want to trigger an
-	            // event when capital A is pressed caps lock will no longer
-	            // trigger the event.  shift+a will though.
-	            if (!e.shiftKey) {
-	                character = character.toLowerCase();
-	            }
-
-	            return character;
-	        }
-
-	        // for non keypress events the special maps are needed
-	        if (_MAP[e.which]) {
-	            return _MAP[e.which];
-	        }
-
-	        if (_KEYCODE_MAP[e.which]) {
-	            return _KEYCODE_MAP[e.which];
-	        }
-
-	        // if it is not in the special map
-
-	        // with keydown and keyup events the character seems to always
-	        // come in as an uppercase character whether you are pressing shift
-	        // or not.  we should make sure it is always lowercase for comparisons
-	        return String.fromCharCode(e.which).toLowerCase();
-	    }
-
-	    /**
-	     * checks if two arrays are equal
-	     *
-	     * @param {Array} modifiers1
-	     * @param {Array} modifiers2
-	     * @returns {boolean}
-	     */
-	    function _modifiersMatch(modifiers1, modifiers2) {
-	        return modifiers1.sort().join(',') === modifiers2.sort().join(',');
-	    }
-
-	    /**
-	     * takes a key event and figures out what the modifiers are
-	     *
-	     * @param {Event} e
-	     * @returns {Array}
-	     */
-	    function _eventModifiers(e) {
-	        var modifiers = [];
-
-	        if (e.shiftKey) {
-	            modifiers.push('shift');
-	        }
-
-	        if (e.altKey) {
-	            modifiers.push('alt');
-	        }
-
-	        if (e.ctrlKey) {
-	            modifiers.push('ctrl');
-	        }
-
-	        if (e.metaKey) {
-	            modifiers.push('meta');
-	        }
-
-	        return modifiers;
-	    }
-
-	    /**
-	     * prevents default for this event
-	     *
-	     * @param {Event} e
-	     * @returns void
-	     */
-	    function _preventDefault(e) {
-	        if (e.preventDefault) {
-	            e.preventDefault();
-	            return;
-	        }
-
-	        e.returnValue = false;
-	    }
-
-	    /**
-	     * stops propogation for this event
-	     *
-	     * @param {Event} e
-	     * @returns void
-	     */
-	    function _stopPropagation(e) {
-	        if (e.stopPropagation) {
-	            e.stopPropagation();
-	            return;
-	        }
-
-	        e.cancelBubble = true;
-	    }
-
-	    /**
-	     * determines if the keycode specified is a modifier key or not
-	     *
-	     * @param {string} key
-	     * @returns {boolean}
-	     */
-	    function _isModifier(key) {
-	        return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
-	    }
-
-	    /**
-	     * reverses the map lookup so that we can look for specific keys
-	     * to see what can and can't use keypress
-	     *
-	     * @return {Object}
-	     */
-	    function _getReverseMap() {
-	        if (!_REVERSE_MAP) {
-	            _REVERSE_MAP = {};
-	            for (var key in _MAP) {
-
-	                // pull out the numeric keypad from here cause keypress should
-	                // be able to detect the keys from the character
-	                if (key > 95 && key < 112) {
-	                    continue;
-	                }
-
-	                if (_MAP.hasOwnProperty(key)) {
-	                    _REVERSE_MAP[_MAP[key]] = key;
-	                }
-	            }
-	        }
-	        return _REVERSE_MAP;
-	    }
-
-	    /**
-	     * picks the best action based on the key combination
-	     *
-	     * @param {string} key - character for key
-	     * @param {Array} modifiers
-	     * @param {string=} action passed in
-	     */
-	    function _pickBestAction(key, modifiers, action) {
-
-	        // if no action was picked in we should try to pick the one
-	        // that we think would work best for this key
-	        if (!action) {
-	            action = _getReverseMap()[key] ? 'keydown' : 'keypress';
-	        }
-
-	        // modifier keys don't work as expected with keypress,
-	        // switch to keydown
-	        if (action == 'keypress' && modifiers.length) {
-	            action = 'keydown';
-	        }
-
-	        return action;
-	    }
-
-	    /**
-	     * Converts from a string key combination to an array
-	     *
-	     * @param  {string} combination like "command+shift+l"
-	     * @return {Array}
-	     */
-	    function _keysFromString(combination) {
-	        if (combination === '+') {
-	            return ['+'];
-	        }
-
-	        combination = combination.replace(/\+{2}/g, '+plus');
-	        return combination.split('+');
-	    }
-
-	    /**
-	     * Gets info for a specific key combination
-	     *
-	     * @param  {string} combination key combination ("command+s" or "a" or "*")
-	     * @param  {string=} action
-	     * @returns {Object}
-	     */
-	    function _getKeyInfo(combination, action) {
-	        var keys;
-	        var key;
-	        var i;
-	        var modifiers = [];
-
-	        // take the keys from this pattern and figure out what the actual
-	        // pattern is all about
-	        keys = _keysFromString(combination);
-
-	        for (i = 0; i < keys.length; ++i) {
-	            key = keys[i];
-
-	            // normalize key names
-	            if (_SPECIAL_ALIASES[key]) {
-	                key = _SPECIAL_ALIASES[key];
-	            }
-
-	            // if this is not a keypress event then we should
-	            // be smart about using shift keys
-	            // this will only work for US keyboards however
-	            if (action && action != 'keypress' && _SHIFT_MAP[key]) {
-	                key = _SHIFT_MAP[key];
-	                modifiers.push('shift');
-	            }
-
-	            // if this key is a modifier then add it to the list of modifiers
-	            if (_isModifier(key)) {
-	                modifiers.push(key);
-	            }
-	        }
-
-	        // depending on what the key combination is
-	        // we will try to pick the best event for it
-	        action = _pickBestAction(key, modifiers, action);
-
-	        return {
-	            key: key,
-	            modifiers: modifiers,
-	            action: action
-	        };
-	    }
-
-	    function _belongsTo(element, ancestor) {
-	        if (element === null || element === document) {
-	            return false;
-	        }
-
-	        if (element === ancestor) {
-	            return true;
-	        }
-
-	        return _belongsTo(element.parentNode, ancestor);
-	    }
-
-	    function Mousetrap(targetElement) {
-	        var self = this;
-
-	        targetElement = targetElement || document;
-
-	        if (!(self instanceof Mousetrap)) {
-	            return new Mousetrap(targetElement);
-	        }
-
-	        /**
-	         * element to attach key events to
-	         *
-	         * @type {Element}
-	         */
-	        self.target = targetElement;
-
-	        /**
-	         * a list of all the callbacks setup via Mousetrap.bind()
-	         *
-	         * @type {Object}
-	         */
-	        self._callbacks = {};
-
-	        /**
-	         * direct map of string combinations to callbacks used for trigger()
-	         *
-	         * @type {Object}
-	         */
-	        self._directMap = {};
-
-	        /**
-	         * keeps track of what level each sequence is at since multiple
-	         * sequences can start out with the same sequence
-	         *
-	         * @type {Object}
-	         */
-	        var _sequenceLevels = {};
-
-	        /**
-	         * variable to store the setTimeout call
-	         *
-	         * @type {null|number}
-	         */
-	        var _resetTimer;
-
-	        /**
-	         * temporary state where we will ignore the next keyup
-	         *
-	         * @type {boolean|string}
-	         */
-	        var _ignoreNextKeyup = false;
-
-	        /**
-	         * temporary state where we will ignore the next keypress
-	         *
-	         * @type {boolean}
-	         */
-	        var _ignoreNextKeypress = false;
-
-	        /**
-	         * are we currently inside of a sequence?
-	         * type of action ("keyup" or "keydown" or "keypress") or false
-	         *
-	         * @type {boolean|string}
-	         */
-	        var _nextExpectedAction = false;
-
-	        /**
-	         * resets all sequence counters except for the ones passed in
-	         *
-	         * @param {Object} doNotReset
-	         * @returns void
-	         */
-	        function _resetSequences(doNotReset) {
-	            doNotReset = doNotReset || {};
-
-	            var activeSequences = false,
-	                key;
-
-	            for (key in _sequenceLevels) {
-	                if (doNotReset[key]) {
-	                    activeSequences = true;
-	                    continue;
-	                }
-	                _sequenceLevels[key] = 0;
-	            }
-
-	            if (!activeSequences) {
-	                _nextExpectedAction = false;
-	            }
-	        }
-
-	        /**
-	         * finds all callbacks that match based on the keycode, modifiers,
-	         * and action
-	         *
-	         * @param {string} character
-	         * @param {Array} modifiers
-	         * @param {Event|Object} e
-	         * @param {string=} sequenceName - name of the sequence we are looking for
-	         * @param {string=} combination
-	         * @param {number=} level
-	         * @returns {Array}
-	         */
-	        function _getMatches(character, modifiers, e, sequenceName, combination, level) {
-	            var i;
-	            var callback;
-	            var matches = [];
-	            var action = e.type;
-
-	            // if there are no events related to this keycode
-	            if (!self._callbacks[character]) {
-	                return [];
-	            }
-
-	            // if a modifier key is coming up on its own we should allow it
-	            if (action == 'keyup' && _isModifier(character)) {
-	                modifiers = [character];
-	            }
-
-	            // loop through all callbacks for the key that was pressed
-	            // and see if any of them match
-	            for (i = 0; i < self._callbacks[character].length; ++i) {
-	                callback = self._callbacks[character][i];
-
-	                // if a sequence name is not specified, but this is a sequence at
-	                // the wrong level then move onto the next match
-	                if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
-	                    continue;
-	                }
-
-	                // if the action we are looking for doesn't match the action we got
-	                // then we should keep going
-	                if (action != callback.action) {
-	                    continue;
-	                }
-
-	                // if this is a keypress event and the meta key and control key
-	                // are not pressed that means that we need to only look at the
-	                // character, otherwise check the modifiers as well
-	                //
-	                // chrome will not fire a keypress if meta or control is down
-	                // safari will fire a keypress if meta or meta+shift is down
-	                // firefox will fire a keypress if meta or control is down
-	                if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
-
-	                    // when you bind a combination or sequence a second time it
-	                    // should overwrite the first one.  if a sequenceName or
-	                    // combination is specified in this call it does just that
-	                    //
-	                    // @todo make deleting its own method?
-	                    var deleteCombo = !sequenceName && callback.combo == combination;
-	                    var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
-	                    if (deleteCombo || deleteSequence) {
-	                        self._callbacks[character].splice(i, 1);
-	                    }
-
-	                    matches.push(callback);
-	                }
-	            }
-
-	            return matches;
-	        }
-
-	        /**
-	         * actually calls the callback function
-	         *
-	         * if your callback function returns false this will use the jquery
-	         * convention - prevent default and stop propogation on the event
-	         *
-	         * @param {Function} callback
-	         * @param {Event} e
-	         * @returns void
-	         */
-	        function _fireCallback(callback, e, combo, sequence) {
-
-	            // if this event should not happen stop here
-	            if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
-	                return;
-	            }
-
-	            if (callback(e, combo) === false) {
-	                _preventDefault(e);
-	                _stopPropagation(e);
-	            }
-	        }
-
-	        /**
-	         * handles a character key event
-	         *
-	         * @param {string} character
-	         * @param {Array} modifiers
-	         * @param {Event} e
-	         * @returns void
-	         */
-	        self._handleKey = function(character, modifiers, e) {
-	            var callbacks = _getMatches(character, modifiers, e);
-	            var i;
-	            var doNotReset = {};
-	            var maxLevel = 0;
-	            var processedSequenceCallback = false;
-
-	            // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
-	            for (i = 0; i < callbacks.length; ++i) {
-	                if (callbacks[i].seq) {
-	                    maxLevel = Math.max(maxLevel, callbacks[i].level);
-	                }
-	            }
-
-	            // loop through matching callbacks for this key event
-	            for (i = 0; i < callbacks.length; ++i) {
-
-	                // fire for all sequence callbacks
-	                // this is because if for example you have multiple sequences
-	                // bound such as "g i" and "g t" they both need to fire the
-	                // callback for matching g cause otherwise you can only ever
-	                // match the first one
-	                if (callbacks[i].seq) {
-
-	                    // only fire callbacks for the maxLevel to prevent
-	                    // subsequences from also firing
-	                    //
-	                    // for example 'a option b' should not cause 'option b' to fire
-	                    // even though 'option b' is part of the other sequence
-	                    //
-	                    // any sequences that do not match here will be discarded
-	                    // below by the _resetSequences call
-	                    if (callbacks[i].level != maxLevel) {
-	                        continue;
-	                    }
-
-	                    processedSequenceCallback = true;
-
-	                    // keep a list of which sequences were matches for later
-	                    doNotReset[callbacks[i].seq] = 1;
-	                    _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
-	                    continue;
-	                }
-
-	                // if there were no sequence matches but we are still here
-	                // that means this is a regular match so we should fire that
-	                if (!processedSequenceCallback) {
-	                    _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
-	                }
-	            }
-
-	            // if the key you pressed matches the type of sequence without
-	            // being a modifier (ie "keyup" or "keypress") then we should
-	            // reset all sequences that were not matched by this event
-	            //
-	            // this is so, for example, if you have the sequence "h a t" and you
-	            // type "h e a r t" it does not match.  in this case the "e" will
-	            // cause the sequence to reset
-	            //
-	            // modifier keys are ignored because you can have a sequence
-	            // that contains modifiers such as "enter ctrl+space" and in most
-	            // cases the modifier key will be pressed before the next key
-	            //
-	            // also if you have a sequence such as "ctrl+b a" then pressing the
-	            // "b" key will trigger a "keypress" and a "keydown"
-	            //
-	            // the "keydown" is expected when there is a modifier, but the
-	            // "keypress" ends up matching the _nextExpectedAction since it occurs
-	            // after and that causes the sequence to reset
-	            //
-	            // we ignore keypresses in a sequence that directly follow a keydown
-	            // for the same character
-	            var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
-	            if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
-	                _resetSequences(doNotReset);
-	            }
-
-	            _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
-	        };
-
-	        /**
-	         * handles a keydown event
-	         *
-	         * @param {Event} e
-	         * @returns void
-	         */
-	        function _handleKeyEvent(e) {
-
-	            // normalize e.which for key events
-	            // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
-	            if (typeof e.which !== 'number') {
-	                e.which = e.keyCode;
-	            }
-
-	            var character = _characterFromEvent(e);
-
-	            // no character found then stop
-	            if (!character) {
-	                return;
-	            }
-
-	            // need to use === for the character check because the character can be 0
-	            if (e.type == 'keyup' && _ignoreNextKeyup === character) {
-	                _ignoreNextKeyup = false;
-	                return;
-	            }
-
-	            self.handleKey(character, _eventModifiers(e), e);
-	        }
-
-	        /**
-	         * called to set a 1 second timeout on the specified sequence
-	         *
-	         * this is so after each key press in the sequence you have 1 second
-	         * to press the next key before you have to start over
-	         *
-	         * @returns void
-	         */
-	        function _resetSequenceTimer() {
-	            clearTimeout(_resetTimer);
-	            _resetTimer = setTimeout(_resetSequences, 1000);
-	        }
-
-	        /**
-	         * binds a key sequence to an event
-	         *
-	         * @param {string} combo - combo specified in bind call
-	         * @param {Array} keys
-	         * @param {Function} callback
-	         * @param {string=} action
-	         * @returns void
-	         */
-	        function _bindSequence(combo, keys, callback, action) {
-
-	            // start off by adding a sequence level record for this combination
-	            // and setting the level to 0
-	            _sequenceLevels[combo] = 0;
-
-	            /**
-	             * callback to increase the sequence level for this sequence and reset
-	             * all other sequences that were active
-	             *
-	             * @param {string} nextAction
-	             * @returns {Function}
-	             */
-	            function _increaseSequence(nextAction) {
-	                return function() {
-	                    _nextExpectedAction = nextAction;
-	                    ++_sequenceLevels[combo];
-	                    _resetSequenceTimer();
-	                };
-	            }
-
-	            /**
-	             * wraps the specified callback inside of another function in order
-	             * to reset all sequence counters as soon as this sequence is done
-	             *
-	             * @param {Event} e
-	             * @returns void
-	             */
-	            function _callbackAndReset(e) {
-	                _fireCallback(callback, e, combo);
-
-	                // we should ignore the next key up if the action is key down
-	                // or keypress.  this is so if you finish a sequence and
-	                // release the key the final key will not trigger a keyup
-	                if (action !== 'keyup') {
-	                    _ignoreNextKeyup = _characterFromEvent(e);
-	                }
-
-	                // weird race condition if a sequence ends with the key
-	                // another sequence begins with
-	                setTimeout(_resetSequences, 10);
-	            }
-
-	            // loop through keys one at a time and bind the appropriate callback
-	            // function.  for any key leading up to the final one it should
-	            // increase the sequence. after the final, it should reset all sequences
-	            //
-	            // if an action is specified in the original bind call then that will
-	            // be used throughout.  otherwise we will pass the action that the
-	            // next key in the sequence should match.  this allows a sequence
-	            // to mix and match keypress and keydown events depending on which
-	            // ones are better suited to the key provided
-	            for (var i = 0; i < keys.length; ++i) {
-	                var isFinal = i + 1 === keys.length;
-	                var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
-	                _bindSingle(keys[i], wrappedCallback, action, combo, i);
-	            }
-	        }
-
-	        /**
-	         * binds a single keyboard combination
-	         *
-	         * @param {string} combination
-	         * @param {Function} callback
-	         * @param {string=} action
-	         * @param {string=} sequenceName - name of sequence if part of sequence
-	         * @param {number=} level - what part of the sequence the command is
-	         * @returns void
-	         */
-	        function _bindSingle(combination, callback, action, sequenceName, level) {
-
-	            // store a direct mapped reference for use with Mousetrap.trigger
-	            self._directMap[combination + ':' + action] = callback;
-
-	            // make sure multiple spaces in a row become a single space
-	            combination = combination.replace(/\s+/g, ' ');
-
-	            var sequence = combination.split(' ');
-	            var info;
-
-	            // if this pattern is a sequence of keys then run through this method
-	            // to reprocess each pattern one key at a time
-	            if (sequence.length > 1) {
-	                _bindSequence(combination, sequence, callback, action);
-	                return;
-	            }
-
-	            info = _getKeyInfo(combination, action);
-
-	            // make sure to initialize array if this is the first time
-	            // a callback is added for this key
-	            self._callbacks[info.key] = self._callbacks[info.key] || [];
-
-	            // remove an existing match if there is one
-	            _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
-
-	            // add this call back to the array
-	            // if it is a sequence put it at the beginning
-	            // if not put it at the end
-	            //
-	            // this is important because the way these are processed expects
-	            // the sequence ones to come first
-	            self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({
-	                callback: callback,
-	                modifiers: info.modifiers,
-	                action: info.action,
-	                seq: sequenceName,
-	                level: level,
-	                combo: combination
-	            });
-	        }
-
-	        /**
-	         * binds multiple combinations to the same callback
-	         *
-	         * @param {Array} combinations
-	         * @param {Function} callback
-	         * @param {string|undefined} action
-	         * @returns void
-	         */
-	        self._bindMultiple = function(combinations, callback, action) {
-	            for (var i = 0; i < combinations.length; ++i) {
-	                _bindSingle(combinations[i], callback, action);
-	            }
-	        };
-
-	        // start!
-	        _addEvent(targetElement, 'keypress', _handleKeyEvent);
-	        _addEvent(targetElement, 'keydown', _handleKeyEvent);
-	        _addEvent(targetElement, 'keyup', _handleKeyEvent);
-	    }
-
-	    /**
-	     * binds an event to mousetrap
-	     *
-	     * can be a single key, a combination of keys separated with +,
-	     * an array of keys, or a sequence of keys separated by spaces
-	     *
-	     * be sure to list the modifier keys first to make sure that the
-	     * correct key ends up getting bound (the last key in the pattern)
-	     *
-	     * @param {string|Array} keys
-	     * @param {Function} callback
-	     * @param {string=} action - 'keypress', 'keydown', or 'keyup'
-	     * @returns void
-	     */
-	    Mousetrap.prototype.bind = function(keys, callback, action) {
-	        var self = this;
-	        keys = keys instanceof Array ? keys : [keys];
-	        self._bindMultiple.call(self, keys, callback, action);
-	        return self;
-	    };
-
-	    /**
-	     * unbinds an event to mousetrap
-	     *
-	     * the unbinding sets the callback function of the specified key combo
-	     * to an empty function and deletes the corresponding key in the
-	     * _directMap dict.
-	     *
-	     * TODO: actually remove this from the _callbacks dictionary instead
-	     * of binding an empty function
-	     *
-	     * the keycombo+action has to be exactly the same as
-	     * it was defined in the bind method
-	     *
-	     * @param {string|Array} keys
-	     * @param {string} action
-	     * @returns void
-	     */
-	    Mousetrap.prototype.unbind = function(keys, action) {
-	        var self = this;
-	        return self.bind.call(self, keys, function() {}, action);
-	    };
-
-	    /**
-	     * triggers an event that has already been bound
-	     *
-	     * @param {string} keys
-	     * @param {string=} action
-	     * @returns void
-	     */
-	    Mousetrap.prototype.trigger = function(keys, action) {
-	        var self = this;
-	        if (self._directMap[keys + ':' + action]) {
-	            self._directMap[keys + ':' + action]({}, keys);
-	        }
-	        return self;
-	    };
-
-	    /**
-	     * resets the library back to its initial state.  this is useful
-	     * if you want to clear out the current keyboard shortcuts and bind
-	     * new ones - for example if you switch to another page
-	     *
-	     * @returns void
-	     */
-	    Mousetrap.prototype.reset = function() {
-	        var self = this;
-	        self._callbacks = {};
-	        self._directMap = {};
-	        return self;
-	    };
-
-	    /**
-	     * should we stop this event before firing off callbacks
-	     *
-	     * @param {Event} e
-	     * @param {Element} element
-	     * @return {boolean}
-	     */
-	    Mousetrap.prototype.stopCallback = function(e, element) {
-	        var self = this;
-
-	        // if the element has the class "mousetrap" then no need to stop
-	        if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
-	            return false;
-	        }
-
-	        if (_belongsTo(element, self.target)) {
-	            return false;
-	        }
-
-	        // stop for input, select, and textarea
-	        return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
-	    };
-
-	    /**
-	     * exposes _handleKey publicly so it can be overwritten by extensions
-	     */
-	    Mousetrap.prototype.handleKey = function() {
-	        var self = this;
-	        return self._handleKey.apply(self, arguments);
-	    };
-
-	    /**
-	     * allow custom key mappings
-	     */
-	    Mousetrap.addKeycodes = function(object) {
-	        for (var key in object) {
-	            if (object.hasOwnProperty(key)) {
-	                _MAP[key] = object[key];
-	            }
-	        }
-	        _REVERSE_MAP = null;
-	    };
-
-	    /**
-	     * Init the global mousetrap functions
-	     *
-	     * This method is needed to allow the global mousetrap functions to work
-	     * now that mousetrap is a constructor function.
-	     */
-	    Mousetrap.init = function() {
-	        var documentMousetrap = Mousetrap(document);
-	        for (var method in documentMousetrap) {
-	            if (method.charAt(0) !== '_') {
-	                Mousetrap[method] = (function(method) {
-	                    return function() {
-	                        return documentMousetrap[method].apply(documentMousetrap, arguments);
-	                    };
-	                } (method));
-	            }
-	        }
-	    };
-
-	    Mousetrap.init();
-
-	    // expose mousetrap to the global object
-	    window.Mousetrap = Mousetrap;
-
-	    // expose as a common js module
-	    if (typeof module !== 'undefined' && module.exports) {
-	        module.exports = Mousetrap;
-	    }
-
-	    // expose mousetrap as an AMD module
-	    if (true) {
-	        !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
-	            return Mousetrap;
-	        }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-	    }
-	}) (typeof window !== 'undefined' ? window : null, typeof  window !== 'undefined' ? document : null);
-
-
-/***/ }),
-/* 222 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var object = __webpack_require__(203);
-	var util = __webpack_require__(83);
-	var Mousetrap = __webpack_require__(221);
-
-	/**
-	 * Map Interactor specification.
-	 *
-	 * @typedef {object} geo.mapInteractor.spec
-	 * @property {number} [throttle=30] Mouse events are throttled so that an event
-	 *      occurs no more often that this number of milliseconds.
-	 * @property {boolean|number} [discreteZoom=false] If `true`, only allow
-	 *      discrete (integer) zoom levels and debounce with a 400 ms delay.  If a
-	 *      positive number, debounce zoom events with the given delay in
-	 *      milliseconds.
-	 * @property {geo.actionRecord[]} [actions] The list of available actions.  See
-	 *      the code for the full default list.
-	 * @property {object} [click] An object specifying if click events should be
-	 *      handled.
-	 * @property {boolean} [click.enabled=true] Truthy to enable click events.
-	 * @property {object} [click.buttons] An object with button names (`left`,
-	 *      `right`, `middle`), each of which is a boolean which indicates if that
-	 *      button triggers a click event.
-	 * @property {number} [click.duration=0] If a positive number, the mouse up
-	 *      event must occur within this time in milliseconds of the mouse down
-	 *      event for it to be considered a click.
-	 * @property {boolean} [click.cancelOnMove=true] If truthy, don't generate
-	 *      click events if the mouse moved at all.
-	 * @property {object} [keyboard] An object describing which keyboard events are
-	 *      handled.
-	 * @property {object} [keyboard.actions] An object with different actions that
-	 *      are trigger by the keyboard.  Each key is the event that is triggered,
-	 *      with the values a list of keys that trigger the event.  See the code
-	 *      for the defaults.
-	 * @property {object} [keyboard.meta] Keyboard events can generate actions of
-	 *      different magnitudes.  This is an object with keys of `0`, `1`, and
-	 *      `2`, corresponding to small, medium, and large actions.  Each entry is
-	 *      an object with keys of the meta keys that are required to be down or
-	 *      up for that scale action to trigger.  If the value of the meta key is
-	 *      truthy, it must be down.  If `false`, it must be up.
-	 * @property {boolean} [keyboard.focusHighlight=true] If truthy, when the map
-	 *      gains focus, a highlight style is shown around it.  This gives an
-	 *      indicator that keyboard events will affect the map, but may not be
-	 *      visuallly desirabel.
-	 * @property {boolean} [alwaysTouch=false] If true, add touch support even if
-	 *      the browser doesn't apepar to be touch-aware.
-	 * @property {number} [wheelScaleX=1] A scale multiplier for horizontal wheel
-	 *      interactions.
-	 * @property {number} [wheelScaleY=1] A scale multiplier for vertical wheel
-	 *      interactions.
-	 * @property {number} [zoomScale=1] This affects how far the mouse must be
-	 *      dragged to zoom one level.  Roughly, the mouse must move `zoomScale` *
-	 *      120 pixels per level.
-	 * @property {number} [rotateWheelScale=0.105] When the mouse wheel is used for
-	 *      rotation, this is the number of radians per wheel step.
-	 * @property {number} [zoomrotateMinimumRotation=0.087] The minimum angle of
-	 *      rotation in radians before a `geo_action.zoomrotate` action will allow
-	 *      rotation.  Set to 0 to always include rotation.
-	 * @property {number} [zoomrotateReverseRotation=0.698] The minimum angle of
-	 *      rotation (in radians) before the `geo_action.zoomrotate` action will
-	 *      reverse the rotation direction.  This helps reduce chatter when zooms
-	 *      and pans are combined with rotations.
-	 * @property {number} [zoomrotateMinimumZoom=0.05] The minimum zoom factor
-	 *      change (increasing or desceasing) before the `geo_action.zoomrotate`
-	 *      action will allow zoom.  Set to 0 to always include zoom.
-	 * @property {number} [zoomrotateMinimumPan=5] The minimum number of pixels
-	 *      before the `geo_action.zoomrotate` action will allow panning.  Set to 0
-	 *      to always include panning.
-	 * @property {number} [touchPanDelay=50] The touch pan delay prevents a touch
-	 *      pan event from immediately following a rotate (including zoom) event.
-	 *      No touch pan event is processed within this number of milliseconds of a
-	 *      non-pan touch event.
-	 * @property {object} [momentum] Enable momentum when panning and zooming.
-	 * @property {boolean} [momentum.enabled=true] Truthy to allow momentum.
-	 * @property {number} [momentum.maxSpeed=2.5] Maximum animation speed.
-	 * @property {number} [momentum.minSpeed=0.01] Animations top when they drop
-	 *      below this speed.
-	 * @property {number} [momentum.stopTime=250] If the mouse hasn't moved in this
-	 *      many milliseconds, don't apply momentum.  The movement is a separate
-	 *      action from the preceding movement.
-	 * @property {number} [momentum.drag=0.01] Drag coefficient; larger values slow
-	 *      down faster.
-	 * @property {string[]} [momentum.actions] A list of actions on which to apply
-	 *      momentum.  Defaults to pan and zoom.
-	 * @property {object} [spring] Enable spring clamping to screen edges.
-	 * @property {boolean} [spring.enabled=true] Truthy to allow edge spring back.
-	 * @property {number} [spring.springConstant=0.00005] Higher values spring back
-	 *      faster.
-	 * @property {object} [zoomAnimation] Enable zoom animation for both discrete
-	 *      and continuous zoom.
-	 * @property {boolean} [zoomAnimation.enabled=true] Truthy to allow zoom
-	 *      animation.
-	 * @property {number} [zoomAnimation.duration=500] The time it takes for the
-	 *      final zoom to be reached.
-	 * @property {function} [zoomAnimation.ease] The easing function for the zoom.
-	 *      The default is `(2 - t) * t`.
-	 */
-
-	/**
-	 * The mapInteractor class is responsible for handling raw events from the
-	 * browser and interpreting them as map navigation interactions.  This class
-	 * will call the navigation methods on the connected map, which will make
-	 * modifications to the camera directly.
-	 *
-	 * @class
-	 * @alias geo.mapInteractor
-	 * @extends geo.object
-	 * @param {geo.mapInterator.spec} args Interactor specification object.
-	 * @returns {geo.mapInteractor}
-	 */
-	var mapInteractor = function (args) {
-	  'use strict';
-	  if (!(this instanceof mapInteractor)) {
-	    return new mapInteractor(args);
-	  }
-	  object.call(this);
-
-	  var $ = __webpack_require__(1);
-	  var geo_event = __webpack_require__(9);
-	  var geo_action = __webpack_require__(10);
-	  var throttle = __webpack_require__(83).throttle;
-	  var debounce = __webpack_require__(83).debounce;
-	  var actionMatch = __webpack_require__(83).actionMatch;
-	  var quadFeature = __webpack_require__(223);
-
-	  var m_options,
-	      m_this = this,
-	      m_mouse,
-	      m_keyHandler,
-	      m_boundKeys,
-	      m_touchHandler,
-	      m_state,
-	      m_queue,
-	      $node,
-	      m_selectionLayer = null,
-	      m_selectionQuad,
-	      m_paused = false,
-	      // if m_clickMaybe is not false, it contains the x, y, and buttons that
-	      // were present when the mouse down event occurred.
-	      m_clickMaybe = false,
-	      m_clickMaybeTimeout,
-	      m_callZoom = function () {};
-
-	  // Helper method to calculate the speed from a velocity
-	  function calcSpeed(v) {
-	    var x = v.x, y = v.y;
-	    return Math.sqrt(x * x + y * y);
-	  }
-
-	  // copy the options object with defaults
-	  m_options = $.extend(
-	    true,
-	    {},
-	    {
-	      throttle: 30,
-	      discreteZoom: false,
-
-	      /* There should only be one action with any specific combination of event
-	       * and modifiers.  When that event and modifiers occur, the specified
-	       * action is triggered.  The event and modifiers fields can either be a
-	       * simple string or an object with multiple entries with each entry set
-	       * to true, false, or undefined.  If an object, all values that are
-	       * truthy must match, all values that are false must not match, and all
-	       * other values that are falsy are ignored.
-	       *   Available actions:
-	       * see geo_action list
-	       *   Available events:
-	       * left, right, middle, wheel
-	       *   Available modifiers:
-	       * shift, ctrl, alt, meta
-	       *   Useful fields:
-	       * action: the name of the action.  Multiple events may trigger the same
-	       *    action.
-	       * input: the name of the input or an object with input names for keys
-	       *    and boolean values that indicates the combination of events that
-	       *    trigger this action.
-	       * modifiers: the name of a modifier or an object with modifier names for
-	       *    keys and boolean values that indicates the combination of modifiers
-	       *    that trigger this action.
-	       * selectionRectangle: truthy if a selection rectangle should be shown
-	       *    during the action.  This can be the name of an event that will be
-	       *    triggered when the selection is complete.
-	       * name: a string that can be used to reference this action.
-	       * owner: a string that can be used to reference this action.
-	       */
-	      actions: [{
-	        action: geo_action.pan,
-	        input: 'left',
-	        modifiers: {shift: false, ctrl: false},
-	        owner: 'geo.mapInteractor',
-	        name: 'button pan'
-	      }, {
-	        action: geo_action.zoom,
-	        input: 'right',
-	        modifiers: {shift: false, ctrl: false},
-	        owner: 'geo.mapInteractor',
-	        name: 'button zoom'
-	      }, {
-	        action: geo_action.zoom,
-	        input: 'wheel',
-	        modifiers: {shift: false, ctrl: false},
-	        owner: 'geo.mapInteractor',
-	        name: 'wheel zoom'
-	      }, {
-	        action: geo_action.rotate,
-	        input: 'left',
-	        modifiers: {shift: false, ctrl: true},
-	        owner: 'geo.mapInteractor',
-	        name: 'button rotate'
-	      }, {
-	        action: geo_action.rotate,
-	        input: 'wheel',
-	        modifiers: {shift: false, ctrl: true},
-	        owner: 'geo.mapInteractor',
-	        name: 'wheel rotate'
-	      }, {
-	        action: geo_action.select,
-	        input: 'left',
-	        modifiers: {shift: true, ctrl: true},
-	        selectionRectangle: geo_event.select,
-	        owner: 'geo.mapInteractor',
-	        name: 'drag select'
-	      }, {
-	        action: geo_action.zoomselect,
-	        input: 'left',
-	        modifiers: {shift: true, ctrl: false},
-	        selectionRectangle: geo_event.zoomselect,
-	        owner: 'geo.mapInteractor',
-	        name: 'drag zoom'
-	      }, {
-	        action: geo_action.unzoomselect,
-	        input: 'right',
-	        modifiers: {shift: true, ctrl: false},
-	        selectionRectangle: geo_event.unzoomselect,
-	        owner: 'geo.mapInteractor',
-	        name: 'drag unzoom'
-	      }, {
-	        action: geo_action.pan,
-	        input: 'pan',
-	        owner: 'geo.mapInteractor',
-	        name: 'touch pan'
-	      }, {
-	        action: geo_action.zoomrotate,
-	        input: 'rotate',
-	        owner: 'geo.mapInteractor',
-	        name: 'touch zoom and rotate'
-	      }],
-
-	      click: {
-	        enabled: true,
-	        buttons: {left: true, right: true, middle: true},
-	        duration: 0,
-	        cancelOnMove: true
-	      },
-
-	      keyboard: {
-	        actions: {
-	          /* Specific actions can be disabled by removing them from this object
-	           * or setting an empty list as the key bindings.  Additional actions
-	           * can be added to the dictionary, each of which gets a list of key
-	           * bindings.  See Mousetrap documentation for special key names. */
-	          'zoom.in': ['plus', 'shift+plus', 'shift+ctrl+plus', '=', 'shift+=', 'shift+ctrl+='],
-	          'zoom.out': ['-', 'shift+-', 'shift+ctrl+-', '_', 'shift+_', 'shift+ctrl+_'],
-	          'zoom.0': ['1'],
-	          'zoom.3': ['2'],
-	          'zoom.6': ['3'],
-	          'zoom.9': ['4'],
-	          'zoom.12': ['5'],
-	          'zoom.15': ['6'],
-	          'zoom.18': ['7'],
-	          'pan.left': ['left', 'shift+left', 'shift+ctrl+left'],
-	          'pan.right': ['right', 'shift+right', 'shift+ctrl+right'],
-	          'pan.up': ['up', 'shift+up', 'shift+ctrl+up'],
-	          'pan.down': ['down', 'shift+down', 'shift+ctrl+down'],
-	          'rotate.ccw': ['<', 'shift+<', 'shift+ctrl+<', '.', 'shift+.', 'shift+ctrl+.'],
-	          'rotate.cw': ['>', 'shift+>', 'shift+ctrl+>', ',', 'shift+,', 'shift+ctrl+,'],
-	          'rotate.0': ['0']
-	        },
-	        meta: {
-	          /* the metakeys that are down during a key event determine the
-	           * magnitude of the action, where 0 is the default small action
-	           * (1-pixel pan, small zoom, small rotation), 1 is a middle-sized
-	           * action, and 2 is the largest action.  Metakeys that aren't listed
-	           * are ignored.  Metakeys include shift, ctrl, alt, and meta (alt is
-	           * either the alt or option key, and meta is either windows or
-	           * command). */
-	          0: {shift: false, ctrl: false},
-	          1: {shift: true, ctrl: true},
-	          2: {shift: true, ctrl: false}
-	        },
-	        /* if focusHighlight is truthy, then a class is added to the map such
-	         * that when the map gets focus, it is indicated inside the border of
-	         * the map -- browsers usually show focus on the outside, which isn't
-	         * useful if the map is full window.  It might be desirable to change
-	         * this so it is only present if the focus is reached via the keyboard
-	         * (which would probably require detecting keyup events). */
-	        focusHighlight: true
-	      },
-	      /* Set alwaysTouch to false to only add touch support on devices that
-	       * report touch support.  Set to true to add touch support on all
-	       * devices. */
-	      alwaysTouch: false,
-	      wheelScaleX: 1,
-	      wheelScaleY: 1,
-	      zoomScale: 1,
-	      rotateWheelScale: 6 * Math.PI / 180,
-	      /* The minimum angle of rotation (in radians) before the
-	       * geo_action.zoomrotate action will allow rotation.  Set to 0 to always
-	       * include rotation. */
-	      zoomrotateMinimumRotation: 5.0 * Math.PI / 180,
-	      /* The minimum angle of rotation (in radians) before the
-	       * geo_action.zoomrotate action will reverse the rotation direction.
-	       * This helps reduce chatter when zooms and pans are combined with
-	       * rotations. */
-	      zoomrotateReverseRotation: 4.0 * Math.PI / 180,
-	      /* The minimum zoom factor change (increasing or decreasing) before the
-	       * geo_action.zoomrotate action will allow zoom.  Set to 0 to always
-	       * include zoom. */
-	      zoomrotateMinimumZoom: 0.05,
-	      /* The minimum number of pixels before the geo_action.zoomrotate action
-	       * will allow panning.  Set to 0 to always include panning. */
-	      zoomrotateMinimumPan: 5,
-	      /* The touch pan delay prevents a touch pan event from immediately
-	       * following a rotate (including zoom) event.  No touch pan event is
-	       * processed within this number of milliseconds of a non-pan touch
-	       * event. */
-	      touchPanDelay: 50,
-	      momentum: {
-	        enabled: true,
-	        maxSpeed: 2.5,
-	        minSpeed: 0.01,
-	        stopTime: 250,
-	        drag: 0.01,
-	        actions: [geo_action.pan, geo_action.zoom]
-	      },
-	      spring: {
-	        enabled: false,
-	        springConstant: 0.00005
-	      },
-	      zoomAnimation: {
-	        enabled: true,
-	        duration: 500,
-	        ease: function (t) { return (2 - t) * t; }
-	      }
-	    },
-	    args || {}
-	  );
-	  /* We don't want to merge the original arrays with arrays passed in the args,
-	   * so override that as necessary for actions. */
-	  if (args && args.actions) {
-	    m_options.actions = $.extend(true, [], args.actions);
-	  }
-	  if (args && args.momentum && args.momentum.actions) {
-	    m_options.momentum.actions = $.extend(true, [], args.momentum.actions);
-	  }
-	  if (args && args.keyboard && args.keyboard.actions !== undefined) {
-	    m_options.keyboard.actions = $.extend(true, {}, args.keyboard.actions);
-	  }
-
-	  // default mouse object
-	  m_mouse = {
-	    page: {x: 0, y: 0}, // mouse position relative to the page
-	    map: {x: 0, y: 0}, // mouse position relative to the map
-	    geo: {x: 0, y: 0},  // mouse position in map interface gcs
-	    mapgcs: {x: 0, y: 0},  // mouse position in map gcs
-	    // mouse button status
-	    buttons: {
-	      left: false,
-	      right: false,
-	      middle: false
-	    },
-	    // keyboard modifier status
-	    modifiers: {
-	      alt: false,
-	      ctrl: false,
-	      shift: false,
-	      meta: false
-	    },
-	    // time the event was captured
-	    time: new Date(),
-	    // time elapsed since the last mouse event
-	    deltaTime: 1,
-	    // pixels/ms
-	    velocity: {
-	      x: 0,
-	      y: 0
-	    }
-	  };
-
-	  /*
-	   * The interactor state determines what actions are taken in response to
-	   * core browser events.
-	   *
-	   * i.e.
-	   *  {
-	   *    'action': geo_action.pan,    * an ongoing pan event
-	   *    'origin': {...},       * mouse object at the start of the action
-	   *    'delta': {x: *, y: *} // mouse movement since action start
-	   *                           * not including the current event
-	   *  }
-	   *
-	   *  {
-	   *    'action': geo_action.zoom,   * an ongoing zoom event
-	   *    ...
-	   *  }
-	   *
-	   *  {
-	   *    'action': geo_action.rotate,   * an ongoing rotate event
-	   *    'origin': {...},       * mouse object at the start of the action
-	   *    'delta': {x: *, y: *} // mouse movement since action start
-	   *                           * not including the current event
-	   *  }
-	   *
-	   *  {
-	   *    'acton': geo_action.select,
-	   *    'origin': {...},
-	   *    'delta': {x: *, y: *}
-	   *  }
-	   *
-	   *  {
-	   *    'action': geo_action.momentum,
-	   *    'origin': {...},
-	   *    'handler': function () { }, // called in animation loop
-	   *    'timer': animate loop timer
-	   *  }
-	   */
-	  m_state = {};
-
-	  /**
-	   * Store queued map navigation commands (due to throttling) here
-	   * {
-	   *   kind: 'move' | 'wheel',  // what kind of mouse action triggered this
-	   *   method: function () {},  // the throttled method
-	   *   scroll: {x: ..., y: ...} // accumulated scroll wheel deltas
-	   * }
-	   */
-	  m_queue = {};
-
-	  /**
-	   * Process keys that we've captured.  Metakeys determine the magnitude of
-	   * the action.
-	   *
-	   * @param {string} action The basic action to take.
-	   * @param {object} evt The event with metakeys.
-	   * @param {object} keys Keys used to trigger the event.  `keys.simulated` is
-	   *    true if artificially triggered.
-	   */
-	  this._handleKeys = function (action, evt, keys) {
-	    if (keys && keys.simulated === true) {
-	      evt = keys;
-	    }
-	    var meta = m_options.keyboard.meta || {0: {}},
-	        map = m_this.map(),
-	        mapSize = map.size(),
-	        actionBase = action, actionValue = '',
-	        value, factor, move = {};
-
-	    for (value in meta) {
-	      if (meta.hasOwnProperty(value)) {
-	        if ((meta[value].shift === undefined || evt.shiftKey === !!meta[value].shift) &&
-	            (meta[value].ctrl === undefined || evt.ctrlKey === !!meta[value].ctrl) &&
-	            (meta[value].alt === undefined || evt.altKey === !!meta[value].alt) &&
-	            (meta[value].meta === undefined || evt.metaKey === !!meta[value].meta)) {
-	          factor = value;
-	        }
-	      }
-	    }
-	    if (factor === undefined) {
-	      /* metakeys don't match, so don't trigger an event. */
-	      return;
-	    }
-
-	    evt.stopPropagation();
-	    evt.preventDefault();
-
-	    if (action.indexOf('.') >= 0) {
-	      actionBase = action.substr(0, action.indexOf('.'));
-	      actionValue = action.substr(action.indexOf('.') + 1);
-	    }
-	    switch (actionBase) {
-	      case 'zoom':
-	        switch (actionValue) {
-	          case 'in':
-	            move.zoomDelta = [0.05, 0.25, 1][factor];
-	            break;
-	          case 'out':
-	            move.zoomDelta = -[0.05, 0.25, 1][factor];
-	            break;
-	          default:
-	            if (!isNaN(parseFloat(actionValue))) {
-	              move.zoom = parseFloat(actionValue);
-	            }
-	            break;
-	        }
-	        break;
-	      case 'pan':
-	        switch (actionValue) {
-	          case 'down':
-	            move.panY = -Math.max(1, [0, 0.05, 0.5][factor] * mapSize.height);
-	            break;
-	          case 'left':
-	            move.panX = Math.max(1, [0, 0.05, 0.5][factor] * mapSize.width);
-	            break;
-	          case 'right':
-	            move.panX = -Math.max(1, [0, 0.05, 0.5][factor] * mapSize.width);
-	            break;
-	          case 'up':
-	            move.panY = Math.max(1, [0, 0.05, 0.5][factor] * mapSize.height);
-	            break;
-	        }
-	        break;
-	      case 'rotate':
-	        switch (actionValue) {
-	          case 'ccw':
-	            move.rotationDelta = [1, 5, 90][factor] * Math.PI / 180;
-	            break;
-	          case 'cw':
-	            move.rotationDelta = -[1, 5, 90][factor] * Math.PI / 180;
-	            break;
-	          default:
-	            if (!isNaN(parseFloat(actionValue))) {
-	              move.rotation = parseFloat(actionValue);
-	            }
-	            break;
-	        }
-	        break;
-	    }
-	    map.geoTrigger(geo_event.keyaction, {
-	      move: move,
-	      action: action,
-	      factor: factor,
-	      event: evt
-	    });
-	    if (move.cancel) {
-	      return;
-	    }
-	    if (move.zoom !== undefined) {
-	      map.zoom(move.zoom);
-	    } else if (move.zoomDelta) {
-	      map.zoom(map.zoom() + move.zoomDelta);
-	    }
-	    if (move.rotation !== undefined) {
-	      map.rotation(move.rotation);
-	    } else if (move.rotationDelta) {
-	      map.rotation(map.rotation() + move.rotationDelta);
-	    }
-	    if (move.panX || move.panY) {
-	      map.pan({x: move.panX || 0, y: move.panY || 0});
-	    }
-	  };
-
-	  /**
-	   * Check if this browser has touch support.
-	   * Copied from https://github.com/hammerjs/touchemulator under the MIT
-	   * license.
-	   *
-	   * @returns {boolean} `true` if there is touch support.
-	   */
-	  this.hasTouchSupport = function () {
-	    return ('ontouchstart' in window) || // touch events
-	           (window.Modernizr && window.Modernizr.touch) || // modernizr
-	           (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 1; // pointer events
-	  };
-
-	  /**
-	   * Handle touch events.
-	   *
-	   * @param {object} evt The touch event.
-	   */
-	  this._handleTouch = function (evt) {
-	    var endIfBound = false;
-	    if (evt.pointerType === 'mouse' && m_touchHandler.touchSupport) {
-	      endIfBound = true;
-	    }
-	    if (evt.type === 'hammer.input') {
-	      if (m_touchHandler.lastEventType === 'pan' && evt.pointers.length !== 1) {
-	        endIfBound = true;
-	        m_touchHandler.lastEventType = null;
-	      } else {
-	        return;
-	      }
-	    }
-	    var evtType = /^(.*)(start|end|move|tap)$/.exec(evt.type);
-	    if (!evtType || evtType.length !== 3) {
-	      endIfBound = true;
-	    }
-	    if (endIfBound) {
-	      if (m_state.boundDocumentHandlers && m_touchHandler.lastEvent) {
-	        m_this._handleMouseUpDocument(m_touchHandler.lastEvent);
-	      }
-	      return;
-	    }
-	    evt.which = evtType[1];
-	    var time = (new Date()).valueOf();
-	    if (evt.which === 'pan' && m_touchHandler.lastEventType !== 'pan' &&
-	        time - m_touchHandler.lastTime < m_options.touchPanDelay) {
-	      return;
-	    }
-	    m_touchHandler.lastTime = time;
-	    m_touchHandler.lastEventType = evt.which;
-	    m_touchHandler.lastEvent = evt;
-	    /* convert touch events to have page locations */
-	    if (evt.pageX === undefined && evt.center !== undefined && evt.center.x !== undefined) {
-	      evt.pageX = evt.center.x;
-	      evt.pageY = evt.center.y;
-	    }
-	    /* start events should occur *before* the triggering delta.  By using the
-	     * mouse handlers, we get all of the action properties we expect (and
-	     * actions can be changed or defined as we see fit). */
-	    if (evtType[2] === 'start') {
-	      m_this._handleMouseDown(evt);
-	      m_this._setClickMaybe(false);
-	      if (m_state.boundDocumentHandlers) {
-	        $(document).on('mousemove.geojs', m_this._handleMouseUpDocument);
-	      }
-	    }
-	    /* start and move events both trigger a movement */
-	    if (evtType[2] === 'start' || evtType[2] === 'move') {
-	      if (m_state.boundDocumentHandlers) {
-	        m_this._handleMouseMoveDocument(evt);
-	      } else {
-	        m_this._handleMouseMove(evt);
-	      }
-	    }
-	    if (evtType[2] === 'end' || evtType[2] === 'cancel') {
-	      if (m_state.boundDocumentHandlers) {
-	        m_this._handleMouseUpDocument(evt);
-	      } else {
-	        m_this._handleMouseUp(evt);
-	      }
-	      m_touchHandler.lastEvent = null;
-	    }
-	    /* tap events are represented as a mouse left button down and up. */
-	    if (evtType[2] === 'tap' && !m_state.boundDocumentHandlers) {
-	      evt.which = 1;
-	      m_touchHandler.lastEventType = null;
-	      m_this._handleMouseDown(evt);
-	      m_this._handleMouseUp(evt);
-	      m_touchHandler.lastEventType = evtType[2];
-	    }
-	  };
-
-	  /**
-	   * Retrigger a mouse movement with the current mouse state.
-	   */
-	  this.retriggerMouseMove = function () {
-	    m_this.map().geoTrigger(geo_event.mousemove, m_this.mouse());
-	  };
-
-	  /**
-	   * Connects events to a map.  If the map is not set, then this does nothing.
-	   * @returns {this}
-	   */
-	  this._connectEvents = function () {
-	    if (!m_options.map) {
-	      return m_this;
-	    }
-
-	    // prevent double binding to DOM elements
-	    m_this._disconnectEvents();
-
-	    // store the connected element
-	    $node = $(m_options.map.node());
-
-	    // set methods related to asynchronous event handling
-	    m_this._handleMouseWheel = throttled_wheel();
-	    m_callZoom = debounced_zoom();
-
-	    // catalog what inputs we are using
-	    util.adjustActions(m_options.actions);
-	    var usedInputs = {};
-	    ['right', 'pan', 'rotate'].forEach(function (input) {
-	      usedInputs[input] = m_options.actions.some(function (action) {
-	        return action.input[input];
-	      });
-	    });
-	    // add event handlers
-	    $node.on('wheel.geojs', m_this._handleMouseWheel);
-	    $node.on('mousemove.geojs', m_this._handleMouseMove);
-	    $node.on('mousedown.geojs', m_this._handleMouseDown);
-	    $node.on('mouseup.geojs', m_this._handleMouseUp);
-	    // Disable dragging images and such
-	    $node.on('dragstart', function () { return false; });
-	    if (usedInputs.right) {
-	      $node.on('contextmenu.geojs', function () { return false; });
-	    }
-
-	    // bind keyboard events
-	    if (m_options.keyboard && m_options.keyboard.actions) {
-	      m_keyHandler = Mousetrap($node[0]);
-	      var bound = [];
-	      for (var keyAction in m_options.keyboard.actions) {
-	        if (m_options.keyboard.actions.hasOwnProperty(keyAction)) {
-	          m_keyHandler.bind(
-	            m_options.keyboard.actions[keyAction],
-	            (function (action) {
-	              return function (evt, keys) {
-	                m_this._handleKeys(action, evt, keys);
-	              };
-	            })(keyAction)
-	          );
-	          bound = bound.concat(m_options.keyboard.actions[keyAction]);
-	        }
-	      }
-	      m_boundKeys = bound;
-	    }
-	    $node.toggleClass('highlight-focus',
-	      !!(m_boundKeys && m_boundKeys.length && m_options.keyboard.focusHighlight));
-	    // bind touch events
-	    if ((m_this.hasTouchSupport() || m_options.alwaysTouch) &&
-	        (usedInputs.pan || usedInputs.rotate)) {
-	      // webpack expects optional dependencies to be wrapped in a try-catch
-	      var Hammer;
-	      try {
-	        Hammer = __webpack_require__(224);
-	      } catch (_error) {}
-	      if (Hammer !== undefined) {
-	        var recog = [],
-	            touchEvents = ['hammer.input'];
-	        if (usedInputs.rotate) {
-	          recog.push([Hammer.Rotate, {enable: true}]);
-	          touchEvents = touchEvents.concat(['rotatestart', 'rotateend', 'rotatemove']);
-	        }
-	        if (usedInputs.pan) {
-	          recog.push([Hammer.Pan, {direction: Hammer.DIRECTION_ALL}]);
-	          touchEvents = touchEvents.concat(['panstart', 'panend', 'panmove']);
-	        }
-	        /* Always handle tap events.  Reject double taps. */
-	        recog.push([Hammer.Tap, {event: 'doubletap', taps: 2}]);
-	        recog.push([Hammer.Tap, {event: 'singletap'}, undefined, ['doubletap']]);
-	        touchEvents = touchEvents.concat(['singletap']);
-	        var hammerParams = {recognizers: recog, preventDefault: true};
-	        m_touchHandler = {
-	          manager: new Hammer.Manager($node[0], hammerParams),
-	          touchSupport: m_this.hasTouchSupport(),
-	          lastTime: 0
-	        };
-	        m_touchHandler.manager.get('doubletap').recognizeWith('singletap');
-	        touchEvents.forEach(function (touchEvent) {
-	          m_touchHandler.manager.on(touchEvent, m_this._handleTouch);
-	        });
-	      }
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Disconnects events to a map.  If the map is not set, then this does
-	   * nothing.
-	   * @returns {this}
-	   */
-	  this._disconnectEvents = function () {
-	    if (m_boundKeys) {
-	      if (m_keyHandler) {
-	        m_keyHandler.unbind(m_boundKeys);
-	      }
-	      m_boundKeys = null;
-	      m_keyHandler = null;
-	    }
-	    if (m_touchHandler) {
-	      m_touchHandler.manager.destroy();
-	      m_touchHandler = null;
-	    }
-	    if ($node) {
-	      $node.off('.geojs');
-	      $node = null;
-	    }
-	    m_this._handleMouseWheel = function () {};
-	    m_callZoom = function () {};
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Sets or gets map for this interactor, adds draw region layer if needed.
-	   *
-	   * @param {geo.map} [val] Either a new map object for `undefined` to return
-	   *    the current map object.
-	   * @returns {geo.map|this} Either the current map object or the
-	   *    mapInteractor class instance.
-	   */
-	  this.map = function (val) {
-	    if (val !== undefined) {
-	      m_options.map = val;
-	      m_this._connectEvents();
-	      return m_this;
-	    }
-	    return m_options.map;
-	  };
-
-	  /**
-	   * Gets/sets the options object for the interactor.
-	   *
-	   * @param {geo.mapInteractor.spec} opts Options to set.
-	   * @returns {geo.mapInteractor.spec|this}
-	   */
-	  this.options = function (opts) {
-	    if (opts === undefined) {
-	      return $.extend({}, m_options);
-	    }
-	    $.extend(m_options, opts);
-
-	    // reset event handlers for new options
-	    this._connectEvents();
-	    return m_this;
-	  };
-
-	  /**
-	   * Stores the current mouse position from an event.
-	   *
-	   * @param {jQuery.Event} evt JQuery event with the mouse position.
-	   */
-	  this._getMousePosition = function (evt) {
-	    var offset = $node.offset(), dt, t;
-
-	    t = (new Date()).valueOf();
-	    dt = t - m_mouse.time;
-	    m_mouse.time = t;
-	    m_mouse.deltaTime = dt;
-	    m_mouse.velocity = {
-	      x: (evt.pageX - m_mouse.page.x) / dt,
-	      y: (evt.pageY - m_mouse.page.y) / dt
-	    };
-	    m_mouse.page = {
-	      x: evt.pageX,
-	      y: evt.pageY
-	    };
-	    m_mouse.map = {
-	      x: evt.pageX - offset.left,
-	      y: evt.pageY - offset.top
-	    };
-	    try {
-	      m_mouse.geo = m_this.map().displayToGcs(m_mouse.map);
-	      m_mouse.mapgcs = m_this.map().displayToGcs(m_mouse.map, null);
-	    } catch (e) {
-	      // catch georeferencing problems and move on
-	      m_mouse.geo = m_mouse.mapgcs = null;
-	    }
-	  };
-
-	  /**
-	   * Stores the current mouse button state in the m_mouse object.
-	   *
-	   * @param {jQuery.Event} evt The event that trigger the mouse action.
-	   */
-	  this._getMouseButton = function (evt) {
-	    for (var prop in m_mouse.buttons) {
-	      if (m_mouse.buttons.hasOwnProperty(prop)) {
-	        m_mouse.buttons[prop] = false;
-	      }
-	    }
-	    /* If the event buttons are specified, use them in preference to the
-	     * evt.which for determining which buttons are down.  buttons is a bitfield
-	     * and therefore can represent more than one button at a time. */
-	    if (evt.buttons !== undefined) {
-	      m_mouse.buttons.left = !!(evt.buttons & 1);
-	      m_mouse.buttons.right = !!(evt.buttons & 2);
-	      m_mouse.buttons.middle = !!(evt.buttons & 4);
-	    } else if (evt.type !== 'mouseup') {
-	      /* If we don't evt.buttons, fall back to which, but not on mouseup. */
-	      switch (evt.which) {
-	        case 1: m_mouse.buttons.left = true; break;
-	        case 2: m_mouse.buttons.middle = true; break;
-	        case 3: m_mouse.buttons.right = true; break;
-	      }
-	    }
-	    /* When handling touch events, evt.which can be a string, in which case
-	     * handle a "button" with that name -- a non-integer string will not
-	     * evaluate as being between 1 and 3. */
-	    if (evt.which && !(evt.which >= 1 && evt.which <= 3)) {
-	      m_mouse.buttons[evt.which] = true;
-	    }
-	  };
-
-	  /**
-	   * Stores the current keyboard modifiers from an event in the m_mouse
-	   * object.
-	   *
-	   * @param {jQuery.Event} evt JQuery event with the keyboard modifiers.
-	   */
-	  this._getMouseModifiers = function (evt) {
-	    m_mouse.modifiers.alt = evt.altKey;
-	    m_mouse.modifiers.ctrl = evt.ctrlKey;
-	    m_mouse.modifiers.meta = evt.metaKey;
-	    m_mouse.modifiers.shift = evt.shiftKey;
-	  };
-
-	  /**
-	   * Compute a selection information object.
-	   *
-	   * @private
-	   * @returns {geo.brushSelection}
-	   */
-	  this._getSelection = function () {
-	    var origin = m_state.origin,
-	        mouse = m_this.mouse(),
-	        map = m_this.map(),
-	        display = {}, gcs = {};
-
-	    // TODO: clamp to map bounds
-	    // Get the display coordinates
-	    display.upperLeft = {
-	      x: Math.min(origin.map.x, mouse.map.x),
-	      y: Math.min(origin.map.y, mouse.map.y)
-	    };
-
-	    display.lowerRight = {
-	      x: Math.max(origin.map.x, mouse.map.x),
-	      y: Math.max(origin.map.y, mouse.map.y)
-	    };
-
-	    display.upperRight = {
-	      x: display.lowerRight.x,
-	      y: display.upperLeft.y
-	    };
-
-	    display.lowerLeft = {
-	      x: display.upperLeft.x,
-	      y: display.lowerRight.y
-	    };
-
-	    // Get the gcs coordinates
-	    gcs.upperLeft = map.displayToGcs(display.upperLeft, null);
-	    gcs.lowerRight = map.displayToGcs(display.lowerRight, null);
-	    gcs.upperRight = map.displayToGcs(display.upperRight, null);
-	    gcs.lowerLeft = map.displayToGcs(display.lowerLeft, null);
-
-	    m_selectionQuad.data([{
-	      ul: gcs.upperLeft,
-	      ur: gcs.upperRight,
-	      ll: gcs.lowerLeft,
-	      lr: gcs.lowerRight
-	    }]);
-	    m_selectionQuad.draw();
-
-	    return {
-	      display: display,
-	      gcs: gcs,
-	      mouse: mouse,
-	      origin: $.extend({}, m_state.origin)
-	    };
-	  };
-
-	  /**
-	   * Immediately cancel an ongoing action.
-	   *
-	   * @param {string?} action The action type, if `null` cancel any action.
-	   * @param {boolean} [keepQueue] If truthy, keep the queue event if an action
-	   *    is canceled.
-	   * @returns {boolean} Set if an action was canceled.
-	   */
-	  this.cancel = function (action, keepQueue) {
-	    var out;
-	    if (!action) {
-	      out = !!m_state.action;
-	    } else {
-	      out = m_state.action === action;
-	    }
-	    if (out) {
-	      // cancel any queued interaction events
-	      if (!keepQueue) {
-	        m_queue = {};
-	      }
-	      clearState();
-	    }
-	    return out;
-	  };
-
-	  /**
-	   * Set the value of whether a click is possible.  Cancel any outstanding
-	   * timer for this process.
-	   *
-	   * @param {false|object} value If `false`, there is no chance of a future
-	   *    click.  If an object with coordinates and the mouse button state,
-	   *    a click is possible if the same button is released nearby.
-	   */
-	  this._setClickMaybe = function (value) {
-	    m_clickMaybe = value;
-	    if (m_clickMaybeTimeout) {
-	      window.clearTimeout(m_clickMaybeTimeout);
-	      m_clickMaybeTimeout = null;
-	    }
-	  };
-
-	  /**
-	   * Handle event when a mouse button is pressed.
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseDown = function (evt) {
-	    var action, actionRecord;
-
-	    if (m_paused) {
-	      return;
-	    }
-	    /* In some scenarios, we get both a tap event and then, somewhat later, a
-	     * set of mousedown/mouseup events.  Ignore the spurious down/up set if we
-	     * just handled a tap. */
-	    if (m_touchHandler && m_touchHandler.lastEventType === 'tap' &&
-	        (new Date()).valueOf() - m_touchHandler.lastTime < 1000) {
-	      return;
-	    }
-
-	    m_this._getMousePosition(evt);
-	    m_this._getMouseButton(evt);
-	    m_this._getMouseModifiers(evt);
-
-	    if (m_options.click.enabled &&
-	        (!m_mouse.buttons.left || m_options.click.buttons.left) &&
-	        (!m_mouse.buttons.right || m_options.click.buttons.right) &&
-	        (!m_mouse.buttons.middle || m_options.click.buttons.middle)) {
-	      m_this._setClickMaybe({
-	        x: m_mouse.page.x,
-	        y: m_mouse.page.y,
-	        buttons: $.extend({}, m_mouse.buttons)
-	      });
-	      if (m_options.click.duration > 0) {
-	        m_clickMaybeTimeout = window.setTimeout(function () {
-	          m_clickMaybe = false;
-	          m_clickMaybeTimeout = null;
-	        }, m_options.click.duration);
-	      }
-	    }
-	    actionRecord = actionMatch(m_mouse.buttons, m_mouse.modifiers,
-	                               m_options.actions);
-	    action = (actionRecord || {}).action;
-
-	    var map = m_this.map();
-	    // cancel transitions and momentum on click
-	    map.transitionCancel('_handleMouseDown' + (action ? '.' + action : ''));
-	    m_this.cancel(geo_action.momentum);
-
-	    m_mouse.velocity = {
-	      x: 0,
-	      y: 0
-	    };
-
-	    if (action) {
-	      // cancel any ongoing interaction queue
-	      m_queue = {
-	        kind: 'move'
-	      };
-
-	      // store the state object
-	      m_state = {
-	        action: action,
-	        actionRecord: actionRecord,
-	        origin: $.extend(true, {}, m_mouse),
-	        initialZoom: map.zoom(),
-	        initialRotation: map.rotation(),
-	        initialEventRotation: evt.rotation,
-	        delta: {x: 0, y: 0}
-	      };
-
-	      if (actionRecord.selectionRectangle) {
-	        // Make sure the old selection layer is gone.
-	        if (m_selectionLayer) {
-	          m_selectionLayer.clear();
-	          map.deleteLayer(m_selectionLayer);
-	          m_selectionLayer = null;
-	        }
-	        m_selectionLayer = map.createLayer(
-	          'feature', {features: [quadFeature.capabilities.color]});
-	        m_selectionQuad = m_selectionLayer.createFeature(
-	          'quad', {gcs: map.gcs()});
-	        m_selectionQuad.style({
-	          opacity: 0.25,
-	          color: {r: 0.3, g: 0.3, b: 0.3}
-	        });
-	        map.geoTrigger(geo_event.brushstart, m_this._getSelection());
-	      }
-	      map.geoTrigger(geo_event.actiondown, {
-	        state: m_this.state(), mouse: m_this.mouse(), event: evt});
-
-	      // bind temporary handlers to document
-	      if (m_options.throttle > 0) {
-	        $(document).on(
-	          'mousemove.geojs',
-	          throttle(
-	            m_options.throttle,
-	            m_this._handleMouseMoveDocument
-	          )
-	        );
-	      } else {
-	        $(document).on('mousemove.geojs', m_this._handleMouseMoveDocument);
-	      }
-	      $(document).on('mouseup.geojs', m_this._handleMouseUpDocument);
-	      m_state.boundDocumentHandlers = true;
-	    }
-
-	  };
-
-	  /**
-	   * Handle mouse move event.
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseMove = function (evt) {
-	    if (m_paused) {
-	      return;
-	    }
-
-	    if (m_state.boundDocumentHandlers) {
-	      // If currently performing a navigation action, the mouse
-	      // coordinates will be captured by the document handler.
-	      return;
-	    }
-
-	    if (m_options.click.cancelOnMove && m_clickMaybe) {
-	      m_this._setClickMaybe(false);
-	    }
-
-	    m_this._getMousePosition(evt);
-	    m_this._getMouseButton(evt);
-	    m_this._getMouseModifiers(evt);
-
-	    if (m_clickMaybe) {
-	      return;
-	    }
-
-	    m_this.map().geoTrigger(geo_event.mousemove, m_this.mouse());
-	  };
-
-	  /**
-	   * Handle the zoomrotate action.
-	   *
-	   * @param {object} evt The mouse event that triggered this.
-	   */
-	  this._handleZoomrotate = function (evt) {
-	    /* Only zoom if we have once exceeded the initial zoom threshold. */
-	    var deltaZoom = Math.log2(evt.scale);
-	    if (!m_state.zoomrotateAllowZoom && deltaZoom &&
-	        Math.abs(deltaZoom) >= Math.log2(1 + m_options.zoomrotateMinimumZoom)) {
-	      if (m_options.zoomrotateMinimumZoom) {
-	        m_state.initialZoom -= deltaZoom;
-	      }
-	      m_state.zoomrotateAllowZoom = true;
-	    }
-	    if (m_state.zoomrotateAllowZoom && deltaZoom) {
-	      var zoom = m_state.initialZoom + deltaZoom;
-	      m_this.map().zoom(zoom, m_state.origin);
-	    }
-	    /* Only rotate if we have once exceeded the initial rotation threshold.  The
-	     * first time this happens (if the threshold is greater than zero), set the
-	     * start of rotation to the current position, so that there is no sudden
-	     * jump. */
-	    var deltaTheta = (evt.rotation - m_state.initialEventRotation) * Math.PI / 180;
-	    if (!m_state.zoomrotateAllowRotation && deltaTheta &&
-	        Math.abs(deltaTheta) >= m_options.zoomrotateMinimumRotation) {
-	      if (m_options.zoomrotateMinimumRotation) {
-	        m_state.initialEventRotation = evt.rotation;
-	        deltaTheta = 0;
-	      }
-	      m_state.zoomrotateAllowRotation = true;
-	    }
-	    if (m_state.zoomrotateAllowRotation) {
-	      var theta = m_state.initialRotation + deltaTheta;
-	      /* Compute the delta in the range of [-PI, PI).  This is involed to work
-	       * around modulo returning a signed value. */
-	      deltaTheta = ((theta - m_this.map().rotation()) % (Math.PI * 2) +
-	                    Math.PI * 3) % (Math.PI * 2) - Math.PI;
-	      /* If we reverse direction, don't rotate until some threshold is
-	       * exceeded.  This helps prevent rotation bouncing while panning. */
-	      if (deltaTheta && (deltaTheta * (m_state.lastRotationDelta || 0) >= 0 ||
-	          Math.abs(deltaTheta) >= m_options.zoomrotateReverseRotation)) {
-	        m_this.map().rotation(theta, m_state.origin);
-	        m_state.lastRotationDelta = deltaTheta;
-	      }
-	    }
-	    /* Only pan if we have once exceed the initial pan threshold. */
-	    var panOrigin = m_state.origin.page;
-	    if (m_state.initialEventGeo) {
-	      var offset = $node.offset();
-	      panOrigin = m_this.map().gcsToDisplay(m_state.initialEventGeo);
-	      panOrigin.x += offset.left;
-	      panOrigin.y += offset.top;
-	    }
-	    var x = evt.pageX, deltaX = x - panOrigin.x,
-	        y = evt.pageY, deltaY = y - panOrigin.y,
-	        deltaPan2 = deltaX * deltaX + deltaY * deltaY;
-	    if (!m_state.zoomrotateAllowPan && deltaPan2 &&
-	        deltaPan2 >= m_options.zoomrotateMinimumPan * m_options.zoomrotateMinimumPan) {
-	      if (m_options.zoomrotateMinimumPan) {
-	        deltaX = deltaY = 0;
-	        m_state.initialEventGeo = m_this.mouse().geo;
-	      } else {
-	        m_state.initialEventGeo = m_state.origin.geo;
-	      }
-	      m_state.zoomrotateAllowPan = true;
-	    }
-	    if (m_state.zoomrotateAllowPan && (deltaX || deltaY)) {
-	      m_this.map().pan({x: deltaX, y: deltaY});
-	    }
-	  };
-
-	  /**
-	   * Handle mouse move event on the document (temporary bindings).
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseMoveDocument = function (evt) {
-	    var dx, dy, selectionObj;
-
-	    // If the map has been disconnected, we do nothing.
-	    if (!m_this.map()) {
-	      return;
-	    }
-
-	    if (m_paused || m_queue.kind !== 'move') {
-	      return;
-	    }
-
-	    m_this._getMousePosition(evt);
-	    m_this._getMouseButton(evt);
-	    m_this._getMouseModifiers(evt);
-
-	    /* Only cancel possible clicks on move if we actually moved */
-	    if (m_options.click.cancelOnMove && (m_clickMaybe.x === undefined ||
-	        m_mouse.page.x !== m_clickMaybe.x ||
-	        m_mouse.page.y !== m_clickMaybe.y)) {
-	      m_this._setClickMaybe(false);
-	    }
-	    if (m_clickMaybe) {
-	      return;
-	    }
-
-	    if (!m_state.action) {
-	      // This shouldn't happen
-	      console.log('WARNING: Invalid state in mapInteractor.');
-	      return;
-	    }
-
-	    // calculate the delta from the origin point to avoid
-	    // accumulation of floating point errors
-	    dx = m_mouse.map.x - m_state.origin.map.x - m_state.delta.x;
-	    dy = m_mouse.map.y - m_state.origin.map.y - m_state.delta.y;
-	    m_state.delta.x += dx;
-	    m_state.delta.y += dy;
-
-	    if (m_state.action === geo_action.pan) {
-	      m_this.map().pan({x: dx, y: dy}, undefined, 'limited');
-	    } else if (m_state.action === geo_action.zoom) {
-	      m_callZoom(-dy * m_options.zoomScale / 120, m_state);
-	    } else if (m_state.action === geo_action.rotate) {
-	      var cx, cy;
-	      if (m_state.origin.rotation === undefined) {
-	        cx = m_state.origin.map.x - m_this.map().size().width / 2;
-	        cy = m_state.origin.map.y - m_this.map().size().height / 2;
-	        m_state.origin.rotation = m_this.map().rotation() - Math.atan2(cy, cx);
-	      }
-	      cx = m_mouse.map.x - m_this.map().size().width / 2;
-	      cy = m_mouse.map.y - m_this.map().size().height / 2;
-	      m_this.map().rotation(m_state.origin.rotation + Math.atan2(cy, cx));
-	    } else if (m_state.action === geo_action.zoomrotate) {
-	      m_this._handleZoomrotate(evt);
-	    } else if (m_state.actionRecord.selectionRectangle) {
-	      // Get the bounds of the current selection
-	      selectionObj = m_this._getSelection();
-	      m_this.map().geoTrigger(geo_event.brush, selectionObj);
-	    }
-	    m_this.map().geoTrigger(geo_event.actionmove, {
-	      state: m_this.state(), mouse: m_this.mouse(), event: evt});
-
-	    // Prevent default to stop text selection in particular
-	    evt.preventDefault();
-	  };
-
-	  /**
-	   * Clear the action state, but remember if we have bound document handlers.
-	   * @private
-	   */
-	  function clearState() {
-	    m_state = {boundDocumentHandlers: m_state.boundDocumentHandlers};
-	  }
-
-	  /**
-	   * Use interactor options to modify the mouse velocity by momentum
-	   * or spring equations depending on the current map state.
-	   * @private
-	   * @param {object} v Current velocity in pixels / millisecond.
-	   * @param {number} deltaT The time delta.
-	   * @returns {object} New velocity.
-	   */
-	  function modifyVelocity(v, deltaT) {
-	    deltaT = deltaT <= 0 ? 30 : deltaT;
-	    var sf = springForce();
-	    var speed = calcSpeed(v);
-	    var vx = v.x / speed;
-	    var vy = v.y / speed;
-
-	    speed = speed * Math.exp(-m_options.momentum.drag * deltaT);
-
-	    // |force| + |velocity| < c <- stopping condition
-	    if (calcSpeed(sf) * deltaT + speed < m_options.momentum.minSpeed) {
-	      return null;
-	    }
-
-	    if (speed > 0) {
-	      vx = vx * speed;
-	      vy = vy * speed;
-	    } else {
-	      vx = 0;
-	      vy = 0;
-	    }
-
-	    return {
-	      x: vx - sf.x * deltaT,
-	      y: vy - sf.y * deltaT
-	    };
-	  }
-
-	  /**
-	   * Get the spring force for the current map bounds.
-	   * @private
-	   * @returns {object} The spring force.
-	   */
-	  function springForce() {
-	    var xplus,  // force to the right
-	        xminus, // force to the left
-	        yplus,  // force to the top
-	        yminus; // force to the bottom
-
-	    if (!m_options.spring.enabled) {
-	      return {x: 0, y: 0};
-	    }
-	    // get screen coordinates of corners
-	    var maxBounds = m_this.map().maxBounds(undefined, null);
-	    var ul = m_this.map().gcsToDisplay({
-	      x: maxBounds.left,
-	      y: maxBounds.top
-	    }, null);
-	    var lr = m_this.map().gcsToDisplay({
-	      x: maxBounds.right,
-	      y: maxBounds.bottom
-	    }, null);
-
-	    var c = m_options.spring.springConstant;
-	    // Arg... map needs to expose the canvas size
-	    var width = m_this.map().node().width();
-	    var height = m_this.map().node().height();
-
-	    xplus = c * Math.max(0, ul.x);
-	    xminus = c * Math.max(0, width - lr.x);
-	    yplus = c * Math.max(0, ul.y) / 2;
-	    yminus = c * Math.max(0, height - lr.y) / 2;
-
-	    return {
-	      x: xplus - xminus,
-	      y: yplus - yminus
-	    };
-	  }
-
-	  /**
-	   * Based on the screen coordinates of a selection, zoom or unzoom and
-	   * recenter.
-	   *
-	   * @private
-	   * @param {string} action Either `geo_action.zoomselect` or
-	   *    `geo_action.unzoomselect`.
-	   * @param {object} lowerLeft The x and y coordinates of the lower left corner
-	   *    of the zoom rectangle.
-	   * @param {object} upperRight The x and y coordinates of the upper right
-	   *    corner of the zoom rectangle.
-	   */
-	  this._zoomFromSelection = function (action, lowerLeft, upperRight) {
-	    if (action !== geo_action.zoomselect && action !== geo_action.unzoomselect) {
-	      return;
-	    }
-	    if (lowerLeft.x === upperRight.x || lowerLeft.y === upperRight.y) {
-	      return;
-	    }
-	    var zoom, center,
-	        map = m_this.map(),
-	        mapsize = map.size();
-	    /* To arbitrarily handle rotation and projection, we center the map at the
-	     * central coordinate of the selection and set the zoom level such that the
-	     * four corners are just barely on the map.  When unzooming (zooming out),
-	     * we ensure that the previous view is centered in the selection but use
-	     * the maximal size for the zoom factor. */
-	    var scaling = {
-	      x: Math.abs((upperRight.x - lowerLeft.x) / mapsize.width),
-	      y: Math.abs((upperRight.y - lowerLeft.y) / mapsize.height)
-	    };
-	    if (action === geo_action.zoomselect) {
-	      center = map.displayToGcs({
-	        x: (lowerLeft.x + upperRight.x) / 2,
-	        y: (lowerLeft.y + upperRight.y) / 2
-	      }, null);
-	      zoom = map.zoom() - Math.log2(Math.max(scaling.x, scaling.y));
-	    } else {  /* unzoom */
-	      /* To make the existing visible map entirely within the selection
-	       * rectangle, this would be changed to Math.min instead of Math.max of
-	       * the scaling factors.  This felt wrong, though. */
-	      zoom = map.zoom() + Math.log2(Math.max(scaling.x, scaling.y));
-	      /* Record the current center.  Later, this is panned to the center of the
-	       * selection rectangle. */
-	      center = map.center(undefined, null);
-	    }
-	    /* When discrete zoom is enable, always round down.  We have to do this
-	     * explicitly, as otherwise we may zoom too far and the selection will not
-	     * be completely visible. */
-	    if (map.discreteZoom()) {
-	      zoom = Math.floor(zoom);
-	    }
-	    map.zoom(zoom);
-	    if (action === geo_action.zoomselect) {
-	      map.center(center, null);
-	    } else {
-	      var newcenter = map.gcsToDisplay(center, null);
-	      map.pan({
-	        x: (lowerLeft.x + upperRight.x) / 2 - newcenter.x,
-	        y: (lowerLeft.y + upperRight.y) / 2 - newcenter.y
-	      });
-	    }
-	  };
-
-	  /**
-	   * Handle event when a mouse button is unpressed on the document.
-	   * Removes temporary bindings.
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseUpDocument = function (evt) {
-	    var selectionObj, oldAction;
-
-	    if (m_paused) {
-	      return;
-	    }
-
-	    // cancel queued interactions
-	    m_queue = {};
-
-	    m_this._setClickMaybe(false);
-	    m_this._getMouseButton(evt);
-	    m_this._getMouseModifiers(evt);
-
-	    // unbind temporary handlers on document
-	    $(document).off('.geojs');
-	    m_state.boundDocumentHandlers = false;
-
-	    if (m_mouse.buttons.right) {
-	      evt.preventDefault();
-	    }
-
-	    if (m_state.actionRecord && m_state.actionRecord.selectionRectangle) {
-	      m_this._getMousePosition(evt);
-	      selectionObj = m_this._getSelection();
-
-	      m_selectionLayer.clear();
-	      m_this.map().deleteLayer(m_selectionLayer);
-	      m_selectionLayer = null;
-	      m_selectionQuad = null;
-
-	      m_this.map().geoTrigger(geo_event.brushend, selectionObj);
-	      if (m_state.actionRecord.selectionRectangle !== true) {
-	        m_this.map().geoTrigger(m_state.actionRecord.selectionRectangle,
-	                                selectionObj);
-	      }
-	      m_this._zoomFromSelection(m_state.action, selectionObj.display.lowerLeft,
-	                                selectionObj.display.upperRight);
-	      m_this.map().geoTrigger(geo_event.actionselection, {
-	        state: m_this.state(),
-	        mouse: m_this.mouse(),
-	        event: evt,
-	        lowerLeft: selectionObj.display.lowerLeft,
-	        upperRight: selectionObj.display.upperRight});
-	    }
-	    m_this.map().geoTrigger(geo_event.actionup, {
-	      state: m_this.state(), mouse: m_this.mouse(), event: evt});
-
-	    // reset the interactor state
-	    oldAction = m_state.action;
-	    clearState();
-
-	    // if momentum is enabled, start the action here
-	    if (m_options.momentum.enabled &&
-	            $.inArray(oldAction, m_options.momentum.actions) >= 0) {
-	      var t = (new Date()).valueOf();
-	      var dt = t - m_mouse.time + m_mouse.deltaTime;
-	      if (t - m_mouse.time < m_options.momentum.stopTime) {
-	        m_mouse.velocity.x *= m_mouse.deltaTime / dt;
-	        m_mouse.velocity.y *= m_mouse.deltaTime / dt;
-	        m_mouse.deltaTime = dt;
-	      } else {
-	        m_mouse.velocity.x = m_mouse.velocity.y = 0;
-	      }
-	      m_this.springBack(true, oldAction);
-	    }
-	  };
-
-	  /**
-	   * Handle event when a mouse button is unpressed.
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseUp = function (evt) {
-	    if (m_paused) {
-	      return;
-	    }
-
-	    m_this._getMouseButton(evt);
-
-	    if (m_clickMaybe) {
-	      m_this._handleMouseClick(evt);
-	    }
-	  };
-
-	  /**
-	   * Handle event when a mouse click is detected.  A mouse click is a simulated
-	   * event that occurs when the time between a mouse down and mouse up
-	   * is less than the configured duration and (optionally) if no mousemove
-	   * events were triggered in the interim.
-	   *
-	   * @param {jQuery.Event} evt The event that triggered this.
-	   */
-	  this._handleMouseClick = function (evt) {
-
-	    /* Cancel a selection if it is occurring */
-	    if (m_state.actionRecord && m_state.actionRecord.selectionRectangle) {
-	      m_selectionLayer.clear();
-	      m_this.map().deleteLayer(m_selectionLayer);
-	      m_selectionLayer = null;
-	      m_selectionQuad = null;
-	      m_state.action = m_state.actionRecord = null;
-	    }
-	    m_this._getMouseButton(evt);
-	    m_this._getMouseModifiers(evt);
-
-	    // cancel any ongoing pan action
-	    m_this.cancel(geo_action.pan);
-
-	    // unbind temporary handlers on document
-	    $(document).off('.geojs');
-	    m_state.boundDocumentHandlers = false;
-	    // add information about the button state to the event information
-	    var details = m_this.mouse();
-	    details.buttonsDown = m_clickMaybe.buttons;
-
-	    // reset click detector variable
-	    m_this._setClickMaybe(false);
-	    // fire a click event
-	    m_this.map().geoTrigger(geo_event.mouseclick, details);
-	  };
-
-	  /**
-	   * Private wrapper around the map zoom method that is debounced to support
-	   * discrete zoom interactions.
-	   *
-	   * @returns {function} A function to handle zooms that debounces such
-	   *    events.  This function is passed the zoom level and the mouse state.
-	   */
-	  function debounced_zoom() {
-	    var deltaZ = 0, delay = 400, origin, startZoom, targetZoom;
-
-	    function accum(dz, org) {
-	      var map = m_this.map(), zoom;
-
-	      origin = $.extend(true, {}, org);
-	      deltaZ += dz;
-	      if (targetZoom === undefined) {
-	        startZoom = targetZoom = map.zoom();
-	      }
-	      targetZoom += dz;
-
-	      // Respond to debounced events when they add up to a change in the
-	      // discrete zoom level.
-	      if (map && Math.abs(deltaZ) >= 1 && m_options.discreteZoom &&
-	            !m_options.zoomAnimation.enabled) {
-
-	        zoom = Math.round(deltaZ + map.zoom());
-
-	        // delta is what is left over from the zoom delta after the new zoom
-	        // value
-	        deltaZ = deltaZ + map.zoom() - zoom;
-
-	        map.zoom(zoom, origin);
-	      }
-
-	    }
-
-	    function apply() {
-	      var map = m_this.map(), zoom;
-	      if (map) {
-	        if (m_options.zoomAnimation.enabled) {
-	          zoom = targetZoom;
-	          if (m_options.discreteZoom) {
-	            zoom = Math.round(zoom);
-	            if (zoom === startZoom && targetZoom !== startZoom) {
-	              zoom = startZoom + (targetZoom > startZoom ? 1 : -1);
-	            }
-	          }
-	          map.transitionCancel('debounced_zoom.' + geo_action.zoom);
-	          map.transition({
-	            zoom: zoom,
-	            zoomOrigin: origin,
-	            duration: m_options.zoomAnimation.duration,
-	            ease: m_options.zoomAnimation.ease,
-	            done: function (status) {
-	              status = status || {};
-	              var zoomRE = new RegExp('\\.' + geo_action.zoom + '$');
-	              if (!status.next && (!status.cancel ||
-	                  ('' + status.source).search(zoomRE) < 0)) {
-	                targetZoom = undefined;
-	              }
-	              /* If we were animating the zoom, if the zoom is continuous, just
-	               * stop where we are.  If using discrete zoom, we need to make
-	               * sure we end up discrete.  However, we don't want to do that if
-	               * the next action is further zooming. */
-	              if (m_options.discreteZoom && status.cancel &&
-	                  status.transition && status.transition.end &&
-	                  ('' + status.source).search(zoomRE) < 0) {
-	                map.zoom(status.transition.end.zoom,
-	                         status.transition.end.zoomOrigin);
-	              }
-	            }
-	          });
-	        } else {
-	          zoom = deltaZ + map.zoom();
-	          if (m_options.discreteZoom) {
-	            // round off the zoom to an integer and throw away the rest
-	            zoom = Math.round(zoom);
-	          }
-	          map.zoom(zoom, origin);
-	        }
-	      }
-	      deltaZ = 0;
-	    }
-
-	    if (m_options.discreteZoom !== true && m_options.discreteZoom > 0) {
-	      delay = m_options.discreteZoom;
-	    }
-	    if ((m_options.discreteZoom === true || m_options.discreteZoom > 0) &&
-	            !m_options.zoomAnimation.enabled) {
-	      return debounce(delay, false, apply, accum);
-	    } else {
-	      return function (dz, org) {
-	        if (!dz && targetZoom === undefined) {
-	          return;
-	        }
-	        accum(dz, org);
-	        apply(dz, org);
-	      };
-	    }
-	  }
-
-	  /**
-	   * Attaches wrapped methods for accumulating fast mouse wheel events and
-	   * throttling map interactions.
-	   * @private
-	   *
-	   * @returns {function} A function that takes a jQuery.Event for mouse wheel
-	   *    actions.
-	   */
-	  function throttled_wheel() {
-	    var my_queue = {};
-
-	    function accum(evt) {
-	      var dx, dy;
-
-	      if (m_paused) {
-	        return;
-	      }
-
-	      if (my_queue !== m_queue) {
-	        my_queue = {
-	          kind: 'wheel',
-	          scroll: {x: 0, y: 0}
-	        };
-	        m_queue = my_queue;
-	      }
-
-	      evt.preventDefault();
-
-	      // try to normalize deltas using the wheel event standard:
-	      //   https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
-	      evt.deltaFactor = 1;
-	      if (evt.originalEvent.deltaMode === 1) {
-	        // DOM_DELTA_LINE -- estimate line height
-	        evt.deltaFactor = 40;
-	      } else if (evt.originalEvent.deltaMode === 2) {
-	        // DOM_DELTA_PAGE -- get window height
-	        evt.deltaFactor = $(window).height();
-	      }
-
-	      // prevent NaN's on legacy browsers
-	      dx = evt.originalEvent.deltaX || 0;
-	      dy = evt.originalEvent.deltaY || 0;
-
-	      // scale according to the options
-	      dx = dx * m_options.wheelScaleX * evt.deltaFactor / 120;
-	      dy = dy * m_options.wheelScaleY * evt.deltaFactor / 120;
-
-	      my_queue.scroll.x += dx;
-	      my_queue.scroll.y += dy;
-	    }
-
-	    function wheel(evt) {
-	      var zoomFactor, action, actionRecord;
-
-	      // If the current queue doesn't match the queue passed in as an argument,
-	      // assume it was cancelled and do nothing.
-	      if (my_queue !== m_queue) {
-	        return;
-	      }
-
-	      // perform the map navigation event
-	      m_this._getMouseModifiers(evt);
-
-	      actionRecord = actionMatch({wheel: true}, m_mouse.modifiers,
-	                                m_options.actions);
-	      action = (actionRecord || {}).action;
-
-	      if (action) {
-	        // if we were moving because of momentum or a transition, cancel it and
-	        // recompute where the mouse action is occurring.
-	        var recompute = m_this.map().transitionCancel('wheel.' + action);
-	        recompute |= m_this.cancel(geo_action.momentum, true);
-	        if (recompute) {
-	          m_mouse.geo = m_this.map().displayToGcs(m_mouse.map);
-	          m_mouse.mapgcs = m_this.map().displayToGcs(m_mouse.map, null);
-	        }
-	        switch (action) {
-	          case geo_action.pan:
-	            m_this.map().pan({
-	              x: m_queue.scroll.x,
-	              y: m_queue.scroll.y
-	            }, undefined, 'limited');
-	            break;
-	          case geo_action.zoom:
-	            zoomFactor = -m_queue.scroll.y;
-	            m_callZoom(zoomFactor, m_mouse);
-	            break;
-	          case geo_action.rotate:
-	            m_this.map().rotation(
-	                m_this.map().rotation() +
-	                m_queue.scroll.y * m_options.rotateWheelScale,
-	                m_mouse);
-	            break;
-	        }
-	        m_this.map().geoTrigger(geo_event.actionwheel, {
-	          state: m_this.state(), mouse: m_this.mouse(), event: evt});
-	      }
-
-	      // reset the queue
-	      m_queue = {};
-	    }
-
-	    if (m_options.throttle > 0) {
-	      return throttle(m_options.throttle, false, wheel, accum);
-	    } else {
-	      return function (evt) {
-	        accum(evt);
-	        wheel(evt);
-	      };
-	    }
-	  }
-
-	  /**
-	   * Handle mouse wheel event.  (Defined inside _connectEvents).
-	   */
-	  this._handleMouseWheel = function () {};
-
-	  /**
-	   * Start up a spring back action when the map bounds are out of range.
-	   * Not to be user callable.
-	   * @protected
-	   *
-	   * @param {boolean} initialVelocity Truthy if the mouse's current velocity
-	   *    should be used as the initial velocity.  False to assume no initial
-	   *    velocity.
-	   * @param {object} origAction The original action that started the spring
-	   *    back.  If this was a zoom action, the spring back includes zoom;
-	   *    otherwise it only includes panning.
-	   */
-	  this.springBack = function (initialVelocity, origAction) {
-	    if (m_state.action === geo_action.momentum) {
-	      return;
-	    }
-	    if (!initialVelocity) {
-	      m_mouse.velocity = {
-	        x: 0,
-	        y: 0
-	      };
-	    }
-	    m_state.origAction = origAction;
-	    m_state.action = geo_action.momentum;
-	    m_state.origin = m_this.mouse();
-	    m_state.momentum = m_this.mouse();
-	    m_state.start = new Date();
-	    m_state.handler = function () {
-	      var v, s, last, dt;
-
-	      if (m_state.action !== geo_action.momentum ||
-	          !m_this.map() ||
-	          m_this.map().transition()) {
-	        // cancel if a new action was performed
-	        return;
-	      }
-	      // Not sure the correct way to do this.  We need the delta t for the
-	      // next time step...  Maybe use a better interpolator and the time
-	      // parameter from requestAnimationFrame.
-	      dt = Math.min(m_state.momentum.deltaTime, 30);
-
-	      last = m_state.start.valueOf();
-	      m_state.start = new Date();
-
-	      v = modifyVelocity(m_state.momentum.velocity, m_state.start - last);
-
-	      // stop panning when the speed is below the threshold
-	      if (!v) {
-	        clearState();
-	        return;
-	      }
-
-	      s = calcSpeed(v);
-	      if (s > m_options.momentum.maxSpeed) {
-	        s = m_options.momentum.maxSpeed / s;
-	        v.x = v.x * s;
-	        v.y = v.y * s;
-	      }
-
-	      if (!isFinite(v.x) || !isFinite(v.y)) {
-	        v.x = 0;
-	        v.y = 0;
-	      }
-	      m_state.momentum.velocity.x = v.x;
-	      m_state.momentum.velocity.y = v.y;
-
-	      switch (m_state.origAction) {
-	        case geo_action.zoom:
-	          var dy = m_state.momentum.velocity.y * dt;
-	          m_callZoom(-dy * m_options.zoomScale / 120, m_state);
-	          break;
-	        default:
-	          m_this.map().pan({
-	            x: m_state.momentum.velocity.x * dt,
-	            y: m_state.momentum.velocity.y * dt
-	          }, undefined, 'limited');
-	          break;
-	      }
-
-	      if (m_state.handler) {
-	        m_this.map().scheduleAnimationFrame(m_state.handler);
-	      }
-	    };
-	    if (m_state.handler) {
-	      m_this.map().scheduleAnimationFrame(m_state.handler);
-	    }
-	  };
-
-	  /**
-	   * Public method that unbinds all events.
-	   */
-	  this.destroy = function () {
-	    m_this._disconnectEvents();
-	    if (m_this.map()) {
-	      $(m_this.map().node()).removeClass('highlight-focus');
-	    }
-	    m_this.map(null);
-	  };
-
-	  /**
-	   * Get current mouse information.
-	   *
-	   * @returns {object} The current mouse state.
-	   */
-	  this.mouse = function () {
-	    return $.extend(true, {}, m_mouse);
-	  };
-
-	  /**
-	   * Get/set current keyboard information.
-	   *
-	   * @param {object} [newValue] Either a object with new options for the
-	   *    keyboard actions or `undefined` to get the current options.
-	   * @returns {object|this} Either the current keyboard options or this
-	   *    mapInteractor class instance.
-	   */
-	  this.keyboard = function (newValue) {
-	    if (newValue === undefined) {
-	      return $.extend(true, {}, m_options.keyboard || {});
-	    }
-	    return m_this.options({keyboard: newValue});
-	  };
-
-	  /**
-	   * Get the current interactor state.
-	   *
-	   * @returns {geo.interactorState}
-	   */
-	  this.state = function () {
-	    return $.extend(true, {}, m_state);
-	  };
-
-	  /**
-	   * Get or set the pause state of the interactor, which ignores all native
-	   * mouse and keyboard events.
-	   *
-	   * @param {boolean} [value] The pause state to set or undefined to return
-	   *    the current state.
-	   * @returns {boolean|this} The current pause state or this class instance.
-	   */
-	  this.pause = function (value) {
-	    if (value === undefined) {
-	      return m_paused;
-	    }
-	    m_paused = !!value;
-	    return m_this;
-	  };
-
-	  /**
-	   * Add an action to the list of handled actions.
-	   *
-	   * @param {object} action An object defining the action.  This must have
-	   *    action and input properties, and may have modifiers, name, and owner.
-	   *    Use action, name, and owner to make this entry distinct if it will need
-	   *    to be removed later.
-	   * @param {boolean} [toEnd] The action is added at the beginning of the
-	   *    actions list unless truthy.  Earlier actions prevent later actions with
-	   *    similar input and modifiers.
-	   */
-	  this.addAction = function (action, toEnd) {
-	    if (!action || !action.action || !action.input) {
-	      return;
-	    }
-	    util.addAction(m_options.actions, action, toEnd);
-	    if (m_options.map && m_options.actions.some(function (action) {
-	      return action.input.right;
-	    })) {
-	      $node.off('contextmenu.geojs');
-	      $node.on('contextmenu.geojs', function () { return false; });
-	    }
-	  };
-
-	  /**
-	   * Check if an action is in the actions list.  An action matches if the
-	   * action, name, and owner match.  A null or undefined value will match all
-	   * actions.  If using an action object, this is the same as passing
-	   * (action.action, action.name, action.owner).
-	   *
-	   * @param {object|string} action Either an action object or the name of an
-	   *    action.
-	   * @param {string} name Optional name associated with the action.
-	   * @param {string} owner Optional owner associated with the action.
-	   * @returns {object|null} The first matching action or null.
-	   */
-	  this.hasAction = function (action, name, owner) {
-	    return util.hasAction(m_options.actions, action, name, owner);
-	  };
-
-	  /**
-	   * Remove all matching actions.  Actions are matched as with hasAction.
-	   *
-	   * @param {object|string} action Either an action object or the name of an
-	   *    action.
-	   * @param {string} name Optional name associated with the action.
-	   * @param {string} owner Optional owner associated with the action.
-	   * @returns {number} The number of actions that were removed.
-	   */
-	  this.removeAction = function (action, name, owner) {
-	    var removed = util.removeAction(
-	        m_options.actions, action, name, owner);
-	    if (m_options.map && !m_options.actions.some(function (action) {
-	      return action.input.right;
-	    })) {
-	      $node.off('contextmenu.geojs');
-	    }
-	    return removed;
-	  };
-
-	  /**
-	   * Simulate a DOM mouse event on connected map.  Not all options are required
-	   * for every event type.
-	   *
-	   * @param {string} type Event type `mousemove`, `mousedown`, `mouseup`, etc.
-	   *    `keyboard` is treated separately from other types.
-	   * @param {object} options Options for the simulated event.
-	   * @param {boolean} [options.shift] A boolean if a `keyboard` event has the
-	   *    shift key down or explicitly up.
-	   * @param {boolean} [options.shiftKey] Alternate name to `shift`.
-	   * @param {boolean} [options.ctrl] A boolean if a `keyboard` event has the
-	   *    control key down or explicitly up.
-	   * @param {boolean} [options.ctrlKey] Alternate name to `ctrl`.
-	   * @param {boolean} [options.alt] A boolean if a `keyboard` event has the
-	   *    alt key down or explicitly up.
-	   * @param {boolean} [options.altKey] Alternate name to `alt`.
-	   * @param {boolean} [options.meta] A boolean if a `keyboard` event has the
-	   *    meta key down or explicitly up.
-	   * @param {boolean} [options.metaKey] Alternate name to `meta`.
-	   * @param {string} [options.keys] A Mousetrap-style key sequence string for
-	   *    `keyboard` events.
-	   * @param {geo.screenPosition} [options.page] The position of the event on
-	   *    the window.  Overridden by `map`.
-	   * @param {geo.screenPosition} [options.map] The position of the event on the
-	   *    map in pixels relative to the map's div.
-	   * @param {geo.screenPosition} [options.center] The position of a touch
-	   *    event center relative to the window.
-	   * @param {string} [button] One of `left`, `middle`, or `right` for mouse
-	   *    events.
-	   * @param {string} [modifiers] A space-separated list of metakeys that are
-	   *    down on mouse events.
-	   * @param {geo.screenPosition} [wheelDelta] The amount the wheel moved in
-	   *    both directions for wheel events.  One step is often 20 units of
-	   *    vement.
-	   * @param {number} [wheelMode] The wheel delta mode.  See
-	   *    https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent .
-	   * @param {boolean} [touch] `truthy` if this is a touch event.
-	   * @param {number} [rotation] Touch event rotation in degrees.
-	   * @param {number} [scale] Touch event scale.  Initial events should have a
-	   *    scale of 1; subsequent events should increase or decrease this to
-	   *    simulate spread and pinch actions.
-	   * @param {number[]} [pointers] A list of pointer numbers involved in a
-	   *    touch event.  Pointers are number from one up, so `[1]` is the first
-	   *    touch point, `[1, 2]` are two touch points, and `[2]` is when the first
-	   *    point was released but the second is still touching.
-	   * @param {string} [pointerType] `mouse` if this is a mouse action rather
-	   *    than a touch action.
-	   * @returns {mapInteractor}
-	   */
-	  this.simulateEvent = function (type, options) {
-	    var evt, page, offset, which, buttons;
-
-	    if (!m_this.map()) {
-	      return m_this;
-	    }
-
-	    if (type === 'keyboard' && m_keyHandler) {
-	      /* Mousetrap passes through the keys we send, but not an event object,
-	       * so we construct an artificial event object as the keys, and use that.
-	       */
-	      var keys = {
-	        shiftKey: options.shift || options.shiftKey || false,
-	        ctrlKey: options.ctrl || options.ctrlKey || false,
-	        altKey: options.alt || options.altKey || false,
-	        metaKey: options.meta || options.metaKey || false,
-	        toString: function () { return options.keys; },
-	        stopPropagation: function () {},
-	        preventDefault: function () {},
-	        simulated: true
-	      };
-	      m_keyHandler.trigger(keys);
-	      return;
-	    }
-
-	    page = options.page || {};
-
-	    if (options.map) {
-	      offset = $node.offset();
-	      page.x = options.map.x + offset.left;
-	      page.y = options.map.y + offset.top;
-	    }
-
-	    if (options.button === 'left') {
-	      which = 1;
-	      buttons = 1;
-	    } else if (options.button === 'right') {
-	      which = 3;
-	      buttons = 2;
-	    } else if (options.button === 'middle') {
-	      which = 2;
-	      buttons = 4;
-	    }
-
-	    options.modifiers = options.modifiers || [];
-	    options.wheelDelta = options.wheelDelta || {};
-
-	    evt = $.Event(
-	      type,
-	      {
-	        pageX: page.x,
-	        pageY: page.y,
-	        which: which,
-	        buttons: buttons,
-	        altKey: options.modifiers.indexOf('alt') >= 0,
-	        ctrlKey: options.modifiers.indexOf('ctrl') >= 0,
-	        metaKey: options.modifiers.indexOf('meta') >= 0,
-	        shiftKey: options.modifiers.indexOf('shift') >= 0,
-	        center: options.center,
-	        rotation: options.touch ? options.rotation || 0 : options.rotation,
-	        scale: options.touch ? options.scale || 1 : options.scale,
-	        pointers: options.pointers,
-	        pointerType: options.pointerType,
-
-	        originalEvent: {
-	          deltaX: options.wheelDelta.x,
-	          deltaY: options.wheelDelta.y,
-	          deltaMode: options.wheelMode,
-	          preventDefault: function () {},
-	          stopPropagation: function () {},
-	          stopImmediatePropagation: function () {}
-	        }
-	      }
-	    );
-	    if (options.touch && m_touchHandler) {
-	      m_this._handleTouch(evt);
-	    } else {
-	      $node.trigger(evt);
-	    }
-	    if (type.indexOf('.geojs') >= 0) {
-	      $(document).trigger(evt);
-	    }
-	  };
-	  this._connectEvents();
-	  return this;
-	};
-
-	inherit(mapInteractor, object);
-	module.exports = mapInteractor;
-
-
-/***/ }),
-/* 223 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Quad feature specification.
-	 *
-	 * @typedef {geo.feature.spec} geo.quadFeature.spec
-	 * @param {geo.geoColor|Function} [color] Color for quads without images.
-	 *   Default is white ({r: 1, g: 1, b: 1}).
-	 * @param {number|Function} [opacity=1] Opacity for the quads.
-	 * @param {number|Function} [depth=0] Default z-coordinate for positions that
-	 *   don't explicitly specify one.
-	 * @param {boolean|Function} [drawOnAsyncResourceLoaded=true] Redraw quads
-	 *   when images or videos are loaded after initial render.
-	 * @param {Image|string|Function} [image] Image for each data item.  If
-	 *   falsy and `video` is also falsy, the quad is a solid color.  Default is
-	 *   (data).image.
-	 * @param {HTMLVideoElement|string|Function} [video] Video for each data item.
-	 *   If falsy and `image` is also falsy, the quad is a solid color.  Default is
-	 *   (data).video.
-	 * @param {boolean|Function} [delayRenderWhenSeeking=true] If any video has a
-	 *   truthy value and is seeking, delaying rendering the entire feature.  This
-	 *   prevents blinking when seeking a playing video, but may cause stuttering
-	 *   when there are multiple videos.
-	 * @param {geo.geoColor|Function} [previewColor=null] If specified, a color to
-	 *   show on image and video quads while waiting for the image or video to
-	 *   load.
-	 * @param {Image|string|Function} [previewImage=null] If specified, an image to
-	 *   show on image quads while waiting for the quad-specific image to load.
-	 *   This will only be shown if it (the preview image) is already loaded.
-	 * @param {object|Function} [position] Position of the quad.  Default is
-	 *   (data).  The position is an object which specifies the corners of the
-	 *   quad: ll, lr, ur, ul.  At least two opposite corners must be specified.
-	 *   The corners do not have to physically correspond to the order specified,
-	 *   but rather correspond to that part of an image or video (if there is one).
-	 *   If a corner is unspecified, it will use the x coordinate from one adjacent
-	 *   corner, the y coordinate from the other adjacent corner, and the average
-	 *   z value of those two corners.  For instance, if ul is unspecified, it is
-	 *   {x: ll.x, y: ur.y}.  Note that each quad is rendered as a pair of
-	 *   triangles: (ll, lr, ul) and (ur, ul, lr).  Nothing special is done for
-	 *   quads that are not convex or quads that have substantially different
-	 *   transformations for those two triangles.
-	 * @param {boolean} [cacheQuads=true] If truthy, a set of internal information
-	 *   is stored on each data item in the _cachedQuad attribute.  If this is
-	 *   falsy, the data item is not altered.  If the data (positions, opacity,
-	 *   etc.) of individual quads will change, set this to `false` or call
-	 *   `cacheUpdate` on the data item or for all data.
-	 */
-
-	/**
-	 * Create a new instance of class quadFeature.
-	 *
-	 * @class geo.quadFeature
-	 * @param {geo.quadFeature.spec} arg Options object.
-	 * @extends geo.feature
-	 * @returns {geo.quadFeature}
-	 */
-	var quadFeature = function (arg) {
-	  'use strict';
-
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-
-	  if (!(this instanceof quadFeature)) {
-	    return new quadFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      m_cacheQuads,
-	      m_nextQuadId = 0,
-	      m_images = [],
-	      m_videos = [],
-	      m_quads;
-
-	  /**
-	   * Track a list of object->object mappings.  The mappings are kept in a list.
-	   * This marks all known mappings as unused.  If they are not marked as used
-	   * before `_objectListEnd` is called, that function will remove them.
-	   *
-	   * @param {array} list The list of mappings.
-	   */
-	  this._objectListStart = function (list) {
-	    $.each(list, function (idx, item) {
-	      item.used = false;
-	    });
-	  };
-
-	  /**
-	   * Get the value from a list of object->object mappings.  If the key object
-	   * is not present, return `undefined`.  If found, the entry is marked as
-	   * being in use.
-	   *
-	   * @param {array} list The list of mappings.
-	   * @param {object} entry The key to search for.
-	   * @returns {object} The associated object or undefined.
-	   */
-	  this._objectListGet = function (list, entry) {
-	    for (var i = 0; i < list.length; i += 1) {
-	      if (list[i].entry === entry) {
-	        list[i].used = true;
-	        return list[i].value;
-	      }
-	    }
-	    return undefined;
-	  };
-
-	  /**
-	   * Add a new object to a list of object->object mappings.  The key object
-	   * should not exist, or this will create a duplicate.  The new entry is
-	   * marked as being in use.
-	   *
-	   * @param {array} list The list of mappings.
-	   * @param {object} entry The key to add.
-	   * @param {object} value The value to store with the entry.
-	   */
-	  this._objectListAdd = function (list, entry, value) {
-	    list.push({entry: entry, value: value, used: true});
-	  };
-
-	  /**
-	   * Remove all unused entries from a list of object->object mappings.
-	   *
-	   * @param {array} list The list of mappings.
-	   */
-	  this._objectListEnd = function (list) {
-	    for (var i = list.length - 1; i >= 0; i -= 1) {
-	      if (!list[i].used) {
-	        list.splice(i, 1);
-	      }
-	    }
-	  };
-
-	  /**
-	   * Point search method for selection api.  Returns markers containing the
-	   * given point.
-	   *
-	   * @param {geo.geoPosition} coordinate Coordinate in input gcs to check if it
-	   *    is located in any quad in map interface gcs.
-	   * @returns {object} An object with `index`: a list of quad indices, and
-	   *    `found`: a list of quads that contain the specified coordinate.
-	   */
-	  this.pointSearch = function (coordinate) {
-	    var found = [], indices = [], extra = {},
-	        poly1 = [{}, {}, {}, {}], poly2 = [{}, {}, {}, {}],
-	        order1 = [0, 1, 2, 0], order2 = [1, 2, 3, 1],
-	        data = m_this.data(),
-	        map = m_this.layer().map(),
-	        i, coordbasis;
-	    coordinate = transform.transformCoordinates(
-	        map.ingcs(), map.gcs(), coordinate);
-	    if (!m_quads) {
-	      this._generateQuads();
-	    }
-	    $.each([m_quads.clrQuads, m_quads.imgQuads, m_quads.vidQuads], function (idx, quadList) {
-	      quadList.forEach(function (quad, idx) {
-	        for (i = 0; i < order1.length; i += 1) {
-	          poly1[i].x = quad.pos[order1[i] * 3];
-	          poly1[i].y = quad.pos[order1[i] * 3 + 1];
-	          poly1[i].z = quad.pos[order1[i] * 3 + 2];
-	          poly2[i].x = quad.pos[order2[i] * 3];
-	          poly2[i].y = quad.pos[order2[i] * 3 + 1];
-	          poly2[i].z = quad.pos[order2[i] * 3 + 2];
-	        }
-	        if (util.pointInPolygon(coordinate, poly1) ||
-	            util.pointInPolygon(coordinate, poly2)) {
-	          indices.push(quad.idx);
-	          found.push(data[quad.idx]);
-	          /* If a point is in the quad (based on pointInPolygon, above), check
-	           * where in the quad it is located.  We want to output coordinates
-	           * where the upper-left is (0, 0) and the lower-right is (1, 1). */
-	          coordbasis = util.pointTo2DTriangleBasis(
-	            coordinate, poly1[0], poly1[1], poly1[2]);
-	          if (!coordbasis || coordbasis.x + coordbasis.y > 1) {
-	            coordbasis = util.pointTo2DTriangleBasis(
-	              coordinate, poly2[2], poly2[1], poly2[0]);
-	            if (coordbasis) {
-	              /* In the second triangle, (0, 0) is upper-right, (1, 0) is
-	               * upper-left, and (0, 1) is lower-right.  Invert x to get to
-	               * the desired output coordinates. */
-	              coordbasis.x = 1 - coordbasis.x;
-	            }
-	          } else {
-	            /* In the first triangle, (0, 0) is lower-left, (1, 0) is lower-
-	             * right, and (0, 1) is upper-left.  Invert y to get to the
-	             * desired output coordinates. */
-	            coordbasis.y = 1 - coordbasis.y;
-	          }
-	          if (coordbasis) {
-	            extra[quad.idx] = {basis: coordbasis};
-	          }
-	        }
-	      });
-	    });
-	    return {
-	      index: indices,
-	      found: found,
-	      extra: extra
-	    };
-	  };
-
-	  /**
-	   * Get/Set position.
-	   *
-	   * @memberof geo.quadFeature
-	   * @param {object|Function} [val] Object or function that returns the
-	   *    position of each quad.  `undefined` to get the current position value.
-	   * @returns {geo.quadFeature}
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else {
-	      m_this.style('position', util.ensureFunction(val));
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Given a data item and its index, fetch its position and ensure we have
-	   * complete information for the quad.  This generates missing corners and z
-	   * values.
-	   *
-	   * @param {function} posFunc A function to call to get the position of a data
-	   *   item.  It is passed (d, i).
-	   * @param {function} depthFunc A function to call to get the z-value of a
-	   *   data item.  It is passed (d, i).
-	   * @param {object} d A data item.  Used to fetch position and possibly depth.
-	   * @param {number} i The index within the data.  Used to fetch position and
-	   *   possibly depth.
-	   * @returns {object|undefined} Either an object with all four corners, or
-	   *   `undefined` if no such object can be generated.  The coordinates have
-	   *   been converted to map coordinates.
-	   */
-	  this._positionToQuad = function (posFunc, depthFunc, d, i) {
-	    var initPos = posFunc.call(m_this, d, i);
-	    if ((!initPos.ll || !initPos.ur) && (!initPos.ul || !initPos.lr)) {
-	      return;
-	    }
-	    var gcs = m_this.gcs(),
-	        map_gcs = m_this.layer().map().gcs(),
-	        pos = {};
-	    $.each(['ll', 'lr', 'ul', 'ur'], function (idx, key) {
-	      if (initPos[key] !== undefined) {
-	        pos[key] = {};
-	        if (initPos[key].x === undefined) {
-	          pos[key] = [initPos[key][0], initPos[key][1], initPos[key][2]];
-	        } else {
-	          pos[key] = [initPos[key].x, initPos[key].y, initPos[key].z];
-	        }
-	        if (pos[key][2] === undefined) {
-	          pos[key][2] = depthFunc.call(m_this, d, i);
-	        }
-	        if (gcs !== map_gcs && gcs !== false) {
-	          pos[key] = transform.transformCoordinates(
-	              gcs, map_gcs, pos[key]);
-	        }
-	      }
-	    });
-	    pos.ll = pos.ll || [pos.ul[0], pos.lr[1], (pos.ul[2] + pos.lr[2]) / 2];
-	    pos.lr = pos.lr || [pos.ur[0], pos.ll[1], (pos.ur[2] + pos.ll[2]) / 2];
-	    pos.ur = pos.ur || [pos.lr[0], pos.ul[1], (pos.lr[2] + pos.ul[2]) / 2];
-	    pos.ul = pos.ul || [pos.ll[0], pos.ur[1], (pos.ll[2] + pos.ur[2]) / 2];
-	    return pos;
-	  };
-
-	  /**
-	   * Renderers can subclass this when needed.
-	   *
-	   * This is called when a video qaud may have changed play state.
-	   * @param {object} quad The quad record that triggered this.
-	   * @param {jQuery.Event} [evt] The event that triggered this.
-	   */
-	  this._checkQuadUpdate = function (quad, evt) {
-	  };
-
-	  /**
-	   * Convert the current data set to a set of 3 arrays: quads that are a solid
-	   * color, quads that have an image, and quads that have a video.  All quads
-	   * are objects with pos (a 12 value array containing 4 three-dimensional
-	   * position coordinates), and opacity.  Color quads also have a color.  Image
-	   * quads may have an image element if the image is loaded.  If it isn't, this
-	   * element will be missing.  For preview images, the image quad will have a
-	   * reference to the preview element that may later be removed.  If a preview
-	   * color is used, the quad will be in both lists, but may be removed from the
-	   * color quad list once the image is loaded.  Video quads may have a video
-	   * element if the video is loaded.
-	   *
-	   * The value for origin is one of an ll corner from one of the quads with the
-	   * smallest sum of diagonals.  The assumption is that, if using the origin to
-	   * improve precision, the smallest quads are the ones most in need of this
-	   * benefit.
-	   *
-	   * @returns {object} An object with `clrQuads`, `imgQuads`, and `vidQuads`,
-	   *   each of which is an array; and `origin`, which is a triplet that is
-	   *   guaranteed to be one of the quads' corners for a quad with the smallest
-	   *   sum of diagonal lengths.
-	   */
-	  this._generateQuads = function () {
-	    var posFunc = m_this.position(),
-	        imgFunc = m_this.style.get('image'),
-	        vidFunc = m_this.style.get('video'),
-	        delayFunc = m_this.style.get('delayRenderWhenSeeking'),
-	        colorFunc = m_this.style.get('color'),
-	        depthFunc = m_this.style.get('depth'),
-	        opacityFunc = m_this.style.get('opacity'),
-	        loadedFunc = m_this.style.get('drawOnAsyncResourceLoaded'),
-	        previewColorFunc = m_this.style.get('previewColor'),
-	        previewImageFunc = m_this.style.get('previewImage'),
-	        data = m_this.data(),
-	        clrQuads = [], imgQuads = [], vidQuads = [],
-	        origin = [0, 0, 0], origindiag2, diag2;
-	    /* Keep track of images that we are using.  This prevents creating
-	     * additional Image elements for repeated urls. */
-	    m_this._objectListStart(m_images);
-	    m_this._objectListStart(m_videos);
-	    $.each(data, function (i, d) {
-	      if (d._cachedQuad) {
-	        diag2 = d._cachedQuad.diag2;
-	        if (origindiag2 === undefined || (d._cachedQuad.diag2 &&
-	            d._cachedQuad.diag2 < origindiag2)) {
-	          origin = d._cachedQuad.ll;
-	          origindiag2 = d._cachedQuad.diag2;
-	        }
-	        if (d._cachedQuad.clrquad) {
-	          clrQuads.push(d._cachedQuad.clrquad);
-	        }
-	        if (d._cachedQuad.imgquad) {
-	          if (d._cachedQuad.imageEntry) {
-	            m_this._objectListGet(m_images, d._cachedQuad.imageEntry);
-	          }
-	          imgQuads.push(d._cachedQuad.imgquad);
-	        }
-	        if (d._cachedQuad.vidquad) {
-	          if (d._cachedQuad.videoEntry) {
-	            m_this._objectListGet(m_videos, d._cachedQuad.videoEntry);
-	          }
-	          vidQuads.push(d._cachedQuad.vidquad);
-	        }
-	        return;
-	      }
-	      var quad, reload, image, video, prev_onload, prev_onerror, defer,
-	          pos, img, vid, opacity, previewColor, previewImage, quadinfo = {};
-
-	      pos = m_this._positionToQuad(posFunc, depthFunc, d, i);
-	      opacity = opacityFunc.call(m_this, d, i);
-	      if (pos === undefined || !opacity || opacity < 0) {
-	        return;
-	      }
-	      diag2 = Math.pow(pos.ll[0] - pos.ur[0], 2) + Math.pow(pos.ll[1] -
-	          pos.ur[1], 2) + Math.pow(pos.ll[2] - pos.ur[0], 2) + Math.pow(
-	          pos.lr[0] - pos.ur[0], 2) + Math.pow(pos.lr[1] - pos.ur[1], 2) +
-	          Math.pow(pos.lr[2] - pos.ur[0], 2);
-	      quadinfo.diag2 = diag2;
-	      quadinfo.ll = pos.ll;
-	      if (origindiag2 === undefined || (diag2 && diag2 < origindiag2)) {
-	        origin = pos.ll;
-	        origindiag2 = diag2;
-	      }
-	      pos = [
-	        pos.ll[0], pos.ll[1], pos.ll[2],
-	        pos.lr[0], pos.lr[1], pos.lr[2],
-	        pos.ul[0], pos.ul[1], pos.ul[2],
-	        pos.ur[0], pos.ur[1], pos.ur[2]
-	      ];
-	      quad = {
-	        idx: i,
-	        pos: pos,
-	        opacity: opacity
-	      };
-	      if (d.reference) {
-	        quad.reference = d.reference;
-	      }
-	      if (d.crop) {
-	        quad.crop = d.crop;
-	      }
-	      img = imgFunc.call(m_this, d, i);
-	      vid = img ? null : vidFunc.call(m_this, d, i);
-	      if (img) {
-	        quadinfo.imageEntry = img;
-	        /* Handle image quads */
-	        image = m_this._objectListGet(m_images, img);
-	        if (image === undefined) {
-	          if (img instanceof Image || img instanceof HTMLCanvasElement) {
-	            image = img;
-	          } else {
-	            image = new Image();
-	            image.src = img;
-	          }
-	          m_this._objectListAdd(m_images, img, image);
-	        }
-	        if (util.isReadyImage(image) || image instanceof HTMLCanvasElement) {
-	          quad.image = image;
-	        } else {
-	          previewColor = undefined;
-	          previewImage = previewImageFunc.call(m_this, d, i);
-	          if (previewImage && util.isReadyImage(previewImage)) {
-	            quad.image = previewImage;
-	          } else {
-	            previewColor = previewColorFunc.call(m_this, d, i);
-	            quad.color = util.convertColor(previewColor);
-	            if (quad.color && quad.color.r !== undefined && quad.color.g !== undefined && quad.color.b !== undefined) {
-	              clrQuads.push(quad);
-	              quadinfo.clrquad = quad;
-	            } else {
-	              previewColor = undefined;
-	            }
-	          }
-	          reload = loadedFunc.call(m_this, d, i);
-	          if (reload) {
-	            // add a promise to the layer if this image might complete
-	            defer = util.isReadyImage(image, true) ? null : $.Deferred();
-	            prev_onload = image.onload;
-	            image.onload = function () {
-	              if (previewColor !== undefined) {
-	                if ($.inArray(quad, clrQuads) >= 0) {
-	                  clrQuads.splice($.inArray(quad, clrQuads), 1);
-	                }
-	                delete quadinfo.clrquad;
-	              }
-	              quad.image = image;
-	              m_this.dataTime().modified();
-	              m_this.modified();
-	              m_this._update();
-	              m_this.layer().draw();
-	              if (defer) {
-	                defer.resolve();
-	              }
-	              if (prev_onload) {
-	                return prev_onload.apply(this, arguments);
-	              }
-	            };
-	            prev_onerror = image.onerror;
-	            image.onerror = function () {
-	              if (defer) {
-	                defer.reject();
-	              }
-	              if (prev_onerror) {
-	                return prev_onerror.apply(this, arguments);
-	              }
-	            };
-	            if (defer) {
-	              m_this.layer().addPromise(defer.promise());
-	            }
-	          } else if (previewColor === undefined && !quad.image) {
-	            /* the image isn't ready and we don't want to reload, so don't add
-	             * it to the list of image quads */
-	            return;
-	          }
-	        }
-	        imgQuads.push(quad);
-	        quadinfo.imgquad = quad;
-	      } else if (vid) {
-	        /* Handle video quads */
-	        quadinfo.videoEntry = vid;
-	        video = m_this._objectListGet(m_videos, vid);
-	        if (video === undefined) {
-	          if (vid instanceof HTMLVideoElement) {
-	            video = vid;
-	          } else {
-	            video = document.createElement('video');
-	            video.src = vid;
-	          }
-	          m_this._objectListAdd(m_videos, vid, video);
-	          /* monitor some media events that may indicate a change of play state
-	           * or seeking */
-	          $(video).off('.geojsvideo')
-	            .on('seeking.geojsvideo canplay.geojsvideo pause.geojsvideo playing.geojsvideo', function (evt) {
-	              m_this._checkQuadUpdate(quad, evt);
-	            });
-	        }
-	        quad.delayRenderWhenSeeking = delayFunc.call(m_this, d, i);
-	        if (quad.delayRenderWhenSeeking === undefined) {
-	          quad.delayRenderWhenSeeking = true;
-	        }
-	        if (util.isReadyVideo(video)) {
-	          quad.video = video;
-	        } else {
-	          previewColor = previewColorFunc.call(m_this, d, i);
-	          quad.color = util.convertColor(previewColor);
-	          if (quad.color && quad.color.r !== undefined && quad.color.g !== undefined && quad.color.b !== undefined) {
-	            clrQuads.push(quad);
-	            quadinfo.clrquad = quad;
-	          } else {
-	            previewColor = undefined;
-	          }
-	          reload = loadedFunc.call(m_this, d, i);
-	          if (reload) {
-	            // add a promise to the layer if this video might load
-	            defer = util.isReadyVideo(video, true) ? null : $.Deferred();
-	            prev_onload = video.onloadeddata;
-	            video.onloadeddata = function () {
-	              if (previewColor !== undefined) {
-	                if ($.inArray(quad, clrQuads) >= 0) {
-	                  clrQuads.splice($.inArray(quad, clrQuads), 1);
-	                }
-	                delete quadinfo.clrquad;
-	              }
-	              quad.video = video;
-	              m_this.dataTime().modified();
-	              m_this.modified();
-	              m_this._update();
-	              m_this.layer().draw();
-	              if (defer) {
-	                defer.resolve();
-	              }
-	              if (prev_onload) {
-	                return prev_onload.apply(this, arguments);
-	              }
-	            };
-	            prev_onerror = video.onerror;
-	            video.onerror = function () {
-	              if (defer) {
-	                defer.reject();
-	              }
-	              if (prev_onerror) {
-	                return prev_onerror.apply(this, arguments);
-	              }
-	            };
-	            if (defer) {
-	              m_this.layer().addPromise(defer.promise());
-	            }
-	          } else if (previewColor === undefined && !quad.video) {
-	            /* the video isn't ready and we don't want to reload, so don't add
-	             * it to the list of video quads */
-	            return;
-	          }
-	        }
-	        vidQuads.push(quad);
-	        quadinfo.vidquad = quad;
-	      } else {
-	        /* Handle color quads */
-	        quad.color = util.convertColor(colorFunc.call(m_this, d, i));
-	        if (!quad.color || quad.color.r === undefined || quad.color.g === undefined || quad.color.b === undefined) {
-	          /* if we can't resolve the color, don't make a quad */
-	          return;
-	        }
-	        clrQuads.push(quad);
-	        quadinfo.clrquad = quad;
-	      }
-	      if (quadinfo.clrquad) {
-	        m_nextQuadId += 1;
-	        quadinfo.clrquad.quadId = m_nextQuadId;
-	      }
-	      if (quadinfo.imgquad) {
-	        m_nextQuadId += 1;
-	        quadinfo.imgquad.quadId = m_nextQuadId;
-	      }
-	      if (quadinfo.vidquad) {
-	        m_nextQuadId += 1;
-	        quadinfo.vidquad.quadId = m_nextQuadId;
-	      }
-	      if (m_cacheQuads !== false) {
-	        d._cachedQuad = quadinfo;
-	      }
-	    });
-	    m_this._objectListEnd(m_images);
-	    m_this._objectListEnd(m_videos);
-	    m_quads = {
-	      clrQuads: clrQuads,
-	      imgQuads: imgQuads,
-	      vidQuads: vidQuads,
-	      origin: origin
-	    };
-	    return m_quads;
-	  };
-
-	  /**
-	   * If the data has changed and caching has been used, update one or all data
-	   * items by clearing their caches and updating the modified flag.
-	   *
-	   * @param {number|object} [indexOrData] If not specified, clear all quad
-	   *    caches.  If a number, clear that index-numbered entry from the data
-	   *    array.  Otherwise, clear the matching entry in the data array.
-	   * @returns {this}
-	   */
-	  this.cacheUpdate = function (indexOrData) {
-	    if (indexOrData === undefined || indexOrData === null) {
-	      $.each(m_this.data(), function (idx, entry) {
-	        if (entry._cachedQuad) {
-	          delete entry._cachedQuad;
-	        }
-	      });
-	    } else {
-	      if (isFinite(indexOrData)) {
-	        indexOrData = m_this.data()[indexOrData];
-	      }
-	      if (indexOrData._cachedQuad) {
-	        delete indexOrData._cachedQuad;
-	      }
-	    }
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the HTML video element associated with a data item.
-	   *
-	   * @param {number|object} indexOrData If a number, use that entry in the data
-	   *    array, otherwise this must be a value in the data array.  If caching is
-	   *    used, this is much more efficient.
-	   * @returns {HTMLVideoElement|null}
-	   */
-	  this.video = function (indexOrData) {
-	    var video, index;
-
-	    if (isFinite(indexOrData)) {
-	      indexOrData = m_this.data()[indexOrData];
-	    }
-	    if (indexOrData._cachedQuad) {
-	      video = (indexOrData._cachedQuad.vidquad || {}).video;
-	    } else {
-	      if (!m_quads) {
-	        m_this._generateQuads();
-	      }
-	      index = m_this.data().indexOf(indexOrData);
-	      if (index >= 0) {
-	        /* If we don't cache the quad, we don't maintain a direct link between
-	         * a data element and the video (partly because videos could be shared
-	         * between multiple quads).  Instead, the video will be in the
-	         * last-used object list with a reference to the video value of the
-	         * data entry. */
-	        video = m_this._objectListGet(m_videos, m_this.style.get('video')(indexOrData, index));
-	      }
-	    }
-	    if (video instanceof HTMLVideoElement) {
-	      return video;
-	    }
-	    return null;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.quadFeature.spec} arg Options for the feature.
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    m_cacheQuads = (arg.cacheQuads !== false);
-
-	    var style = $.extend(
-	      {},
-	      {
-	        color: { r: 1.0, g: 1, b: 1 },
-	        opacity: 1,
-	        depth: 0,
-	        drawOnAsyncResourceLoaded: true,
-	        previewColor: null,
-	        previewImage: null,
-	        image: function (d) { return d.image; },
-	        video: function (d) { return d.video; },
-	        position: function (d) { return d; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.position !== undefined) {
-	      style.position = util.ensureFunction(arg.position);
-	    }
-	    m_this.style(style);
-	    m_this.dataTime().modified();
-	  };
-
-	  return m_this;
-	};
-
-	/**
-	 * Create a quadFeature from an object.
-	 *
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to.
-	 * @param {geo.quadFeature.spec} spec The object specification.
-	 * @returns {geo.quadFeature|null}
-	 */
-	quadFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'quad';
-	  return feature.create(layer, spec);
-	};
-
-	quadFeature.capabilities = {
-	  /* support for solid-colored quads */
-	  color: 'quad.color',
-	  /* support for parallelogram image quads */
-	  image: 'quad.image',
-	  /* support for cropping quad images */
-	  imageCrop: 'quad.imageCrop',
-	  /* support for fixed-scale quad images */
-	  imageFixedScale: 'quad.imageFixedScale',
-	  /* support for arbitrary quad images */
-	  imageFull: 'quad.imageFull',
-	  /* support for canvas elements as content in image quads */
-	  canvas: 'quad.canvas',
-	  /* support for parallelogram video quads */
-	  video: 'quad.video'
-	};
-
-	inherit(quadFeature, feature);
-	module.exports = quadFeature;
-
-
-/***/ }),
-/* 224 */
-/***/ (function(module, exports) {
-
-	if(typeof __WEBPACK_EXTERNAL_MODULE_224__ === 'undefined') {var e = new Error("Cannot find module \"hammerjs\""); e.code = 'MODULE_NOT_FOUND'; throw e;}
-	module.exports = __WEBPACK_EXTERNAL_MODULE_224__;
-
-/***/ }),
-/* 225 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Create a new instance of class choroplethFeature
-	 *
-	 * @class geo.choroplethFeature
-	 * @param {Object} arg Options object
-	 * @extends geo.feature
-	 * @param {Array} [colorRange] Color lookup table for
-	 *   choroplethFeature. Default is 9-step color table.
-	 * @param {Function} [scale] A scale converts a input domain into the
-	 *   the colorRange. Default is d3.scale.quantize.
-	 * @param {Object} [accessor] A accessor defines three functions; Retrieve
-	 *   geoId from geometry feature and scalarId and scalarValue from scalarData. By
-	 *   default geoId uses GEO_ID key in the geometry feature property,
-	 *   scalarId uses id key and scalarValue uses value from a object within
-	 *   the scalar array.
-	 * @param {Object|Function} [scalar] A scalar is a array of objects with keys id
-	 *   that maps scalar value to the geometry and value of the scalar.
-	 *   Multiple values mapped to the same id are aggregated by the aggregator.
-	 * @param {Object|Function} [choropleth] Defines accessor for choropleth. It
-	 *   provides an API to replace or add attributes to the choropleth.
-	 * @param {Object|Function} [choropleth.get] A uniform getter that
-	 *   always returns a function even for constant values.
-	 *   If undefined input, return all the choropleth values as an object.
-	 * @returns {geo.choroplethFeature}
-	 */
-	var choroplethFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof choroplethFeature)) {
-	    return new choroplethFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var ensureFunction = __webpack_require__(83).ensureFunction;
-
-	  /**
-	   * @private
-	   */
-	  var d3 = __webpack_require__(226).d3,
-	      m_this = this,
-	      s_init = this._init,
-	      m_choropleth = $.extend({},
-	        {
-	          colorRange: [
-	                {r: 0.07514311, g: 0.468049805, b: 1},
-	                {r: 0.468487184, g: 0.588057293, b: 1},
-	                {r: 0.656658579, g: 0.707001303, b: 1},
-	                {r: 0.821573924, g: 0.837809045, b: 1},
-	                {r: 0.943467973, g: 0.943498599, b: 0.943398095},
-	                {r: 1, g: 0.788626485, b: 0.750707739},
-	                {r: 1, g: 0.6289553, b: 0.568237474},
-	                {r: 1, g: 0.472800903, b: 0.404551679},
-	                {r: 0.916482116, g: 0.236630659, b: 0.209939162}
-	          ],
-	          scale: d3.scale.quantize(),
-	          accessors: {
-	            //accessor for ID on geodata feature
-	            geoId: function (geoFeature) {
-	              return geoFeature.properties.GEO_ID;
-	            },
-	            //accessor for ID on scalar element
-	            scalarId: function (scalarElement) {
-	              return scalarElement.id;
-	            },
-	            //accessor for value on scalar element
-	            scalarValue: function (scalarElement) {
-	              return scalarElement.value;
-	            }
-	          }
-	        },
-	        arg.choropleth);
-
-	  /**
-	   * Get/Set choropleth scalar data
-	   *
-	   * @memberof geo.choroplethFeature
-	   * @param {Array} [data] Scalar data is an array of objects in which each
-	   *   object provides an id and a value for that id. The id uniquely identifies
-	   *   the geometry this scalar is associated with.
-	   * @param {Function} [aggregator] The aggregator aggregates the scalar in case
-	   *   there are multiple values are found with the same id. The default
-	   *   is d3.mean.
-	   * @returns {geo.feature.choropleth}
-	   */
-	  this.scalar = function (data, aggregator) {
-	    var scalarId, scalarValue;
-
-	    if (data === undefined) {
-	      return m_this.choropleth.get('scalar')() || [];
-	    } else {
-	      scalarId = m_this.choropleth.get('accessors')().scalarId;
-	      scalarValue = m_this.choropleth.get('accessors')().scalarValue;
-	      m_choropleth.scalar = data;
-	      m_choropleth.scalarAggregator = aggregator || d3.mean;
-	      // we make internal dictionary from array for faster lookup
-	      // when matching geojson features to scalar values,
-	      // note that we also allow for multiple scalar elements
-	      // for the same geo feature
-	      m_choropleth.scalar._dictionary = data
-	        .reduce(function (accumeDictionary, scalarElement) {
-	          var id, value;
-
-	          id = scalarId(scalarElement);
-	          value = scalarValue(scalarElement);
-
-	          accumeDictionary[id] =
-	            accumeDictionary[id] ?
-	            accumeDictionary[id].push(value) : [value];
-
-	          return accumeDictionary;
-	        }, {});
-	      m_this.dataTime().modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set choropleth accessor
-	   *
-	   * @memberof geo.choroplethFeature
-	   * @param {Object|String} [arg1] The first argument is the key for a
-	   *   attributes of the choropleth. If the argument is a string and the second
-	   *   argument is undefined, the value of the key is returned. If the arg1 is
-	   *   an object and the second argument is undefined, choropleth attributes
-	   *   are extended by that object. If arg1 is an object and arg2 is defined,
-	   *   a new key-value pair is then added to the choropleth as an attribute.
-	   * @param {Object|String} [arg2] arg2 defines the value of the key (arg1).
-	   * @returns {geo.feature.choropleth}
-	   */
-	  this.choropleth = function (arg1, arg2) {
-	    var choropleth;
-
-	    if (arg1 === undefined) {
-	      return m_choropleth;
-	    }
-	    if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return m_choropleth[arg1];
-	    }
-	    if (arg2 === undefined) {
-	      choropleth = $.extend(
-	        {},
-	        m_choropleth,
-	        arg1
-	      );
-	      m_choropleth = choropleth;
-	    } else {
-	      m_choropleth[arg1] = arg2; //if you pass in accessor for prop
-	    }
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set choropleth getter
-	   *
-	   * @memberof geo.choroplethFeature
-	   * A uniform getter that always returns a function even for constant values.
-	   * If undefined input, return all the choropleth values as an object.
-	   *
-	   * @param {string|undefined} key defines one of the attributes of a
-	   *  choropleth.
-	   * @return {function}
-	   */
-	  this.choropleth.get = function (key) {
-	    var all = {}, k;
-	    if (key === undefined) {
-	      for (k in m_choropleth) {
-	        if (m_choropleth.hasOwnProperty(k)) {
-	          all[k] = m_this.choropleth.get(k);
-	        }
-	      }
-	      return all;
-	    }
-	    return ensureFunction(m_choropleth[key]);
-	  };
-
-	  /**
-	   * A method that adds a polygon feature to the current layer.
-	   *
-	   * @param {array} coordinateArray
-	   * @param {geo.color} fillColor
-	   * @return {geo.feature}
-	   */
-	  this._addPolygonFeature = function (feature, fillColor) {
-	    var newFeature = m_this.layer()
-	        .createFeature('polygon', {});
-
-	    if (feature.geometry.type === 'Polygon') {
-	      newFeature.data([{
-	        type: 'Polygon',
-	        coordinates: feature.geometry.coordinates
-	      }]);
-	    } else if (feature.geometry.type === 'MultiPolygon') {
-	      newFeature.data(feature.geometry.coordinates.map(function (coordinateMap) {
-	        return {
-	          type: 'Polygon',
-	          coordinates: coordinateMap
-	        };
-	      }));
-	    }
-
-	    newFeature
-	      .polygon(function (d) {
-	        return {
-	          'outer': d.coordinates[0],
-	          'inner': d.coordinates[1] // undefined but ok
-	        };
-	      })
-	      .position(function (d) {
-	        return {
-	          x: d[0],
-	          y: d[1]
-	        };
-	      })
-	      .style({
-	        'fillColor': fillColor
-	      });
-
-	    return newFeature;
-	  };
-
-	  /**
-	   * A method that adds polygons from a given feature to the current layer.
-	   *
-	   * @param {} geoJsonFeature
-	   * @param geo.color
-	   * @return [{geo.feature}]
-	   */
-	  this._featureToPolygons = function (feature, fillValue) {
-	    return m_this
-	      ._addPolygonFeature(feature, fillValue);
-	  };
-
-	  /**
-	   * A method that sets a choropleth scale's domain and range.
-	   *
-	   * @param {undefined | function({})} valueAccessor
-	   * @return {geo.feature.choropleth}
-	   */
-	  this._generateScale = function (valueAccessor) {
-	    var extent =
-	        d3.extent(m_this.scalar(), valueAccessor || undefined);
-
-	    m_this.choropleth()
-	      .scale
-	      .domain(extent)
-	      .range(m_this.choropleth().colorRange);
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Generate scale for choropleth.data(), make polygons from features.
-	   * @returns: [ [geo.feature.polygon, ...] , ... ]
-	   */
-	  this.createChoropleth = function () {
-	    var choropleth = m_this.choropleth,
-	        data = m_this.data(),
-	        scalars = m_this.scalar(),
-	        valueFunc = choropleth.get('accessors')().scalarValue,
-	        getFeatureId = choropleth.get('accessors')().geoId;
-
-	    m_this._generateScale(valueFunc);
-
-	    return data
-	      .map(function (feature) {
-	        var id = getFeatureId(feature);
-	        var valueArray = scalars._dictionary[id];
-	        var accumulatedScalarValue = choropleth().scalarAggregator(valueArray);
-	        // take average of this array of values
-	        // which allows for non-bijective correspondence
-	        // between geo data and scalar data
-	        var fillColor =
-	            m_this
-	            .choropleth()
-	            .scale(accumulatedScalarValue);
-
-	        return m_this
-	          ._featureToPolygons(feature, fillColor);
-	      });
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    if (m_choropleth) {
-	      m_this.dataTime().modified();
-	    }
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(choroplethFeature, feature);
-	module.exports = choroplethFeature;
-
-
-/***/ }),
-/* 226 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerRenderer = __webpack_require__(201).registerRenderer;
-	var renderer = __webpack_require__(202);
-
-	/**
-	 * Create a new instance of class d3Renderer.
-	 *
-	 * @class geo.d3.renderer
-	 * @extends geo.renderer
-	 * @param {object} arg Options for the renderer.
-	 * @param {geo.layer} [arg.layer] Layer associated with the renderer.
-	 * @param {HTMLElement} [arg.canvas] Canvas element associated with the
-	 *   renderer.
-	 * @param {boolean} [arg.widget=false] Set to `true` if this is a stand-alone
-	 *   widget.  If it is not a widget, svg elements are wrapped in a parent
-	 *   group.
-	 * @param {HTMLElement} [arg.d3Parent] If specified, the parent for any
-	 *   rendered objects; otherwise the renderer's layer's main node is used.
-	 * @returns {geo.d3.d3Renderer}
-	 */
-	var d3Renderer = function (arg) {
-	  'use strict';
-
-	  var d3 = d3Renderer.d3;
-	  var object = __webpack_require__(227);
-	  var util = __webpack_require__(83);
-	  var geo_event = __webpack_require__(9);
-	  var d3Rescale = __webpack_require__(229);
-
-	  if (!(this instanceof d3Renderer)) {
-	    return new d3Renderer(arg);
-	  }
-	  renderer.call(this, arg);
-
-	  var s_exit = this._exit;
-
-	  object.call(this, arg);
-
-	  arg = arg || {};
-
-	  var m_this = this,
-	      m_sticky = null,
-	      m_features = {},
-	      m_corners = null,
-	      m_width = null,
-	      m_height = null,
-	      m_diagonal = null,
-	      m_scale = 1,
-	      m_transform = {dx: 0, dy: 0, rx: 0, ry: 0, rotation: 0},
-	      m_renderIds = {},
-	      m_removeIds = {},
-	      m_svg = null,
-	      m_defs = null;
-
-	  /**
-	   * Set attributes to a d3 selection.
-	   * @private
-	   * @param {d3Selector} select The d3 selector with the elements to change.
-	   * @param {object} attrs A map of attributes to set on the elements.
-	   */
-	  function setAttrs(select, attrs) {
-	    var key;
-	    for (key in attrs) {
-	      if (attrs.hasOwnProperty(key)) {
-	        select.attr(key, attrs[key]);
-	      }
-	    }
-	  }
-
-	  /**
-	   * Meta functions for converting from geojs styles to d3.
-	   * @private
-	   * @param {function|object} f The style value or function to convert.
-	   * @param {function} [g] An optional function that returns a boolean; if it
-	   *    returns false, the style is set to `'none'`.
-	   * @returns {function} A function for converting styles.
-	   */
-	  this._convertColor = function (f, g) {
-	    f = util.ensureFunction(f);
-	    g = g || function () { return true; };
-	    return function () {
-	      var c = 'none';
-	      if (g.apply(m_this, arguments)) {
-	        c = f.apply(m_this, arguments);
-	        if (c.hasOwnProperty('r') &&
-	            c.hasOwnProperty('g') &&
-	            c.hasOwnProperty('b')) {
-	          c = d3.rgb(255 * c.r, 255 * c.g, 255 * c.b);
-	        }
-	      }
-	      return c;
-	    };
-	  };
-
-	  /**
-	   * Return a function for converting a size in pixels to an appropriate
-	   * d3 scale.
-	   * @private
-	   * @param {function|object} f The style value or function to convert.
-	   * @returns {function} A function for converting scale.
-	   */
-	  this._convertScale = function (f) {
-	    f = util.ensureFunction(f);
-	    return function () {
-	      return f.apply(m_this, arguments) / m_scale;
-	    };
-	  };
-
-	  /**
-	   * Set styles to a d3 selection. Ignores unknown style keys.
-	   * @private
-	   * @param {d3Selector} select The d3 selector with the elements to change.
-	   * @param {object} styles Style object associated with a feature.
-	   */
-	  function setStyles(select, styles) {
-	    var key, k, f;
-	    /**
-	     * Check if the fill parameter is truthy.
-	     *
-	     * @returns {null|'none'} `null` to fill the element, `'none'` to skip
-	     *  filling it.
-	     */
-	    function fillFunc() {
-	      if (styles.fill.apply(m_this, arguments)) {
-	        return null;
-	      } else {
-	        return 'none';
-	      }
-	    }
-	    /**
-	     * Check if the stroke parameter is truthy.
-	     *
-	     * @returns {null|'none'} `null` to fill the element, `'none'` to skip
-	     *  filling it.
-	     */
-	    function strokeFunc() {
-	      if (styles.stroke.apply(m_this, arguments)) {
-	        return null;
-	      } else {
-	        return 'none';
-	      }
-	    }
-	    for (key in styles) {
-	      if (styles.hasOwnProperty(key)) {
-	        f = null;
-	        k = null;
-	        if (key === 'strokeColor') {
-	          k = 'stroke';
-	          f = m_this._convertColor(styles[key], styles.stroke);
-	        } else if (key === 'stroke' && styles[key] &&
-	                   !styles.hasOwnProperty('strokeColor')) {
-	          k = 'stroke';
-	          f = strokeFunc;
-	        } else if (key === 'strokeWidth') {
-	          k = 'stroke-width';
-	          f = m_this._convertScale(styles[key]);
-	        } else if (key === 'strokeOpacity') {
-	          k = 'stroke-opacity';
-	          f = styles[key];
-	        } else if (key === 'fillColor') {
-	          k = 'fill';
-	          f = m_this._convertColor(styles[key], styles.fill);
-	        } else if (key === 'fill' && !styles.hasOwnProperty('fillColor')) {
-	          k = 'fill';
-	          f = fillFunc;
-	        } else if (key === 'fillOpacity') {
-	          k = 'fill-opacity';
-	          f = styles[key];
-	        } else if (key === 'lineCap') {
-	          k = 'stroke-linecap';
-	          f = styles[key];
-	        } else if (key === 'lineJoin') {
-	          k = 'stroke-linejoin';
-	          f = styles[key];
-	        } else if (key === 'miterLimit') {
-	          k = 'stroke-miterlimit';
-	          f = styles[key];
-	        }
-	        if (k) {
-	          select.style(k, f);
-	        }
-	      }
-	    }
-	  }
-
-	  /**
-	   * Get the svg group element associated with this renderer instance, or of a
-	   * group within the render instance.
-	   *
-	   * @private
-	   * @param {string} [parentId] Optional parent ID name.
-	   * @returns {d3Selector} Selector with the d3 group.
-	   */
-	  function getGroup(parentId) {
-	    if (parentId) {
-	      return m_svg.select('.group-' + parentId);
-	    }
-	    return m_svg.select('.group-' + m_this._d3id());
-	  }
-
-	  /**
-	   * Set the initial lat-lon coordinates of the map view.
-	   * @private
-	   */
-	  function initCorners() {
-	    var layer = m_this.layer(),
-	        map = layer.map(),
-	        width = map.size().width,
-	        height = map.size().height;
-
-	    m_width = width;
-	    m_height = height;
-	    if (!m_width || !m_height) {
-	      throw new Error('Map layer has size 0');
-	    }
-	    m_diagonal = Math.pow(width * width + height * height, 0.5);
-	    m_corners = {
-	      upperLeft: map.displayToGcs({x: 0, y: 0}, null),
-	      lowerRight: map.displayToGcs({x: width, y: height}, null),
-	      center: map.displayToGcs({x: width / 2, y: height / 2}, null)
-	    };
-	  }
-
-	  /**
-	   * Set the translation, scale, and zoom for the current view.
-	   * @private
-	   */
-	  this._setTransform = function () {
-	    if (!m_corners) {
-	      initCorners();
-	    }
-
-	    if (!m_sticky) {
-	      return;
-	    }
-
-	    var layer = m_this.layer();
-
-	    var map = layer.map(),
-	        upperLeft = map.gcsToDisplay(m_corners.upperLeft, null),
-	        lowerRight = map.gcsToDisplay(m_corners.lowerRight, null),
-	        center = map.gcsToDisplay(m_corners.center, null),
-	        group = getGroup(),
-	        dx, dy, scale, rotation, rx, ry;
-
-	    scale = Math.sqrt(
-	      Math.pow(lowerRight.y - upperLeft.y, 2) +
-	      Math.pow(lowerRight.x - upperLeft.x, 2)) / m_diagonal;
-	    // calculate the translation
-	    rotation = map.rotation();
-	    rx = -m_width / 2;
-	    ry = -m_height / 2;
-	    dx = scale * rx + center.x;
-	    dy = scale * ry + center.y;
-
-	    // set the group transform property
-	    if (!rotation) {
-	      dx = Math.round(dx);
-	      dy = Math.round(dy);
-	    }
-	    var transform = 'matrix(' + [scale, 0, 0, scale, dx, dy].join() + ')';
-	    if (rotation) {
-	      transform += ' rotate(' + [
-	        rotation * 180 / Math.PI, -rx, -ry].join() + ')';
-	    }
-	    group.attr('transform', transform);
-
-	    // set internal variables
-	    m_scale = scale;
-	    m_transform.dx = dx;
-	    m_transform.dy = dy;
-	    m_transform.rx = rx;
-	    m_transform.ry = ry;
-	    m_transform.rotation = rotation;
-	  };
-
-	  /**
-	   * Convert from screen pixel coordinates to the local coordinate system
-	   * in the SVG group element taking into account the transform.
-	   * @private
-	   * @param {geo.screenPosition} pt The coordinates to convert.
-	   * @returns {geo.geoPosition} The converted coordinates.
-	   */
-	  this.baseToLocal = function (pt) {
-	    pt = {
-	      x: (pt.x - m_transform.dx) / m_scale,
-	      y: (pt.y - m_transform.dy) / m_scale
-	    };
-	    if (m_transform.rotation) {
-	      var sinr = Math.sin(-m_transform.rotation),
-	          cosr = Math.cos(-m_transform.rotation);
-	      var x = pt.x + m_transform.rx, y = pt.y + m_transform.ry;
-	      pt = {
-	        x: x * cosr - y * sinr - m_transform.rx,
-	        y: x * sinr + y * cosr - m_transform.ry
-	      };
-	    }
-	    return pt;
-	  };
-
-	  /**
-	   * Convert from the local coordinate system in the SVG group element
-	   * to screen pixel coordinates.
-	   * @private
-	   * @param {geo.geoPosition} pt The coordinates to convert.
-	   * @returns {geo.screenPosition} The converted coordinates.
-	   */
-	  this.localToBase = function (pt) {
-	    if (m_transform.rotation) {
-	      var sinr = Math.sin(m_transform.rotation),
-	          cosr = Math.cos(m_transform.rotation);
-	      var x = pt.x + m_transform.rx, y = pt.y + m_transform.ry;
-	      pt = {
-	        x: x * cosr - y * sinr - m_transform.rx,
-	        y: x * sinr + y * cosr - m_transform.ry
-	      };
-	    }
-	    pt = {
-	      x: pt.x * m_scale + m_transform.dx,
-	      y: pt.y * m_scale + m_transform.dy
-	    };
-	    return pt;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {object} arg The options used to create the renderer.
-	   * @param {boolean} [arg.widget=false] Set to `true` if this is a stand-alone
-	   *   widget.  If it is not a widget, svg elements are wrapped in a parent
-	   *   group.
-	   * @param {HTMLElement} [arg.d3Parent] If specified, the parent for any
-	   *   rendered objects; otherwise the renderer's layer's main node is used.
-	   * @returns {this}
-	   */
-	  this._init = function (arg) {
-	    if (!m_this.canvas()) {
-	      var canvas;
-	      arg.widget = arg.widget || false;
-
-	      if ('d3Parent' in arg) {
-	        m_svg = d3.select(arg.d3Parent).append('svg');
-	      } else {
-	        m_svg = d3.select(m_this.layer().node().get(0)).append('svg');
-	      }
-	      m_svg.attr('display', 'block');
-
-	      // create a global svg definitions element
-	      m_defs = m_svg.append('defs');
-
-	      var shadow = m_defs
-	        .append('filter')
-	          .attr('id', 'geo-highlight')
-	          .attr('x', '-100%')
-	          .attr('y', '-100%')
-	          .attr('width', '300%')
-	          .attr('height', '300%');
-	      shadow
-	        .append('feMorphology')
-	          .attr('operator', 'dilate')
-	          .attr('radius', 2)
-	          .attr('in', 'SourceAlpha')
-	          .attr('result', 'dilateOut');
-	      shadow
-	        .append('feGaussianBlur')
-	          .attr('stdDeviation', 5)
-	          .attr('in', 'dilateOut')
-	          .attr('result', 'blurOut');
-	      shadow
-	        .append('feColorMatrix')
-	          .attr('type', 'matrix')
-	          .attr('values', '-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0')
-	          .attr('in', 'blurOut')
-	          .attr('result', 'invertOut');
-	      shadow
-	        .append('feBlend')
-	          .attr('in', 'SourceGraphic')
-	          .attr('in2', 'invertOut')
-	          .attr('mode', 'normal');
-
-	      if (!arg.widget) {
-	        canvas = m_svg.append('g');
-	      }
-
-	      shadow = m_defs.append('filter')
-	          .attr('id', 'geo-blur')
-	          .attr('x', '-100%')
-	          .attr('y', '-100%')
-	          .attr('width', '300%')
-	          .attr('height', '300%');
-
-	      shadow
-	        .append('feGaussianBlur')
-	          .attr('stdDeviation', 20)
-	          .attr('in', 'SourceGraphic');
-
-	      m_sticky = m_this.layer().sticky();
-	      m_svg.attr('class', m_this._d3id());
-	      m_svg.attr('width', m_this.layer().node().width());
-	      m_svg.attr('height', m_this.layer().node().height());
-
-	      if (!arg.widget) {
-	        canvas.attr('class', 'group-' + m_this._d3id());
-
-	        m_this.canvas(canvas);
-	      } else {
-	        m_this.canvas(m_svg);
-	      }
-	    }
-	    m_this._setTransform();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get API used by the renderer.
-	   *
-	   * @returns {string} 'd3'.
-	   */
-	  this.api = function () {
-	    return 'd3';
-	  };
-
-	  /**
-	   * Return the current scaling factor to build features that shouldn't
-	   * change size during zooms.  For example:
-	   *
-	   *  selection.append('circle')
-	   *    .attr('r', r0 / renderer.scaleFactor());
-	   *
-	   * This will create a circle element with radius r0 independent of the
-	   * current zoom level.
-	   *
-	   * @returns {number} The current scale factor.
-	   */
-	  this.scaleFactor = function () {
-	    return m_scale;
-	  };
-
-	  /**
-	   * Handle resize event.
-	   *
-	   * @param {number} x Ignored.
-	   * @param {number} y Ignored.
-	   * @param {number} w New width in pixels.
-	   * @param {number} h New height in pixels.
-	   * @returns {this}
-	   */
-	  this._resize = function (x, y, w, h) {
-	    if (!m_corners) {
-	      initCorners();
-	    }
-	    m_svg.attr('width', w);
-	    m_svg.attr('height', h);
-	    m_this._setTransform();
-	    m_this.layer().geoTrigger(d3Rescale, { scale: m_scale }, true);
-	    return m_this;
-	  };
-
-	  /**
-	   * Exit.
-	   */
-	  this._exit = function () {
-	    m_features = {};
-	    m_this.canvas().remove();
-	    m_svg.remove();
-	    m_svg = undefined;
-	    m_defs.remove();
-	    m_defs = undefined;
-	    m_renderIds = {};
-	    m_removeIds = {};
-	    s_exit();
-	  };
-
-	  /**
-	   * Get the definitions DOM element for the layer.
-	   * @protected
-	   * @returns {HTMLElement} The definitions DOM element.
-	   */
-	  this._definitions = function () {
-	    return m_defs;
-	  };
-
-	  /**
-	   * Create a new feature element from an object that describes the feature
-	   * attributes.  To be called from feature classes only.
-	   *
-	   * @param {object} arg Options for the features.
-	   * @param {string} arg.id A unique string identifying the feature.
-	   * @param {array} arg.data Array of data objects used in a d3 data method.
-	   * @param {function} [aeg.dataIndex] A function that returns a unique id for
-	   *    each data element.  This is passed to the data access function.
-	   * @param {object} arg.style An object with style values or functions.
-	   * @param {object} arg.attributes An object containing element attributes.
-	   *    The keys are the attribute names, and the values are either constants
-	   *    or functions that get passed a data element and a data index.
-	   * @param {string[]} arg.classes An array of classes to add to the elements.
-	   * @param {string} arg.append The element type as used in d3 append methods.
-	   *    This is something like `'path'`, `'circle'`, or `'line'`.
-	   * @param {boolean} [arg.onlyRenderNew] If truthy, features only get
-	   *    attributes and styles set when new.  If falsy, features always have
-	   *    attributes and styles updated.
-	   * @param {boolean} [arg.sortByZ] If truthy, sort features by the `d.zIndex`.
-	   * @param {string} [parentId] If set, the group ID of the parent element.
-	   * @returns {this}
-	   */
-	  this._drawFeatures = function (arg) {
-	    m_features[arg.id] = {
-	      data: arg.data,
-	      index: arg.dataIndex,
-	      style: arg.style,
-	      visible: arg.visible,
-	      attributes: arg.attributes,
-	      classes: arg.classes,
-	      append: arg.append,
-	      onlyRenderNew: arg.onlyRenderNew,
-	      sortByZ: arg.sortByZ,
-	      parentId: arg.parentId
-	    };
-	    return m_this.__render(arg.id, arg.parentId);
-	  };
-
-	  /**
-	   * Updates a feature by performing a d3 data join.  If no input id is
-	   * provided then this method will update all features.
-	   *
-	   * @param {string} [id] The id of the feature to update.  `undefined` to
-	   *    update all features.
-	   * @param {string} [parentId] The parent of the feature(s).  If not
-	   *    specified, features are rendered on the next animation frame.
-	   * @returns {this}
-	   */
-	  this.__render = function (id, parentId) {
-	    var key;
-	    if (id === undefined) {
-	      for (key in m_features) {
-	        if (m_features.hasOwnProperty(key)) {
-	          m_this.__render(key);
-	        }
-	      }
-	      return m_this;
-	    }
-	    if (parentId) {
-	      m_this._renderFeature(id, parentId);
-	    } else {
-	      m_renderIds[id] = true;
-	      m_this.layer().map().scheduleAnimationFrame(m_this._renderFrame);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Render all features that are marked as needing an update.  This should
-	   * only be called duration an animation frame.
-	   */
-	  this._renderFrame = function () {
-	    var id;
-	    for (id in m_removeIds) {
-	      m_this.select(id).remove();
-	      m_defs.selectAll('.' + id).remove();
-	    }
-	    m_removeIds = {};
-	    var ids = m_renderIds;
-	    m_renderIds = {};
-	    for (id in ids) {
-	      if (ids.hasOwnProperty(id)) {
-	        m_this._renderFeature(id);
-	      }
-	    }
-	  };
-
-	  /**
-	   * Render a single feature.
-	   *
-	   * @param {string} id The id of the feature to update.
-	   * @param {string} [parentId] The parent of the feature.  This is used to
-	   *    select the feature.
-	   * @returns {this}
-	   */
-	  this._renderFeature = function (id, parentId) {
-	    if (!m_features[id]) {
-	      return m_this;
-	    }
-	    var data = m_features[id].data,
-	        index = m_features[id].index,
-	        style = m_features[id].style,
-	        visible = m_features[id].visible,
-	        attributes = m_features[id].attributes,
-	        classes = m_features[id].classes,
-	        append = m_features[id].append,
-	        selection = m_this.select(id, parentId).data(data, index),
-	        entries, rendersel;
-	    entries = selection.enter().append(append);
-	    selection.exit().remove();
-	    rendersel = m_features[id].onlyRenderNew ? entries : selection;
-	    setAttrs(rendersel, attributes);
-	    rendersel.attr('class', classes.concat([id]).join(' '));
-	    setStyles(rendersel, style);
-	    if (visible) {
-	      rendersel.style('visibility', visible() ? 'visible' : 'hidden');
-	    }
-	    if (entries.size() && m_features[id].sortByZ) {
-	      selection.sort(function (a, b) {
-	        return (a.zIndex || 0) - (b.zIndex || 0);
-	      });
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Returns a d3 selection for the given feature id.
-	   *
-	   * @param {string} id The id of the feature to select.
-	   * @param {string} [parentId] The parent of the feature.  This is used to
-	   *    determine the feature's group.
-	   * @returns {d3Selector}
-	   */
-	  this.select = function (id, parentId) {
-	    return getGroup(parentId).selectAll('.' + id);
-	  };
-
-	  /**
-	   * Removes a feature from the layer.
-	   *
-	   * @param {string} id The id of the feature to remove.
-	   * @returns {this}
-	   */
-	  this._removeFeature = function (id) {
-	    m_removeIds[id] = true;
-	    m_this.layer().map().scheduleAnimationFrame(m_this._renderFrame);
-	    delete m_features[id];
-	    if (m_renderIds[id]) {
-	      delete m_renderIds[id];
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Override draw method to do nothing.
-	   */
-	  this.draw = function () {
-	  };
-
-	  // connect to pan event
-	  this.layer().geoOn(geo_event.pan, m_this._setTransform);
-
-	  // connect to rotate event
-	  this.layer().geoOn(geo_event.rotate, m_this._setTransform);
-
-	  // connect to zoom event
-	  this.layer().geoOn(geo_event.zoom, function () {
-	    m_this._setTransform();
-	    m_this.__render();
-	    m_this.layer().geoTrigger(d3Rescale, { scale: m_scale }, true);
-	  });
-
-	  this.layer().geoOn(geo_event.resize, function (event) {
-	    m_this._resize(event.x, event.y, event.width, event.height);
-	  });
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(d3Renderer, renderer);
-
-	registerRenderer('d3', d3Renderer);
-
-	(function () {
-	  'use strict';
-
-	  /**
-	   * Report if the d3 renderer is supported.  This is just a check if d3 is
-	   * available.
-	   *
-	   * @returns {boolean} true if available.
-	   */
-	  d3Renderer.supported = function () {
-	    delete d3Renderer.d3;
-	    // webpack expects optional dependencies to be wrapped in a try-catch
-	    try {
-	      d3Renderer.d3 = __webpack_require__(230);
-	    } catch (_error) {}
-	    return d3Renderer.d3 !== undefined;
-	  };
-
-	  /**
-	   * If the d3 renderer is not supported, supply the name of a renderer that
-	   * should be used instead.  This asks for the null renderer.
-	   *
-	   * @returns {null} `null` for the null renderer.
-	   */
-	  d3Renderer.fallback = function () {
-	    return null;
-	  };
-
-	  d3Renderer.supported();  // cache reference to d3 if it is available
-	})();
-
-	module.exports = d3Renderer;
-
-
-/***/ }),
-/* 227 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-
-	/**
-	 * D3 specific subclass of object which adds an id property for d3 selections
-	 * on groups of objects by class id.
-	 *
-	 * @class
-	 * @alias geo.d3.object
-	 * @extends geo.sceneObject
-	 * @param {object} arg Options for the object.
-	 * @returns {geo.d3.object}
-	 */
-	var d3_object = function (arg) {
-	  'use strict';
-
-	  var object = __webpack_require__(203);
-	  var uniqueID = __webpack_require__(228);
-
-	  // this is used to extend other geojs classes, so only generate
-	  // a new object when that is not the case... like if this === window
-	  if (!(this instanceof object)) {
-	    return new d3_object(arg);
-	  }
-	  sceneObject.call(this);
-
-	  var m_id = 'd3-' + uniqueID(),
-	      s_exit = this._exit,
-	      m_this = this,
-	      s_draw = this.draw;
-
-	  this._d3id = function () {
-	    return m_id;
-	  };
-
-	  /**
-	   * Returns a d3 selection for the feature elements.
-	   *
-	   * @returns {d3.selector} A d3 selector of the features in this object.
-	   */
-	  this.select = function () {
-	    return m_this.renderer().select(m_this._d3id());
-	  };
-
-	  /**
-	   * Redraw the object.
-	   *
-	   * @returns {this}
-	   */
-	  this.draw = function () {
-	    m_this._update();
-	    s_draw();
-	    return m_this;
-	  };
-
-	  /**
-	   * Removes the element from the svg and the renderer.
-	   */
-	  this._exit = function () {
-	    m_this.renderer()._removeFeature(m_this._d3id());
-	    s_exit();
-	  };
-
-	  return this;
-	};
-
-	inherit(d3_object, sceneObject);
-	module.exports = d3_object;
-
-
-/***/ }),
-/* 228 */
-/***/ (function(module, exports) {
-
-	var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz',
-	    strLength = 8;
-
-	/**
-	 * Get a random string to use as a div ID
-	 * @function geo.d3.uniqueID
-	 * @returns {string}
-	 */
-	var uniqueID = function () {
-	  var strArray = [],
-	      i;
-	  strArray.length = strLength;
-	  for (i = 0; i < strLength; i += 1) {
-	    strArray[i] = chars.charAt(Math.floor(Math.random() * chars.length));
-	  }
-	  return strArray.join('');
-	};
-
-	module.exports = uniqueID;
-
-
-/***/ }),
-/* 229 */
-/***/ (function(module, exports) {
-
-	module.exports = 'geo_d3_rescale';
-
-
-/***/ }),
-/* 230 */
-/***/ (function(module, exports) {
-
-	if(typeof __WEBPACK_EXTERNAL_MODULE_230__ === 'undefined') {var e = new Error("Cannot find module \"d3\""); e.code = 'MODULE_NOT_FOUND'; throw e;}
-	module.exports = __WEBPACK_EXTERNAL_MODULE_230__;
-
-/***/ }),
-/* 231 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Contour feature specification.
-	 *
-	 * @typedef {geo.feature.spec} geo.contourFeature.spec
-	 * @property {object[]} [data=[]] An array of arbitrary objects used to
-	 *  construct the feature.
-	 * @property {object} [style] An object that contains style values for the
-	 *      feature.
-	 * @property {function|number} [style.opacity=1] The opacity on a scale of 0 to
-	 *      1.
-	 * @property {function|geo.geoPosition} [style.position=data] The position of
-	 *      each data element.  This defaults to just using `x`, `y`, and `z`
-	 *      properties of the data element itself.  The position is in the
-	 *      feature's gcs coordinates.
-	 * @property {function|number} [style.value=data.z] The contour value of each
-	 *      data element.  This defaults `z` properties of the data element.  If
-	 *      the value of a grid point is `null` or `undefined`, that point will not
-	 *      be included in the contour display.  Since the values are on a grid, if
-	 *      this point is in the interior of the grid, this can remove up to four
-	 *      squares.
-	 * @property {geo.contourFeature.contourSpec} [contour] The contour
-	 *      specification for the feature.
-	 */
-
-	/**
-	 * Contour specification.
-	 *
-	 * @typedef {object} geo.contourFeature.contourSpec
-	 * @property {function|number} [gridWidth] The number of data columns in the
-	 *      grid.  If this is not specified and `gridHeight` is given, this is the
-	 *      number of data elements divided by `gridHeight`.  If neither
-	 *      `gridWidth` not `gridHeight` are specified,  the square root of the
-	 *      number of data elements is used.  If both are specified, some data
-	 *      could be unused.
-	 * @property {function|number} [gridHeight] The number of data rows in the
-	 *      grid.  If this is not specified and `gridWidth` is given, this is the
-	 *      number of data elements divided by `gridWidth`.  If neither
-	 *      `gridWidth` not `gridHeight` are specified,  the square root of the
-	 *      number of data elements is used.  If both are specified, some data
-	 *      could be unused.
-	 * @property {function|number} [x0] The x coordinate of the 0th point in the
-	 *      `value` array.  If `null` or `undefined`, the coordinate is taken from
-	 *      the `position` style.
-	 * @property {function|number} [y0] The y coordinate of the 0th point in the
-	 *      `value` array.  If `null` or `undefined`, the coordinate is taken from
-	 *      the `position` style.
-	 * @property {function|number} [dx] The distance in the x direction between the
-	 *      0th and 1st point in the `value` array.  This may be positive or
-	 *      negative.  If 0, `null`, or `undefined`, the coordinate is taken from
-	 *      the `position` style.
-	 * @property {function|number} [dy] The distance in the y direction between the
-	 *      0th and `gridWidth`th point in the `value` array.  This may be positive
-	 *      or negative.  If 0, `null`, or `undefined`, the coordinate is taken
-	 *      from the `position` style.
-	 * @property {function|boolean} [wrapLongitude] If truthy and `position` is not
-	 *      used (`x0`, `y0`, `dx`, and `dy` are all set appropriately), assume the
-	 *      x coordinates is longitude and should be adjusted to be within -180 to
-	 *      180.  If the data spans 180 degrees, the points or squares that
-	 *      straddle the meridian will be duplicated to ensure that
-	 *      the map is covered from -180 to 180 as appropriate.  Set this to
-	 *      `false` if using a non-longitude x coordinate.  This is ignored if
-	 *      `position` is used.
-	 * @property {function|number} [min] Minimum contour value.  If unspecified,
-	 *      taken from the computed minimum of the `value` style.
-	 * @property {function|number} [max] Maximum contour value.  If unspecified,
-	 *      taken from the computed maxi,um of the `value` style.
-	 * @property {function|geo.geoColor} [minColor='black'] Color used for any
-	 *      value below the minimum.
-	 * @property {function|number} [minOpacity=0] Opacity used for any value below
-	 *      the minimum.
-	 * @property {function|geo.geoColor} [maxColor='black'] Color used for any
-	 *      value above the maximum.
-	 * @property {function|number} [maxOpacity=0] Opacity used for any value above
-	 *      the maximum.
-	 * @property {function|boolean} [stepped] If falsy but not `undefined`, smooth
-	 *      transitions between colors.
-	 * @property {function|geo.geoColor[]} [colorRange=<color table>] An array of
-	 *      colors used to show the range of values.  The default is a 9-step color
-	 *      table.
-	 * @property {function|number[]} [opacityRange] An array of opacities used to
-	 *      show the range of values.  If unspecified, the opacity is 1.  If this
-	 *      is a shorter list than the `colorRange`, an opacity of 1 is used for
-	 *      the entries near the end of the color range.
-	 * @property {function|number[]} [rangeValues] An array used to map values to
-	 *      the `colorRange`.  By default, values are spaced linearly.  If
-	 *      specified, the entries must be increasing weakly monotonic, and there
-	 *      must be one more entry then the length of `colorRange`.
-	 */
-
-	/**
-	 * Computed contour information.
-	 *
-	 * @typedef {object} geo.contourFeature.contourInfo
-	 * @property {number[]} elements An array of 0-based indices into the values
-	 *    array.  Each set of the three values forms a triangle that should be
-	 *    rendered.  If no contour data can be used, this will be a zero-length
-	 *    array and other properties may not be set.
-	 * @property {number[]} pos An flat array of coordinates for the vertices in
-	 *    the triangular mesh.  The array is in the order x0, y0, z0, x1, y1, z1,
-	 *    x2, ..., and is always three times as long as the number of vertices.
-	 * @property {number[]} value An array of values that have been normalized to a
-	 *    range of [0, steps].  There is one value per vertex.
-	 * @property {number[]} opacity An array of opacities per vertex.
-	 * @property {number} minValue the minimum value used for the contour.  If
-	 *    `rangeValues` was specified, this is the first entry of that array.
-	 * @property {number} maxValue the maximum value used for the contour.  If
-	 *    `rangeValues` was specified, this is the last entry of that array.
-	 * @property {number} factor If linear value scaling is used, this is the
-	 *    number of color values divided by the difference between the maximum and
-	 *    minimum values.  It is ignored if non-linear value scaling is used.
-	 * @property {geo.geoColorObject} minColor The color used for values below
-	 *    minValue.  Includes opacity.
-	 * @property {geo.geoColorObject} maxColor The color used for values above
-	 *    maxValue.  Includes opacity.
-	 * @property {geo.geoColorObject[]} colorMap The specified `colorRange` and
-	 *    `opacityRange` converted into objects that include opacity.
-	 */
-
-	/**
-	 * Create a new instance of class contourFeature.
-	 *
-	 * @class
-	 * @alias geo.contourFeature
-	 * @extends geo.feature
-	 *
-	 * @param {geo.contourFeature.spec} arg
-	 * @returns {geo.contourFeature}
-	 */
-	var contourFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof contourFeature)) {
-	    return new contourFeature(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-	  var util = __webpack_require__(83);
-
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_contour = {},
-	      s_init = this._init;
-
-	  if (arg.contour === undefined) {
-	    m_contour = function (d) {
-	      return d;
-	    };
-	  } else {
-	    m_contour = arg.contour;
-	  }
-
-	  /**
-	   * Get/Set contour accessor.
-	   *
-	   * @param {string|geo.contourFeature.contourSpec} [specOrProperty] If
-	   *    `undefined`, return the current contour specification.  If a string is
-	   *    specified, either get or set the named contour property.  If an object
-	   *    is given, set or update the contour specification with the specified
-	   *    parameters.
-	   * @param {object} [value] If `specOrProperty` is a string, set that property
-	   *    to `value`.  If `undefined`, return the current value of the named
-	   *    property.
-	   * @returns {geo.contourFeature.contourSpec|object|this} The current contour
-	   *    specification, the value of a named contour property, or this contour
-	   *    feature.
-	   */
-	  this.contour = function (specOrProperty, value) {
-	    if (specOrProperty === undefined) {
-	      return m_contour;
-	    }
-	    if (typeof specOrProperty === 'string' && value === undefined) {
-	      return m_contour[specOrProperty];
-	    }
-	    if (value === undefined) {
-	      var contour = $.extend(
-	        {},
-	        {
-	          gridWidth: function () {
-	            if (specOrProperty.gridHeight) {
-	              return Math.floor(m_this.data().length / specOrProperty.gridHeight);
-	            }
-	            return Math.floor(Math.sqrt(m_this.data().length));
-	          },
-	          gridHeight: function () {
-	            if (specOrProperty.gridWidth) {
-	              return Math.floor(m_this.data().length / specOrProperty.gridWidth);
-	            }
-	            return Math.floor(Math.sqrt(m_this.data().length));
-	          },
-	          minColor: 'black',
-	          minOpacity: 0,
-	          maxColor: 'black',
-	          maxOpacity: 0,
-	          /* 9-step based on paraview bwr colortable */
-	          colorRange: [
-	            {r: 0.07514311, g: 0.468049805, b: 1},
-	            {r: 0.468487184, g: 0.588057293, b: 1},
-	            {r: 0.656658579, g: 0.707001303, b: 1},
-	            {r: 0.821573924, g: 0.837809045, b: 1},
-	            {r: 0.943467973, g: 0.943498599, b: 0.943398095},
-	            {r: 1, g: 0.788626485, b: 0.750707739},
-	            {r: 1, g: 0.6289553, b: 0.568237474},
-	            {r: 1, g: 0.472800903, b: 0.404551679},
-	            {r: 0.916482116, g: 0.236630659, b: 0.209939162}
-	          ]
-	        },
-	        m_contour,
-	        specOrProperty
-	      );
-	      m_contour = contour;
-	    } else {
-	      m_contour[specOrProperty] = value;
-	    }
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * A uniform getter that always returns a function even for constant values.
-	   * If undefined input, return all the contour values as an object.
-	   *
-	   * @param {string|undefined} key The name of the contour key or `undefined`
-	   *    to return an object with all keys as functions.
-	   * @returns {function|object} A function related to the key, or an object
-	   *    with all contour keys, each of which is a function.
-	   */
-	  this.contour.get = function (key) {
-	    if (key === undefined) {
-	      var all = {}, k;
-	      for (k in m_contour) {
-	        if (m_contour.hasOwnProperty(k)) {
-	          all[k] = m_this.contour.get(k);
-	        }
-	      }
-	      return all;
-	    }
-	    return util.ensureFunction(m_contour[key]);
-	  };
-
-	  /**
-	   * Get/Set position accessor.  This is identical to getting or setting the
-	   * `position` style.
-	   *
-	   * @param {function|array} [val] If specified, set the position style.  If
-	   *    `undefined`, return the current value.
-	   * @returns {function|array|this} Either the position style or this.
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else {
-	      m_this.style('position', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Create a set of vertices, values at the vertices, and opacities at the
-	   * vertices.  Create a set of triangles of indices into the vertex array.
-	   * Create a color and opacity map corresponding to the values.
-	   *
-	   * @returns {geo.contourFeature.contourInfo} An object with the contour
-	   *    information.
-	   */
-	  this.createContours = function () {
-	    var i, i3, j, idx, k, val, numPts, usedPts = 0, usePos, item,
-	        idxMap = {},
-	        minval, maxval, range,
-	        contour = m_this.contour,
-	        data = m_this.data(),
-	        posFunc = m_this.position(), posVal,
-	        gridW = contour.get('gridWidth')(),
-	        gridH = contour.get('gridHeight')(),
-	        x0 = contour.get('x0')(),
-	        y0 = contour.get('y0')(),
-	        dx = contour.get('dx')(),
-	        dy = contour.get('dy')(),
-	        opacityFunc = m_this.style.get('opacity'),
-	        opacityRange = contour.get('opacityRange')(),
-	        rangeValues = contour.get('rangeValues')(),
-	        valueFunc = m_this.style.get('value'), values = [],
-	        stepped = contour.get('stepped')(),
-	        wrapLong = contour.get('wrapLongitude')(),
-	        calcX, skipColumn, x, origI, /* used for wrapping */
-	        gridWorig = gridW,  /* can be different when wrapping */
-	        result = {
-	          minValue: contour.get('min')(),
-	          maxValue: contour.get('max')(),
-	          stepped: stepped === undefined || stepped ? true : false,
-	          wrapLongitude: wrapLong === undefined || wrapLong ? true : false,
-	          colorMap: [],
-	          elements: []
-	        };
-	    /* Create the min/max colors and the color array */
-	    result.minColor = $.extend({a: contour.get('minOpacity')() || 0},
-	        util.convertColor(contour.get('minColor')()));
-	    result.maxColor = $.extend({a: contour.get('maxOpacity')() || 0},
-	        util.convertColor(contour.get('maxColor')()));
-	    contour.get('colorRange')().forEach(function (clr, idx) {
-	      result.colorMap.push($.extend({
-	        a: opacityRange && opacityRange[idx] !== undefined ? opacityRange[idx] : 1
-	      }, util.convertColor(clr)));
-	    });
-	    /* Determine which values are usable */
-	    if (gridW * gridH > data.length) {
-	      gridH = Math.floor(data.length) / gridW;
-	    }
-	    /* If we are not using the position values (we are using x0, y0, dx, dy),
-	     * and wrapLongitude is turned on, and the position spans 180 degrees,
-	     * duplicate one or two columns of points at opposite ends of the map. */
-	    usePos = (x0 === null || x0 === undefined || y0 === null ||
-	        y0 === undefined || !dx || !dy);
-	    if (!usePos && result.wrapLongitude && (x0 < -180 || x0 > 180 ||
-	        x0 + dx * (gridW - 1) < -180 || x0 + dx * (gridW - 1) > 180) &&
-	        dx > -180 && dx < 180) {
-	      calcX = [];
-	      for (i = 0; i < gridW; i += 1) {
-	        x = x0 + i * dx;
-	        while (x < -180) { x += 360; }
-	        while (x > 180) { x -= 360; }
-	        if (i && Math.abs(x - calcX[calcX.length - 1]) > 180) {
-	          if (x > calcX[calcX.length - 1]) {
-	            calcX.push(x - 360);
-	            calcX.push(calcX[calcX.length - 2] + 360);
-	          } else {
-	            calcX.push(x + 360);
-	            calcX.push(calcX[calcX.length - 2] - 360);
-	          }
-	          skipColumn = i;
-	        }
-	        calcX.push(x);
-	      }
-	      gridW += 2;
-	      if (Math.abs(Math.abs(gridWorig * dx) - 360) < 0.01) {
-	        gridW += 1;
-	        x = x0 + gridWorig * dx;
-	        while (x < -180) { x += 360; }
-	        while (x > 180) { x -= 360; }
-	        calcX.push(x);
-	      }
-	    }
-	    /* Calculate the value for point */
-	    numPts = gridW * gridH;
-	    for (i = 0; i < numPts; i += 1) {
-	      if (skipColumn === undefined) {
-	        val = parseFloat(valueFunc(data[i], i));
-	      } else {
-	        j = Math.floor(i / gridW);
-	        origI = i - j * gridW;
-	        origI += (origI > skipColumn ? -2 : 0);
-	        if (origI >= gridWorig) {
-	          origI -= gridWorig;
-	        }
-	        origI += j * gridWorig;
-	        val = parseFloat(valueFunc(data[origI], origI));
-	      }
-	      values[i] = isNaN(val) ? null : val;
-	      if (values[i] !== null) {
-	        idxMap[i] = usedPts;
-	        usedPts += 1;
-	        if (minval === undefined) {
-	          minval = maxval = values[i];
-	        }
-	        if (values[i] < minval) {
-	          minval = values[i];
-	        }
-	        if (values[i] > maxval) {
-	          maxval = values[i];
-	        }
-	      }
-	    }
-	    if (!usedPts) {
-	      return result;
-	    }
-	    if (!$.isNumeric(result.minValue)) {
-	      result.minValue = minval;
-	    }
-	    if (!$.isNumeric(result.maxValue)) {
-	      result.maxValue = maxval;
-	    }
-	    if (!rangeValues || rangeValues.length !== result.colorMap.length + 1) {
-	      rangeValues = null;
-	    }
-	    if (rangeValues) {  /* ensure increasing monotonicity */
-	      for (k = 1; k < rangeValues.length; k += 1) {
-	        if (rangeValues[k] > rangeValues[k + 1]) {
-	          rangeValues = null;
-	          break;
-	        }
-	      }
-	    }
-	    if (rangeValues) {
-	      result.minValue = rangeValues[0];
-	      result.maxValue = rangeValues[rangeValues.length - 1];
-	    }
-	    range = result.maxValue - result.minValue;
-	    if (!range) {
-	      result.colorMap = result.colorMap.slice(0, 1);
-	      range = 1;
-	      rangeValues = null;
-	    }
-	    result.rangeValues = rangeValues;
-	    result.factor = result.colorMap.length / range;
-	    /* Create triangles */
-	    for (j = idx = 0; j < gridH - 1; j += 1, idx += 1) {
-	      for (i = 0; i < gridW - 1; i += 1, idx += 1) {
-	        if (values[idx] !== null && values[idx + 1] !== null &&
-	            values[idx + gridW] !== null &&
-	            values[idx + gridW + 1] !== null && i !== skipColumn) {
-	          result.elements.push(idxMap[idx]);
-	          result.elements.push(idxMap[idx + 1]);
-	          result.elements.push(idxMap[idx + gridW]);
-	          result.elements.push(idxMap[idx + gridW + 1]);
-	          result.elements.push(idxMap[idx + gridW]);
-	          result.elements.push(idxMap[idx + 1]);
-	        }
-	      }
-	    }
-	    /* Only locate the points that are in use. */
-	    result.pos = new Array(usedPts * 3);
-	    result.value = new Array(usedPts);
-	    result.opacity = new Array(usedPts);
-	    for (j = i = i3 = 0; j < numPts; j += 1) {
-	      val = values[j];
-	      if (val !== null) {
-	        item = data[j];
-	        if (usePos) {
-	          posVal = posFunc(item);
-	          result.pos[i3] = posVal.x;
-	          result.pos[i3 + 1] = posVal.y;
-	          result.pos[i3 + 2] = posVal.z || 0;
-	        } else {
-	          if (skipColumn === undefined) {
-	            result.pos[i3] = x0 + dx * (j % gridW);
-	          } else {
-	            result.pos[i3] = calcX[j % gridW];
-	          }
-	          result.pos[i3 + 1] = y0 + dy * Math.floor(j / gridW);
-	          result.pos[i3 + 2] = 0;
-	        }
-	        result.opacity[i] = opacityFunc(item, j);
-	        if (rangeValues && val >= result.minValue && val <= result.maxValue) {
-	          for (k = 1; k < rangeValues.length; k += 1) {
-	            if (val <= rangeValues[k]) {
-	              result.value[i] = k - 1 + (val - rangeValues[k - 1]) /
-	                  (rangeValues[k] - rangeValues[k - 1]);
-	              break;
-	            }
-	          }
-	        } else {
-	          result.value[i] = (val - result.minValue) * result.factor;
-	        }
-	        i += 1;
-	        i3 += 3;
-	      }
-	    }
-	    return result;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.contourFeature.spec} arg The contour feature specification.
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        opacity: 1.0,
-	        position: function (d) {
-	          /* We could construct a new object and return
-	           *  {x: d.x, y: d.y, z: d.z};
-	           * but that isn't necessary. */
-	          return d;
-	        },
-	        value: function (d) {
-	          return m_this.position()(d).z;
-	        }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    m_this.style(defaultStyle);
-
-	    if (m_contour) {
-	      m_this.dataTime().modified();
-	    }
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(contourFeature, feature);
-	module.exports = contourFeature;
-
-	/* Example:
-
-	layer.createFeature('contour', {
-	})
-	.data(<array with w x h elements>)
-	.position(function (d) {
-	  return { x: <longitude>, y: <latitude>, z: <altitude>};
-	})
-	.style({
-	  opacity: function (d) {
-	    return <opacity of grid point>;
-	  },
-	  value: function (d) {            // defaults to position().z
-	    return <contour value>;
-	  }
-	})
-	.contour({
-	  gridWidth: <width of grid>,
-	  gridHeight: <height of grid>,
-	  x0: <the x coordinate of the 0th point in the value array>,
-	  y0: <the y coordinate of the 0th point in the value array>,
-	  dx: <the distance in the x direction between the 0th and 1st point in the
-	    value array>,
-	  dy: <the distance in the y direction between the 0th and (gridWidth)th point
-	    in the value array>,
-	  wrapLongitude: <boolean (default true).  If true, AND the position array is
-	    not used, assume the x coordinates is longitude and should be adjusted to
-	    be within -180 to 180.  If the data spans 180 degrees, the points or
-	    squares will be duplicated to ensure that the map is covered from -180 to
-	    180 as appropriate.  Set this to false if using a non longitude x
-	    coordinate.  This is ignored if the position array is used.>,
-	  min: <optional minimum contour value, otherwise taken from style.value>,
-	  max: <optional maximum contour value, otherwise taken from style.value>,
-	  minColor: <color for any value below the minimum>,
-	  minOpacity: <opacity for any value below the minimum>,
-	  maxColor: <color for any value above the maximum>,
-	  maxOpacity: <opacity for any value above the maximum>,
-	  stepped: <boolean (default true).  If false, smooth transitions between
-	    colors>,
-	  colorRange: [<array of colors used for the contour>],
-	  opacityRange: [<optional array of opacities used for the contour, expected to
-	    be the same length as colorRange>],
-	  rangeValues: [<if specified, instead of spacing the colors linearly, use this
-	    spacing.  Must be increasing monotonic and one value longer than the length
-	    of colorRange>]
-	})
-	 */
-
-
-/***/ }),
-/* 232 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var renderer = __webpack_require__(202);
-	var registerRenderer = __webpack_require__(201).registerRenderer;
-
-	/**
-	 * Create a new instance of class domRenderer.
-	 *
-	 * @class geo.domRenderer
-	 * @extends geo.renderer
-	 * @param {object} arg Options for the renderer.
-	 * @param {geo.layer} [arg.layer] Layer associated with the renderer.
-	 * @param {HTMLElement} [arg.canvas] Canvas element associated with the
-	 *   renderer.
-	 * @returns {geo.domRenderer}
-	 */
-	var domRenderer = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof domRenderer)) {
-	    return new domRenderer(arg);
-	  }
-	  renderer.call(this, arg);
-
-	  arg = arg || {};
-
-	  var m_this = this;
-
-	  /**
-	   * Get API used by the renderer.
-	   *
-	   * @returns {string} 'dom'.
-	   */
-	  this.api = function () {
-	    return 'dom';
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    var layer = m_this.layer().node();
-
-	    if (!m_this.canvas() && layer && layer.length) {
-	      // The renderer and the UI Layer share the same canvas
-	      // at least for now. This renderer is essentially a noop renderer
-	      // designed for backwards compatibility
-	      m_this.canvas(layer[0]);
-	    }
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(domRenderer, renderer);
-	registerRenderer('dom', domRenderer);
-	module.exports = domRenderer;
-
-
-/***/ }),
-/* 233 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = (function () {
-	  'use strict';
-
-	  var $ = __webpack_require__(1);
-
-	  /**
-	   * This class implements a queue for Deferred objects.  Whenever one of the
-	   * objects in the queue completes (resolved or rejected), another item in the
-	   * queue is processed.  The number of concurrently processing items can be
-	   * adjusted.  At this time (2015-12-29) most major browsers support 6
-	   * concurrent requests from any given server, so, when using the queue for
-	   * tile images, thie number of concurrent requests should be 6 * (number of
-	   * subdomains serving tiles).
-	   *
-	   * @class geo.fetchQueue
-	   *
-	   * @param {Object?} [options] A configuration object for the queue
-	   * @param {Number} [options.size=6] The maximum number of concurrent deferred
-	   *    objects.
-	   * @param {Number} [options.track=600] The number of objects that are tracked
-	   *    that trigger checking if any of them have been abandoned.  The fetch
-	   *    queue can grow to the greater of this size and the number of items that
-	   *    are still needed.  Setting this to a low number will increase
-	   *    processing time, to a high number can increase memory.  Ideally, it
-	   *    should reflect the number of items that are kept in memory elsewhere.
-	   *    If needed is null, this is ignored.
-	   * @param {function} [options.needed=null] If set, this function is passed a
-	   *    Deferred object and must return a truthy value if the object is still
-	   *    needed.
-	   */
-	  var fetchQueue = function (options) {
-	    if (!(this instanceof fetchQueue)) {
-	      return new fetchQueue(options);
-	    }
-
-	    options = options || {};
-	    this._size = options.size || 6;
-	    this._track = options.track || 600;
-	    this._needed = options.needed || null;
-	    this._batch = false;
-
-	    var m_this = this,
-	        m_next_batch = 1;
-
-	    /**
-	     * Get/set the maximum concurrent deferred object size.
-	     */
-	    Object.defineProperty(this, 'size', {
-	      get: function () { return this._size; },
-	      set: function (n) {
-	        this._size = n;
-	        this.next_item();
-	      }
-	    });
-
-	    /**
-	     * Get the current queue size.
-	     */
-	    Object.defineProperty(this, 'length', {
-	      get: function () { return this._queue.length; }
-	    });
-
-	    /**
-	     * Get the current number of processing items.
-	     */
-	    Object.defineProperty(this, 'processing', {
-	      get: function () { return this._processing; }
-	    });
-
-	    /**
-	     * Remove all items from the queue.
-	     */
-	    this.clear = function () {
-	      this._queue = [];
-	      this._processing = 0;
-	      return this;
-	    };
-
-	    /**
-	     * Add a Deferred object to the queue.
-	     * @param {Deferred} defer Deferred object to add to the queue.
-	     * @param {function} callback a function to call when the item's turn is
-	     *  granted.
-	     * @param {boolean} atEnd if false, add the item to the front of the queue
-	     *  if batching is turned off or at the end of the current batch if it is
-	     *  turned on.  If true, always add the item to the end of the queue.
-	     */
-	    this.add = function (defer, callback, atEnd) {
-	      if (defer.__fetchQueue) {
-	        var pos = $.inArray(defer, this._queue);
-	        if (pos >= 0) {
-	          this._queue.splice(pos, 1);
-	          this._addToQueue(defer, atEnd);
-	          return defer;
-	        }
-	      }
-	      var wait = $.Deferred();
-	      var process = $.Deferred();
-	      wait.done(function () {
-	        $.when(callback.call(defer)).always(process.resolve);
-	      }).fail(process.resolve);
-	      defer.__fetchQueue = wait;
-	      this._addToQueue(defer, atEnd);
-	      $.when(wait, process).always(function () {
-	        if (m_this._processing > 0) {
-	          m_this._processing -= 1;
-	        }
-	        m_this.next_item();
-	      }).promise(defer);
-	      m_this.next_item();
-	      return defer;
-	    };
-
-	    /**
-	     * Add an item to the queue.  If batches are being used, add it at after
-	     * other items in the same batch.
-	     * @param {Deferred} defer Deferred object to add to the queue.
-	     * @param {boolean} atEnd if false, add the item to the front of the queue
-	     *  if batching is turned off or at the end of the current batch if it is
-	     *  turned on.  If true, always add the item to the end of the queue.
-	     */
-	    this._addToQueue = function (defer, atEnd) {
-	      defer.__fetchQueue._batch = this._batch;
-	      if (atEnd) {
-	        this._queue.push(defer);
-	      } else if (!this._batch) {
-	        this._queue.unshift(defer);
-	      } else {
-	        for (var i = 0; i < this._queue.length; i += 1) {
-	          if (this._queue[i].__fetchQueue._batch !== this._batch) {
-	            break;
-	          }
-	        }
-	        this._queue.splice(i, 0, defer);
-	      }
-	    };
-
-	    /**
-	     * Get the position of a deferred object in the queue.
-	     * @param {Deferred} defer Deferred object to get the position of.
-	     * @returns {number} -1 if not in the queue, or the position in the queue.
-	     */
-	    this.get = function (defer) {
-	      return $.inArray(defer, this._queue);
-	    };
-
-	    /**
-	     * Remove a Deferred object from the queue.
-	     * @param {Deferred} defer Deferred object to add to the queue.
-	     * @returns {bool} true if the object was removed
-	     */
-	    this.remove = function (defer) {
-	      var pos = $.inArray(defer, this._queue);
-	      if (pos >= 0) {
-	        this._queue.splice(pos, 1);
-	        return true;
-	      }
-	      return false;
-	    };
-
-	    /**
-	     * Start a new batch or clear using batches.
-	     * @param {boolean} start true to start a new batch, false to turn off
-	     *                        using batches.  Undefined to return the current
-	     *                        state of batches.
-	     * @return {Number|boolean|Object} the current batch state or this object.
-	     */
-	    this.batch = function (start) {
-	      if (start === undefined) {
-	        return this._batch;
-	      }
-	      if (!start) {
-	        this._batch = false;
-	      } else {
-	        this._batch = m_next_batch;
-	        m_next_batch += 1;
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Check if any items are queued and if there if there are not too many
-	     * deferred objects being processed.  If so, process more items.
-	     */
-	    this.next_item = function () {
-	      if (m_this._innextitem) {
-	        return;
-	      }
-	      m_this._innextitem = true;
-	      /* if the queue is greater than the track size, check each item to see
-	       * if it is still needed. */
-	      if (m_this._queue.length > m_this._track && this._needed) {
-	        for (var i = m_this._queue.length - 1; i >= 0; i -= 1) {
-	          if (!m_this._needed(m_this._queue[i])) {
-	            var discard = m_this._queue.splice(i, 1)[0];
-	            m_this._processing += 1;
-	            discard.__fetchQueue.reject();
-	            delete discard.__fetchQueue;
-	          }
-	        }
-	      }
-	      while (m_this._processing < m_this._size && m_this._queue.length) {
-	        var defer = m_this._queue.shift();
-	        if (defer.__fetchQueue) {
-	          m_this._processing += 1;
-	          var needed = m_this._needed ? m_this._needed(defer) : true;
-	          if (needed) {
-	            defer.__fetchQueue.resolve();
-	          } else {
-	            defer.__fetchQueue.reject();
-	          }
-	          delete defer.__fetchQueue;
-	        }
-	      }
-	      m_this._innextitem = false;
-	    };
-
-	    this.clear();
-	    return this;
-	  };
-
-	  return fetchQueue;
-	})();
-
-
-/***/ }),
-/* 234 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var featureLayer = __webpack_require__(220);
-	var object = __webpack_require__(203);
-
-	/**
-	 * Create a new instance of class fileReader
-	 *
-	 * @class geo.fileReader
-	 * @extends geo.object
-	 * @returns {geo.fileReader}
-	 */
-	var fileReader = function (arg) {
-	  'use strict';
-	  if (!(this instanceof fileReader)) {
-	    return new fileReader(arg);
-	  }
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  arg = arg || {};
-
-	  if (!(arg.layer instanceof featureLayer)) {
-	    throw new Error('fileReader must be given a feature layer');
-	  }
-
-	  var m_layer = arg.layer;
-
-	  /**
-	   * Get the feature layer attached to the reader
-	   */
-	  this.layer = function () {
-	    return m_layer;
-	  };
-
-	  /**
-	   * Tells the caller if it can handle the given file by returning a boolean.
-	   */
-	  this.canRead = function () {
-	    return false;
-	  };
-
-	  /**
-	   * Reads the file object and calls the done function when finished.  As an
-	   * argument to done, provides a boolean that reports if the read was a
-	   * success.  Possibly, it can call done with an object containing details
-	   * of the read operation.
-	   */
-	  this.read = function (file, done) {
-	    done(false);
-	  };
-
-	  /**
-	   * Return a FileReader with handlers attached.
-	   */
-	  function newFileReader(done, progress) {
-	    var reader = new FileReader();
-	    if (progress) {
-	      reader.onprogress = progress;
-	    }
-	    reader.onloadend = function () {
-	      if (!reader.result) {
-	        done(reader.error);
-	      }
-	      done(reader.result);
-	    };
-	    return reader;
-	  }
-
-	  /**
-	   * Private method for reading a file object as a string.  Calls done with
-	   * the string content when finished or an error object if unsuccessful.
-	   * Optionally, the caller can provide a progress method that is called
-	   * after reading each slice.
-	   */
-	  this._getString = function (file, done, progress) {
-	    var reader = newFileReader(done, progress);
-	    reader.readAsText(file);
-	  };
-
-	  /**
-	   * Like _getString, but returns an ArrayBuffer object.
-	   */
-	  this._getArrayBuffer = function (file, done, progress) {
-	    var reader = newFileReader(done, progress);
-	    reader.readAsText(file);
-	  };
-
-	  return this;
-	};
-
-	inherit(fileReader, object);
-	module.exports = fileReader;
-
-
-/***/ }),
-/* 235 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Create a new instance of class graphFeature
-	 *
-	 * @class geo.graphFeature
-	 * @extends geo.feature
-	 * @returns {geo.graphFeature}
-	 */
-	var graphFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof graphFeature)) {
-	    return new graphFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var util = __webpack_require__(83);
-	  var registry = __webpack_require__(201);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_draw = this.draw,
-	      s_style = this.style,
-	      m_nodes = null,
-	      m_points = null,
-	      m_children = function (d) { return d.children; },
-	      m_links = [],
-	      s_init = this._init,
-	      s_exit = this._exit;
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(true, {},
-	      {
-	        nodes: {
-	          radius: 5.0,
-	          fill: true,
-	          fillColor: { r: 1.0, g: 0.0, b: 0.0 },
-	          strokeColor: { r: 0, g: 0, b: 0 }
-	        },
-	        links: {
-	          strokeColor: { r: 0.0, g: 0.0, b: 0.0 }
-	        },
-	        linkType: 'path' /* 'path' || 'line' */
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    m_this.style(defaultStyle);
-	    m_this.nodes(function (d) { return d; });
-	  };
-
-	  /**
-	   * Call child _build methods
-	   */
-	  this._build = function () {
-	    m_this.children().forEach(function (child) {
-	      child._build();
-	    });
-	  };
-
-	  /**
-	   * Call child _update methods
-	   */
-	  this._update = function () {
-	    m_this.children().forEach(function (child) {
-	      child._update();
-	    });
-	  };
-
-	  /**
-	   * Custom _exit method to remove all sub-features
-	   */
-	  this._exit = function () {
-	    m_this.data([]);
-	    m_links.forEach(function (link) {
-	      link._exit();
-	      m_this.removeChild(link);
-	    });
-	    m_links = [];
-	    m_points._exit();
-	    m_this.removeChild(m_points);
-	    s_exit();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set style
-	   */
-	  this.style = function (arg, arg2) {
-	    var out = s_style.call(m_this, arg, arg2);
-	    if (out !== m_this) {
-	      return out;
-	    }
-	    // set styles for sub-features
-	    m_points.style(arg.nodes);
-	    m_links.forEach(function (l) {
-	      l.style(arg.links);
-	    });
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set links accessor.
-	   */
-	  this.links = function (arg) {
-	    if (arg === undefined) {
-	      return m_children;
-	    }
-
-	    m_children = util.ensureFunction(arg);
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set nodes
-	   */
-	  this.nodes = function (val) {
-	    if (val === undefined) {
-	      return m_nodes;
-	    }
-	    m_nodes = val;
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get internal node feature
-	   */
-	  this.nodeFeature = function () {
-	    return m_points;
-	  };
-
-	  /**
-	   * Get internal link features
-	   */
-	  this.linkFeatures = function () {
-	    return m_links;
-	  };
-
-	  /**
-	   * Build the feature for drawing
-	   */
-	  this.draw = function () {
-
-	    var layer = m_this.layer(),
-	        data = m_this.data(),
-	        nLinks = 0,
-	        style;
-
-	    // get the feature style object
-	    style = m_this.style();
-
-	    // Bind data to the point nodes
-	    m_points.data(data);
-	    m_points.style(style.nodes);
-
-	    // get links from node connections
-	    data.forEach(function (source) {
-	      (source.children || []).forEach(function (target) {
-	        var link;
-	        nLinks += 1;
-	        if (m_links.length < nLinks) {
-	          link = registry.createFeature(
-	            style.linkType, layer, layer.renderer()
-	          ).style(style.links);
-	          m_this.addChild(link);
-	          m_links.push(link);
-	        }
-	        m_links[nLinks - 1].data([source, target]);
-	      });
-	    });
-
-	    m_links.splice(nLinks, m_links.length - nLinks).forEach(function (l) {
-	      l._exit();
-	      m_this.removeChild(l);
-	    });
-
-	    s_draw();
-	    return m_this;
-	  };
-
-	  m_points = registry.createFeature(
-	    'point',
-	    this.layer(),
-	    this.layer().renderer()
-	  );
-	  m_this.addChild(m_points);
-
-	  if (arg.nodes) {
-	    this.nodes(arg.nodes);
-	  }
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(graphFeature, feature);
-	module.exports = graphFeature;
-
-
-/***/ }),
-/* 236 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-	var transform = __webpack_require__(11);
-
-	/**
-	 * Create a new instance of class heatmapFeature
-	 *
-	 * @class geo.heatmapFeature
-	 * @param {Object} arg Options object
-	 * @extends geo.feature
-	 * @param {Object|Function} [position] Position of the data.  Default is
-	 *   (data).
-	 * @param {Object|Function} [intensity] Scalar value of each data point. Scalar
-	 *   value must be a positive real number and will be used to compute
-	 *   the weight for each data point.
-	 * @param {number} [maxIntensity=null] Maximum intensity of the data. Maximum
-	 *   intensity must be a positive real number and will be used to normalize all
-	 *   intensities with a dataset. If no value is given, then a it will
-	 *   be computed.
-	 * @param {number} [minIntensity=null] Minimum intensity of the data. Minimum
-	 *   intensity must be a positive real number will be used to normalize all
-	 *   intensities with a dataset. If no value is given, then a it will
-	 *   be computed.
-	 * @param {number} [updateDelay=1000] Delay in milliseconds after a zoom,
-	 *   rotate, or pan event before recomputing the heatmap.
-	 * @param {boolean|number|'auto'} [binned='auto'] If true or a number,
-	 *   spatially bin data as part of producing the heatpmap.  If false, each
-	 *   datapoint stands on its own.  If 'auto', bin data if there are more data
-	 *   points than there would be bins.  Using true or auto uses bins that are
-	 *   max(Math.floor((radius + blurRadius) / 8), 3).
-	 * @param {Object|string|Function} [style.color] Color transfer function that.
-	 *   will be used to evaluate color of each pixel using normalized intensity
-	 *   as the look up value.
-	 * @param {Object|Function} [style.radius=10] Radius of a point in terms of
-	 *   number of pixels.
-	 * @param {Object|Function} [style.blurRadius=10] Blur radius for each point in
-	 *  terms of number of pixels.
-	 * @param {boolean} [style.gaussian=true] If true, appoximate a gaussian
-	 *   distribution for each point using a multi-segment linear radial
-	 *   appoximation.  The total weight of the gaussian area is approximately the
-	 *   9/16 r^2.  The sum of radius + blurRadius is used as the radius for the
-	 *   gaussian distribution.
-	 * @returns {geo.heatmapFeature}
-	 */
-
-	var heatmapFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof heatmapFeature)) {
-	    return new heatmapFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_position,
-	      m_intensity,
-	      m_maxIntensity,
-	      m_minIntensity,
-	      m_updateDelay,
-	      m_binned,
-	      m_gcsPosition,
-	      s_init = this._init;
-
-	  m_position = arg.position || function (d) { return d; };
-	  m_intensity = arg.intensity || function (d) { return 1; };
-	  m_maxIntensity = arg.maxIntensity !== undefined ? arg.maxIntensity : null;
-	  m_minIntensity = arg.minIntensity !== undefined ? arg.minIntensity : null;
-	  m_binned = arg.binned !== undefined ? arg.binned : 'auto';
-	  m_updateDelay = arg.updateDelay ? parseInt(arg.updateDelay, 10) : 1000;
-
-	  /**
-	   * Get/Set maxIntensity
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.maxIntensity = function (val) {
-	    if (val === undefined) {
-	      return m_maxIntensity;
-	    } else {
-	      m_maxIntensity = val;
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set maxIntensity
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.minIntensity = function (val) {
-	    if (val === undefined) {
-	      return m_minIntensity;
-	    } else {
-	      m_minIntensity = val;
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set updateDelay
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.updateDelay = function (val) {
-	    if (val === undefined) {
-	      return m_updateDelay;
-	    } else {
-	      m_updateDelay = parseInt(val, 10);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set binned
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.binned = function (val) {
-	    if (val === undefined) {
-	      return m_binned;
-	    } else {
-	      if (val === 'true') {
-	        val = true;
-	      } else if (val === 'false') {
-	        val = false;
-	      } else if (val !== 'auto' && val !== true && val !== false) {
-	        val = parseInt(val, 10);
-	        if (val <= 0 || isNaN(val)) {
-	          val = false;
-	        }
-	      }
-	      m_binned = val;
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set position accessor
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_position;
-	    } else {
-	      m_position = val;
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get pre-computed gcs position accessor
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.gcsPosition = function () {
-	    this._update();
-	    return m_gcsPosition;
-	  };
-
-	  /**
-	   * Get/Set intensity
-	   *
-	   * @returns {geo.heatmap}
-	   */
-	  this.intensity = function (val) {
-	    if (val === undefined) {
-	      return m_intensity;
-	    } else {
-	      m_intensity = val;
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        radius: 10,
-	        blurRadius: 10,
-	        gaussian: true,
-	        color: {0:    {r: 0, g: 0, b: 0.0, a: 0.0},
-	                0.25: {r: 0, g: 0, b: 1, a: 0.5},
-	                0.5:  {r: 0, g: 1, b: 1, a: 0.6},
-	                0.75: {r: 1, g: 1, b: 0, a: 0.7},
-	                1:    {r: 1, g: 0, b: 0, a: 0.8}}
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    m_this.style(defaultStyle);
-
-	    if (m_position) {
-	      m_this.dataTime().modified();
-	    }
-	  };
-
-	  /**
-	   * Build
-	   * @override
-	   */
-	  this._build = function () {
-	    var data = m_this.data(),
-	        intensity = null,
-	        position = [],
-	        setMax = (m_maxIntensity === null || m_maxIntensity === undefined),
-	        setMin = (m_minIntensity === null || m_minIntensity === undefined);
-
-	    data.forEach(function (d) {
-	      position.push(m_this.position()(d));
-	      if (setMax || setMin) {
-	        intensity = m_this.intensity()(d);
-	        if (m_maxIntensity === null || m_maxIntensity === undefined) {
-	          m_maxIntensity = intensity;
-	        }
-	        if (m_minIntensity === null || m_minIntensity === undefined) {
-	          m_minIntensity = intensity;
-	        }
-	        if (setMax && intensity > m_maxIntensity) {
-	          m_maxIntensity = intensity;
-	        }
-	        if (setMin && intensity < m_minIntensity) {
-	          m_minIntensity = intensity;
-	        }
-
-	      }
-	    });
-	    if (setMin && setMax && m_minIntensity === m_maxIntensity) {
-	      m_minIntensity -= 1;
-	    }
-	    m_gcsPosition = transform.transformCoordinates(
-	        m_this.gcs(), m_this.layer().map().gcs(), position);
-
-	    m_this.buildTime().modified();
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(heatmapFeature, feature);
-	module.exports = heatmapFeature;
-
-
-/***/ }),
-/* 237 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var tile = __webpack_require__(238);
-
-	module.exports = (function () {
-	  'use strict';
-
-	  /**
-	   * This class defines a tile that is part of a standard "image pyramid", such
-	   * as an open street map tile set.  Every tile is uniquely indexed by a row,
-	   * column, and zoom level.  The number of rows/columns at zoom level z is
-	   * `2^z`, the number of pixels per tile is configurable.
-	   *
-	   * By default, this class assumes that images are fetch from the url, but
-	   * subclasses may define additional rendering steps to produce the images
-	   * before passing them off to the handlers.
-	   *
-	   * @class geo.imageTile
-	   * @param {object} spec The tile specification object
-	   *
-	   * @param {object} spec.index The global position of the tile
-	   * @param {number} spec.index.x The x-coordinate (usually the column number)
-	   * @param {number} spec.index.y The y-coordinate (usually the row number)
-	   * @param {number} spec.index.level The zoom level
-	   *
-	   * @param {object?} spec.size The size of each tile
-	   * @param {number} [spec.size.x=256] Width in pixels
-	   * @param {number} [spec.size.y=256] Height in pixels
-	   *
-	   * @param {string} spec.url A url to the image
-	   * @param {string} [spec.crossDomain='anonymous'] Image CORS attribute
-	   *
-	   * @param {object} spec.overlap The size of overlap with neighboring tiles
-	   * @param {number} [spec.overlap.x=0]
-	   * @param {number} [spec.overlap.y=0]
-	   */
-	  var imageTile = function (spec) {
-	    if (!(this instanceof imageTile)) {
-	      return new imageTile(spec);
-	    }
-
-	    var $ = __webpack_require__(1);
-
-	    spec.size = spec.size || {x: 256, y: 256};
-	    this._image = null;
-
-	    this._cors = (spec.crossDomain || spec.crossDomain === null) ? spec.crossDomain : 'anonymous';
-
-	    // Call superclass constructor
-	    tile.call(this, spec);
-
-	    /**
-	     * Read only accessor to the Image object used by the
-	     * tile.  Note, this method does not gaurantee that the
-	     * image data is available.  Use the promise interface
-	     * to add asyncronous handlers.
-	     * @returns {Image}
-	     */
-	    Object.defineProperty(this, 'image', {
-	      get: function () { return this._image; }
-	    });
-
-	    /**
-	     * Initiate the image request.
-	     *
-	     * @returns {this} The current tile class instance.
-	     */
-	    this.fetch = function () {
-	      var defer;
-	      if (!this._image) {
-	        this._image = new Image(this.size.x, this.size.y);
-	        // Only set the crossOrigin parameter if this is going across origins.
-	        if (this._cors && this._url.indexOf(':') >= 0 &&
-	            this._url.indexOf('/') === this._url.indexOf(':') + 1) {
-	          this._image.crossOrigin = this._cors;
-	        }
-	        defer = $.Deferred();
-	        this._image.onload = defer.resolve;
-	        this._image.onerror = defer.reject;
-	        this._image.src = this._url;
-
-	        // attach a promise interface to `this`
-	        defer.done(function () {
-	          this._fetched = true;
-	        }.bind(this)).promise(this);
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Set the opacity of the tile to 0 and gradually fade in
-	     * over the given number of milliseconds.  This will also
-	     * resolve the embedded promise interface.
-	     * @param {number} duration the duration of the animation in ms
-	     * @returns {this} chainable
-	     */
-	    this.fadeIn = function (duration) {
-	      var promise = this.fetch(), defer = $.Deferred();
-	      $(this._image).css('display', 'none');
-	      promise.done(function () {
-	        $(this._image).fadeIn(duration, function () {
-	          defer.resolve();
-	        });
-	      }.bind(this));
-	      return defer.promise(this);
-	    };
-
-	    return this;
-	  };
-
-	  inherit(imageTile, tile);
-	  return imageTile;
-	})();
-
-
-/***/ }),
-/* 238 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = (function () {
-	  'use strict';
-
-	  var $ = __webpack_require__(1);
-
-	  /**
-	   * This class defines the raw interface for a "tile" on a map.  A tile is
-	   * defined as a rectangular section of a map.  The base implementation
-	   * is independent of the actual content of the tile, but assumes that
-	   * the content is loaded asynchronously via a url.  The tile object
-	   * has a promise-like interface.  For example,
-	   *
-	   * tile.then(function (data) {...}).catch(function (data) {...});
-	   *
-	   * @class geo.tile
-	   * @param {Object} spec The tile specification object
-	   *
-	   * @param {Object} spec.index The global position of the tile
-	   * @param {Number} spec.index.x The x-coordinate (usually the column number)
-	   * @param {Number} spec.index.y The y-coordinate (usually the row number)
-	   *
-	   * @param {Object} spec.size The size of each tile
-	   * @param {Number} spec.size.x Width (usually in pixels)
-	   * @param {Number} spec.size.y Height (usually in pixels)
-	   *
-	   * @param {Object|String} spec.url A url or jQuery ajax config object
-	   *
-	   * @param {Object?} spec.overlap The size of overlap with neighboring tiles
-	   * @param {Number} [spec.overlap.x=0]
-	   * @param {Number} [spec.overlap.y=0]
-	   */
-	  var tile = function (spec) {
-	    if (!(this instanceof tile)) {
-	      return new tile(spec);
-	    }
-
-	    this._index = spec.index;
-	    this._size = spec.size;
-	    this._overlap = spec.overlap || {x: 0, y: 0};
-	    this._wrap = spec.wrap || {x: 1, y: 1};
-	    this._url = spec.url;
-	    this._fetched = false;
-	    this._queue = spec.queue || null;
-
-	    /**
-	     * Return the index coordinates.
-	     */
-	    Object.defineProperty(this, 'index', {
-	      get:
-	        function () { return this._index; }
-	    });
-
-	    /**
-	     * Return the tile sizes.
-	     */
-	    Object.defineProperty(this, 'size', {
-	      get: function () { return this._size; }
-	    });
-
-	    /**
-	     * Return the tile overlap sizes.
-	     */
-	    Object.defineProperty(this, 'overlap', {
-	      get: function () { return this._overlap; }
-	    });
-
-	    /**
-	     * Initiate the ajax request and add a promise interface
-	     * to the tile object.  This method exists to allow
-	     * derived classes the ability to override how the tile
-	     * is obtained.  For example, imageTile uses an Image
-	     * element rather than $.get.
-	     */
-	    this.fetch = function () {
-	      if (!this._fetched) {
-	        $.get(this._url).done(function () {
-	          this._fetched = true;
-	        }.bind(this)).promise(this);
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Return whether this tile has been fetched already.
-	     *
-	     * @returns {boolean} True if the tile has been fetched.
-	     */
-	    this.fetched = function () {
-	      return this._fetched;
-	    };
-
-	    /**
-	     * Add a method to be called with the data when the ajax request is
-	     * successfully resolved.
-	     *
-	     * @param {function?} onSuccess The success handler
-	     * @param {function?} onFailure The failure handler
-	     * @returns {this} Supports chained calling
-	     *
-	     */
-	    this.then = function (onSuccess, onFailure) {
-	      // both fetch and _queueAdd can replace the current then method
-	      if (!this.fetched() && this._queue && this._queue.add && (!this.state ||
-	          this.state() === 'pending')) {
-	        this._queue.add(this, this.fetch);
-	      } else {
-	        this.fetch();
-	      }
-	      // Call then on the new promise
-	      if (this.done && this.fail) {
-	        this.done(onSuccess).fail(onFailure);
-	      } else {
-	        this.then(onSuccess, onFailure);
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Add a method to be called with the data when the ajax fails.
-	     *
-	     * @param {function} method The rejection handler
-	     * @returns {this} Supports chained calling
-	     *
-	     */
-	    this.catch = function (method) {
-	      this.then(undefined, method);
-	      return this;
-	    };
-
-	    /**
-	     * Return a unique string representation of the given tile useable
-	     * as a hash key.  Possibly extend later to include url information
-	     * to make caches aware of the tile source.
-	     * @returns {string}
-	     */
-	    this.toString = function () {
-	      return [this._index.level || 0, this._index.y, this._index.x].join('_');
-	    };
-
-	    /**
-	     * Return the bounds of the tile given an index offset and
-	     * a translation.
-	     *
-	     * @param {object} index The tile index containing (0, 0)
-	     * @param {object} shift The coordinates of (0, 0) inside the tile
-	     */
-	    this.bounds = function (index, shift) {
-	      var left, right, bottom, top;
-	      left = this.size.x * (this.index.x - index.x) - this.overlap.x - shift.x;
-	      right = left + this.size.x + this.overlap.x * 2;
-	      top = this.size.y * (this.index.y - index.y) - this.overlap.y - shift.y;
-	      bottom = top + this.size.y + this.overlap.y * 2;
-	      return {
-	        left: left,
-	        right: right,
-	        bottom: bottom,
-	        top: top
-	      };
-	    };
-
-	    /**
-	     * Computes the global coordinates of the bottom edge.
-	     * @returns {number}
-	     */
-	    Object.defineProperty(this, 'bottom', {
-	      get: function () {
-	        return this.size.y * (this.index.y + 1) + this.overlap.y;
-	      }
-	    });
-
-	    /**
-	     * Computes the global coordinates of the top edge.
-	     * @returns {number}
-	     */
-	    Object.defineProperty(this, 'top', {
-	      get: function () {
-	        return this.size.y * this.index.y - this.overlap.y;
-	      }
-	    });
-
-	    /**
-	     * Computes the global coordinates of the left edge.
-	     * @returns {number}
-	     */
-	    Object.defineProperty(this, 'left', {
-	      get: function () {
-	        return this.size.x * this.index.x - this.overlap.x;
-	      }
-	    });
-
-	    /**
-	     * Computes the global coordinates of the right edge.
-	     * @returns {number}
-	     */
-	    Object.defineProperty(this, 'right', {
-	      get: function () {
-	        return this.size.x * (this.index.x + 1) + this.overlap.x;
-	      }
-	    });
-
-	    /**
-	     * Returns the global image size at this level.
-	     * @returns {number}
-	     */
-	    Object.defineProperty(this, 'levelSize', {
-	      value: {
-	        width: Math.pow(2, this.index.level || 0) * this.size.x,
-	        height: Math.pow(2, this.index.level || 0) * this.size.y
-	      }
-	    });
-
-	    /**
-	     * Set the opacity of the tile to 0 and gradually fade in
-	     * over the given number of milliseconds.  This will also
-	     * resolve the embedded promise interface.
-	     * @param {number} duration the duration of the animation in ms
-	     * @returns {this} chainable
-	     */
-	    this.fadeIn = function (duration) {
-	      $.noop(duration);
-	      return this;
-	    };
-	  };
-	  return tile;
-	})();
-
-
-/***/ }),
-/* 239 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFileReader = __webpack_require__(201).registerFileReader;
-	var fileReader = __webpack_require__(234);
-
-	/**
-	* Create a new instance of class jsonReader
-	*
-	* @class geo.jsonReader
-	* @extends geo.fileReader
-	* @returns {geo.jsonReader}
-	*/
-	var jsonReader = function (arg) {
-	  'use strict';
-	  if (!(this instanceof jsonReader)) {
-	    return new jsonReader(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-	  var convertColor = __webpack_require__(83).convertColor;
-
-	  var m_this = this;
-
-	  fileReader.call(this, arg);
-
-	  this.canRead = function (file) {
-	    if (file instanceof File) {
-	      return (file.type === 'application/json' || file.name.match(/\.json$/));
-	    } else if (typeof file === 'string') {
-	      try {
-	        JSON.parse(file);
-	      } catch (e) {
-	        return false;
-	      }
-	      return true;
-	    }
-	    try {
-	      if (Array.isArray(m_this._featureArray(file))) {
-	        return true;
-	      }
-	    } catch (e) {}
-	    return false;
-	  };
-
-	  this._readObject = function (file, done, progress) {
-	    var object;
-	    function onDone(fileString) {
-	      if (typeof fileString !== 'string') {
-	        done(false);
-	      }
-
-	      // We have two possibilities here:
-	      // 1) fileString is a JSON string or is
-	      // a URL.
-	      try {
-	        object = JSON.parse(fileString);
-	        done(object);
-	      } catch (e) {
-	        if (!object) {
-	          $.ajax({
-	            type: 'GET',
-	            url: fileString,
-	            dataType: 'text'
-	          }).done(function (data) {
-	            object = JSON.parse(data);
-	            done(object);
-	          }).fail(function () {
-	            done(false);
-	          });
-	        }
-	      }
-	    }
-
-	    if (file instanceof File) {
-	      m_this._getString(file, onDone, progress);
-	    } else if (typeof file === 'string') {
-	      onDone(file);
-	    } else {
-	      done(file);
-	    }
-	  };
-
-	  /**
-	   * Return an array of normalized geojson features.  This
-	   * will do the following:
-	   *
-	   * 1. Turn bare geometries into features
-	   * 2. Turn multi-geometry features into single geometry features
-	   *
-	   * Returns an array of Point, LineString, or Polygon features.
-	   * @protected
-	   */
-	  this._featureArray = function (spec) {
-	    var features, normalized = [];
-	    switch (spec.type) {
-	      case 'FeatureCollection':
-	        features = spec.features;
-	        break;
-
-	      case 'Feature':
-	        features = [spec];
-	        break;
-
-	      case 'GeometryCollection':
-	        features = spec.geometries.map(function (g) {
-	          return {
-	            type: 'Feature',
-	            geometry: g,
-	            properties: {}
-	          };
-	        });
-	        break;
-
-	      case 'Point':
-	      case 'LineString':
-	      case 'Polygon':
-	      case 'MultiPoint':
-	      case 'MultiLineString':
-	      case 'MultiPolygon':
-	        features = [{
-	          type: 'Feature',
-	          geometry: spec,
-	          properties: {}
-	        }];
-	        break;
-
-	      default:
-	        throw new Error('Invalid json type');
-	    }
-
-	    // flatten multi features
-	    features.forEach(function (feature) {
-	      Array.prototype.push.apply(normalized, m_this._feature(feature));
-	    });
-
-	    // remove features with empty geometries
-	    normalized = normalized.filter(function (feature) {
-	      return feature.geometry &&
-	        feature.geometry.coordinates &&
-	        feature.geometry.coordinates.length;
-	    });
-	    return normalized;
-	  };
-
-	  /**
-	   * Normalize a feature object turning multi geometry features
-	   * into an array of features, and single geometry features into
-	   * an array containing one feature.
-	   */
-	  this._feature = function (spec) {
-	    if (spec.type !== 'Feature') {
-	      throw new Error('Invalid feature object');
-	    }
-	    switch (spec.geometry.type) {
-	      case 'Point':
-	      case 'LineString':
-	      case 'Polygon':
-	        return [spec];
-
-	      case 'MultiPoint':
-	      case 'MultiLineString':
-	      case 'MultiPolygon':
-	        return spec.geometry.coordinates.map(function (c) {
-	          return {
-	            type: 'Feature',
-	            geometry: {
-	              type: spec.geometry.type.replace('Multi', ''),
-	              coordinates: c
-	            },
-	            properties: spec.properties
-	          };
-	        });
-
-	      default:
-	        throw new Error('Invalid geometry type');
-	    }
-	  };
-
-	  /**
-	   * Convert from a geojson position array into a geojs position object.
-	   */
-	  this._position = function (p) {
-	    return {
-	      x: p[0],
-	      y: p[1],
-	      z: p[2] || 0
-	    };
-	  };
-
-	  /**
-	   * Defines a style accessor the returns the given
-	   * value of the property object, or a default value.
-	   *
-	   * @protected
-	   * @param {string} prop The property name
-	   * @param {object} default The default value
-	   * @param {object} [spec] The argument containing the main property object
-	   * @param {function} [convert] An optional conversion function
-	   */
-	  this._style = function (prop, _default, spec, convert) {
-	    convert = convert || function (d) { return d; };
-	    _default = convert(_default);
-	    return function (d, i, e, j) {
-	      var p;
-	      if (spec && j !== undefined && spec[j] !== undefined) {
-	        p = spec[j].properties;
-	      } else {
-	        p = d.properties;
-	      }
-	      if (p !== undefined && p.hasOwnProperty(prop)) {
-	        return convert(p[prop]);
-	      }
-	      return _default;
-	    };
-	  };
-
-	  this.read = function (file, done, progress) {
-
-	    function _done(object) {
-	      var features, allFeatures = [], points, lines, polygons;
-
-	      features = m_this._featureArray(object);
-
-	      // process points
-	      points = features.filter(function (f) { return f.geometry.type === 'Point'; });
-	      if (points.length) {
-	        allFeatures.push(
-	          m_this.layer().createFeature('point')
-	            .data(points)
-	            .position(function (d) {
-	              return m_this._position(d.geometry.coordinates);
-	            })
-	            .style({
-	              fill: m_this._style('fill', true),
-	              fillColor: m_this._style('fillColor', '#ff7800', null, convertColor),
-	              fillOpacity: m_this._style('fillOpacity', 0.8),
-	              stroke: m_this._style('stroke', true),
-	              strokeColor: m_this._style('strokeColor', '#000000', null, convertColor),
-	              strokeWidth: m_this._style('strokeWidth', 1),
-	              strokeOpacity: m_this._style('strokeOpacity', 1),
-	              radius: m_this._style('radius', 8)
-	            })
-	        );
-	      }
-
-	      // process lines
-	      lines = features.filter(function (f) { return f.geometry.type === 'LineString'; });
-	      if (lines.length) {
-	        allFeatures.push(
-	          m_this.layer().createFeature('line')
-	            .data(lines)
-	            .line(function (d) {
-	              return d.geometry.coordinates;
-	            })
-	            .position(m_this._position)
-	            .style({
-	              strokeColor: m_this._style('strokeColor', '#ff7800', lines, convertColor),
-	              strokeWidth: m_this._style('strokeWidth', 4, lines),
-	              strokeOpacity: m_this._style('strokeOpacity', 0.5, lines),
-	              strokeOffset: m_this._style('strokeOffset', 0, lines),
-	              lineCap: m_this._style('lineCap', 'butt', lines),
-	              lineJoin: m_this._style('lineCap', 'miter', lines),
-	              closed: m_this._style('closed', false, lines)
-	            })
-	        );
-	      }
-
-	      // process polygons
-	      polygons = features.filter(function (f) { return f.geometry.type === 'Polygon'; });
-	      if (polygons.length) {
-	        allFeatures.push(
-	          m_this.layer().createFeature('polygon')
-	            .data(polygons)
-	            .polygon(function (d, i) {
-	              return {
-	                outer: d.geometry.coordinates[0],
-	                inner: d.geometry.coordinates.slice(1)
-	              };
-	            })
-	            .position(m_this._position)
-	            .style({
-	              fill: m_this._style('fill', true),
-	              fillColor: m_this._style('fillColor', '#b0de5c', polygons, convertColor),
-	              fillOpacity: m_this._style('fillOpacity', 0.8, polygons),
-	              stroke: m_this._style('stroke', true),
-	              strokeColor: m_this._style('strokeColor', '#999999', polygons, convertColor),
-	              strokeWidth: m_this._style('strokeWidth', 2, polygons),
-	              strokeOpacity: m_this._style('strokeOpacity', 1, polygons)
-	            })
-	        );
-	      }
-	      if (done) {
-	        done(allFeatures);
-	      }
-	    }
-
-	    m_this._readObject(file, _done, progress);
-	  };
-	};
-
-	inherit(jsonReader, fileReader);
-	registerFileReader('jsonReader', jsonReader);
-	module.exports = jsonReader;
-
-
-/***/ }),
-/* 240 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var vgl = __webpack_require__(86);
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-
-	/**
-	 * Creates a new map object.
-	 *
-	 * @class
-	 * @alias geo.map
-	 * @extends geo.sceneObject
-	 *
-	 * @param {object} arg Options object
-	 *
-	 * @param {string} arg.node DOM selector for the map container.
-	 *   *** Always required ***
-	 *
-	 * @param {string|geo.transform} [arg.gcs='EPSG:3857']
-	 *   The main coordinate system of the map (this is often web Mercator).
-	 *   * Required when using a domain/CS different from OSM *
-	 * @param {string|geo.transform} [arg.ingcs='EPSG:4326']
-	 *   The default coordinate system of interface calls (this is often latitude
-	 *   and longitude).
-	 * @param {number} [arg.unitsPerPixel=156543] GCS to pixel unit scaling at zoom
-	 *   0 (i.e. meters per pixel or degrees per pixel).
-	 * @param {object?} [arg.maxBounds] The maximum visible map bounds.
-	 * @param {number} [arg.maxBounds.left=-20037508] The left bound.
-	 * @param {number} [arg.maxBounds.right=20037508] The right bound.
-	 * @param {number} [arg.maxBounds.bottom=-20037508] The bottom bound.
-	 * @param {number} [arg.maxBounds.top=20037508] The top bound.
-	 * @param {number} [arg.maxBounds.gcs=arg.ingcs] The coordinate system for the
-	 *   bounds.
-	 *
-	 * @param {number} [arg.zoom=4] Initial zoom.
-	 * @param {object?} [arg.center] Initial map center.
-	 * @param {number} arg.center.x=0
-	 * @param {number} arg.center.y=0
-	 * @param {number} [arg.rotation=0] Initial clockwise rotation in radians.
-	 * @param {number?} [arg.width] The map width (default node width).
-	 * @param {number?} [arg.height] The map height (default node height).
-	 *
-	 * @param {number} [arg.min=0] Minimum zoom level (though fitting to the
-	 *   viewport may make it so this is smaller than the smallest possible value).
-	 * @param {number} [arg.max=16] Maximum zoom level.
-	 * @param {boolean} [arg.discreteZoom=false] If `true`, only allow integer zoom
-	 *   levels.  `false` for any zoom level.
-	 * @param {boolean|function} [arg.allowRotation=true] `false` prevents
-	 *   rotation, `true` allows any rotation.  If a function, the function is
-	 *   called with a rotation (angle in radians) and returns a valid rotation
-	 *   (this can be used to constrain the rotation to a range or specific to
-	 *   values).
-	 *
-	 * @param {geo.camera?} [arg.camera] The camera to control the view.
-	 * @param {geo.mapInteractor?} [arg.interactor] The UI event handler.  If
-	 *   `undefined`, a default interactor is created and used.  If `null`, no
-	 *   interactor is attached to the map.
-	 * @param {array} [arg.animationQueue] An array used to synchronize animations.
-	 *   If specified, this should be an empty array or the same array as passed to
-	 *   other map instances.
-	 * @param {boolean} [arg.autoResize=true] Adjust map size on window resize.
-	 * @param {boolean} [arg.clampBoundsX=false] Prevent panning outside of the
-	 *   maximum bounds in the horizontal direction.
-	 * @param {boolean} [arg.clampBoundsY=true] Prevent panning outside of the
-	 *   maximum bounds in the vertical direction.
-	 * @param {boolean} [arg.clampZoom=true] Prevent zooming out so that the map
-	 *   area is smaller than the window.
-	 *
-	 * @returns {geo.map}
-	 */
-	var map = function (arg) {
-	  'use strict';
-	  if (!(this instanceof map)) {
-	    return new map(arg);
-	  }
-	  arg = arg || {};
-
-	  if (arg.node === undefined || arg.node === null) {
-	    console.warn('map creation requires a node');
-	    return this;
-	  }
-
-	  sceneObject.call(this, arg);
-
-	  var camera = __webpack_require__(211);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var registry = __webpack_require__(201);
-	  var geo_event = __webpack_require__(9);
-	  var mapInteractor = __webpack_require__(222);
-	  var uiLayer = __webpack_require__(241);
-
-	  /**
-	   * Private member variables
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      // See https://en.wikipedia.org/wiki/Web_Mercator
-	      // phiMax = 180 / Math.PI * (2 * Math.atan(Math.exp(Math.PI)) - Math.PI / 2),
-	      m_node = $(arg.node),
-	      m_width = arg.width || m_node.width() || 512,
-	      m_height = arg.height || m_node.height() || 512,
-	      m_gcs = arg.gcs === undefined ? 'EPSG:3857' : arg.gcs,
-	      m_ingcs = arg.ingcs === undefined ? 'EPSG:4326' : arg.ingcs,
-	      m_center = {x: 0, y: 0},
-	      m_zoom = arg.zoom === undefined ? 4 : arg.zoom,
-	      m_rotation = arg.rotation ? arg.rotation : 0,
-	      m_fileReader = null,
-	      m_interactor = null,
-	      m_validZoomRange = {min: 0, max: 16, origMin: 0},
-	      m_transition = null,
-	      m_queuedTransition = null,
-	      m_discreteZoom = arg.discreteZoom ? true : false,
-	      m_allowRotation = (typeof arg.allowRotation === 'function' ?
-	                         arg.allowRotation : (arg.allowRotation === undefined ?
-	                         true : !!arg.allowRotation)),
-	      m_maxBounds = arg.maxBounds || {},
-	      m_camera = arg.camera || camera(),
-	      m_unitsPerPixel,
-	      m_clampBoundsX,
-	      m_clampBoundsY,
-	      m_clampZoom,
-	      m_animationQueue = arg.animationQueue || [],
-	      m_autoResize = arg.autoResize === undefined ? true : arg.autoResize,
-	      m_origin;
-
-	  /* Compute the maximum bounds on our map projection.  By default, x ranges
-	   * from [-180, 180] in the interface projection, and y matches the x range in
-	   * the map (not the interface) projection.  For images, this might be
-	   * [0, width] and [0, height] instead. */
-	  var mcx = ((m_maxBounds.left || 0) + (m_maxBounds.right || 0)) / 2,
-	      mcy = ((m_maxBounds.bottom || 0) + (m_maxBounds.top || 0)) / 2;
-	  m_maxBounds.left = transform.transformCoordinates(m_maxBounds.gcs || m_ingcs, m_gcs, {
-	    x: m_maxBounds.left !== undefined ? m_maxBounds.left : -180, y: mcy
-	  }).x;
-	  m_maxBounds.right = transform.transformCoordinates(m_maxBounds.gcs || m_ingcs, m_gcs, {
-	    x: m_maxBounds.right !== undefined ? m_maxBounds.right : 180, y: mcy
-	  }).x;
-	  m_maxBounds.top = (m_maxBounds.top !== undefined ?
-	    transform.transformCoordinates(m_maxBounds.gcs || m_ingcs, m_gcs, {
-	      x: mcx, y: m_maxBounds.top}).y : m_maxBounds.right);
-	  m_maxBounds.bottom = (m_maxBounds.bottom !== undefined ?
-	    transform.transformCoordinates(m_maxBounds.gcs || m_ingcs, m_gcs, {
-	      x: mcx, y: m_maxBounds.bottom}).y : m_maxBounds.left);
-	  m_unitsPerPixel = (arg.unitsPerPixel || (
-	    m_maxBounds.right - m_maxBounds.left) / 256);
-
-	  m_camera.viewport = {
-	    width: m_width,
-	    height: m_height,
-	    left: m_node.offset().left,
-	    top: m_node.offset().top
-	  };
-	  arg.center = util.normalizeCoordinates(arg.center);
-	  m_clampBoundsX = arg.clampBoundsX === undefined ? false : arg.clampBoundsX;
-	  m_clampBoundsY = arg.clampBoundsY === undefined ? true : arg.clampBoundsY;
-	  m_clampZoom = arg.clampZoom === undefined ? true : arg.clampZoom;
-
-	  /**
-	   * Get/set the number of world space units per display pixel at the given
-	   * zoom level.
-	   *
-	   * @param {number} [zoom=0] The target zoom level.
-	   * @param {number?} [unit] If present, set the `unitsPerPixel` at the
-	   *   specified zoom level.  Otherwise return the current value.
-	   * @returns {number|this}
-	   */
-	  this.unitsPerPixel = function (zoom, unit) {
-	    zoom = zoom || 0;
-	    if (unit) {
-	      // get the units at level 0
-	      m_unitsPerPixel = Math.pow(2, zoom) * unit;
-
-	      // redraw all the things
-	      m_this.draw();
-	      return m_this;
-	    }
-	    return Math.pow(2, -zoom) * m_unitsPerPixel;
-	  };
-
-	  /**
-	   * Get/set the animation queue.  Two maps can share a single animation queue
-	   * to ensure synchronized animations.  When setting, the animation queue will
-	   * merge values from the existing queue into the new queue.
-	   *
-	   * @param {array} [queue] The animation queue to use.
-	   * @returns {array|this} The current animation queue or the current map.
-	   */
-	  this.animationQueue = function (queue) {
-	    if (queue === undefined) {
-	      return m_animationQueue;
-	    }
-	    if (queue !== m_animationQueue) {
-	      if (m_animationQueue.length) {
-	        /* If the specified queue already has data in, don't copy the 0th
-	         * element of the existing queue, since the 0th element is always the
-	         * actual requestAnimationFrame reference.  In this case, cancel the
-	         * existing requestAnimationFrame.  By using a property of window,
-	         * tests can override this if needed. */
-	        if (queue.length && queue[0] !== m_animationQueue[0]) {
-	          window['cancelAnimationFrame'](m_animationQueue[0]);
-	        }
-	        for (var i = queue.length ? 1 : 0; i < m_animationQueue.length; i += 1) {
-	          queue.push(m_animationQueue[i]);
-	        }
-	      }
-	      m_animationQueue = queue;
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * Get/set the autoResize flag.
-	   *
-	   * @param {boolean} [autoResize] Truthy to automaticaly resize the map when
-	   *    the size of the browser window changes.
-	   * @returns {boolean|this} The current state of autoResize or the current map.
-	   */
-	  this.autoResize = function (autoResize) {
-	    if (autoResize === undefined) {
-	      return m_autoResize;
-	    }
-	    if (autoResize !== m_autoResize) {
-	      $(window).off('resize', resizeSelf);
-	      m_autoResize = autoResize;
-	      if (m_autoResize) {
-	        $(window).on('resize', resizeSelf);
-	      }
-	    }
-	    return this;
-	  };
-
-	  /**
-	   * Get/set the `clampBoundsX` setting.  If changed, adjust the bounds of the
-	   * map as needed.
-	   *
-	   * @param {boolean} [clamp] The new clamp value.
-	   * @returns {boolean|this}
-	   */
-	  this.clampBoundsX = function (clamp) {
-	    if (clamp === undefined) {
-	      return m_clampBoundsX;
-	    }
-	    if (clamp !== m_clampBoundsX) {
-	      m_clampBoundsX = !!clamp;
-	      m_this.pan({x: 0, y: 0});
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/set the `clampBoundsY` setting.  If changed, adjust the bounds of the
-	   * map as needed.
-	   *
-	   * @param {boolean} [clamp] The new clamp value.
-	   * @returns {boolean|this}
-	   */
-	  this.clampBoundsY = function (clamp) {
-	    if (clamp === undefined) {
-	      return m_clampBoundsY;
-	    }
-	    if (clamp !== m_clampBoundsY) {
-	      m_clampBoundsY = !!clamp;
-	      m_this.pan({x: 0, y: 0});
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/set the `clampZoom` setting.  If changed, adjust the bounds of the map
-	   * as needed.
-	   *
-	   * @param {boolean} [clamp] The new clamp value.
-	   * @returns {boolean|this}
-	   */
-	  this.clampZoom = function (clamp) {
-	    if (clamp === undefined) {
-	      return m_clampZoom;
-	    }
-	    if (clamp !== m_clampZoom) {
-	      m_clampZoom = !!clamp;
-	      reset_minimum_zoom();
-	      m_this.zoom(m_zoom);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/set the `allowRotation` setting.  If changed, adjust the map as
-	   * needed.
-	   *
-	   * @param {boolean|function} [allowRotation] The new `allowRotation` value.
-	   *    `false` prevents rotation, `true` allows any rotation.  If a function,
-	   *    the function is called with a rotation (angle in radians) and returns a
-	   *    valid rotation (this can be used to constrain the rotation to a range
-	   *    or to specific values).
-	   * @returns {boolean|function|this}
-	   */
-	  this.allowRotation = function (allowRotation) {
-	    if (allowRotation === undefined) {
-	      return m_allowRotation;
-	    }
-	    if (typeof allowRotation !== 'function') {
-	      allowRotation = !!allowRotation;
-	    }
-	    if (allowRotation !== m_allowRotation) {
-	      m_allowRotation = allowRotation;
-	      m_this.rotation(m_rotation);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the map's world coordinate origin in gcs coordinates.
-	   *
-	   * @returns {geo.geoPosition}
-	   */
-	  this.origin = function () {
-	    return $.extend({}, m_origin);
-	  };
-
-	  /**
-	   * Get the camera.
-	   *
-	   * @returns {geo.camera}
-	   */
-	  this.camera = function () {
-	    return m_camera;
-	  };
-
-	  /**
-	   * Get or set the map gcs.  This is the coordinate system used in drawing the
-	   * map.
-	   *
-	   * @param {string} [arg] If `undefined`, return the current gcs.  Otherwise,
-	   *    a new value for the gcs.
-	   * @returns {string|this} A string used by {@linkcode geo.transform}.
-	   */
-	  this.gcs = function (arg) {
-	    if (arg === undefined) {
-	      return m_gcs;
-	    }
-	    if (arg !== m_gcs) {
-	      var oldCenter = m_this.center(undefined, undefined);
-	      m_gcs = arg;
-	      reset_minimum_zoom();
-	      var newZoom = fix_zoom(m_zoom);
-	      if (newZoom !== m_zoom) {
-	        m_this.zoom(newZoom);
-	      }
-	      m_this.center(oldCenter, undefined);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the map interface gcs.  This is the coordinate system used when
-	   * getting or setting map bounds, center, and other values.
-	   *
-	   * @param {string} [arg] If `undefined`, returtn the current interface gcs.
-	   *    Otherwise, a new value for the interface gcs.
-	   * @returns {string|this} A string used by {@linkcode geo.transform}.
-	   */
-	  this.ingcs = function (arg) {
-	    if (arg === undefined) {
-	      return m_ingcs;
-	    }
-	    m_ingcs = arg;
-	    return m_this;
-	  };
-
-	  /**
-	   * Get root DOM node of the map.
-	   *
-	   * @returns {object}
-	   */
-	  this.node = function () {
-	    return m_node;
-	  };
-
-	  /**
-	   * Get/Set zoom level of the map.
-	   *
-	   * @param {number} [val] If `undefined`, return the current zoom level.
-	   *    Otherwise, the new zoom level to set.
-	   * @param {object} [origin] If present, specifies the center of the zoom;
-	   *    otherwise the map's display center is used.
-	   * @param {geo.geoPosition} origin.geo The gcs coordinates of the zoom
-	   *    center.
-	   * @param {geo.screenPosition} origin.map The display coordinates of the zoom
-	   *    center.
-	   * @param {boolean} [ignoreDiscreteZoom] If `true`, ignore the discreteZoom
-	   *    option when determining the new view.
-	   * @returns {number|this}
-	   * @fires geo.event.zoom
-	   * @fires geo.event.pan
-	   */
-	  this.zoom = function (val, origin, ignoreDiscreteZoom) {
-	    if (val === undefined) {
-	      return m_zoom;
-	    }
-	    var evt, bounds;
-	    /* If we are zooming around a point, ignore the clamp bounds */
-	    var aroundPoint = (origin && (origin.mapgcs || origin.geo) && origin.map);
-	    var ignoreClampBounds = aroundPoint;
-
-	    /* The ignoreDiscreteZoom flag is intended to allow non-integer zoom values
-	     * during animation. */
-	    val = fix_zoom(val, ignoreDiscreteZoom);
-	    if (val === m_zoom) {
-	      return m_this;
-	    }
-
-	    m_zoom = val;
-
-	    bounds = m_this.boundsFromZoomAndCenter(
-	      val, m_center, m_rotation, null, ignoreDiscreteZoom, ignoreClampBounds);
-	    m_this.modified();
-
-	    camera_bounds(bounds, m_rotation);
-	    evt = {
-	      zoomLevel: m_zoom,
-	      screenPosition: origin ? origin.map : undefined
-	    };
-	    m_this.geoTrigger(geo_event.zoom, evt);
-
-	    if (aroundPoint) {
-	      var shifted = m_this.gcsToDisplay(origin.mapgcs || origin.geo,
-	                                        origin.mapgcs ? null : undefined);
-	      m_this.pan({x: origin.map.x - shifted.x, y: origin.map.y - shifted.y},
-	                 ignoreDiscreteZoom, true);
-	    } else {
-	      m_this.pan({x: 0, y: 0}, ignoreDiscreteZoom);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Pan the map by a number of display pixels.
-	   *
-	   * @param {object} delta Amount to pan in display pixels.
-	   * @param {number} delta.x Horizontal distance on the display.
-	   * @param {number} delta.y Vertical distance on the display.
-	   * @param {boolean} [ignoreDiscreteZoom] If `true`, ignore the `discreteZoom`
-	   *    option when determining the new view.
-	   * @param {boolean|'limited'} [ignoreClampBounds] If `true` ignore the
-	   *    `clampBoundsX` and `clampBoundsY` options when determining the new
-	   *    view.  When `'limited'`, the `clampBoundsX` and `clampBoundsY` options
-	   *    are selectively enforced so that the map will not end up more out of
-	   *    bounds than its current state.
-	   * @returns {this}
-	   * @fires geo.event.pan
-	   */
-	  this.pan = function (delta, ignoreDiscreteZoom, ignoreClampBounds) {
-	    var evt = {
-	      screenDelta: delta
-	    };
-
-	    if (delta.x || delta.y) {
-	      var unit = m_this.unitsPerPixel(m_zoom);
-
-	      var sinr = Math.sin(m_rotation), cosr = Math.cos(m_rotation);
-	      m_camera.pan({
-	        x: (delta.x * cosr - (-delta.y) * sinr) * unit,
-	        y: (delta.x * sinr + (-delta.y) * cosr) * unit
-	      });
-	    }
-	    /* If m_clampBoundsX or m_clampBoundsY is true, clamp the pan */
-	    var bounds = m_camera.bounds;
-	    bounds = fix_bounds(bounds, m_rotation, ignoreClampBounds === 'limited' ? {
-	      x: delta.x, y: delta.y, unit: unit} : undefined,
-	      ignoreClampBounds === true);
-	    if (bounds !== m_camera.bounds) {
-	      var panPos = m_this.gcsToDisplay({
-	        x: m_camera.bounds.left, y: m_camera.bounds.top}, null);
-	      bounds = m_this.boundsFromZoomAndCenter(m_zoom, {
-	        x: (bounds.left + bounds.right) / 2,
-	        y: (bounds.top + bounds.bottom) / 2
-	      }, m_rotation, null, ignoreDiscreteZoom, true);
-	      camera_bounds(bounds, m_rotation);
-	      var clampPos = m_this.gcsToDisplay({
-	        x: m_camera.bounds.left, y: m_camera.bounds.top}, null);
-	      evt.screenDelta.x += clampPos.x - panPos.x;
-	      evt.screenDelta.y += clampPos.y - panPos.y;
-	    }
-
-	    m_center = m_camera.displayToWorld({
-	      x: m_width / 2,
-	      y: m_height / 2
-	    });
-
-	    m_this.geoTrigger(geo_event.pan, evt);
-
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/set the map rotation.  The rotation is performed around the current
-	   * view center.  Rotation mostly ignores `clampBoundsX`, as the behavior
-	   * feels peculiar otherwise.
-	   *
-	   * @param {number} rotation Absolute angle in radians (positive is
-	   *    clockwise).
-	   * @param {object} [origin] If specified, rotate about this origin.
-	   * @param {geo.geoPosition} origin.geo The gcs coordinates of the
-	   *    rotation center.
-	   * @param {geo.screenPosition} origin.map The display coordinates of the
-	   *    rotation center.
-	   * @param {boolean} [ignoreRotationFunc] If `true`, don't constrain the
-	   *    rotation.
-	   * @returns {number|this}
-	   * @fires geo.event.rotate
-	   * @fires geo.event.pan
-	   */
-	  this.rotation = function (rotation, origin, ignoreRotationFunc) {
-	    if (rotation === undefined) {
-	      return m_rotation;
-	    }
-	    var aroundPoint = (origin && origin.geo && origin.map);
-
-	    rotation = fix_rotation(rotation, ignoreRotationFunc);
-	    if (rotation === m_rotation) {
-	      return m_this;
-	    }
-	    m_rotation = rotation;
-
-	    var bounds = m_this.boundsFromZoomAndCenter(
-	        m_zoom, m_center, m_rotation, null, ignoreRotationFunc, true);
-	    m_this.modified();
-
-	    camera_bounds(bounds, m_rotation);
-
-	    var evt = {
-	      rotation: m_rotation,
-	      screenPosition: origin ? origin.map : undefined
-	    };
-
-	    m_this.geoTrigger(geo_event.rotate, evt);
-
-	    if (aroundPoint) {
-	      var shifted = m_this.gcsToDisplay(origin.geo);
-	      m_this.pan({x: origin.map.x - shifted.x, y: origin.map.y - shifted.y},
-	                 undefined, true);
-	    } else {
-	      m_this.pan({x: 0, y: 0}, undefined, true);
-	    }
-	    /* Changing the rotation can change our minimum zoom */
-	    reset_minimum_zoom();
-	    m_this.zoom(m_zoom, undefined, ignoreRotationFunc);
-	    return m_this;
-	  };
-
-	  /**
-	   * Set center of the map to the given geographic coordinates, or get the
-	   * current center.  Uses bare objects {x: 0, y: 0}.
-	   *
-	   * @param {geo.geoPosition} coordinates If specified, the new center of the
-	   *    map.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.  If setting the
-	   *    center, it is converted from this gcs to the map projection.  The
-	   *    returned center is converted from the map projection to this gcs.
-	   * @param {boolean} [ignoreDiscreteZoom] If `true`, ignore the `discreteZoom`
-	   *    option when determining the new view.
-	   * @param {boolean|'limited'} [ignoreClampBounds] If `true` ignore the
-	   *    `clampBoundsX` and `clampBoundsY` options when determining the new
-	   *    view.  When `'limited'`, the `clampBoundsX` and `clampBoundsY` options
-	   *    are selectively enforced so that the map will not end up more out of
-	   *    bounds than its current state.
-	   * @returns {geo.geoPosition|this}
-	   * @fires geo.event.pan
-	   */
-	  this.center = function (coordinates, gcs, ignoreDiscreteZoom,
-	                          ignoreClampBounds) {
-	    var center;
-	    if (coordinates === undefined) {
-	      center = $.extend({}, m_this.worldToGcs(m_center, gcs));
-	      return center;
-	    }
-
-	    // get the screen coordinates of the new center
-	    center = m_this.gcsToWorld(coordinates, gcs);
-
-	    camera_bounds(m_this.boundsFromZoomAndCenter(
-	        m_zoom, center, m_rotation, null, ignoreDiscreteZoom,
-	        ignoreClampBounds), m_rotation);
-	    m_this.modified();
-	    // trigger a pan event
-	    m_this.geoTrigger(geo_event.pan, {
-	      screenDelta: {x: 0, y: 0}
-	    });
-	    return m_this;
-	  };
-
-	  /**
-	   * Add a layer to the map.
-	   *
-	   * @param {string} layerName The type of layer to add to the map.
-	   * @param {object} arg Parameters for the new layer.
-	   * @returns {geo.layer}
-	   * @fires geo.event.layerAdd
-	   */
-	  this.createLayer = function (layerName, arg) {
-	    arg = arg || {};
-	    var newLayer = registry.createLayer(
-	      layerName, m_this, arg);
-
-	    if (newLayer) {
-	      m_this.addChild(newLayer);
-	      m_this.children().forEach(function (c) {
-	        if (c instanceof uiLayer) {
-	          c.moveToTop();
-	        }
-	      });
-	      newLayer._update();
-	      m_this.modified();
-
-	      m_this.geoTrigger(geo_event.layerAdd, {
-	        target: m_this,
-	        layer: newLayer
-	      });
-	    }
-
-	    return newLayer;
-	  };
-
-	  /**
-	   * Remove a layer from the map.
-	   *
-	   * @param {geo.layer?} layer Layer to remove from the map.
-	   * @returns {geo.layer}
-	   * @fires geo.event.layerRemove
-	   */
-	  this.deleteLayer = function (layer) {
-
-	    if (layer !== null && layer !== undefined) {
-	      layer._exit();
-	      m_this.removeChild(layer);
-
-	      m_this.modified();
-
-	      m_this.geoTrigger(geo_event.layerRemove, {
-	        target: m_this,
-	        layer: layer
-	      });
-	    }
-
-	    // Return deleted layer (similar to createLayer) as in the future
-	    // we may provide extension of this method to support deletion of
-	    // layer using id or some sort.
-	    return layer;
-	  };
-
-	  /**
-	   * Get or set the size of the map.
-	   *
-	   * @param {geo.screenSize} [arg] Size in pixels.
-	   * @returns {geo.screenSize|this} The size in pixels or the map object.
-	   */
-	  this.size = function (arg) {
-	    if (arg === undefined) {
-	      return {
-	        width: m_width,
-	        height: m_height
-	      };
-	    }
-	    // store the original center and restore it after the resize
-	    var oldCenter = m_this.center();
-	    m_width = arg.width || m_width;
-	    m_height = arg.height || m_height;
-
-	    reset_minimum_zoom();
-	    var newZoom = fix_zoom(m_zoom);
-	    if (newZoom !== m_zoom) {
-	      m_this.zoom(newZoom);
-	    }
-	    m_this.camera().viewport = {
-	      width: m_width,
-	      height: m_height,
-	      left: m_node.offset().left,
-	      top: m_node.offset().top
-	    };
-	    m_this.center(oldCenter);
-
-	    m_this.geoTrigger(geo_event.resize, {
-	      target: m_this,
-	      width: m_width,
-	      height: m_height
-	    });
-
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the rotated size of the map.  This is the width and height of the
-	   * non-rotated area necessary to enclose the rotated area in pixels.
-	   *
-	   * @returns {geo.screenSize} The size that fits the rotated map.
-	   */
-	  this.rotatedSize = function () {
-	    if (!this.rotation()) {
-	      return {
-	        width: m_width,
-	        height: m_height
-	      };
-	    }
-	    var bds = rotate_bounds_center(
-	        {x: 0, y: 0}, {width: m_width, height: m_height}, this.rotation());
-	    return {
-	      width: Math.abs(bds.right - bds.left),
-	      height: Math.abs(bds.top - bds.bottom)
-	    };
-	  };
-
-	  /**
-	   * Convert from gcs coordinates to map world coordinates.
-	   *
-	   * @param {geo.geoPosition|geo.geoPosition[]} c The input coordinate to
-	   *    convert.
-	   * @param {string|geo.transform|null} [gcs] Input gcs.  `undefined` to use
-	   *    the interface gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.worldPosition|geo.worldPosition[]} World space coordinates.
-	   */
-	  this.gcsToWorld = function (c, gcs) {
-	    if (Array.isArray(c)) {
-	      return c.map(function (pt) { return m_this.gcsToWorld(pt, gcs); });
-	    }
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    if (gcs !== m_gcs) {
-	      c = transform.transformCoordinates(gcs, m_gcs, c);
-	    }
-	    if (m_origin.x || m_origin.y || m_origin.z) {
-	      c = transform.affineForward(
-	        {origin: m_origin},
-	        [c]
-	      )[0];
-	    } else if (!('z' in c)) {
-	      c = {x: c.x, y: c.y, z: 0};
-	    }
-	    return c;
-	  };
-
-	  /**
-	   * Convert from map world coordinates to gcs coordinates.
-	   *
-	   * @param {geo.worldPosition|geo.worldPosition[]} c The input coordinate to
-	   *    convert.
-	   * @param {string|geo.transform|null} [gcs] output gcs.  `undefined` to use
-	   *    the interface gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.geoPosition|geo.geoPosition[]} GCS space coordinates.
-	   */
-	  this.worldToGcs = function (c, gcs) {
-	    if (Array.isArray(c)) {
-	      return c.map(function (pt) { return m_this.worldToGcs(pt, gcs); });
-	    }
-	    if (m_origin.x || m_origin.y || m_origin.z) {
-	      c = transform.affineInverse(
-	        {origin: m_origin},
-	        [c]
-	      )[0];
-	    } else if (!('z' in c)) {
-	      c = {x: c.x, y: c.y, z: 0};
-	    }
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    if (gcs !== m_gcs) {
-	      c = transform.transformCoordinates(m_gcs, gcs, c);
-	    }
-	    return c;
-	  };
-
-	  /**
-	   * Convert from gcs coordinates to display coordinates.  This is identical to
-	   * calling `gcsToWorld` and then `worldToDisplay`.
-	   *
-	   * @param {geo.geoPosition|geo.geoPosition[]} c The input coordinate to
-	   *    convert.
-	   * @param {string|geo.transform|null} [gcs] Input gcs.  `undefined` to use
-	   *    the interface gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.screenPosition|geo.screenPosition[]} Display space
-	   *    coordinates.
-	   */
-	  this.gcsToDisplay = function (c, gcs) {
-	    c = m_this.gcsToWorld(c, gcs);
-	    return m_this.worldToDisplay(c);
-	  };
-
-	  /**
-	   * Convert from world coordinates to display coordinates using the attached
-	   * camera.
-	   *
-	   * @param {geo.worldPosition|geo.worldPosition[]} c The input coordinate to
-	   *    convert.
-	   * @returns {geo.screenPosition|geo.screenPosition[]} Display space
-	   *    coordinates.
-	   */
-	  this.worldToDisplay = function (c) {
-	    if (Array.isArray(c)) {
-	      return c.map(function (pt) { return m_camera.worldToDisplay(pt); });
-	    }
-	    return m_camera.worldToDisplay(c);
-	  };
-
-	  /**
-	   * Convert from display to gcs coordinates.  This is identical to calling
-	   * `displayToWorld` and then `worldToGcs`.
-	   *
-	   * @param {geo.screenPosition|geo.screenPosition[]} c The input display
-	   *    coordinate to convert.
-	   * @param {string|geo.transform|null} [gcs] Output gcs.  `undefined` to use
-	   *    the interface gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.geoPosition|geoPosition[]} GCS space coordinates.
-	   */
-	  this.displayToGcs = function (c, gcs) {
-	    c = m_this.displayToWorld(c); // done via camera
-	    return m_this.worldToGcs(c, gcs);
-	  };
-
-	  /**
-	   * Convert from display coordinates to world coordinates using the attached
-	   * camera.
-	   *
-	   * @param {geo.screenPosition|geo.screenPosition[]} c The input coordinate to
-	   *    convert.
-	   * @returns {geo.worldPosition|geo.worldPosition[]} World space coordinates.
-	   */
-	  this.displayToWorld = function (c) {
-	    if (Array.isArray(c)) {
-	      return c.map(function (pt) { return m_camera.displayToWorld(pt); });
-	    }
-	    return m_camera.displayToWorld(c);
-	  };
-
-	  /**
-	   * Redraw the map and all its layers.
-	   *
-	   * @returns {this} The map object.
-	   * @fires geo.event.draw
-	   * @fires geo.event.drawEnd
-	   */
-	  this.draw = function () {
-	    var i, layers = m_this.children();
-
-	    m_this.geoTrigger(geo_event.draw, {
-	      target: m_this
-	    });
-
-	    m_this._update();
-
-	    for (i = 0; i < layers.length; i += 1) {
-	      layers[i].draw();
-	    }
-
-	    m_this.geoTrigger(geo_event.drawEnd, {
-	      target: m_this
-	    });
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Get, set, or create and set a file reader to a layer in the map to be used
-	   * as a drop target.
-	   *
-	   * @param {string|object} [readerOrName] `undefined` to get the current
-	   *    reader, an instance of a file reader to set the reader, or a name to
-	   *    create a file reader.
-	   * @param {object} [opts] options Parameters for creating a file reader when
-	   *    the reader is specified by name.  If this includes `layer`, use that
-	   *    layer, otherwise create a layer using these options.
-	   * @returns {geo.fileReader|this}
-	   */
-	  this.fileReader = function (readerOrName, opts) {
-	    if (readerOrName === undefined) {
-	      return m_fileReader;
-	    }
-	    if (typeof readerOrName === 'string') {
-	      opts = opts || {};
-	      if (!opts.layer) {
-	        opts.layer = m_this.createLayer('feature', $.extend({}, opts));
-	      }
-	      opts.renderer = opts.layer.renderer().api();
-	      m_fileReader = registry.createFileReader(readerOrName, opts);
-	    } else {
-	      m_fileReader = readerOrName;
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize the map.
-	   *
-	   * @returns {this} The map object.
-	   */
-	  this._init = function () {
-
-	    if (m_node === undefined || m_node === null) {
-	      throw new Error('Map require DIV node');
-	    }
-
-	    if (m_node.data('data-geojs-map') && $.isFunction(m_node.data('data-geojs-map').exit)) {
-	      m_node.data('data-geojs-map').exit();
-	    }
-	    m_node.addClass('geojs-map');
-	    m_node.data('data-geojs-map', m_this);
-	    return m_this;
-	  };
-
-	  /**
-	   * Update map.  This updates all layers of the map.
-	   *
-	   * @param {object} [request] Optional information about the source of this
-	   *    update request.  This could be an event, for instance.  It is passed
-	   *    to individual layer's `_update` function.
-	   * @returns {this} The map object.
-	   */
-	  this._update = function (request) {
-	    var i, layers = m_this.children();
-	    for (i = 0; i < layers.length; i += 1) {
-	      layers[i]._update(request);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Exit this map.  This removes all layers, destroys current interactor, and
-	   * empties the associated DOM node.
-	   */
-	  this.exit = function () {
-	    var i, layers = m_this.children();
-	    for (i = layers.length - 1; i >= 0; i -= 1) {
-	      layers[i]._exit();
-	      m_this.removeChild(layers[i]);
-	    }
-	    if (m_this.interactor()) {
-	      m_this.interactor().destroy();
-	      m_this.interactor(null);
-	    }
-	    // if the animation queue was shared, this clears it
-	    m_animationQueue = [];
-	    m_this.node().data('data-geojs-map', null);
-	    m_this.node().off('.geo');
-	    /* make sure the map node has nothing left in it */
-	    m_this.node().empty();
-	    $(window).off('resize', resizeSelf);
-	    s_exit();
-	  };
-
-	  /**
-	   * Get or set the map interactor.
-	   *
-	   * @param {geo.mapInteractor} [arg] If specified, the map interactor to set.
-	   * @returns {geo.mapInteractor|this} The current map interactor or the map
-	   *    object.
-	   */
-	  this.interactor = function (arg) {
-	    if (arg === undefined) {
-	      return m_interactor;
-	    }
-	    if (m_interactor && m_interactor !== arg) {
-	      m_interactor.destroy();
-	    }
-	    m_interactor = arg;
-
-	    // this makes it possible to set a null interactor
-	    // i.e. map.interactor(null);
-	    if (m_interactor) {
-	      /* If we set a map interactor, make sure we have a tabindex */
-	      if (!m_node.attr('tabindex')) {
-	        m_node.attr('tabindex', 0);
-	      }
-	      m_interactor.map(m_this);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the min/max zoom range.
-	   *
-	   * @param {object} [arg] The zoom range.
-	   * @param {number} [arg.min] The minimum zoom level.
-	   * @param {number} [arg.max] The maximum zoom level.
-	   * @param {boolean} [noRefresh] If `true`, don't update the map if the zoom
-	   *    level has changed.
-	   * @returns {object|this} The current zoom range or the map object.  The
-	   *    `min` value is the minimum value that the map can go to based on the
-	   *    current dimensions and settings, the `origMin` value is the value that
-	   *    was specified via this function or when the map was created.
-	   */
-	  this.zoomRange = function (arg, noRefresh) {
-	    if (arg === undefined) {
-	      return $.extend({}, m_validZoomRange);
-	    }
-	    if (arg.max !== undefined) {
-	      m_validZoomRange.max = arg.max;
-	    }
-	    if (arg.min !== undefined) {
-	      m_validZoomRange.min = m_validZoomRange.origMin = arg.min;
-	    }
-	    reset_minimum_zoom();
-	    if (!noRefresh) {
-	      m_this.zoom(m_zoom);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the current transition or start an animated zoom/pan/rotate.  If a
-	   * second transition is requested while a transition is already in progress,
-	   * a new transition is created that is functionally from wherever the map has
-	   * moved to (possibly partway through the first transition) going to the end
-	   * point of the new transition.
-	   *
-	   * @param {object} [opts] Options for a transition, or `undefined` to get the
-	   *    current transition.
-	   * @param {geo.geoPosition} [opts.center] A new map center.
-	   * @param {number} [opts.zoom] A new map zoom level.
-	   * @param {geo.geoPosition} [opts.zoomOrigin] An origin to use when zooming
-	   *    to a new zoom level.
-	   * @param {number} [opts.rotation] A new map rotation.
-	   * @param {number} [opts.duration=1000] Transition duration in milliseconds.
-	   * @param {function} [opts.ease] Easing function for the transition.  This is
-	   *    in the style of a d3 easing function.
-	   * @param {function} [opts.interp] Function to use when interpolating
-	   *    between values.  This gets passed two arrays, the start and end values
-	   *    for [`x`, `y`, `z` or `zoom`, `rotation`], and returns a function that,
-	   *    when passed a time value returns an array of the interpolated [`x`,
-	   *    `y`, `z` or `zoom`, `rotation`] values.
-	   * @param {boolean} [opts.zCoord] If `true`, convert zoom values to z values
-	   *    for interpolation.
-	   * @param {function} [opts.done] If specified, call this function when a
-	   *    transition completes.  The function is called with an object that
-	   *    contains `cancel`: a boolean if the transition was canceled, `source`:
-	   *    a value based on what canceled a transition, `transition`: the current
-	   *    transition that just completed, `next`: a boolean if another transition
-	   *    follows immediately.
-	   * @param {string|geo.transform|null} [gcs] Input gcs.  `undefined` to use
-	   *    the interface gcs, `null` to use the map gcs, or any other transform.
-	   *    Applies only to `opts.center` and to converting zoom values to height,
-	   *    if specified.
-	   * @param {number} [animTime] The animation frame time (from a
-	   *    `window.requestAnimationFrame` callback).  Used if a new transition is
-	   *    requested because the current transition has completed to keep things
-	   *    synchronized.
-	   * @returns {geo.map}
-	   * @fires geo.event.transitionstart
-	   * @fires geo.event.transitionend
-	   * @fires geo.event.transitioncancel
-	   */
-	  this.transition = function (opts, gcs, animTime) {
-
-	    if (opts === undefined) {
-	      return m_transition;
-	    }
-
-	    if (m_transition) {
-	      /* The queued transition needs to combine the current transition's
-	       * endpoint, any other queued transition, and the new transition to be
-	       * complete. */
-	      var transitionEnd = $.extend(true, {}, m_transition.end);
-	      if (transitionEnd.center && m_gcs !== m_ingcs) {
-	        transitionEnd.center = transform.transformCoordinates(
-	          m_gcs, m_ingcs, transitionEnd.center);
-	      }
-	      m_queuedTransition = $.extend(
-	        {}, transitionEnd || {}, m_queuedTransition || {}, opts);
-	      return m_this;
-	    }
-
-	    /* Basic linear interpolation between two values. */
-	    function interp1(p0, p1, t) {
-	      return p0 + (p1 - p0) * t;
-	    }
-	    /**
-	     * Generate an interpolation function that interpolates all array entries.
-	     *
-	     * @param {array} p0 An array of numbers to interpolate from.
-	     * @param {array} p1 An array of numbers to interpolate to.
-	     * @returns {function} A function that, given `t`, returns an array of
-	     *      interpolated values.
-	     * @private
-	     */
-	    function defaultInterp(p0, p1) {
-	      return function (t) {
-	        var result = [];
-	        $.each(p0, function (idx) {
-	          result.push(interp1(p0[idx], p1[idx], t));
-	        });
-	        return result;
-	      };
-	    }
-
-	    var units = m_this.unitsPerPixel(0);
-
-	    // Transform zoom level into z-coordinate and inverse.
-	    function zoom2z(z) {
-	      return vgl.zoomToHeight(z + 1, m_width, m_height) * units;
-	    }
-	    function z2zoom(z) {
-	      return vgl.heightToZoom(z / units, m_width, m_height) - 1;
-	    }
-
-	    var defaultOpts = {
-	      center: undefined,
-	      zoom: m_this.zoom(),
-	      rotation: m_this.rotation(),
-	      duration: 1000,
-	      ease: function (t) {
-	        return t;
-	      },
-	      interp: defaultInterp,
-	      done: null,
-	      zCoord: true
-	    };
-
-	    if (opts.center) {
-	      gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	      opts = $.extend(true, {}, opts);
-	      opts.center = util.normalizeCoordinates(opts.center);
-	      if (gcs !== m_gcs) {
-	        opts.center = transform.transformCoordinates(gcs, m_gcs, opts.center);
-	      }
-	    }
-	    opts = $.extend(true, {}, defaultOpts, opts);
-
-	    m_transition = {
-	      start: {
-	        center: m_this.center(undefined, null),
-	        zoom: m_this.zoom(),
-	        rotation: m_this.rotation()
-	      },
-	      end: {
-	        center: opts.center,
-	        zoom: fix_zoom(opts.zoom),
-	        rotation: fix_rotation(opts.rotation, undefined, true)
-	      },
-	      ease: opts.ease,
-	      zCoord: opts.zCoord,
-	      done: opts.done,
-	      duration: opts.duration,
-	      zoomOrigin: opts.zoomOrigin
-	    };
-
-	    m_transition.interp = opts.interp([
-	      m_transition.start.center.x,
-	      m_transition.start.center.y,
-	      opts.zCoord ? zoom2z(m_transition.start.zoom) : m_transition.start.zoom,
-	      m_transition.start.rotation
-	    ], [
-	      m_transition.end.center ? m_transition.end.center.x : m_transition.start.center.x,
-	      m_transition.end.center ? m_transition.end.center.y : m_transition.start.center.y,
-	      opts.zCoord ? zoom2z(m_transition.end.zoom) : m_transition.end.zoom,
-	      m_transition.end.rotation
-	    ]);
-
-	    /**
-	     * Process an animation from during a transition.
-	     *
-	     * @param {number} time The animation frame time.  Used to ensure multiple
-	     *      transitions are smooth.
-	     * @private
-	     */
-	    function anim(time) {
-	      var done = m_transition.done,
-	          next = m_queuedTransition;
-	      if (m_transition.cancel === true) {
-	        /* Finish cancelling a transition. */
-	        m_this.geoTrigger(geo_event.transitioncancel, opts);
-	        if (done) {
-	          done({
-	            cancel: true,
-	            source: m_transition.cancelSource,
-	            transition: m_transition
-	          });
-	        }
-	        m_transition = null;
-	        /* There will only be a queuedTransition if it was created after this
-	         * transition was cancelled */
-	        if (m_queuedTransition) {
-	          next = m_queuedTransition;
-	          m_queuedTransition = null;
-	          m_this.transition(next, undefined, time);
-	        }
-	        return;
-	      }
-
-	      if (!m_transition.start.time) {
-	        m_transition.start.time = time;
-	        m_transition.end.time = time + opts.duration;
-	      }
-	      m_transition.time = time - m_transition.start.time;
-	      if (time >= m_transition.end.time || next) {
-	        if (!next) {
-	          if (m_transition.end.center) {
-	            var needZoom = m_zoom !== fix_zoom(m_transition.end.zoom);
-	            m_this.center(m_transition.end.center, null, needZoom, needZoom);
-	          }
-	          m_this.zoom(m_transition.end.zoom, m_transition.zoomOrigin);
-	          m_this.rotation(fix_rotation(m_transition.end.rotation));
-	        }
-
-	        m_this.geoTrigger(geo_event.transitionend, opts);
-
-	        if (done) {
-	          done({next: !!next});
-	        }
-
-	        m_transition = null;
-	        if (m_queuedTransition) {
-	          next = m_queuedTransition;
-	          m_queuedTransition = null;
-	          m_this.transition(next, undefined, time);
-	        }
-
-	        return;
-	      }
-
-	      var z = m_transition.ease(
-	        (time - m_transition.start.time) / opts.duration
-	      );
-
-	      var p = m_transition.interp(z);
-	      if (m_transition.zCoord) {
-	        p[2] = z2zoom(p[2]);
-	      }
-	      if (fix_zoom(p[2], true) === m_zoom) {
-	        m_this.center({
-	          x: p[0],
-	          y: p[1]
-	        }, null, true, true);
-	      } else {
-	        m_center = m_this.gcsToWorld({x: p[0], y: p[1]}, null, true, true);
-	        m_this.zoom(p[2], m_transition.zoomOrigin, true);
-	      }
-	      m_this.rotation(p[3], undefined, true);
-
-	      m_this.scheduleAnimationFrame(anim);
-	    }
-
-	    m_this.geoTrigger(geo_event.transitionstart, opts);
-
-	    if (geo_event.cancelNavigation) {
-	      m_transition = null;
-	      m_this.geoTrigger(geo_event.transitionend, opts);
-	      return m_this;
-	    } else if (geo_event.cancelAnimation) {
-	      // run the navigation synchronously
-	      opts.duration = 0;
-	      anim(0);
-	    } else if (animTime) {
-	      anim(animTime);
-	    } else {
-	      m_this.scheduleAnimationFrame(anim);
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Cancel any existing transition.  The transition will send a cancel event
-	   * at the next animation frame, but no further activity occurs.
-	   *
-	   * @param {string} [source] Optional cause of the cancel.  This can be any
-	   *    value, but something like `(method name).(action)` is recommended to
-	   *    allow other functions to determine the source and cause of the
-	   *    transition being canceled.
-	   * @returns {boolean} `true` if a transition was in progress.
-	   * @fires geo.event.transitioncancel
-	   */
-	  this.transitionCancel = function (source) {
-	    if (m_transition && (m_transition.cancel !== true || m_queuedTransition)) {
-	      m_transition.cancel = true;
-	      m_transition.cancelSource = source || m_transition.cancelSource || '';
-	      m_queuedTransition = null;
-	      return true;
-	    }
-	    return false;
-	  };
-
-	  /**
-	   * Get/set the locations of the current map edges.  When set, the left-top
-	   * and right-bottom corners are transformed to the map's gcs and then used
-	   * to set the bounds.
-	   *
-	   * @param {geo.geoBounds} [bds] The requested map bounds.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.  If setting the
-	   *    bounds, they are converted from this gcs to the map projection.  The
-	   *    returned bounds are converted from the map projection to this gcs.
-	   * @returns {geo.geoBounds} The actual new map bounds.
-	   */
-	  this.bounds = function (bds, gcs) {
-	    var nav;
-
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    if (bds !== undefined) {
-	      if (gcs !== m_gcs) {
-	        var trans = transform.transformCoordinates(gcs, m_gcs, [{
-	          x: bds.left, y: bds.top}, {x: bds.right, y: bds.bottom}]);
-	        bds = {
-	          left: trans[0].x,
-	          top: trans[0].y,
-	          right: trans[1].x,
-	          bottom: trans[1].y
-	        };
-	      }
-	      bds = fix_bounds(bds, m_rotation);
-	      nav = m_this.zoomAndCenterFromBounds(bds, m_rotation, null);
-
-	      // This might have consequences in terms of bounds/zoom clamping.
-	      // What behavior do we expect from this method in that case?
-	      m_this.zoom(nav.zoom);
-	      m_this.center(nav.center, null);
-	    }
-
-	    return m_this.boundsFromZoomAndCenter(m_zoom, m_center, m_rotation, gcs,
-	                                          true);
-	  };
-
-	  /**
-	   * Get/set the maximum view area of the map.  If the map wraps, this is the
-	   * unwrapped area.
-	   *
-	   * @param {geo.geoBounds} [bounds] The map bounds.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.  If setting the
-	   *    bounds, they are converted from this gcs to the map projection.  The
-	   *    returned bounds are converted from the map projection to this gcs.
-	   * @returns {geo.geoBounds|this} The map maximum bounds or the map object.
-	   */
-	  this.maxBounds = function (bounds, gcs) {
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    if (bounds === undefined) {
-	      return {
-	        left: transform.transformCoordinates(m_gcs, gcs, {
-	          x: m_maxBounds.left, y: 0}).x,
-	        right: transform.transformCoordinates(m_gcs, gcs, {
-	          x: m_maxBounds.right, y: 0}).x,
-	        bottom: transform.transformCoordinates(m_gcs, gcs, {
-	          x: 0, y: m_maxBounds.bottom}).y,
-	        top: transform.transformCoordinates(m_gcs, gcs, {
-	          x: 0, y: m_maxBounds.top}).y
-	      };
-	    }
-	    var cx = ((bounds.left || 0) + (bounds.right || 0)) / 2,
-	        cy = ((bounds.bottom || 0) + (bounds.top || 0)) / 2;
-	    if (bounds.left !== undefined) {
-	      m_maxBounds.left = transform.transformCoordinates(gcs, m_gcs, {
-	        x: bounds.left, y: cy}).x;
-	    }
-	    if (bounds.right !== undefined) {
-	      m_maxBounds.right = transform.transformCoordinates(gcs, m_gcs, {
-	        x: bounds.right, y: cy}).x;
-	    }
-	    if (bounds.bottom !== undefined) {
-	      m_maxBounds.bottom = transform.transformCoordinates(gcs, m_gcs, {
-	        x: cx, y: bounds.bottom}).y;
-	    }
-	    if (bounds.top !== undefined) {
-	      m_maxBounds.top = transform.transformCoordinates(gcs, m_gcs, {
-	        x: cx, y: bounds.top}).y;
-	    }
-	    reset_minimum_zoom();
-	    m_this.zoom(m_zoom);
-	    m_this.pan({x: 0, y: 0});
-	    return this;
-	  };
-
-	  /**
-	   * Get the center zoom level necessary to display the given bounds.
-	   *
-	   * @param {geo.geoBounds} bounds The requested map bounds.  `right` must be
-	   *    greater than `left` and `bottom` must be greater than `top` in the
-	   *    map's gcs (after conversion from the provided gcs).
-	   * @param {number} rotation Rotation in clockwise radians.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @returns {geo.zoomAndCenter}
-	   */
-	  this.zoomAndCenterFromBounds = function (bounds, rotation, gcs) {
-	    var center, zoom;
-
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    if (gcs !== m_gcs) {
-	      var trans = transform.transformCoordinates(gcs, m_gcs, [{
-	        x: bounds.left, y: bounds.top}, {x: bounds.right, y: bounds.bottom}]);
-	      bounds = {
-	        left: trans[0].x,
-	        top: trans[0].y,
-	        right: trans[1].x,
-	        bottom: trans[1].y
-	      };
-	    }
-	    if (bounds.left >= bounds.right || bounds.bottom >= bounds.top) {
-	      throw new Error('Invalid bounds provided');
-	    }
-
-	    // calculate the zoom to fit the bounds
-	    zoom = fix_zoom(calculate_zoom(bounds, rotation));
-
-	    // clamp bounds if necessary
-	    bounds = fix_bounds(bounds, rotation);
-
-	    /* This relies on having the map projection coordinates be uniform
-	     * regardless of location.  If not, the center will not be correct. */
-	    // calculate new center
-	    center = {
-	      x: (bounds.left + bounds.right) / 2 - m_origin.x,
-	      y: (bounds.top + bounds.bottom) / 2 - m_origin.y
-	    };
-	    if (gcs !== m_gcs) {
-	      center = transform.transformCoordinates(m_gcs, gcs, center);
-	    }
-	    return {
-	      zoom: zoom,
-	      center: center
-	    };
-	  };
-
-	  /**
-	   * Get the bounds that will be displayed with the given zoom and center.
-	   *
-	   * Note: the bounds may not have the requested zoom and center due to map
-	   * restrictions.
-	   *
-	   * @param {number} zoom The requested zoom level.
-	   * @param {geo.geoPosition} center The requested center.
-	   * @param {number} rotation The requested rotation in clockwise radians.
-	   * @param {string|geo.transform|null} [gcs] `undefined` to use the interface
-	   *    gcs, `null` to use the map gcs, or any other transform.
-	   * @param {boolean} ignoreDiscreteZoom If `true`, ignore the `discreteZoom`
-	   *    option when determining the new view.
-	   * @param {boolean} [ignoreClampBounds] If `true` and `clampBoundsX` or
-	   *    `clampBoundsY` is set, allow the bounds to be less clamped.
-	   *    The map's `maxBounds` can be shifted so that they lie no further than
-	   *    the center of the bounds (rather than being forced to be at the edge).
-	   * @returns {geo.geoBounds}
-	   */
-	  this.boundsFromZoomAndCenter = function (zoom, center, rotation, gcs,
-	        ignoreDiscreteZoom, ignoreClampBounds) {
-	    var width, height, halfw, halfh, bounds, units;
-
-	    gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
-	    // preprocess the arguments
-	    zoom = fix_zoom(zoom, ignoreDiscreteZoom);
-	    units = m_this.unitsPerPixel(zoom);
-	    center = m_this.gcsToWorld(center, null);
-
-	    // get half the width and height in world coordinates
-	    width = m_width * units;
-	    height = m_height * units;
-	    halfw = width / 2;
-	    halfh = height / 2;
-
-	    // calculate the bounds.  This is only valid if the map projection has
-	    // uniform units in each direction.  If not, then worldToGcs should be
-	    // used.
-
-	    if (rotation) {
-	      center.x += m_origin.x;
-	      center.y += m_origin.y;
-	      bounds = rotate_bounds_center(
-	        center, {width: width, height: height}, rotation);
-	      // correct the bounds when clamping is enabled
-	      bounds.width = width;
-	      bounds.height = height;
-	      bounds = fix_bounds(bounds, rotation, undefined, ignoreClampBounds);
-	    } else {
-	      bounds = {
-	        left: center.x - halfw + m_origin.x,
-	        right: center.x + halfw + m_origin.x,
-	        bottom: center.y - halfh + m_origin.y,
-	        top: center.y + halfh + m_origin.y
-	      };
-	      // correct the bounds when clamping is enabled
-	      bounds = fix_bounds(bounds, 0, undefined, ignoreClampBounds);
-	    }
-	    if (gcs !== m_gcs) {
-	      var bds = transform.transformCoordinates(
-	        m_gcs, gcs,
-	        [[bounds.left, bounds.top], [bounds.right, bounds.bottom]]);
-	      bounds = {
-	        left: bds[0][0], top: bds[0][1], right: bds[1][0], bottom: bds[1][1]
-	      };
-	    }
-	    /* Add the original width and height of the viewport before rotation. */
-	    bounds.width = width;
-	    bounds.height = height;
-	    return bounds;
-	  };
-
-	  /**
-	   * Get/set the discrete zoom flag.  If `true`, the map will snap to integer
-	   * zoom levels.
-	   *
-	   * @param {boolean} [discreteZoom] If specified, the new discrete zoom flag.
-	   * @returns {boolean|this} The current discrete zoom flag or the map object.
-	   */
-	  this.discreteZoom = function (discreteZoom) {
-	    if (discreteZoom === undefined) {
-	      return m_discreteZoom;
-	    }
-	    discreteZoom = discreteZoom ? true : false;
-	    if (m_discreteZoom !== discreteZoom) {
-	      m_discreteZoom = discreteZoom;
-	      if (m_discreteZoom) {
-	        m_this.zoom(Math.round(m_this.zoom()));
-	      }
-	      if (m_this.interactor()) {
-	        m_this.interactor().options({discreteZoom: m_discreteZoom});
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the layers contained in the map.
-	   * Alias of {@linkcode geo.sceneObject#children}.
-	   * @method
-	   */
-	  this.layers = this.children;
-
-	  /**
-	   * Update the attribution notice displayed on the bottom right corner of
-	   * the map.  The content of this notice is managed by individual layers.
-	   * This method queries all of the visible layers and joins the individual
-	   * attribution notices into a single element.  By default, this method
-	   * is called on each of the following events:
-	   *
-	   *   * geo.event.layerAdd
-	   *   * geo.event.layerRemove
-	   *
-	   * In addition, layers should call this method when their own attribution
-	   * notices have changed.  Users, in general, should not need to call this.
-	   *
-	   * @returns {this} Chainable.
-	   */
-	  this.updateAttribution = function () {
-	    // clear any existing attribution content
-	    m_this.node().find('.geo-attribution').remove();
-
-	    // generate a new attribution node
-	    var $a = $('<div/>')
-	      .addClass('geo-attribution')
-	      .on('mousedown', function (evt) {
-	        evt.stopPropagation();
-	      });
-
-	    // append content from each layer
-	    m_this.children().forEach(function (layer) {
-	      var content = layer.attribution();
-	      if (content) {
-	        $('<span/>')
-	          .addClass('geo-attribution-layer')
-	          .html(content)
-	          .appendTo($a);
-	      }
-	    });
-
-	    /* Only add the element if there is at least one attribution */
-	    if ($('span', $a).length) {
-	      $a.appendTo(m_this.node());
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get a screen-shot of all or some of the canvas layers of map.  Note that
-	   * webGL layers are rerendered, even if
-	   *   `window.overrideContextAttributes.preserveDrawingBuffer = true;`
-	   * is set before creating the map object.  Chrome, at least, may not keep the
-	   * drawing buffers if the tab loses focus (and returning focus won't
-	   * necessarily rerender).
-	   *
-	   * @param {geo.layer|geo.layer[]|false|object} [layers] Either a layer, a
-	   *    list of layers, falsy to get all layers, or an object that contains
-	   *    optional values of `layers`, `type`, `encoderOptions`, and additional
-	   *    values listed in the `opts` parameter (this last form allows a single
-	   *    argument for the function).
-	   * @param {string} [type='image/png'] See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
-	   *    canvas.toDataURL}.  Use `'canvas'` to return the canvas element (this
-	   *    can be used to get the results as a blob, which can be faster for some
-	   *    operations but is not supported as widely).
-	   * @param {number} [encoderOptions] See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
-	   *    canvas.toDataURL}.
-	   * @param {object} [opts] Additional screenshot options.
-	   * @param {false|string|CanvasRenderingContext2D.fillStyle}
-	   *    [opts.background='white'] If `false` or `null`, don't prefill the
-	   *    background.  Otherwise, a css color or
-	   *    `CanvasRenderingContext2D.fillStyle` to fill the initial canvas.  This
-	   *    could match the background of the browser page, for instance.
-	   * @param {boolean|'idle'} [opts.wait=false] If `'idle'`, wait for the map to
-	   *    be idle and one additional animation frame to occur.  If truthy, wait
-	   *    for an animation frame to occur.  Otherwise, take the screenshot as
-	   *    soon as possible.
-	   * @param {boolean|null} [opts.attribution=null] If `null` or unspecified,
-	   *    include the attribution only if all layers are used.  If false, never
-	   *    include the attribution.  If `true`, always include it.
-	   * @returns {deferred} A jQuery Deferred object.  The done function receives
-	   *    either a data URL or an `HTMLCanvasElement` with the result.
-	   */
-	  this.screenshot = function (layers, type, encoderOptions, opts) {
-	    var defer;
-
-	    if (layers && !Array.isArray(layers) && !layers.renderer) {
-	      type = type || layers.type;
-	      encoderOptions = encoderOptions || layers.encoderOptions;
-	      opts = opts || layers;
-	      layers = layers.layers;
-	    }
-	    opts = opts || {};
-	    /* if asked to wait, return a Deferred that will do so, calling the
-	     * screenshot function without waiting once it is done. */
-	    if (opts.wait) {
-	      var optsWithoutWait = $.extend({}, opts, {wait: false});
-	      defer = $.Deferred();
-
-	      var waitForRAF = function () {
-	        window.requestAnimationFrame(function () {
-	          defer.resolve();
-	        });
-	      };
-
-	      if (opts.wait === 'idle') {
-	        m_this.onIdle(waitForRAF);
-	      } else {
-	        waitForRAF();
-	      }
-	      return defer.then(function () {
-	        return m_this.screenshot(layers, type, encoderOptions, optsWithoutWait);
-	      });
-	    }
-	    defer = $.when();
-	    // ensure layers is a list of all the layers we want to include
-	    if (!layers) {
-	      layers = m_this.layers();
-	      if (opts.attribution === null || opts.attribution === undefined) {
-	        opts.attribution = true;
-	      }
-	    } else if (!Array.isArray(layers)) {
-	      layers = [layers];
-	    }
-	    // filter to only the included layers
-	    layers = layers.filter(function (l) {
-	      return m_this.layers().indexOf(l) >= 0 &&
-	             l.opacity() > 0 && (!l.visible || l.visible());
-	    });
-	    // sort layers by z-index
-	    layers = layers.sort(
-	      function (a, b) { return (a.zIndex() - b.zIndex()); }
-	    );
-	    // create a new canvas element
-	    var result = document.createElement('canvas');
-	    result.width = m_width;
-	    result.height = m_height;
-	    var context = result.getContext('2d');
-	    // optionally start with a white or custom background
-	    if (opts.background !== false && opts.background !== null) {
-	      var background = opts.background;
-	      if (opts.background === undefined) {
-	        /* If we are using the map's current background, start with white as a
-	         * fallback, then fill with the backgrounds of all parents and the map
-	         * node.  Since each may be partially transparent, this is required to
-	         * match the web page's color.  It won't use background patterns. */
-	        context.fillStyle = 'white';
-	        context.fillRect(0, 0, result.width, result.height);
-	        m_this.node().parents().get().reverse().forEach(function (elem) {
-	          background = window.getComputedStyle(elem).backgroundColor;
-	          if (background && background !== 'transparent') {
-	            context.fillStyle = background;
-	            context.fillRect(0, 0, result.width, result.height);
-	          }
-	        });
-	        background = window.getComputedStyle(m_this.node()[0]).backgroundColor;
-	      }
-	      if (background && background !== 'transparent') {
-	        context.fillStyle = background;
-	        context.fillRect(0, 0, result.width, result.height);
-	      }
-	    }
-	    // for each layer, copy to our new canvas.
-	    layers.forEach(function (layer) {
-	      var opacity = layer.opacity();
-	      layer.node().children('canvas').each(function () {
-	        var canvasElem = $(this);
-	        defer = defer.then(function () {
-	          if (layer.renderer().api() === 'vgl') {
-	            layer.renderer()._renderFrame();
-	          }
-	          drawLayerImageToContext(context, opacity, canvasElem, canvasElem[0]);
-	        });
-	      });
-	      if (layer.node().children().not('canvas').length || !layer.node().children().length) {
-	        defer = defer.then(function () {
-	          return util.htmlToImage(layer.node(), 1).done(function (img) {
-	            drawLayerImageToContext(context, 1, $([]), img);
-	          });
-	        });
-	      }
-	    });
-	    if (opts.attribution) {
-	      m_this.node().find('.geo-attribution').each(function () {
-	        var attrElem = $(this);
-	        defer = defer.then(function () {
-	          return util.htmlToImage(attrElem, 1).done(function (img) {
-	            drawLayerImageToContext(context, 1, $([]), img);
-	          });
-	        });
-	      });
-	    }
-	    defer = defer.then(function () {
-	      var canvas = result;
-	      if (type !== 'canvas') {
-	        try {
-	          result = result.toDataURL(type, encoderOptions);
-	        } catch (err) {
-	          console.warn('Failed to convert screenshot to output', err);
-	          var failure = $.Deferred();
-	          failure.reject();
-	          return failure;
-	        }
-	      }
-	      m_this.geoTrigger(geo_event.screenshot.ready, {
-	        canvas: canvas,
-	        screenshot: result
-	      });
-	      return result;
-	    });
-	    return defer;
-	  };
-
-	  /**
-	   * Instead of each function using `window.requestAnimationFrame`, schedule
-	   * all such frames through this function.  This allows the callbacks to be
-	   * reordered or removed as needed and reduces overhead in Chrome a small
-	   * amount.  Also, if the animation queue is shared between map instances, the
-	   * callbacks will be called in a single time slice, providing better
-	   * synchronization.
-	   *
-	   * @param {function} callback Function to call during the animation frame.
-	   *    It is called with an animation epoch, exactly as
-	   *    `requestAnimationFrame`.
-	   * @param {boolean|'remove'} [action=false] Falsy to only add the callback if
-	   *    it is not already scheduled.  `'remove'` to remove the callback (use
-	   *    this instead of `cancelAnimationFrame`).  Any other truthy value moves
-	   *    the callback to the end of the list.
-	   * @returns {number} An integer as returned by
-	   *    `window.requestAnimationFrame`.
-	   */
-	  this.scheduleAnimationFrame = function (callback, action) {
-	    if (!m_animationQueue.length) {
-	      /* By refering to requestAnimationFrame as a property of window, versus
-	       * explicitly using window.requestAnimationFrame, we prevent the
-	       * stripping of 'window' off of the reference and allow our tests to
-	       * override this if needed. */
-	      m_animationQueue.push(window['requestAnimationFrame'](processAnimationFrame));
-	    }
-	    var pos = m_animationQueue.indexOf(callback, 1);
-	    if (pos >= 0) {
-	      if (!action) {
-	        return;
-	      }
-	      m_animationQueue.splice(pos, 1);
-	      if (action === 'remove') {
-	        return;
-	      }
-	    }
-	    m_animationQueue.push(callback);
-	    return m_animationQueue[0];
-	  };
-
-	  /**
-	   * Draw a layer image to a canvas context.  The layer's opacity and transform
-	   * are applied.  This is used as part of making a screenshot.
-	   *
-	   * @param {context} context The 2d canvas context to draw into.
-	   * @param {number} opacity The opacity in the range [0, 1].
-	   * @param {object} elem A jQuery element that might have a transform.
-	   * @param {HTMLImageObject} img The image or canvas to draw to the canvas.
-	   * @private
-	   */
-	  function drawLayerImageToContext(context, opacity, elem, img) {
-	    context.globalAlpha = opacity;
-	    var transform = elem.css('transform');
-	    // if the canvas is being transformed, apply the same transformation
-	    if (transform && transform.substr(0, 7) === 'matrix(') {
-	      context.setTransform.apply(context, transform.substr(7, transform.length - 8).split(',').map(parseFloat));
-	    } else {
-	      context.setTransform(1, 0, 0, 1, 0, 0);
-	    }
-	    context.drawImage(img, 0, 0);
-	  }
-
-	  /**
-	   * Sevice the callback during an animation frame.  This uses splice to modify
-	   * the `animationQueue` to allow multiple map instances to share the queue.
-	   * @private
-	   */
-	  function processAnimationFrame() {
-	    var queue = m_animationQueue.splice(0, m_animationQueue.length);
-
-	    /* The first entry is the reference to the window.requestAnimationFrame. */
-	    for (var i = 1; i < queue.length; i += 1) {
-	      queue[i].apply(this, arguments);
-	    }
-	  }
-
-	  /*
-	   * The following are some private methods for interacting with the camera.
-	   * In order to hide the complexity of dealing with map aspect ratios,
-	   * clamping behavior, resetting zoom levels on resize, etc. from the
-	   * layers, the map handles camera movements directly.  This requires
-	   * passing all camera movement events through the map initially.  The
-	   * map uses these methods to fix up the events according to the constraints
-	   * of the display and passes the event to the layers.
-	   */
-	  /**
-	   * Calculate the scaling factor to fit the given map bounds into the viewport
-	   * with the correct aspect ratio.
-	   *
-	   * @param {geo.geoBounds} bounds A desired bounds.
-	   * @returns {object} Multiplicative aspect ratio correction with x and y
-	   *    values.
-	   * @private
-	   */
-	  function camera_scaling(bounds) {
-	    var width = bounds.right - bounds.left,
-	        height = bounds.top - bounds.bottom,
-	        ar_bds = Math.abs(width / height),
-	        ar_vp = m_width / m_height,
-	        sclx, scly;
-
-	    if (ar_bds > ar_vp) {
-	      // fit left and right
-	      sclx = 1;
-
-	      // grow top and bottom
-	      scly = ar_bds / ar_vp;
-	    } else {
-	      // fit top and bottom
-	      scly = 1;
-
-	      // grow left and right
-	      sclx = ar_vp / ar_bds;
-	    }
-	    return {x: sclx, y: scly};
-	  }
-
-	  /**
-	   * Adjust a set of bounds based on a rotation.  If a rotation exists, the
-	   * returned bounds are typically larger than the source bounds.
-	   *
-	   * @param {geo.geoBounds} bounds Bounds to adjust.
-	   * @param {number} rotation Angle in radians (positive is clockwise).
-	   * @returns {geo.geoBounds}
-	   * @private
-	   */
-	  function rotate_bounds(bounds, rotation) {
-	    if (rotation) {
-	      var center = {
-	        x: (bounds.left + bounds.right) / 2,
-	        y: (bounds.top + bounds.bottom) / 2
-	      };
-	      var size = {
-	        width: Math.abs(bounds.left - bounds.right),
-	        height: Math.abs(bounds.top - bounds.bottom)
-	      };
-	      bounds = rotate_bounds_center(center, size, rotation);
-	    }
-	    return bounds;
-	  }
-
-	  /**
-	   * Generate a set of bounds based on a center point, a width and height, and
-	   * a rotation.
-	   *
-	   * @param {geo.geoPosition} center
-	   * @param {object} size Size of the screen in map gcs.
-	   * @param {number} size.width
-	   * @param {number} size.height
-	   * @param {number} rotation Angle in radians (positive is clockwise).
-	   * @returns {geo.geoBounds}
-	   * @private
-	   */
-	  function rotate_bounds_center(center, size, rotation) {
-	    // calculate the half width and height
-	    var width = size.width / 2, height = size.height / 2;
-	    var sinr = Math.sin(rotation), cosr = Math.cos(rotation);
-	    var ul = {}, ur = {}, ll = {}, lr = {};
-	    ul.x = center.x + (-width) * cosr - (-height) * sinr;
-	    ul.y = center.y + (-width) * sinr + (-height) * cosr;
-	    ur.x = center.x + width * cosr - (-height) * sinr;
-	    ur.y = center.y + width * sinr + (-height) * cosr;
-	    ll.x = center.x + (-width) * cosr - height * sinr;
-	    ll.y = center.y + (-width) * sinr + height * cosr;
-	    lr.x = center.x + width * cosr - height * sinr;
-	    lr.y = center.y + width * sinr + height * cosr;
-	    return {
-	      left: Math.min(ul.x, ur.x, ll.x, lr.x),
-	      right: Math.max(ul.x, ur.x, ll.x, lr.x),
-	      bottom: Math.min(ul.y, ur.y, ll.y, lr.y),
-	      top: Math.max(ul.y, ur.y, ll.y, lr.y)
-	    };
-	  }
-
-	  /**
-	   * Calculate the minimum zoom level to fit the given bounds inside the view
-	   * port using the view port size, the given bounds, and the number of units
-	   * per pixel.  The method sets the valid zoom bounds as well as the current
-	   * zoom level to be within that range.
-	   *
-	   * @param {geo.geoBounds} bounds Bounds to fit to the screen.
-	   * @param {number} [rotation] Rotation in radians.  If unspecified, use the
-	   *    current map rotation.
-	   * @returns {number} The necessary zoom level.
-	   * @private
-	   */
-	  function calculate_zoom(bounds, rotation) {
-	    if (rotation === undefined) {
-	      rotation = m_rotation;
-	    }
-	    bounds = rotate_bounds(bounds, rotation);
-	    // compare the aspect ratios of the viewport and bounds
-	    var scl = camera_scaling(bounds), z;
-
-	    if (scl.y > scl.x) {
-	      // left to right matches exactly
-	      // center map vertically and have blank borders on the
-	      // top and bottom (or repeat tiles)
-	      z = -Math.log2(
-	        Math.abs(bounds.right - bounds.left) * scl.x /
-	        (m_width * m_unitsPerPixel)
-	      );
-	    } else {
-	      // top to bottom matches exactly, blank border on the
-	      // left and right (or repeat tiles)
-	      z = -Math.log2(
-	        Math.abs(bounds.top - bounds.bottom) * scl.y /
-	        (m_height * m_unitsPerPixel)
-	      );
-	    }
-	    return z;
-	  }
-
-	  /**
-	   * Reset the minimum zoom level given the current window size.
-	   * @private
-	   */
-	  function reset_minimum_zoom() {
-	    if (m_clampZoom) {
-	      m_validZoomRange.min = Math.max(
-	          m_validZoomRange.origMin, calculate_zoom(m_maxBounds));
-	    } else {
-	      m_validZoomRange.min = m_validZoomRange.origMin;
-	    }
-	  }
-
-	  /**
-	   * Return the nearest valid zoom level to the requested zoom.
-	   * @param {number} zoom A zoom level to adjust to current settings
-	   * @param {boolean} ignoreDiscreteZoom If `true`, ignore the `discreteZoom`
-	   *    option when determining the new view.
-	   * @returns {number} The zoom level clamped to the allowed zoom range and
-	   *    with other settings applied.
-	   * @private
-	   */
-	  function fix_zoom(zoom, ignoreDiscreteZoom) {
-	    zoom = Math.round(zoom * 1e6) / 1e6;
-	    zoom = Math.max(
-	      Math.min(
-	        m_validZoomRange.max,
-	        zoom
-	      ),
-	      m_validZoomRange.min
-	    );
-	    if (m_discreteZoom && !ignoreDiscreteZoom) {
-	      zoom = Math.round(zoom);
-	      if (zoom < m_validZoomRange.min) {
-	        zoom = Math.ceil(m_validZoomRange.min);
-	      }
-	    }
-	    return zoom;
-	  }
-
-	  /**
-	   * Return a valid rotation angle.
-	   *
-	   * @param {number} rotation Proposed rotation.
-	   * @param {boolean} ignoreRotationFunc If truthy and rotations are allowed,
-	   *    allow any rotation.  Otherwise, the rotation is passed through the
-	   *    `allowRotation` function.
-	   * @param {boolean} noRangeLimit If falsy, ensure that the rotation is in the
-	   *    range [0, 2*PI).  If it is very close to zero, it is snapped to zero.
-	   *    If true, the rotation can have any value.
-	   * @returns {number} the validated rotation
-	   * @private
-	   */
-	  function fix_rotation(rotation, ignoreRotationFunc, noRangeLimit) {
-	    if (!m_allowRotation) {
-	      return 0;
-	    }
-	    if (!ignoreRotationFunc && typeof m_allowRotation === 'function') {
-	      rotation = m_allowRotation(rotation);
-	    }
-	    /* Ensure that the rotation is in the range [0, 2pi) */
-	    if (!noRangeLimit) {
-	      var range = Math.PI * 2;
-	      rotation = (rotation % range) + (rotation >= 0 ? 0 : range);
-	      if (Math.min(Math.abs(rotation), Math.abs(rotation - range)) < 0.00001) {
-	        rotation = 0;
-	      }
-	    }
-	    return rotation;
-	  }
-
-	  /**
-	   * Return the nearest valid bounds maintaining the width and height.  Does
-	   * nothing if `clampBoundsX` and `clampBoundsY` are false.  If a delta is
-	   * specified, will only clamp if the out-of-bounds condition would be worse.
-	   * If `ignoreClampBounds` is true, clamping is applied only to prevent more
-	   * than half the image from being off screen.
-	   *
-	   * @param {geo.geoBounds} bounds The new bounds to apply in map gcs
-	   *    coordinates.
-	   * @param {number} [rotation] The angle of rotation in radians.  May be falsy
-	   *    to have no rotation.
-	   * @param {object} [delta] If present, the shift in position in screen
-	   *    coordinates.  Bounds will only be adjusted if the bounds would be
-	   *    more out of position after the shift.
-	   * @param {number} delta.x
-	   * @param {number} delta.y
-	   * @param {number} delta.unit Units per pixel at the current zoom level.
-	   * @param {boolean} [ignoreClampBounds] If `true` and `clampBoundsX` or
-	   *    `clampBoundsY` are set, allow the bounds to be less clamped.
-	   *    Specifically, the map's `maxBounds` can be shifted so that they lie no
-	   *    further than the center of the bounds (rather than being forced to be
-	   *    at the edge).
-	   * @returns {geo.geoBounds} The adjusted bounds.  This may be the same object
-	   *    passed in `bounds`.
-	   * @private
-	   */
-	  function fix_bounds(bounds, rotation, delta, ignoreClampBounds) {
-	    if (!m_clampBoundsX && !m_clampBoundsY) {
-	      return bounds;
-	    }
-	    var dx, dy, maxBounds = m_maxBounds;
-	    if (rotation) {
-	      maxBounds = $.extend({}, m_maxBounds);
-	      /* When rotated, expand the maximum bounds so that they will allow the
-	       * corners to be visible.  We know the rotated bounding box, plus the
-	       * original maximum bounds.  To fit the corners of the maximum bounds, we
-	       * can expand the total bounds by the same factor that the rotated
-	       * bounding box is expanded from the non-rotated bounding box (for a
-	       * small rotation, this is sin(rotation) * (original bounding box height)
-	       * in the width).  This feels like appropriate behaviour with one of the
-	       * two bounds clamped.  With both, it seems mildly peculiar. */
-	      var bw = Math.abs(bounds.right - bounds.left),
-	          bh = Math.abs(bounds.top - bounds.bottom),
-	          absinr = Math.abs(Math.sin(rotation)),
-	          abcosr = Math.abs(Math.cos(rotation)),
-	          ow, oh;
-	      if (bounds.width && bounds.height) {
-	        ow = bounds.width;
-	        oh = bounds.height;
-	      } else if (Math.abs(absinr - abcosr) < 0.0005) {
-	        /* If we are close to a 45 degree rotation, it is ill-determined to
-	         * compute the original (pre-rotation) bounds width and height.  In
-	         * this case, assume that we are using the map's aspect ratio. */
-	        if (m_width && m_height) {
-	          var aspect = Math.abs(m_width / m_height);
-	          var fac = Math.pow(1 + Math.pow(aspect, 2), 0.5);
-	          ow = Math.max(bw, bh) / fac;
-	          oh = ow * aspect;
-	        } else {
-	          /* Fallback if we don't have width or height */
-	          ow = bw * abcosr;
-	          oh = bh * absinr;
-	        }
-	      } else {
-	        /* Compute the pre-rotation (original) bounds width and height */
-	        ow = (abcosr * bw - absinr * bh) / (abcosr * abcosr - absinr * absinr);
-	        oh = (abcosr * bh - absinr * bw) / (abcosr * abcosr - absinr * absinr);
-	      }
-	      /* Our maximum bounds are expanded based on the projected length of a
-	       * tilted side of the original bounding box in the rotated bounding box.
-	       * To handle all rotations, take the minimum difference in width or
-	       * height. */
-	      var bdx = bw - Math.max(abcosr * ow, absinr * oh),
-	          bdy = bh - Math.max(abcosr * oh, absinr * ow);
-	      maxBounds.left -= bdx;
-	      maxBounds.right += bdx;
-	      maxBounds.top += bdy;
-	      maxBounds.bottom -= bdy;
-	    }
-	    if (ignoreClampBounds) {
-	      maxBounds = {
-	        left: maxBounds.left - (bounds.right - bounds.left) / 2,
-	        right: maxBounds.right + (bounds.right - bounds.left) / 2,
-	        top: maxBounds.top - (bounds.bottom - bounds.top) / 2,
-	        bottom: maxBounds.bottom + (bounds.bottom - bounds.top) / 2
-	      };
-	    }
-	    if (m_clampBoundsX) {
-	      if (bounds.right - bounds.left > maxBounds.right - maxBounds.left) {
-	        dx = maxBounds.left - ((bounds.right - bounds.left - (
-	          maxBounds.right - maxBounds.left)) / 2) - bounds.left;
-	      } else if (bounds.left < maxBounds.left) {
-	        dx = maxBounds.left - bounds.left;
-	      } else if (bounds.right > maxBounds.right) {
-	        dx = maxBounds.right - bounds.right;
-	      }
-	      if (dx && (!delta || delta.x * dx > 0)) {
-	        if (delta && Math.abs(dx) > Math.abs(delta.x * delta.unit)) {
-	          dx = Math.abs(delta.x * delta.unit) * dx / Math.abs(dx);
-	        }
-	        bounds = {
-	          left: bounds.left += dx,
-	          right: bounds.right += dx,
-	          top: bounds.top,
-	          bottom: bounds.bottom
-	        };
-	      }
-	    }
-	    if (m_clampBoundsY) {
-	      if (bounds.top - bounds.bottom > maxBounds.top - maxBounds.bottom) {
-	        dy = maxBounds.bottom - ((bounds.top - bounds.bottom - (
-	          maxBounds.top - maxBounds.bottom)) / 2) - bounds.bottom;
-	      } else if (bounds.top > maxBounds.top) {
-	        dy = maxBounds.top - bounds.top;
-	      } else if (bounds.bottom < maxBounds.bottom) {
-	        dy = maxBounds.bottom - bounds.bottom;
-	      }
-	      if (dy && (!delta || -delta.y * dy > 0)) {
-	        if (delta && Math.abs(dy) > Math.abs(delta.y * delta.unit)) {
-	          dy = Math.abs(delta.y * delta.unit) * dy / Math.abs(dy);
-	        }
-	        bounds = {
-	          top: bounds.top += dy,
-	          bottom: bounds.bottom += dy,
-	          left: bounds.left,
-	          right: bounds.right
-	        };
-	      }
-	    }
-	    return bounds;
-	  }
-
-	  /**
-	   * Call the camera bounds method with the given bounds, but correct for the
-	   * viewport aspect ratio.
-	   *
-	   * @param {geo.geoBounds} bounds The bounds for the camera.  If a rotation
-	   *    is specified, the bounds need to also contain the map gcs width and
-	   *    height.
-	   * @param {number} [rotation] The map rotation in radians.
-	   * @private
-	   */
-	  function camera_bounds(bounds, rotation) {
-	    m_camera.rotation = rotation || 0;
-	    /* When dealing with rotation, use the original width and height of the
-	     * bounds, as the rotation will have expanded them. */
-	    if (bounds.width && bounds.height && rotation) {
-	      var cx = (bounds.left + bounds.right) / 2,
-	          cy = (bounds.top + bounds.bottom) / 2;
-	      m_camera.viewFromCenterSizeRotation({x: cx, y: cy}, bounds, rotation);
-	    } else {
-	      m_camera.bounds = bounds;
-	    }
-	    /* Update the center to what was set. */
-	    m_center = {
-	      x: (m_camera.bounds.left + m_camera.bounds.right) / 2,
-	      y: (m_camera.bounds.top + m_camera.bounds.bottom) / 2
-	    };
-	  }
-
-	  /**
-	   * Resize the map based on the size of the associated DOM node.
-	   * @private
-	   */
-	  function resizeSelf() {
-	    m_this.size({width: m_node.width(), height: m_node.height()});
-	  }
-
-	  /*
-	   * All the methods are now defined.  From here, we are initializing all
-	   * internal variables and event handlers.
-	   */
-
-	  this._init(arg);
-
-	  // set up drag/drop handling
-	  this.node().on('dragover.geo', function (e) {
-	    var evt = e.originalEvent;
-
-	    if (m_this.fileReader()) {
-	      evt.stopPropagation();
-	      evt.preventDefault();
-	      evt.dataTransfer.dropEffect = 'copy';
-	    }
-	  })
-	  .on('drop.geo', function (e) {
-	    var evt = e.originalEvent, reader = m_this.fileReader(),
-	        i, file;
-
-	    function done() {
-	      m_this.draw();
-	    }
-
-	    if (reader) {
-	      evt.stopPropagation();
-	      evt.preventDefault();
-
-	      for (i = 0; i < evt.dataTransfer.files.length; i += 1) {
-	        file = evt.dataTransfer.files[i];
-	        if (reader.canRead(file)) {
-	          reader.read(file, done); // to do: trigger event on done
-	        }
-	      }
-	    }
-	  });
-
-	  /*
-	   * The map coordinates for the default world map, where c = half
-	   * circumference at equator in meters, o = origin:
-	   *   (-c, c) + o                   (c, c) + o
-	   *            (center.x, center.y) + o            <-- center of viewport
-	   *   (-c, -c) + o                  (c, -c) + o
-	   */
-	  // Set the world origin
-	  m_origin = {x: 0, y: 0};
-
-	  // Fix the zoom level (minimum and initial)
-	  this.zoomRange(arg, true);
-	  m_zoom = fix_zoom(m_zoom);
-	  m_rotation = fix_rotation(m_rotation);
-	  // Now update to the correct center and zoom level
-	  this.center($.extend({}, arg.center || m_center), undefined);
-
-	  if (arg.interactor !== null) {
-	    this.interactor(arg.interactor || mapInteractor({discreteZoom: m_discreteZoom}));
-	  }
-
-	  if (m_autoResize) {
-	    $(window).on('resize', resizeSelf);
-	  }
-
-	  // attach attribution updates to layer events
-	  m_this.geoOn([
-	    geo_event.layerAdd,
-	    geo_event.layerRemove
-	  ], m_this.updateAttribution);
-
-	  return this;
-	};
-
-	/**
-	 * Create a map from an object.  Any errors in the creation
-	 * of the map will result in returning `null`.
-	 *
-	 * @param {geo.map.spec} spec The object specification.
-	 * @returns {geo.map|null}
-	 */
-	map.create = function (spec) {
-	  'use strict';
-
-	  var _map = map(spec),
-	      layer = __webpack_require__(210);
-
-	  /* If the spec is bad, we still end up with an object, but it won't have a
-	   * zoom function */
-	  if (!_map || !_map.zoom) {
-	    console.warn('Could not create map.');
-	    return null;
-	  }
-
-	  spec.data = spec.data || [];
-	  spec.layers = spec.layers || [];
-
-	  spec.layers.forEach(function (l) {
-	    l.data = l.data || spec.data;
-	    l.layer = layer.create(_map, l);
-	  });
-
-	  return _map;
-	};
-
-	inherit(map, sceneObject);
-	module.exports = map;
-
-
-/***/ }),
-/* 241 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerLayer = __webpack_require__(201).registerLayer;
-	var layer = __webpack_require__(210);
-
-	/**
-	 * Create a new instance of class uiLayer.
-	 *
-	 * @class
-	 * @alias geo.gui.uiLayer
-	 * @extends {geo.layer}
-	 * @param {object} [arg] Options for the layer.
-	 * @returns {geo.gui.uiLayer}
-	 */
-	var uiLayer = function (arg) {
-	  'use strict';
-
-	  var createWidget = __webpack_require__(201).createWidget;
-
-	  // The widget stays fixed on the screen.
-	  arg.renderer = 'dom';
-	  arg.sticky = false;
-
-	  if (!(this instanceof uiLayer)) {
-	    return new uiLayer(arg);
-	  }
-	  layer.call(this, arg);
-
-	  var m_this = this,
-	      s_exit = this._exit;
-
-	  /**
-	   * Create a new ui control.
-	   *
-	   * @param {string} widgetName The name of the widget.
-	   * @param {object} arg Options for the widget.
-	   * @param {geo.object} [arg.parent] A parent object for the widget.
-	   * @returns {geo.gui.widget} The new widget.
-	   */
-	  this.createWidget = function (widgetName, arg) {
-	    var newWidget = createWidget(widgetName, m_this, arg);
-
-	    // We only want top level widgets to be a child of the uiLayer
-	    if (!(arg && 'parent' in arg)) {
-	      m_this.addChild(newWidget);
-	    }
-
-	    newWidget._init(arg);
-	    m_this.modified();
-	    return newWidget;
-	  };
-
-	  /**
-	   * Delete a ui control.
-	   *
-	   * @param {geo.gui.widget} widget The widget to remove.
-	   * @returns {this}
-	   */
-	  this.deleteWidget = function (widget) {
-	    widget._exit();
-	    m_this.removeChild(widget);
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Free memory and destroy the layer.
-	   */
-	  this._exit = function () {
-	    m_this.children().forEach(function (child) {
-	      m_this.deleteWidget(child);
-	    });
-	    s_exit();
-	  };
-	};
-
-	inherit(uiLayer, layer);
-
-	registerLayer('ui', uiLayer);
-	module.exports = uiLayer;
-
-
-/***/ }),
-/* 242 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = (function () {
-	  'use strict';
-
-	  var $ = __webpack_require__(1);
-	  var inherit = __webpack_require__(8);
-	  var tileLayer = __webpack_require__(243);
-	  var registry = __webpack_require__(201);
-	  var quadFeature = __webpack_require__(223);
-
-	  /**
-	   * Create a new instance of osmLayer.
-	   *
-	   * @class geo.osmLayer
-	   * @extends geo.featureLayer
-	   *
-	   * @param {object} arg - arg can contain following keys: baseUrl,
-	   *        imageFormat (such as png or jpeg), and displayLast
-	   *        (to decide whether or not render tiles from last zoom level).
-	   */
-	  var osmLayer = function (arg) {
-
-	    var imageTile = __webpack_require__(237);
-
-	    if (!(this instanceof osmLayer)) {
-	      return new osmLayer(arg);
-	    }
-	    if (arg.mapOpacity !== undefined && arg.opacity === undefined) {
-	      arg.opacity = arg.mapOpacity;
-	    }
-	    tileLayer.call(this, arg);
-
-	    /* mapOpacity is just another name for the layer opacity. */
-	    this.mapOpacity = this.opacity;
-
-	    /**
-	     * Returns an instantiated imageTile object with the given indices.  This
-	     * method always returns a new tile object.  Use `_getTileCached`
-	     * to use the caching layer.
-	     * @param {object} index The tile index
-	     * @param {number} index.x
-	     * @param {number} index.y
-	     * @param {number} index.level
-	     * @param {object} source The tile index used for constructing the url
-	     * @param {number} source.x
-	     * @param {number} source.y
-	     * @param {number} source.level
-	     * @returns {geo.tile}
-	     */
-	    this._getTile = function (index, source) {
-	      var urlParams = source || index;
-	      return imageTile({
-	        index: index,
-	        size: {x: this._options.tileWidth, y: this._options.tileHeight},
-	        queue: this._queue,
-	        overlap: this._options.tileOverlap,
-	        scale: this._options.tileScale,
-	        url: this._options.url.call(
-	            this, urlParams.x, urlParams.y, urlParams.level || 0,
-	            this._options.subdomains),
-	        crossDomain: this._options.crossDomain
-	      });
-	    }.bind(this);
-	  };
-
-	  /**
-	   * This object contains the default options used to initialize the osmLayer.
-	   */
-	  osmLayer.defaults = $.extend({}, tileLayer.defaults, {
-	    minLevel: 0,
-	    maxLevel: 18,
-	    tileOverlap: 0,
-	    tileWidth: 256,
-	    tileHeight: 256,
-	    tileOffset : function (level) {
-	      var s = Math.pow(2, level - 1) * 256;
-	      return {x: s, y: s};
-	    },
-	    wrapX: true,
-	    wrapY: false,
-	    url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
-	    attribution: 'Tile data &copy; <a href="http://osm.org/copyright">' +
-	      'OpenStreetMap</a> contributors'
-	  });
-
-	  inherit(osmLayer, tileLayer);
-	  /* By default, ask to support image quads.  If the user needs full
-	   * reprojection, they will need to require the
-	   * quadFeature.capabilities.imageFull feature */
-	  registry.registerLayer('osm', osmLayer, [quadFeature.capabilities.image]);
-	  return osmLayer;
-	})();
-
-
-/***/ }),
-/* 243 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = (function () {
-	  'use strict';
-
-	  var inherit = __webpack_require__(8);
-	  var featureLayer = __webpack_require__(220);
-
-	  /**
-	   * Standard modulo operator where the output is in [0, b) for all inputs.
-	   * @private
-	   * @param {number} a Any finite number.
-	   * @param {number} b A positive number.
-	   * @returns {number} The positive version of `a % b`.
-	   */
-	  function modulo(a, b) {
-	    return ((a % b) + b) % b;
-	  }
-
-	  /**
-	   * Pick a subdomain from a list of subdomains based on a the tile location.
-	   *
-	   * @param {number} x The x tile coordinate.
-	   * @param {number} y The y tile coordinate.
-	   * @param {number} z The tile layer.
-	   * @param {string[]} subdomains The list of known subdomains.
-	   * @returns {string} A subdomain based on the location.
-	   */
-	  function m_getTileSubdomain(x, y, z, subdomains) {
-	    return subdomains[modulo(x + y + z, subdomains.length)];
-	  }
-
-	  /**
-	   * Returns an OSM tile server formatting function from a standard format
-	   * string. Replaces `{s}`, `{z}`, `{x}`, and `{y}`.  These may be any case
-	   * and may be prefixed with `$` (e.g., `${X}` is the same as `{x}`).  The
-	   * subdomain can be specifed by a string of characters, listed as a range,
-	   * or as a comma-separated list (e.g., `{s:abc}`, `{a-c}`, `{a,b,c}` are
-	   * all equivalent.  The comma-separated list can have subdimains that are of
-	   * any length; the string and range both use one-character subdomains.
-	   *
-	   * @param {string} base The tile format string
-	   * @returns {function} A conversion function.
-	   * @private.
-	   */
-	  function m_tileUrlFromTemplate(base) {
-	    var xPattern = new RegExp(/\$?\{[xX]\}/),
-	        yPattern = new RegExp(/\$?\{[yY]\}/),
-	        zPattern = new RegExp(/\$?\{[zZ]\}/),
-	        sPattern = new RegExp(/\$?\{(s|S|[sS]:[^{}]+|[^-{}]-[^-{}]|([^,{}]+,)+[^,{}]+)\}/);
-	    var url = base
-	        .replace(sPattern, '{s}')
-	        .replace(xPattern, '{x}')
-	        .replace(yPattern, '{y}')
-	        .replace(zPattern, '{z}');
-	    var urlSubdomains;
-	    var sMatch = base.match(sPattern);
-	    if (sMatch) {
-	      if (sMatch[2]) {
-	        urlSubdomains = sMatch[1].split(',');
-	      } else if (sMatch[1][1] === ':') {
-	        urlSubdomains = sMatch[1].substr(2).split('');
-	      } else if (sMatch[1][1] === '-') {
-	        urlSubdomains = [];
-	        var start = sMatch[1].charCodeAt(0),
-	            end = sMatch[1].charCodeAt(2);
-	        for (var i = Math.min(start, end); i <= Math.max(start, end); i += 1) {
-	          urlSubdomains.push(String.fromCharCode(i));
-	        }
-	      }
-	    }
-
-	    return function (x, y, z, subdomains) {
-	      return url
-	        .replace('{s}', m_getTileSubdomain(x, y, z, urlSubdomains || subdomains))
-	        .replace('{x}', x)
-	        .replace('{y}', y)
-	        .replace('{z}', z);
-	    };
-	  }
-
-	  /**
-	   * This method defines a tileLayer, which is an abstract class defining a
-	   * layer divided into tiles of arbitrary data.  Notably, this class provides
-	   * the core functionality of the osmLayer, but hooks exist to render tiles
-	   * dynamically from vector data, or to display arbitrary grids of images
-	   * in a custom coordinate system.  When multiple zoom levels are present
-	   * in a given dataset, this class assumes that the space occupied by
-	   * tile (i, j) at level z is covered by a 2x2 grid of tiles at zoom
-	   * level z + 1:
-	   *
-	   *   (2i, 2j),     (2i, 2j + 1)
-	   *   (2i + 1, 2j), (2i + 1, 2j + 1)
-	   *
-	   * The higher level tile set should represent a 2x increase in resolution.
-	   *
-	   * Although not currently supported, this class is intended to extend to
-	   * 3D grids of tiles as well where 1 tile is covered by a 2x2x2 grid of
-	   * tiles at the next level.  The tiles are assumed to be rectangular,
-	   * identically sized, and aligned with x/y axis of the underlying
-	   * coordinate system.  The local coordinate system is in pixels relative
-	   * to the current zoom level and changes when the zoom level crosses an
-	   * integer threshold.
-	   *
-	   * The constructor takes a number of optional parameters that customize
-	   * the display of the tiles.  The default values of these options are
-	   * stored as the `defaults` attribution on the constructor.  Supporting
-	   * alternate tiling protocols often only requires adjusting these
-	   * defaults.
-	   *
-	   * @class geo.tileLayer
-	   * @extends geo.featureLayer
-	   * @param {object?} options
-	   * @param {number} [options.minLevel=0]    The minimum zoom level available
-	   * @param {number} [options.maxLevel=18]   The maximum zoom level available
-	   * @param {number} [options.tileOverlap=0]
-	   *    Number of pixels of overlap between tiles
-	   * @param {number} [options.tileWidth=256]
-	   *    The tile width as displayed without overlap
-	   * @param {number} [options.tileHeight=256]
-	   *    The tile height as displayed without overlap
-	   * @param {function} [options.tilesAtZoom=null]
-	   *    A function that is given a zoom level and returns {x: (num), y: (num)}
-	   *    with the number of tiles at that zoom level.
-	   * @param {number} [options.cacheSize=400] The maximum number of tiles to
-	   *    cache.  The default is 200 if keepLower is false.
-	   * @param {boolean}   [options.keepLower=true]
-	   *    Keep lower zoom level tiles when showing high zoom level tiles.  This
-	   *    uses more memory but results in smoother transitions.
-	   * @param {boolean}   [options.wrapX=true]    Wrap in the x-direction
-	   * @param {boolean}   [options.wrapY=false]   Wrap in the y-direction
-	   * @param {function|string} [options.url=null]
-	   *   A function taking the current tile indices and returning a URL or jquery
-	   *   ajax config to be passed to the {geo.tile} constructor.
-	   *   Example:
-	   *     (x, y, z, subdomains) => "http://example.com/z/y/x.png"
-	   *   If this is a string, a template url with {x}, {y}, {z}, and {s} as
-	   *   template variables.  {s} picks one of the subdomains parameter.
-	   * @param {string|list} [options.subdomain="abc"]  Subdomains to use in
-	   *   template url strings.  If a string, this is converted to a list before
-	   *   being passed to a url function.
-	   * @param {string} [options.baseUrl=null]  If defined, use the old-style base
-	   *   url instead of the options.url parameter.  This is functionally the same
-	   *   as using a url of baseUrl/{z}/{x}/{y}.(options.imageFormat || png).  If
-	   *   the specified string does not end in a slash, one is added.
-	   * @param {string} [options.imageFormat='png']
-	   *   This is only used if a baseUrl is specified, in which case it determines
-	   *   the image name extension used in the url.
-	   * @param {number} [options.animationDuration=0]
-	   *   The number of milliseconds for the tile loading animation to occur.  **This
-	   *   option is currently buggy because old tiles will purge before the animation
-	   *   is complete.**
-	   * @param {string} [options.attribution]
-	   *   An attribution to display with the layer (accepts HTML)
-	   * @param {function} [options.tileRounding=Math.round]
-	   *   This function determines which tiles will be loaded when the map is at
-	   *   a non-integer zoom.  For example, `Math.floor`, will use tile level 2
-	   *   when the map is at zoom 2.9.
-	   * @param {function} [options.tileOffset]
-	   *   This function takes a zoom level argument and returns, in units of
-	   *   pixels, the coordinates of the point (0, 0) at the given zoom level
-	   *   relative to the bottom left corner of the domain.
-	   * @param {function} [options.tilesMaxBounds=null]
-	   *   This function takes a zoom level argument and returns, in units of
-	   *   pixels, the top, left, right, and bottom maximum value for which tiles
-	   *   should be drawn at the given zoom level relative to the bottom left
-	   *   corner of the domain.  This can be used to crop tiles at the edges of
-	   *   tile layer.  Note that if tiles wrap, only complete tiles in the
-	   *   wrapping direction(s) are supported, and this max bounds will probably
-	   *   not behave properly.
-	   * @param {boolean}   [options.topDown=false]  True if the gcs is top-down,
-	   *   false if bottom-up (the ingcs does not matter, only the gcs coordinate
-	   *   system).  When false, this inverts the gcs y-coordinate when calculating
-	   *   local coordinates.
-	   * @returns {geo.tileLayer}
-	   */
-	  var tileLayer = function (options) {
-	    'use strict';
-	    if (!(this instanceof tileLayer)) {
-	      return new tileLayer(options);
-	    }
-	    featureLayer.call(this, options);
-
-	    var $ = __webpack_require__(1);
-	    var geo_event = __webpack_require__(9);
-	    var transform = __webpack_require__(11);
-	    var tileCache = __webpack_require__(244);
-	    var fetchQueue = __webpack_require__(233);
-	    var adjustLayerForRenderer = __webpack_require__(201).adjustLayerForRenderer;
-	    var Tile = __webpack_require__(238);
-
-	    options = $.extend(true, {}, this.constructor.defaults, options || {});
-	    if (!options.cacheSize) {
-	      // this size should be sufficient for a 4k display
-	      options.cacheSize = options.keepLower ? 600 : 200;
-	    }
-	    if ($.type(options.subdomains) === 'string') {
-	      options.subdomains = options.subdomains.split('');
-	    }
-	    /* We used to call the url option baseUrl.  If a baseUrl is specified, use
-	     * it instead of url, interpretting it as before. */
-	    if (options.baseUrl) {
-	      var url = options.baseUrl;
-	      if (url && url.charAt(url.length - 1) !== '/') {
-	        url += '/';
-	      }
-	      options.url = url + '{z}/{x}/{y}.' + (options.imageFormat || 'png');
-	    }
-	    /* Save the original url so that we can return it if asked */
-	    options.originalUrl = options.url;
-	    if ($.type(options.url) === 'string') {
-	      options.url = m_tileUrlFromTemplate(options.url);
-	    }
-
-	    var s_init = this._init,
-	        s_exit = this._exit,
-	        s_visible = this.visible,
-	        m_lastTileSet = [],
-	        m_maxBounds = [],
-	        m_exited;
-
-	    // copy the options into a private variable
-	    this._options = $.extend(true, {}, options);
-
-	    // set the layer attribution text
-	    this.attribution(options.attribution);
-
-	    // initialize the object that keeps track of actively drawn tiles
-	    this._activeTiles = {};
-
-	    // initialize the object that stores active tile regions in a
-	    // tree-like structure providing quick queries to determine
-	    // if tiles are completely obscured or not.
-	    this._tileTree = {};
-
-	    // initialize the in memory tile cache
-	    this._cache = tileCache({size: options.cacheSize});
-
-	    // initialize the tile fetch queue
-	    this._queue = fetchQueue({
-	      // this should probably be 6 * subdomains.length if subdomains are used
-	      size: 6,
-	      // if track is the same as the cache size, then neither processing time
-	      // nor memory will be wasted.  Larger values will use more memory,
-	      // smaller values will do needless computations.
-	      track: options.cacheSize,
-	      needed: function (tile) {
-	        return tile === this.cache.get(tile.toString(), true);
-	      }.bind(this)
-	    });
-
-	    var m_tileOffsetValues = {};
-
-	    /**
-	     * Readonly accessor to the options object
-	     */
-	    Object.defineProperty(this, 'options', {get: function () {
-	      return $.extend({}, this._options);
-	    }});
-
-	    /**
-	     * Readonly accessor to the tile cache object.
-	     */
-	    Object.defineProperty(this, 'cache', {get: function () {
-	      return this._cache;
-	    }});
-
-	    /**
-	     * Readonly accessor to the active tile mapping.  This is an object containing
-	     * all currently drawn tiles (hash(tile) => tile).
-	     */
-	    Object.defineProperty(this, 'activeTiles', {get: function () {
-	      return $.extend({}, this._activeTiles); // copy on output
-	    }});
-
-	    /**
-	     * The number of tiles at the given zoom level
-	     * The default implementation just returns `Math.pow(2, z)`.
-	     *
-	     * @param {number} level A zoom level
-	     * @returns {{x: nx, y: ny}} The number of tiles in each axis
-	     */
-	    this.tilesAtZoom = function (level) {
-	      if (this._options.tilesAtZoom) {
-	        return this._options.tilesAtZoom.call(this, level);
-	      }
-	      var s = Math.pow(2, level);
-	      return {x: s, y: s};
-	    };
-
-	    /**
-	     * The maximum tile bounds at the given zoom level, or null if no special
-	     * tile bounds.
-	     *
-	     * @param {number} level A zoom level
-	     * @returns {object} {x: width, y: height} The maximum tile bounds in
-	     *      pixels for the specified level, or null if none specified.
-	     */
-	    this.tilesMaxBounds = function (level) {
-	      if (this._options.tilesMaxBounds) {
-	        return this._options.tilesMaxBounds.call(this, level);
-	      }
-	      return null;
-	    };
-
-	    /**
-	     * Get the crop values for a tile based on the tilesMaxBounds function.
-	     * Returns undefined if the tile should not be cropped.
-	     *
-	     * @param {object} tile: the tile to compute crop values for.
-	     * @returns {object} either undefined or an object with x and y values
-	     *      which is the size in pixels for the tile.
-	     */
-	    this.tileCropFromBounds = function (tile) {
-	      if (!this._options.tilesMaxBounds) {
-	        return;
-	      }
-	      var level = tile.index.level,
-	          bounds = this._tileBounds(tile);
-	      if (m_maxBounds[level] === undefined) {
-	        m_maxBounds[level] = this.tilesMaxBounds(level) || null;
-	      }
-	      if (m_maxBounds[level] && (bounds.right > m_maxBounds[level].x ||
-	          bounds.bottom > m_maxBounds[level].y)) {
-	        return {
-	          x: Math.max(0, Math.min(m_maxBounds[level].x, bounds.right) - bounds.left),
-	          y: Math.max(0, Math.min(m_maxBounds[level].y, bounds.bottom) - bounds.top)
-	        };
-	      }
-	    };
-
-	    /**
-	     * Returns true if the given tile index is valid:
-	     *   * min level <= level <= max level
-	     *   * 0 <= x <= 2^level - 1
-	     *   * 0 <= y <= 2^level - 1
-	     * If the layer wraps, the x and y values may be allowed to extend beyond
-	     * these values.
-	     *
-	     * @param {object} index The tile index.
-	     * @param {number} index.x
-	     * @param {number} index.y
-	     * @param {number} index.level
-	     * @returns {geo.tile}
-	     */
-	    this.isValid = function (index) {
-	      if (!(this._options.minLevel <= index.level &&
-	           index.level <= this._options.maxLevel)) {
-	        return false;
-	      }
-	      if (!(this._options.wrapX || (
-	            0 <= index.x &&
-	            index.x <= this.tilesAtZoom(index.level).x - 1))) {
-	        return false;
-	      }
-	      if (!(this._options.wrapY || (
-	            0 <= index.y &&
-	            index.y <= this.tilesAtZoom(index.level).y - 1))) {
-	        return false;
-	      }
-	      return true;
-	    };
-
-	    /**
-	     * Returns the current origin tile and offset at the given zoom level.
-	     * This is intended to be cached in the future to optimize coordinate
-	     * transformations.
-	     * @protected
-	     * @param {number} level The target zoom level.
-	     * @returns {object} {index: {x, y}, offset: {x, y}}
-	     */
-	    this._origin = function (level) {
-	      var origin = this.toLevel(this.toLocal(this.map().origin()), level),
-	          o = this._options,
-	          index, offset;
-
-	      // get the tile index
-	      index = {
-	        x: Math.floor(origin.x / o.tileWidth),
-	        y: Math.floor(origin.y / o.tileHeight)
-	      };
-
-	      // get the offset inside the tile (in pixels)
-	      // This computation should contain the only numerically unstable
-	      // subtraction in this class.  All other methods will assume
-	      // coordinates are given relative to the map origin.
-	      offset = {
-	        x: origin.x - o.tileWidth * index.x,
-	        y: origin.y - o.tileHeight * index.y
-	      };
-	      return {index: index, offset: offset};
-	    };
-
-	    /**
-	     * Returns a tile's bounds in its level coordinates.
-	     * @param {geo.tile} tile The tile to check.
-	     * @returns {object} The tile's bounds.
-	     */
-	    this._tileBounds = function (tile) {
-	      var origin = this._origin(tile.index.level);
-	      return tile.bounds(origin.index, origin.offset);
-	    };
-
-	    /**
-	     * Returns the tile indices at the given point.
-	     * @param {object} point The coordinates in pixels relative to the map origin.
-	     * @param {number} point.x
-	     * @param {number} point.y
-	     * @param {number} level The target zoom level.
-	     * @returns {object} The tile indices.
-	     */
-	    this.tileAtPoint = function (point, level) {
-	      var o = this._origin(level);
-	      var map = this.map();
-	      point = this.displayToLevel(map.gcsToDisplay(point, null), level);
-	      if (isNaN(point.x)) { point.x = 0; }
-	      if (isNaN(point.y)) { point.y = 0; }
-	      var to = this._tileOffset(level);
-	      if (to) {
-	        point.x += to.x;
-	        point.y += to.y;
-	      }
-	      var tile = {
-	        x: Math.floor(
-	          o.index.x + (o.offset.x + point.x) / this._options.tileWidth
-	        ),
-	        y: Math.floor(
-	          o.index.y + (o.offset.y + point.y) / this._options.tileHeight
-	        )
-	      };
-	      return tile;
-	    };
-
-	    /**
-	     * Returns a tile's bounds in a gcs.
-	     *
-	     * @param {object|tile} indexOrTile Either a tile or an object with
-	     *    {x, y, level}` specifying a tile.
-	     * @param {string|geo.transform|null} [gcs] `undefined` to use the
-	     *    interface gcs, `null` to use the map gcs, or any other transform.
-	     * @returns {object} The tile bounds in the specified gcs.
-	     */
-	    this.gcsTileBounds = function (indexOrTile, gcs) {
-	      var tile = (indexOrTile.index ? indexOrTile : Tile({
-	        index: indexOrTile,
-	        size: {x: this._options.tileWidth, y: this._options.tileHeight},
-	        url: ''
-	      }));
-	      var to = this._tileOffset(tile.index.level),
-	          bounds = tile.bounds({x: 0, y: 0}, to),
-	          map = this.map(),
-	          unit = map.unitsPerPixel(tile.index.level);
-	      var coord = [{
-	        x: bounds.left * unit, y: this._topDown() * bounds.top * unit
-	      }, {
-	        x: bounds.right * unit, y: this._topDown() * bounds.bottom * unit
-	      }];
-	      gcs = (gcs === null ? map.gcs() : (
-	          gcs === undefined ? map.ingcs() : gcs));
-	      if (gcs !== map.gcs()) {
-	        coord = transform.transformCoordinates(map.gcs(), gcs, coord);
-	      }
-	      return {
-	        left: coord[0].x,
-	        top: coord[0].y,
-	        right: coord[1].x,
-	        bottom: coord[1].y
-	      };
-	    };
-
-	    /**
-	     * Returns an instantiated tile object with the given indices.  This
-	     * method always returns a new tile object.  Use `_getTileCached`
-	     * to use the caching layer.
-	     *
-	     * @param {object} index The tile index.
-	     * @param {number} index.x
-	     * @param {number} index.y
-	     * @param {number} index.level
-	     * @param {object} source The tile index used for constructing the url.
-	     * @param {number} source.x
-	     * @param {number} source.y
-	     * @param {number} source.level
-	     * @returns {geo.tile}
-	     */
-	    this._getTile = function (index, source) {
-	      var urlParams = source || index;
-	      return Tile({
-	        index: index,
-	        size: {x: this._options.tileWidth, y: this._options.tileHeight},
-	        queue: this._queue,
-	        url: this._options.url.call(
-	            this, urlParams.x, urlParams.y, urlParams.level || 0,
-	            this._options.subdomains)
-	      });
-	    };
-
-	    /**
-	     * Returns an instantiated tile object with the given indices.  This
-	     * method is similar to `_getTile`, but checks the cache before
-	     * generating a new tile.
-	     *
-	     * @param {object} index The tile index.
-	     * @param {number} index.x
-	     * @param {number} index.y
-	     * @param {number} index.level
-	     * @param {object} source The tile index used for constructing the url.
-	     * @param {number} source.x
-	     * @param {number} source.y
-	     * @param {number} source.level
-	     * @param {boolean} delayPurge If true, don't purge tiles from the cache.
-	     * @returns {geo.tile}
-	     */
-	    this._getTileCached = function (index, source, delayPurge) {
-	      var tile = this.cache.get(this._tileHash(index));
-	      if (tile === null) {
-	        tile = this._getTile(index, source);
-	        this.cache.add(tile, this.remove.bind(this), delayPurge);
-	      }
-	      return tile;
-	    };
-
-	    /**
-	     * Returns a string representation of the tile at the given index.
-	     *
-	     * Note: This method _must_ return the same string as:
-	     *
-	     *   tile({index: index}).toString();
-	     *
-	     * This method is used as a hashing function for the caching layer.
-	     *
-	     * @param {object} index The tile index
-	     * @param {number} index.x
-	     * @param {number} index.y
-	     * @param {number} index.level
-	     * @returns {string}
-	     */
-	    this._tileHash = function (index) {
-	      return [index.level || 0, index.y, index.x].join('_');
-	    };
-
-	    /**
-	     * Returns the optimal starting and ending tile indices (inclusive)
-	     * necessary to fill the given viewport.
-	     *
-	     * @param {number} level The zoom level
-	     * @param {geo.geoBounds} bounds The map bounds in world coordinates.
-	     * @returns {object} The tile range with a `start`  and `end` record, each
-	     *      with `x` and `y` tile indices.
-	     */
-	    this._getTileRange = function (level, bounds) {
-	      var corners = [
-	        this.tileAtPoint({x: bounds.left, y: bounds.top}, level),
-	        this.tileAtPoint({x: bounds.right, y: bounds.top}, level),
-	        this.tileAtPoint({x: bounds.left, y: bounds.bottom}, level),
-	        this.tileAtPoint({x: bounds.right, y: bounds.bottom}, level)
-	      ];
-	      return {
-	        start: {
-	          x: Math.min(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
-	          y: Math.min(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
-	        },
-	        end: {
-	          x: Math.max(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
-	          y: Math.max(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
-	        }
-	      };
-	    };
-
-	    /**
-	     * Returns a list of tiles necessary to fill the screen at the given
-	     * zoom level, center point, and viewport size.  The list is optionally
-	     * ordered by loading priority (center tiles first).
-	     *
-	     * @protected
-	     * @param {number} maxLevel The zoom level
-	     * @param {geo.geoBounds} bounds The map bounds
-	     * @param {boolean} sorted Return a sorted list
-	     * @param {boolean} onlyIfChanged If the set of tiles have not changed
-	     *     (even if their desired order has), return undefined instead of an
-	     *     array of tiles.
-	     * @returns {geo.tile[]} An array of tile objects
-	     */
-	    this._getTiles = function (maxLevel, bounds, sorted, onlyIfChanged) {
-	      var i, j, tiles = [], index, nTilesLevel,
-	          start, end, indexRange, source, center, changed = false, old, level,
-	          minLevel = (this._options.keepLower ? this._options.minLevel :
-	                      maxLevel);
-	      if (maxLevel < minLevel) {
-	        maxLevel = minLevel;
-	      }
-
-	      /* Generate a list of the tiles that we want to create.  This is done
-	       * before sorting, because we want to actually generate the tiles in
-	       * the sort order. */
-	      for (level = minLevel; level <= maxLevel; level += 1) {
-	        // get the tile range to fetch
-	        indexRange = this._getTileRange(level, bounds);
-	        start = indexRange.start;
-	        end = indexRange.end;
-	        // total number of tiles existing at this level
-	        nTilesLevel = this.tilesAtZoom(level);
-
-	        if (!this._options.wrapX) {
-	          start.x = Math.min(Math.max(start.x, 0), nTilesLevel.x - 1);
-	          end.x = Math.min(Math.max(end.x, 0), nTilesLevel.x - 1);
-	          if (level === minLevel && this._options.keepLower) {
-	            start.x = 0;
-	            end.x = nTilesLevel.x - 1;
-	          }
-	        }
-	        if (!this._options.wrapY) {
-	          start.y = Math.min(Math.max(start.y, 0), nTilesLevel.y - 1);
-	          end.y = Math.min(Math.max(end.y, 0), nTilesLevel.y - 1);
-	          if (level === minLevel && this._options.keepLower) {
-	            start.y = 0;
-	            end.y = nTilesLevel.y - 1;
-	          }
-	        }
-	        /* If we are reprojecting tiles, we need a check to not use all levels
-	         * if the number of tiles is excessive. */
-	        if (this._options.gcs && this._options.gcs !== this.map().gcs() &&
-	            level !== minLevel &&
-	            (end.x + 1 - start.x) * (end.y + 1 - start.y) >
-	            (this.map().size().width * this.map().size().height /
-	            this._options.tileWidth / this._options.tileHeight) * 16) {
-	          break;
-	        }
-
-	        // loop over the tile range
-	        for (i = start.x; i <= end.x; i += 1) {
-	          for (j = start.y; j <= end.y; j += 1) {
-	            index = {level: level, x: i, y: j};
-	            source = {level: level, x: i, y: j};
-	            if (this._options.wrapX) {
-	              source.x = modulo(source.x, nTilesLevel.x);
-	            }
-	            if (this._options.wrapY) {
-	              source.y = modulo(source.y, nTilesLevel.y);
-	            }
-	            if (this.isValid(source)) {
-	              if (onlyIfChanged && tiles.length < m_lastTileSet.length) {
-	                old = m_lastTileSet[tiles.length];
-	                changed = changed || (index.level !== old.level ||
-	                    index.x !== old.x || index.y !== old.y);
-	              }
-	              tiles.push({index: index, source: source});
-	            }
-	          }
-	        }
-	      }
-
-	      if (onlyIfChanged) {
-	        if (!changed && tiles.length === m_lastTileSet.length) {
-	          return;
-	        }
-	        m_lastTileSet.splice(0, m_lastTileSet.length);
-	        $.each(tiles, function (idx, tile) {
-	          m_lastTileSet.push(tile.index);
-	        });
-	      }
-
-	      if (sorted) {
-	        center = {
-	          x: (start.x + end.x) / 2,
-	          y: (start.y + end.y) / 2,
-	          level: maxLevel,
-	          bottomLevel: maxLevel
-	        };
-	        var numTiles = Math.max(end.x - start.x, end.y - start.y) + 1;
-	        for (; numTiles >= 1; numTiles /= 2) {
-	          center.bottomLevel -= 1;
-	        }
-	        tiles.sort(this._loadMetric(center));
-	        /* If we are using a fetch queue, start a new batch */
-	        if (this._queue) {
-	          this._queue.batch(true);
-	        }
-	      }
-	      if (this.cache.size < tiles.length) {
-	        console.log('Increasing cache size to ' + tiles.length);
-	        this.cache.size = tiles.length;
-	      }
-	      /* Actually get the tiles. */
-	      for (i = 0; i < tiles.length; i += 1) {
-	        tiles[i] = this._getTileCached(tiles[i].index, tiles[i].source, true);
-	      }
-	      this.cache.purge(this.remove.bind(this));
-	      return tiles;
-	    };
-
-	    /**
-	     * Prefetches tiles up to a given zoom level around a given bounding box.
-	     *
-	     * @param {number} level The zoom level.
-	     * @param {geo.geoBounds} bounds The map bounds.
-	     * @returns {jQuery.Deferred} resolves when all of the tiles are fetched.
-	     */
-	    this.prefetch = function (level, bounds) {
-	      var tiles;
-	      tiles = this._getTiles(level, bounds, true);
-	      return $.when.apply($,
-	        tiles.map(function (tile) {
-	          return tile.fetch();
-	        })
-	      );
-	    };
-
-	    /**
-	     * This method returns a metric that determines tile loading order.  The
-	     * default implementation prioritizes tiles that are closer to the center,
-	     * or at a lower zoom level.
-	     * @protected
-	     * @param {object} center The center tile.
-	     * @param {number} center.x
-	     * @param {number} center.y
-	     * @returns {function} A function accepted by `Array.prototype.sort`.
-	     */
-	    this._loadMetric = function (center) {
-	      return function (a, b) {
-	        var a0, b0, dx, dy, cx, cy, scale;
-
-	        a = a.index || a;
-	        b = b.index || b;
-	        // shortcut if zoom level differs
-	        if (a.level !== b.level) {
-	          if (center.bottomLevel && ((a.level >= center.bottomLevel) !==
-	                                     (b.level >= center.bottomLevel))) {
-	            return a.level >= center.bottomLevel ? -1 : 1;
-	          }
-	          return a.level - b.level;
-	        }
-
-	        /* compute the center coordinates relative to a.level.  Since we really
-	         * care about the center of the tiles, use an offset */
-	        scale = Math.pow(2, a.level - center.level);
-	        cx = (center.x + 0.5) * scale - 0.5;
-	        cy = (center.y + 0.5) * scale - 0.5;
-
-	        // calculate distances to the center squared
-	        dx = a.x - cx;
-	        dy = a.y - cy;
-	        a0 = dx * dx + dy * dy;
-
-	        dx = b.x - cx;
-	        dy = b.y - cy;
-	        b0 = dx * dx + dy * dy;
-
-	        // return negative if a < b, or positive if a > b
-	        return a0 - b0;
-	      };
-	    };
-
-	    /**
-	     * Convert a coordinate from pixel coordinates at the given zoom
-	     * level to world coordinates.
-	     *
-	     * @param {object} coord
-	     * @param {number} coord.x The offset in pixels (level 0) from the left
-	     *      edge.
-	     * @param {number} coord.y The offset in pixels (level 0) from the bottom
-	     *      edge.
-	     * @param {number} level The zoom level of the source coordinates.
-	     * @returns {object} World coordinates with `x` and `y`.
-	     */
-	    this.fromLevel = function (coord, level) {
-	      var s = Math.pow(2, -level);
-	      return {
-	        x: coord.x * s,
-	        y: coord.y * s
-	      };
-	    };
-
-	    /**
-	     * Convert a coordinate from layer coordinates to pixel coordinates at the
-	     * given zoom level.
-	     *
-	     * @param {object} coord
-	     * @param {number} coord.x The offset in pixels (level 0) from the left
-	     *      edge.
-	     * @param {number} coord.y The offset in pixels (level 0) from the bottom
-	     *      edge.
-	     * @param {number} level The zoom level of the new coordinates.
-	     * @returns {object} The pixel coordinates.
-	     */
-	    this.toLevel = function (coord, level) {
-	      var s = Math.pow(2, level);
-	      return {
-	        x: coord.x * s,
-	        y: coord.y * s
-	      };
-	    };
-
-	    /**
-	     * Draw the given tile on the active canvas.
-	     * @param {geo.tile} tile The tile to draw
-	     */
-	    this.drawTile = function (tile) {
-	      var hash = tile.toString();
-
-	      if (this._activeTiles.hasOwnProperty(hash)) {
-	        // the tile is already drawn, move it to the top
-	        this._moveToTop(tile);
-	      } else {
-	        // pass to the rendering implementation
-	        this._drawTile(tile);
-	      }
-
-	      // add the tile to the active cache
-	      this._activeTiles[hash] = tile;
-	    };
-
-	    /**
-	     * Render the tile on the canvas.  This implementation draws the tiles
-	     * directly on the DOM using <img> tags.  Derived classes should override
-	     * this method to draw the tile on a renderer specific context.
-	     *
-	     * @protected
-	     * @param {geo.tile} tile The tile to draw
-	     */
-	    this._drawTile = function (tile) {
-	      // Make sure this method is not called when there is
-	      // a renderer attached.
-	      if (this.renderer() !== null) {
-	        throw new Error('This draw method is not valid on renderer managed layers.');
-	      }
-
-	      // get the layer node
-	      var level = tile.index.level,
-	          div = $(this._getSubLayer(level)),
-	          bounds = this._tileBounds(tile),
-	          duration = this._options.animationDuration,
-	          container = $('<div class="geo-tile-container"/>').attr(
-	            'tile-reference', tile.toString()),
-	          crop;
-
-	      // apply a transform to place the image correctly
-	      container.append(tile.image);
-	      container.css({
-	        left: (bounds.left - parseInt(div.attr('offsetx') || 0, 10)) + 'px',
-	        top: (bounds.top - parseInt(div.attr('offsety') || 0, 10)) + 'px'
-	      });
-
-	      crop = this.tileCropFromBounds(tile);
-	      if (crop) {
-	        container.addClass('crop').css({
-	          width: crop.x + 'px',
-	          height: crop.y + 'px'
-	        });
-	      }
-
-	      // apply fade in animation
-	      if (duration > 0) {
-	        tile.fadeIn(duration);
-	      }
-
-	      // append the image element
-	      div.append(container);
-
-	      // add an error handler
-	      tile.catch(function () {
-	        // May want to do something special here later
-	        console.warn('Could not load tile at ' + tile.toString());
-	        this._remove(tile);
-	      }.bind(this));
-	    };
-
-	    /**
-	     * Remove the given tile from the canvas and the active cache.
-	     * @param {geo.tile|string} tile The tile (or hash) to remove.
-	     * @returns {geo.tile} The tile removed from the active layer.
-	     */
-	    this.remove = function (tile) {
-	      var hash = tile.toString();
-	      var value = this._activeTiles[hash];
-
-	      if (value instanceof Tile) {
-	        this._remove(value);
-	      }
-
-	      delete this._activeTiles[hash];
-	      return value;
-	    };
-
-	    /**
-	     * Remove the given tile from the canvas.  This implementation just
-	     * finds and removes the <img> element created for the tile.
-	     * @param {geo.tile|string} tile The tile object to remove.
-	     */
-	    this._remove = function (tile) {
-	      if (tile.image) {
-	        if (tile.image.parentElement) {
-	          $(tile.image.parentElement).remove();
-	        } else {
-	          /* This shouldn't happen, but sometimes does.  Originally it happened
-	           * when a tile was removed from the cache before it was finished
-	           * being used; there is still some much rarer condition that can
-	           * cause it.  Log that it happened until we can figure out how to fix
-	           * the issue. */
-	          console.log('No parent element to remove ' + tile.toString(), tile);
-	        }
-	        $(tile.image).remove();
-	      }
-	    };
-
-	    /**
-	     * Move the given tile to the top on the canvas.
-	     * @param {geo.tile} tile The tile object to move.
-	     */
-	    this._moveToTop = function (tile) {
-	      $.noop(tile);
-	    };
-
-	    /**
-	     * Query the attached map for the current bounds and return them as pixels
-	     *      at the current zoom level.
-	     *
-	     * @returns {object} Bounds object with `left`, `right`, `top`, `bottom`,
-	     *      `scale`, and `level` keys.
-	     * @protected
-	     */
-	    this._getViewBounds = function () {
-	      var map = this.map(),
-	          mapZoom = map.zoom(),
-	          zoom = this._options.tileRounding(mapZoom),
-	          scale = Math.pow(2, mapZoom - zoom),
-	          size = map.size();
-	      var ul = this.displayToLevel({x: 0, y: 0}),
-	          ur = this.displayToLevel({x: size.width, y: 0}),
-	          ll = this.displayToLevel({x: 0, y: size.height}),
-	          lr = this.displayToLevel({x: size.width, y: size.height});
-	      return {
-	        level: zoom,
-	        scale: scale,
-	        left: Math.min(ul.x, ur.x, ll.x, lr.x),
-	        right: Math.max(ul.x, ur.x, ll.x, lr.x),
-	        top: Math.min(ul.y, ur.y, ll.y, lr.y),
-	        bottom: Math.max(ul.y, ur.y, ll.y, lr.y)
-	      };
-	    };
-
-	    /**
-	     * Remove all inactive tiles from the display.  An inactive tile is one
-	     * that is no longer visible either because it was panned out of the active
-	     * view or the zoom has changed.
-	     *
-	     * @protected
-	     * @param {number} zoom Tiles (in bounds) at this zoom level will be kept
-	     * @param {boolean} doneLoading If true, allow purging additional tiles.
-	     * @param {object} bounds view bounds.  If not specified, this is
-	     *   obtained from _getViewBounds().
-	     * @returns {this}
-	     */
-	    this._purge = function (zoom, doneLoading, bounds) {
-	      var tile, hash;
-
-	      // Don't purge tiles in an active update
-	      if (this._updating) {
-	        return this;
-	      }
-
-	      // get the view bounds
-	      if (!bounds) {
-	        bounds = this._getViewBounds();
-	      }
-
-	      for (hash in this._activeTiles) {
-
-	        tile = this._activeTiles[hash];
-	        if (this._canPurge(tile, bounds, zoom, doneLoading)) {
-	          this.remove(tile);
-	        }
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Remove all active tiles from the canvas.
-	     * @returns {geo.tile[]} The array of tiles removed.
-	     */
-	    this.clear = function () {
-	      var tiles = [], tile;
-
-	      // ignoring the warning here because this is a privately
-	      // controlled object with simple keys
-	      for (tile in this._activeTiles) {
-	        tiles.push(this.remove(tile));
-	      }
-
-	      // clear out the tile coverage tree
-	      this._tileTree = {};
-
-	      m_lastTileSet = [];
-
-	      return tiles;
-	    };
-
-	    /**
-	     * Reset the layer to the initial state, clearing the canvas
-	     * and resetting the tile cache.
-	     * @returns {this} Chainable.
-	     */
-	    this.reset = function () {
-	      this.clear();
-	      this._cache.clear();
-	      return this;
-	    };
-
-	    /**
-	     * Compute local coordinates from the given world coordinates.  The
-	     * tile layer uses units of pixels relative to the world space
-	     * coordinate origin.
-	     * @param {object} pt A point in world space coordinates.
-	     * @param {number|undefined} zoom If unspecified, use the map zoom.
-	     * @returns {object} Local coordinates.
-	     */
-	    this.toLocal = function (pt, zoom) {
-	      var map = this.map(),
-	          unit = map.unitsPerPixel(zoom === undefined ? map.zoom() : zoom);
-	      return {
-	        x: pt.x / unit,
-	        y: this._topDown() * pt.y / unit
-	      };
-	    };
-
-	    /**
-	     * Compute world coordinates from the given local coordinates.  The
-	     * tile layer uses units of pixels relative to the world space
-	     * coordinate origin.
-	     * @param {object} pt A point in world space coordinates.
-	     * @param {number|undefined} zoom If unspecified, use the map zoom.
-	     * @returns {object} Local coordinates.
-	     */
-	    this.fromLocal = function (pt, zoom) {
-	      // these need to always use the *layer* unitsPerPixel, or possibly
-	      // convert tile space using a transform
-	      var map = this.map(),
-	          unit = map.unitsPerPixel(zoom === undefined ? map.zoom() : zoom);
-	      return {
-	        x: pt.x * unit,
-	        y: this._topDown() * pt.y * unit
-	      };
-	    };
-
-	    /**
-	     * Return a factor for inverting the y units as appropriate.
-	     *
-	     * @returns {number} Either 1 to not invert y, or -1 to invert it.
-	     */
-	    this._topDown = function () {
-	      return this._options.topDown ? 1 : -1;
-	    };
-
-	    /**
-	     * Return the DOM element containing a level specific layer.  This will
-	     * create the element if it doesn't already exist.
-	     * @param {number} level The zoom level of the layer to fetch.
-	     * @returns {DOM} The layer's DOM element.
-	     */
-	    this._getSubLayer = function (level) {
-	      if (!this.canvas()) {
-	        return;
-	      }
-	      var node = this.canvas()
-	        .find('div[data-tile-layer=' + level.toFixed() + ']').get(0);
-	      if (!node) {
-	        node = $(
-	          '<div class=geo-tile-layer data-tile-layer="' + level.toFixed() + '"/>'
-	        ).get(0);
-	        this.canvas().append(node);
-	      }
-	      return node;
-	    };
-
-	    /**
-	     * Set sublayer transforms to align them with the given zoom level.
-	     * @param {number} level The target zoom level.
-	     * @param {geo.geoBounds} view The view bounds.  The top and left are used
-	     *      to adjust the offset of tile layers.
-	     * @returns {object} The x and y offsets for the current level.
-	     */
-	    this._updateSubLayers = function (level, view) {
-	      var canvas = this.canvas(),
-	          lastlevel = parseInt(canvas.attr('lastlevel'), 10),
-	          lastx = parseInt(canvas.attr('lastoffsetx') || 0, 10),
-	          lasty = parseInt(canvas.attr('lastoffsety') || 0, 10);
-	      if (lastlevel === level && Math.abs(lastx - view.left) < 65536 &&
-	          Math.abs(lasty - view.top) < 65536) {
-	        return {x: lastx, y: lasty};
-	      }
-	      var map = this.map(),
-	          to = this._tileOffset(level),
-	          x = parseInt((view.left + view.right - map.size().width) / 2 + to.x, 10),
-	          y = parseInt((view.top + view.bottom - map.size().height) / 2 + to.y, 10);
-	      canvas.find('.geo-tile-layer').each(function (idx, el) {
-	        var $el = $(el),
-	            layer = parseInt($el.data('tileLayer'), 10);
-	        $el.css(
-	          'transform',
-	          'scale(' + Math.pow(2, level - layer) + ')'
-	        );
-	        var layerx = parseInt(x / Math.pow(2, level - layer), 10),
-	            layery = parseInt(y / Math.pow(2, level - layer), 10),
-	            dx = layerx - parseInt($el.attr('offsetx') || 0, 10),
-	            dy = layery - parseInt($el.attr('offsety') || 0, 10);
-	        $el.attr({offsetx: layerx, offsety: layery});
-	        $el.find('.geo-tile-container').each(function (tileidx, tileel) {
-	          $(tileel).css({
-	            left: (parseInt($(tileel).css('left'), 10) - dx) + 'px',
-	            top: (parseInt($(tileel).css('top'), 10) - dy) + 'px'
-	          });
-	        });
-	      });
-	      canvas.attr({lastoffsetx: x, lastoffsety: y, lastlevel: level});
-	      return {x: x, y: y};
-	    };
-
-	    /**
-	     * Update the view according to the map/camera.
-	     * @param {geo.event} evt The event that triggered the change.  Zoom and
-	     *      rotate events do nothing, since they are always followed by a pan
-	     *      event which will cause appropriate action.
-	     * @returns {this} Chainable.
-	     */
-	    this._update = function (evt) {
-	      /* Ignore zoom and rotate events, as they are ALWAYS followed by a pan
-	       * event */
-	      if (evt && evt.event && (evt.event.event === geo_event.zoom ||
-	          evt.event.event === geo_event.rotate)) {
-	        return this;
-	      }
-	      if (!this.visible()) {
-	        return this;
-	      }
-	      var map = this.map(),
-	          bounds = map.bounds(undefined, null),
-	          mapZoom = map.zoom(),
-	          zoom = this._options.tileRounding(mapZoom),
-	          tiles;
-	      if (this._updateSubLayers) {
-	        var view = this._getViewBounds();
-	        // Update the transform for the local layer coordinates
-	        var offset = this._updateSubLayers(zoom, view) || {x: 0, y: 0};
-
-	        var to = this._tileOffset(zoom);
-	        if (this.renderer() === null) {
-	          var scale = Math.pow(2, mapZoom - zoom),
-	              rotation = map.rotation(),
-	              rx = -to.x + -(view.left + view.right) / 2 + offset.x,
-	              ry = -to.y + -(view.bottom + view.top) / 2 + offset.y,
-	              dx = (rx + map.size().width / 2),
-	              dy = (ry + map.size().height / 2);
-
-	          this.canvas().css({
-	            'transform-origin': '' +
-	                -rx + 'px ' +
-	                -ry + 'px'
-	          });
-	          var transform = 'translate(' + dx + 'px' + ',' + dy + 'px' + ')' +
-	              'scale(' + scale + ')';
-	          if (rotation) {
-	            transform += 'rotate(' + (rotation * 180 / Math.PI) + 'deg)';
-	          }
-	          this.canvas().css('transform', transform);
-	        }
-	        /* Set some attributes that can be used by non-css based viewers.  This
-	         * doesn't include the map center, as that may need to be handled
-	         * differently from the view center. */
-	        this.canvas().attr({
-	          scale: Math.pow(2, mapZoom - zoom),
-	          dx: -to.x + -(view.left + view.right) / 2,
-	          dy: -to.y + -(view.bottom + view.top) / 2,
-	          offsetx: offset.x,
-	          offsety: offset.y,
-	          rotation: map.rotation()
-	        });
-	      }
-
-	      tiles = this._getTiles(
-	        zoom, bounds, true, true
-	      );
-
-	      if (tiles === undefined) {
-	        return this;
-	      }
-
-	      // reset the tile coverage tree
-	      this._tileTree = {};
-
-	      tiles.forEach(function (tile) {
-	        if (tile.fetched()) {
-	          /* if we have already fetched the tile, we know we can just draw it,
-	           * as the bounds won't have changed since the call to _getTiles. */
-	          this.drawTile(tile);
-
-	          // mark the tile as covered
-	          this._setTileTree(tile);
-	        } else {
-	          if (!tile._queued) {
-	            tile.then(function () {
-	              if (m_exited) {
-	                /* If we have disconnected the renderer, do nothing.  This
-	                 * happens when the layer is being deleted. */
-	                return;
-	              }
-	              if (tile !== this.cache.get(tile.toString())) {
-	                /* If the tile has fallen out of the cache, don't draw it -- it
-	                 * is untracked.  This may be an indication that a larger cache
-	                 * should have been used. */
-	                return;
-	              }
-	              /* Check if a tile is still desired.  Don't draw it if it
-	               * isn't. */
-	              var mapZoom = map.zoom(),
-	                  zoom = this._options.tileRounding(mapZoom),
-	                  view = this._getViewBounds();
-	              if (this._canPurge(tile, view, zoom)) {
-	                this.remove(tile);
-	                return;
-	              }
-
-	              this.drawTile(tile);
-
-	              // mark the tile as covered
-	              this._setTileTree(tile);
-	            }.bind(this));
-
-	            this.addPromise(tile);
-	            tile._queued = true;
-	          } else {
-	            /* If we are using a fetch queue, tell the queue so this tile can
-	             * be reprioritized. */
-	            var pos = this._queue ? this._queue.get(tile) : -1;
-	            if (pos >= 0) {
-	              this._queue.add(tile);
-	            }
-	          }
-	        }
-	      }.bind(this));
-	      // purge all old tiles when the new tiles are loaded (successfully or not)
-	      $.when.apply($, tiles)
-	        .done(// called on success and failure
-	          function () {
-	            var map = this.map(),
-	                mapZoom = map.zoom(),
-	                zoom = this._options.tileRounding(mapZoom);
-	            this._purge(zoom, true);
-	          }.bind(this)
-	        );
-	      return this;
-	    };
-
-	    /**
-	     * Set a value in the tile tree object indicating that the given area of
-	     * the canvas is covered by the tile.
-	     * @protected
-	     * @param {geo.tile} tile
-	     */
-	    this._setTileTree = function (tile) {
-	      if (this._options.keepLower) {
-	        return;
-	      }
-	      var index = tile.index;
-	      this._tileTree[index.level] = this._tileTree[index.level] || {};
-	      this._tileTree[index.level][index.x] = this._tileTree[index.level][index.x] || {};
-	      this._tileTree[index.level][index.x][index.y] = tile;
-	    };
-
-	    /**
-	     * Get a value in the tile tree object if it exists or return null.
-	     * @protected
-	     * @param {object} index A tile index object
-	     * @param {object} index.level
-	     * @param {object} index.x
-	     * @param {object} index.y
-	     * @returns {geo.tile|null}
-	     */
-	    this._getTileTree = function (index) {
-	      return (
-	          (
-	            this._tileTree[index.level] || {}
-	          )[index.x] || {}
-	        )[index.y] || null;
-	    };
-
-	    /**
-	     * Returns true if the tile is completely covered by other tiles on the
-	     * canvas.  Currently this method only checks layers +/- 1 away from
-	     * `tile`.  If the zoom level is allowed to change by 2 or more in a single
-	     * update step, this method will need to be refactored to make a more
-	     * robust check.  Returns an array of tiles covering it or null if any
-	     * part of the tile is exposed.
-	     *
-	     * @protected
-	     * @param {geo.tile} tile
-	     * @returns {geo.tile[]|null}
-	     */
-	    this._isCovered = function (tile) {
-	      var level = tile.index.level,
-	          x = tile.index.x,
-	          y = tile.index.y,
-	          tiles = [];
-
-	      // Check one level up
-	      tiles = this._getTileTree({
-	        level: level - 1,
-	        x: Math.floor(x / 2),
-	        y: Math.floor(y / 2)
-	      });
-	      if (tiles) {
-	        return [tiles];
-	      }
-
-	      // Check one level down
-	      tiles = [
-	        this._getTileTree({
-	          level: level + 1,
-	          x: 2 * x,
-	          y: 2 * y
-	        }),
-	        this._getTileTree({
-	          level: level + 1,
-	          x: 2 * x + 1,
-	          y: 2 * y
-	        }),
-	        this._getTileTree({
-	          level: level + 1,
-	          x: 2 * x,
-	          y: 2 * y + 1
-	        }),
-	        this._getTileTree({
-	          level: level + 1,
-	          x: 2 * x + 1,
-	          y: 2 * y + 1
-	        })
-	      ];
-	      if (tiles.every(function (t) { return t !== null; })) {
-	        return tiles;
-	      }
-
-	      return null;
-	    };
-
-	    /**
-	     * Returns true if the provided tile is outside of the current view bounds
-	     * and can be removed from the canvas.
-	     * @protected
-	     * @param {geo.tile} tile
-	     * @param {geo.geoBounds} bounds The view bounds.
-	     * @returns {boolean}
-	     */
-	    this._outOfBounds = function (tile, bounds) {
-	      /* We may want to add an (n) tile edge buffer so we appear more
-	       * responsive */
-	      var to = this._tileOffset(tile.index.level);
-	      var scale = 1;
-	      if (tile.index.level !== bounds.level) {
-	        scale = Math.pow(2, (bounds.level || 0) - (tile.index.level || 0));
-	      }
-	      return (tile.bottom - to.y) * scale < bounds.top ||
-	             (tile.left - to.x) * scale > bounds.right ||
-	             (tile.top - to.y) * scale > bounds.bottom ||
-	             (tile.right - to.x) * scale < bounds.left;
-	    };
-
-	    /**
-	     * Returns true if the provided tile can be purged from the canvas.  This method
-	     * will return `true` if the tile is completely covered by one or more other tiles
-	     * or it is outside of the active view bounds.  This method returns the logical and
-	     * of `_isCovered` and `_outOfBounds`.
-	     * @protected
-	     * @param {geo.tile} tile
-	     * @param {geo.geoBounds} [bounds] The view bounds (if unspecified, assume
-	     *      global bounds)
-	     * @param {number} bounds.level The zoom level the bounds are given as.
-	     * @param {number} zoom Keep in bound tile at this zoom level.
-	     * @param {boolean} doneLoading If true, allow purging additional tiles.
-	     * @returns {boolean}
-	     */
-	    this._canPurge = function (tile, bounds, zoom, doneLoading) {
-	      if (this._options.keepLower) {
-	        zoom = zoom || 0;
-	        if (zoom < tile.index.level &&
-	            tile.index.level !== this._options.minLevel) {
-	          return true;
-	        }
-	        if (tile.index.level === this._options.minLevel &&
-	            !this._options.wrapX && !this._options.wrapY) {
-	          return false;
-	        }
-	      } else {
-	        /* For tile layers that should only keep one layer, if loading is
-	         * finished, purge all but the current layer.  This is important for
-	         * semi-transparanet layers. */
-	        if ((doneLoading || this._isCovered(tile)) &&
-	            zoom !== tile.index.level) {
-	          return true;
-	        }
-	      }
-	      if (bounds) {
-	        return this._outOfBounds(tile, bounds);
-	      }
-	      return false;
-	    };
-
-	    /**
-	     * Convert display pixel coordinates (where (0,0) is the upper left) to
-	     * layer pixel coordinates (typically (0,0) is the center of the map and
-	     * the upper-left has the most negative values).
-	     * By default, this is done at the current base zoom level.
-	     *
-	     * @param {object} [pt] The point to convert with `x` and `y`.  If
-	     *      `undefined`, use the center of the display.
-	     * @param {number} [zoom] If specified, the zoom level to use.
-	     * @returns {object} The point in level coordinates.
-	     */
-	    this.displayToLevel = function (pt, zoom) {
-	      var map = this.map(),
-	          mapzoom = map.zoom(),
-	          roundzoom = this._options.tileRounding(mapzoom),
-	          unit = map.unitsPerPixel(zoom === undefined ? roundzoom : zoom),
-	          gcsPt;
-	      if (pt === undefined) {
-	        var size = map.size();
-	        pt = {x: size.width / 2, y: size.height / 2};
-	      }
-	      /* displayToGcs can fail under certain projections.  If this happens,
-	       * just return the origin. */
-	      try {
-	        gcsPt = map.displayToGcs(pt, this._options.gcs || null);
-	      } catch (err) {
-	        gcsPt = {x: 0, y: 0};
-	      }
-	      /* Reverse the y coordinate, since we expect the gcs coordinate system
-	       * to be right-handed and the level coordinate system to be
-	       * left-handed. */
-	      var lvlPt = {x: gcsPt.x / unit, y: this._topDown() * gcsPt.y / unit};
-	      return lvlPt;
-	    };
-
-	    /**
-	     * Get or set the tile url string or function.  If changed, load the new
-	     * tiles.
-	     *
-	     * @param {string|function} [url] The new tile url.
-	     * @returns {string|function|this}
-	     */
-	    this.url = function (url) {
-	      if (url === undefined) {
-	        return this._options.originalUrl;
-	      }
-	      if (url === this._options.originalUrl) {
-	        return this;
-	      }
-	      this._options.originalUrl = url;
-	      if ($.type(url) === 'string') {
-	        url = m_tileUrlFromTemplate(url);
-	      }
-	      this._options.url = url;
-	      this.reset();
-	      this.map().draw();
-	      return this;
-	    };
-
-	    /**
-	     * Get or set the subdomains used for templating.
-	     *
-	     * @param {string|list} [subdomains] A comma-separated list, a string of
-	     *      single character subdomains, or a list.
-	     * @returns {string|list|this}
-	     */
-	    this.subdomains = function (subdomains) {
-	      if (subdomains === undefined) {
-	        return this._options.subdomains;
-	      }
-	      if (subdomains) {
-	        if ($.type(subdomains) === 'string') {
-	          if (subdomains.indexOf(',') >= 0) {
-	            subdomains = subdomains.split(',');
-	          } else {
-	            subdomains = subdomains.split('');
-	          }
-	        }
-	        this._options.subdomains = subdomains;
-	        this.reset();
-	        this.map().draw();
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Return a value from the tileOffset function, caching it for different
-	     * levels.
-	     *
-	     * @param {number} level The level to pass to the tileOffset function.
-	     * @returns {object} A tile offset object with `x` and `y` properties.
-	     */
-	    this._tileOffset = function (level) {
-	      if (m_tileOffsetValues[level] === undefined) {
-	        m_tileOffsetValues[level] = this._options.tileOffset(level);
-	      }
-	      return m_tileOffsetValues[level];
-	    };
-
-	    /**
-	     * Get/Set visibility of the layer.
-	     *
-	     * @param {boolean|undefined} val If unspecified, return the visibility,
-	     *    otherwise set it.
-	     * @returns {boolean|this} Either the visibility (if getting) or the layer
-	     *    (if setting).
-	     */
-	    this.visible = function (val) {
-	      if (val === undefined) {
-	        return s_visible();
-	      }
-	      if (this.visible() !== val) {
-	        s_visible(val);
-
-	        if (val) {
-	          this._update();
-	        }
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Initialize after the layer is added to the map.
-	     *
-	     * @returns {this}
-	     */
-	    this._init = function () {
-	      var sublayer;
-
-	      // call super method
-	      s_init.apply(this, arguments);
-
-	      if (this.renderer() === null) {
-	        // Initialize sublayers in the correct order
-	        for (sublayer = 0; sublayer <= this._options.maxLevel; sublayer += 1) {
-	          this._getSubLayer(sublayer);
-	        }
-	      }
-	      return this;
-	    };
-
-	    /**
-	     * Clean up the layer.
-	     *
-	     * @returns {this}
-	     */
-	    this._exit = function () {
-	      this.reset();
-	      // call super method
-	      s_exit.apply(this, arguments);
-	      m_exited = true;
-	      return this;
-	    };
-
-	    adjustLayerForRenderer('tile', this);
-
-	    return this;
-	  };
-
-	  /**
-	   * This object contains the default options used to initialize the tileLayer.
-	   */
-	  tileLayer.defaults = {
-	    minLevel: 0,
-	    maxLevel: 18,
-	    tileOverlap: 0,
-	    tileWidth: 256,
-	    tileHeight: 256,
-	    wrapX: true,
-	    wrapY: false,
-	    url: null,
-	    subdomains: 'abc',
-	    tileOffset: function (level) {
-	      return {x: 0, y: 0};
-	    },
-	    tilesMaxBounds: null,
-	    topDown: false,
-	    keepLower: true,
-	    // cacheSize: 400,  // set depending on keepLower
-	    tileRounding: Math.round,
-	    attribution: '',
-	    animationDuration: 0
-	  };
-
-	  inherit(tileLayer, featureLayer);
-	  return tileLayer;
-	})();
-
-
-/***/ }),
-/* 244 */
-/***/ (function(module, exports) {
-
-	module.exports = (function () {
-	  'use strict';
-
-	  /**
-	   * This class implements a simple cache for tile objects.  Each tile is
-	   * stored in cache object keyed by a configurable hashing function.  Another
-	   * array keeps track of last access times for each tile to purge old tiles
-	   * once the maximum cache size is reached.
-	   *
-	   * @class geo.tileCache
-	   *
-	   * @param {object?} [options] A configuratoin object for the cache
-	   * @param {number} [options.size=64] The maximum number of tiles to store
-	   */
-	  var tileCache = function (options) {
-	    if (!(this instanceof tileCache)) {
-	      return new tileCache(options);
-	    }
-	    options = options || {};
-	    this._size = options.size || 64;
-
-	    /**
-	     * Get/set the maximum cache size.
-	     */
-	    Object.defineProperty(this, 'size', {
-	      get: function () { return this._size; },
-	      set: function (n) {
-	        while (this._atime.length > n) {
-	          this.remove(this._atime[this._atime.length - 1]);
-	        }
-	        this._size = n;
-	      }
-	    });
-
-	    /**
-	     * Get the current cache size.
-	     */
-	    Object.defineProperty(this, 'length', {
-	      get: function () { return this._atime.length; }
-	    });
-
-	    /**
-	     * Get the position of the tile in the access queue.
-	     * @param {string} hash The tile's hash value
-	     * @returns {number} The position in the queue or -1
-	     */
-	    this._access = function (hash) {
-	      return this._atime.indexOf(hash);
-	    };
-
-	    /**
-	     * Remove a tile from the cache.
-	     * @param {string|geo.tile} tile The tile or its hash
-	     * @returns {bool} true if a tile was removed
-	     */
-	    this.remove = function (tile) {
-	      var hash = typeof tile === 'string' ? tile : tile.toString();
-
-	      // if the tile is not in the cache
-	      if (!(hash in this._cache)) {
-	        return false;
-	      }
-
-	      // Remove the tile from the access queue
-	      this._atime.splice(this._access(hash), 1);
-
-	      // Remove the tile from the cache
-	      delete this._cache[hash];
-	      return true;
-	    };
-
-	    /**
-	     * Remove all tiles from the cache.
-	     */
-	    this.clear = function () {
-	      this._cache = {};  // The hash -> tile mapping
-	      this._atime = [];  // The access queue (the hashes are stored)
-	      return this;
-	    };
-
-	    /**
-	     * Get a tile from the cache if it exists, otherwise
-	     * return null.  This method also moves the tile to the
-	     * front of the access queue.
-	     *
-	     * @param {string|geo.tile} hash The tile or the tile hash value
-	     * @param {boolean} noMove if true, don't move the tile to the front of the
-	     *     access queue.
-	     * @returns {geo.tile|null}
-	     */
-	    this.get = function (hash, noMove) {
-	      hash = typeof hash === 'string' ? hash : hash.toString();
-	      if (!(hash in this._cache)) {
-	        return null;
-	      }
-
-	      if (!noMove) {
-	        this._atime.splice(this._access(hash), 1);
-	        this._atime.unshift(hash);
-	      }
-	      return this._cache[hash];
-	    };
-
-	    /**
-	     * Add a tile to the cache.
-	     * @param {geo.tile} tile
-	     * @param {function} removeFunc if specified and tiles must be purged from
-	     *      the cache, call this function on each tile before purging.
-	     * @param {boolean} noPurge if true, don't purge tiles.
-	     */
-	    this.add = function (tile, removeFunc, noPurge) {
-	      // remove any existing tiles with the same hash
-	      this.remove(tile);
-	      var hash = tile.toString();
-
-	      // add the tile
-	      this._cache[hash] = tile;
-	      this._atime.unshift(hash);
-
-	      if (!noPurge) {
-	        this.purge(removeFunc);
-	      }
-	    };
-
-	    /**
-	     * Purge tiles from the cache if it is full.
-	     * @param {function} removeFunc if specified and tiles must be purged from
-	     *      the cache, call this function on each tile before purging.
-	     */
-	    this.purge = function (removeFunc) {
-	      var hash;
-	      while (this._atime.length > this.size) {
-	        hash = this._atime.pop();
-	        var tile = this._cache[hash];
-	        if (removeFunc) {
-	          removeFunc(tile);
-	        }
-	        delete this._cache[hash];
-	      }
-	    };
-
-	    this.clear();
-	    return this;
-	  };
-	  return tileCache;
-	})();
-
-
-/***/ }),
-/* 245 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Create a new instance of class pathFeature
-	 *
-	 * @class geo.pathFeature
-	 * @extends geo.feature
-	 * @returns {geo.pathFeature}
-	 */
-	var pathFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof pathFeature)) {
-	    return new pathFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_position = arg.position === undefined ? [] : arg.position,
-	      s_init = this._init;
-
-	  /**
-	   * Get/Set positions
-	   *
-	   * @returns {geo.pathFeature}
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_position;
-	    }
-	    // Copy incoming array of positions
-	    m_position = val;
-	    m_this.dataTime().modified();
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        'strokeWidth': function () { return 1; },
-	        'strokeColor': function () { return { r: 1.0, g: 1.0, b: 1.0 }; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    m_this.style(defaultStyle);
-
-	    if (m_position) {
-	      m_this.dataTime().modified();
-	    }
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(pathFeature, feature);
-	module.exports = pathFeature;
-
-
-/***/ }),
-/* 246 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-	var geo_event = __webpack_require__(9);
-	var util = __webpack_require__(83);
-
-	/**
-	 * Create a new instance of class imagemapFeature
-	 *
-	 * @class geo.pixelmapFeature
-	 * @param {Object} arg Options object
-	 * @extends geo.feature
-	 * @param {Object|Function|HTMLImageElement} [url] URL of a pixel map or an
-	 *   HTML Image element.  The rgb data is interpretted as an index of the form
-	 *   0xbbggrr.  The alpha channel is ignored.
-	 * @param {Object|Function} [color] The color that should be used for each data
-	 *   element.  Data elements correspond to the indices in the pixel map.  If an
-	 *   index is larger than the number of data elements, it will be transparent.
-	 *   If there is more data than there are indices, it is ignored.
-	 * @param {Object|Function} [position] Position of the image.  Default is
-	 *   (data).  The position is an Object which specifies the corners of the
-	 *   quad: ll, lr, ur, ul.  At least two opposite corners must be specified.
-	 *   The corners do not have to physically correspond to the order specified,
-	 *   but rather correspond to that part of the image map.  If a corner is
-	 *   unspecified, it will use the x coordinate from one adjacent corner, the y
-	 *   coordinate from the other adjacent corner, and the average z value of
-	 *   those two corners.  For instance, if ul is unspecified, it is
-	 *   {x: ll.x, y: ur.y}.  Note that each quad is rendered as a pair of
-	 *   triangles: (ll, lr, ul) and (ur, ul, lr).  Nothing special is done for
-	 *   quads that are not convex or quads that have substantially different
-	 *   transformations for those two triangles.
-	 * @returns {geo.pixelmapFeature}
-	 */
-
-	var pixelmapFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof pixelmapFeature)) {
-	    return new pixelmapFeature(arg);
-	  }
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_quadFeature,
-	      m_srcImage,
-	      m_info,
-	      s_update = this._update,
-	      s_init = this._init,
-	      s_exit = this._exit;
-
-	  /**
-	   * Get/Set position accessor
-	   *
-	   * @returns {geo.pixelmap}
-	   */
-	  this.position = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('position');
-	    } else if (val !== m_this.style('position')) {
-	      m_this.style('position', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get/Set url accessor
-	   *
-	   * @returns {geo.pixelmap}
-	   */
-	  this.url = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('url');
-	    } else if (val !== m_this.style('url')) {
-	      m_srcImage = m_info = undefined;
-	      m_this.style('url', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the maximum index value from the pixelmap.  This is a value present in
-	   * the pixelmap.
-	   *
-	   * @returns {geo.pixelmap}
-	   */
-	  this.maxIndex = function () {
-	    if (m_info) {
-	      /* This isn't just m_info.mappedColors.length - 1, since there
-	       * may be more data than actual indices. */
-	      if (m_info.maxIndex === undefined) {
-	        m_info.maxIndex = 0;
-	        for (var idx in m_info.mappedColors) {
-	          if (m_info.mappedColors.hasOwnProperty(idx)) {
-	            m_info.maxIndex = Math.max(m_info.maxIndex, idx);
-	          }
-	        }
-	      }
-	      return m_info.maxIndex;
-	    }
-	  };
-
-	  /**
-	   * Get/Set color accessor
-	   *
-	   * @returns {geo.pixelmap}
-	   */
-	  this.color = function (val) {
-	    if (val === undefined) {
-	      return m_this.style('color');
-	    } else if (val !== m_this.style('color')) {
-	      m_this.style('color', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * If the specified coordinates are in the rendered quad, use the basis
-	   * information from the quad to determine the pixelmap index value so that it
-	   * can be included in the found results.
-	   *
-	   * @param {geo.geoPosition} coordinate point to search for in map interface
-	   *    gcs.
-	   */
-	  this.pointSearch = function (coordinate) {
-	    if (m_quadFeature && m_info) {
-	      var result = m_quadFeature.pointSearch(coordinate);
-	      if (result.index.length === 1 && result.extra && result.extra[result.index[0]].basis) {
-	        var basis = result.extra[result.index[0]].basis, x, y, idx;
-	        x = Math.floor(basis.x * m_info.width);
-	        y = Math.floor(basis.y * m_info.height);
-	        if (x >= 0 && x < m_info.width &&
-	            y >= 0 && y < m_info.height) {
-	          idx = m_info.indices[y * m_info.width + x];
-	          result = {
-	            index: [idx],
-	            found: [m_this.data()[idx]]
-	          };
-	          return result;
-	        }
-	      }
-	    }
-	    return {index: [], found: []};
-	  };
-
-	  /**
-	   * Build
-	   */
-	  this._build = function () {
-	    /* Set the build time at the start of the call.  A build can result in
-	     * drawing a quad, which can trigger a full layer update, which in tern
-	     * checks if this feature is built.  Setting the build time avoid calling
-	     * this a second time. */
-	    m_this.buildTime().modified();
-	    if (!m_srcImage) {
-	      var src = this.style.get('url')();
-	      if (util.isReadyImage(src)) {
-	        /* we have an already loaded image, so we can just use it. */
-	        m_srcImage = src;
-	        this._computePixelmap();
-	      } else if (src) {
-	        var defer = $.Deferred(), prev_onload, prev_onerror;
-	        if (src instanceof Image) {
-	          /* we have an unloaded image.  Hook to the load and error callbacks
-	           * so that when it is loaded we can use it. */
-	          m_srcImage = src;
-	          prev_onload = src.onload;
-	          prev_onerror = src.onerror;
-	        } else {
-	          /* we were given a url, so construct a new image */
-	          m_srcImage = new Image();
-	          // Only set the crossOrigin parameter if this is going across origins.
-	          if (src.indexOf(':') >= 0 &&
-	              src.indexOf('/') === src.indexOf(':') + 1) {
-	            m_srcImage.crossOrigin = this.style.get('crossDomain')() || 'anonymous';
-	          }
-	        }
-	        m_srcImage.onload = function () {
-	          if (prev_onload) {
-	            prev_onload.apply(this, arguments);
-	          }
-	          /* Only use this image if our pixelmap hasn't changed since we
-	           * attached our handler */
-	          if (m_this.style.get('url')() === src) {
-	            m_info = undefined;
-	            m_this._computePixelmap();
-	          }
-	          defer.resolve();
-	        };
-	        m_srcImage.onerror = function () {
-	          if (prev_onerror) {
-	            prev_onerror.apply(this, arguments);
-	          }
-	          defer.reject();
-	        };
-	        defer.promise(this);
-	        this.layer().addPromise(this);
-	        if (!(src instanceof Image)) {
-	          m_srcImage.src = src;
-	        }
-	      }
-	    } else if (m_info) {
-	      this._computePixelmap();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Compute information for this pixelmap image.  It is wasterful to call this
-	   * if the pixelmap has already been prepared (it is invalidated by a change
-	   * in the image).
-	   */
-	  this._preparePixelmap = function () {
-	    var i, idx, pixelData;
-
-	    if (!util.isReadyImage(m_srcImage)) {
-	      return;
-	    }
-	    m_info = {
-	      width: m_srcImage.naturalWidth,
-	      height: m_srcImage.naturalHeight,
-	      canvas: document.createElement('canvas'),
-	      updateIdx: {}
-	    };
-
-	    m_info.canvas.width = m_info.width;
-	    m_info.canvas.height = m_info.height;
-	    m_info.context = m_info.canvas.getContext('2d');
-
-	    m_info.context.drawImage(m_srcImage, 0, 0);
-	    m_info.imageData = m_info.context.getImageData(
-	      0, 0, m_info.canvas.width, m_info.canvas.height);
-	    pixelData = m_info.imageData.data;
-	    m_info.indices = new Array(pixelData.length / 4);
-	    m_info.area = pixelData.length / 4;
-
-	    m_info.mappedColors = {};
-	    for (i = 0; i < pixelData.length; i += 4) {
-	      idx = pixelData[i] + (pixelData[i + 1] << 8) + (pixelData[i + 2] << 16);
-	      m_info.indices[i / 4] = idx;
-	      if (!m_info.mappedColors[idx]) {
-	        m_info.mappedColors[idx] = {first: i / 4};
-	      }
-	      m_info.mappedColors[idx].last = i / 4;
-	    }
-	    return m_info;
-	  };
-
-	  /**
-	   * Given the loaded pixelmap image, create a canvas the size of the image.
-	   * Compute a color for each distinct index and recolor the canvas based on
-	   * thise colors, then draw the resultant image as a quad.
-	   */
-	  this._computePixelmap = function () {
-	    var data = m_this.data() || [],
-	        colorFunc = m_this.style.get('color'),
-	        i, idx, lastidx, color, pixelData, indices, mappedColors,
-	        updateFirst, updateLast = -1, update, prepared;
-
-	    if (!m_info) {
-	      if (!m_this._preparePixelmap()) {
-	        return;
-	      }
-	      prepared = true;
-	    }
-	    mappedColors = m_info.mappedColors;
-	    updateFirst = m_info.area;
-	    for (idx in mappedColors) {
-	      if (mappedColors.hasOwnProperty(idx)) {
-	        color = colorFunc(data[idx], +idx) || {};
-	        color = [
-	          (color.r || 0) * 255,
-	          (color.g || 0) * 255,
-	          (color.b || 0) * 255,
-	          color.a === undefined ? 255 : (color.a * 255)
-	        ];
-	        mappedColors[idx].update = (
-	          !mappedColors[idx].color ||
-	          mappedColors[idx].color[0] !== color[0] ||
-	          mappedColors[idx].color[1] !== color[1] ||
-	          mappedColors[idx].color[2] !== color[2] ||
-	          mappedColors[idx].color[3] !== color[3]);
-	        if (mappedColors[idx].update) {
-	          mappedColors[idx].color = color;
-	          updateFirst = Math.min(mappedColors[idx].first, updateFirst);
-	          updateLast = Math.max(mappedColors[idx].last, updateLast);
-	        }
-	      }
-	    }
-	    /* If nothing was updated, we are done */
-	    if (updateFirst >= updateLast) {
-	      return;
-	    }
-	    /* Update only the extent that has changed */
-	    pixelData = m_info.imageData.data;
-	    indices = m_info.indices;
-	    for (i = updateFirst; i <= updateLast; i += 1) {
-	      idx = indices[i];
-	      if (idx !== lastidx) {
-	        lastidx = idx;
-	        color = mappedColors[idx].color;
-	        update = mappedColors[idx].update;
-	      }
-	      if (update) {
-	        pixelData[i * 4] = color[0];
-	        pixelData[i * 4 + 1] = color[1];
-	        pixelData[i * 4 + 2] = color[2];
-	        pixelData[i * 4 + 3] = color[3];
-	      }
-	    }
-	    /* Place the updated area into the canvas */
-	    m_info.context.putImageData(
-	      m_info.imageData, 0, 0, 0, Math.floor(updateFirst / m_info.width),
-	      m_info.width, Math.ceil((updateLast + 1) / m_info.width));
-
-	    /* If we haven't made a quad feature, make one now.  The quad feature needs
-	     * to have the canvas capability. */
-	    if (!m_quadFeature) {
-	      m_quadFeature = m_this.layer().createFeature('quad', {
-	        selectionAPI: false,
-	        gcs: m_this.gcs(),
-	        visible: m_this.visible(undefined, true)
-	      });
-	      m_this.dependentFeatures([m_quadFeature]);
-	      m_quadFeature.style({
-	        image: m_info.canvas,
-	        position: m_this.style.get('position')})
-	      .data([{}])
-	      .draw();
-	    }
-	    /* If we prepared the pixelmap and rendered it, send a prepared event */
-	    if (prepared) {
-	      m_this.geoTrigger(geo_event.pixelmap.prepared, {
-	        pixelmap: m_this
-	      });
-	    }
-	  };
-
-	  /**
-	   * Update
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
-	        m_this.updateTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Destroy
-	   * @memberof geo.pixelmapFeature
-	   */
-	  this._exit = function (abc) {
-	    if (m_quadFeature && m_this.layer()) {
-	      m_this.layer().deleteFeature(m_quadFeature);
-	      m_quadFeature = null;
-	      m_this.dependentFeatures([]);
-	    }
-	    s_exit();
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    arg = arg || {};
-	    s_init.call(m_this, arg);
-
-	    var style = $.extend(
-	      {},
-	      {
-	        color: function (d, idx) {
-	          return {
-	            r: (idx & 0xFF) / 255,
-	            g: ((idx >> 8) & 0xFF) / 255,
-	            b: ((idx >> 16) & 0xFF) / 255,
-	            a: 1
-	          };
-	        },
-	        position: function (d) { return d; }
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-	    if (arg.position !== undefined) {
-	      style.position = arg.position;
-	    }
-	    if (arg.url !== undefined) {
-	      style.url = arg.url;
-	    }
-	    if (arg.color !== undefined) {
-	      style.color = arg.color;
-	    }
-	    m_this.style(style);
-	    m_this.dataTime().modified();
-	  };
-
-	  return this;
-	};
-
-	/**
-	 * Create a pixelmapFeature from an object.
-	 *
-	 * @see {@link geo.feature.create}
-	 * @param {geo.layer} layer The layer to add the feature to
-	 * @param {geo.pixelmapFeature.spec} spec The object specification
-	 * @returns {geo.pixelmapFeature|null}
-	 */
-	pixelmapFeature.create = function (layer, spec) {
-	  'use strict';
-
-	  spec = spec || {};
-	  spec.type = 'pixelmap';
-	  return feature.create(layer, spec);
-	};
-
-	pixelmapFeature.capabilities = {
-	  /* core feature name -- support in any manner */
-	  feature: 'pixelmap'
-	};
-
-	inherit(pixelmapFeature, feature);
-	module.exports = pixelmapFeature;
-
-
-/***/ }),
-/* 247 */
-/***/ (function(module, exports) {
-
-	/*
-	 * Type definitions for jsdoc.
-	 */
-
-	/**
-	 * General object specification for map types.  Any additional values in the
-	 * object are passed to the map constructor.
-	 *
-	 * @typedef geo.map.spec
-	 * @type {object}
-	 * @property {object[]} [data=[]] The default data array to apply to each
-	 *      feature if none exists.
-	 * @property {geo.layer.spec[]} [layers=[]] Layers to create.
-	 */
-
-	/**
-	 * General representation of rectangular bounds in world coordinates.
-	 *
-	 * @typedef geo.geoBounds
-	 * @type {object}
-	 * @property {number} left Horizontal coordinate of the top-left corner.
-	 * @property {number} top Vertical coordinate of the top-left corner.
-	 * @property {number} right Horizontal coordinate of the bottom-right corner.
-	 * @property {number} bottom Vertical coordinate of the bottom-right corner.
-	 */
-
-	/**
-	 * A location and zoom value.
-	 *
-	 * @typedef geo.zoomAndCenter
-	 * @type {object}
-	 * @property {geo.geoPosition} center The center coordinates.
-	 * @property {number} zoom The zoom level.
-	 */
-
-	/**
-	 * General representation of rectangular bounds in pixel coordinates.
-	 *
-	 * @typedef geo.screenBounds
-	 * @type {object}
-	 * @property {geo.screenPosition} upperLeft Upper left corner.
-	 * @property {geo.screenPosition} upperRight Upper right corner.
-	 * @property {geo.screenPosition} lowerLeft Lower left corner.
-	 * @property {geo.screenPosition} lowerRight Lower right corner.
-	 */
-
-	/**
-	 * General representation of a point on the screen.
-	 *
-	 * @typedef geo.screenPosition
-	 * @type {object}
-	 * @property {number} x Horizontal coordinate in pixels.
-	 * @property {number} y Vertical coordinate in pixels.
-	 */
-
-	/**
-	 * General represention of a point on the earth.  The coordinates are most
-	 * commonly in longitude and latitude, but the coordinate system is changed
-	 * by the interface gcs.
-	 *
-	 * @typedef geo.geoPosition
-	 * @type {object}
-	 * @property {number} x Horizontal coordinate, often degrees longitude.
-	 * @property {number} y Vertical coordinate, often degrees latitude.
-	 * @property {number} [z=0] Altitude coordinate.
-	 */
-
-	/**
-	 * General represention of a two-dimensional point in any coordinate system.
-	 *
-	 * @typedef geo.point2D
-	 * @type {object}
-	 * @property {number} x Horizontal coordinate.
-	 * @property {number} y Vertical coordinate.
-	 */
-
-	/**
-	 * Represention of a point on the map.  The coordinates are in the map's
-	 * reference system, possibly with an affine transformation.
-	 *
-	 * @typedef geo.worldPosition
-	 * @type {object}
-	 * @property {number} x Horizontal coordinate in map coordinates.
-	 * @property {number} y Vertical coordinate in map coordinates.
-	 * @property {number} [z=0] Altitude coordinate, often zero.
-	 */
-
-	/**
-	 * Represention of a size in pixels.
-	 *
-	 * @typedef geo.screenSize
-	 * @type {object}
-	 * @property {number} width Width in pixels.
-	 * @property {number} height Height in pixels.
-	 */
-
-	/**
-	 * The status of all mouse buttons.
-	 *
-	 * @typedef geo.mouseButtons
-	 * @type {object}
-	 * @property {boolean} left True if the left mouse button is down.
-	 * @property {boolean} right True if the right mouse button is down.
-	 * @property {boolean} middle True if the middle mouse button is down.
-	 */
-
-	/**
-	 * The status of all modifier keys.  These are usually copied from the
-	 * standard DOM events.
-	 *
-	 * @typedef geo.modifierKeys
-	 * @type {object}
-	 * @property {boolean} alt True if the alt or option key is down.
-	 * @property {boolean} ctrl True if the control key is down.
-	 * @property {boolean} shift True if the shift key is down.
-	 * @property {boolean} meta True if the meta, windows, or command key
-	 *      is down.
-	 */
-
-	/**
-	 * The state of the mouse.
-	 *
-	 * @typedef geo.mouseState
-	 * @type {object}
-	 * @property {geo.screenPosition} page Mouse location in pixel space relative
-	 *      to the entire browser window.
-	 * @property {geo.screenPosition} map Mouse location in pixel space relative to
-	 *      the map DOM node.
-	 * @property {geo.geoPosition} geo Mouse location in interface gcs space.
-	 * @property {geo.geoPosition} mapgcs Mouse location in gcs space.
-	 * @property {geo.mouseButtons} buttons The current state of the mouse buttons.
-	 * @property {geo.modifierKeys} modifiers The current state of all modifier
-	 *      keys.
-	 * @property {Date} time The timestamp the event took place.
-	 * @property {number} deltaTime The time in milliseconds since the last mouse
-	 *      event.
-	 * @property {geo.screenPosition} velocity The velocity of the mouse pointer
-	 *      in pixels per millisecond.
-	 */
-
-	/**
-	 * The current brush selection (this is when a rectangular area is selected by
-	 * dragging).
-	 *
-	 * @typedef geo.brushSelection
-	 * @type {object}
-	 * @property {geo.screenBounds} display The selection bounds in pixel space.
-	 * @property {object} gcs The selection bounds in the map's gcs.
-	 * @property {geo.geoPosition} gcs.upperLeft Upper left corner.
-	 * @property {geo.geoPosition} gcs.upperRight Upper right corner.
-	 * @property {geo.geoPosition} gcs.lowerLeft Lower left corner.
-	 * @property {geo.geoPosition} gcs.lowerRight Lower right corner.
-	 * @property {geo.mouseState} mouse The current mouse state.
-	 * @property {geo.mouseState} origin The mouse state at the start of the
-	 *      brush action.
-	 */
-
-	/**
-	 * The conditions that are necessary to make an action occur.
-	 *
-	 * @typedef geo.actionRecord
-	 * @type {object}
-	 * @property {string} action The name of the action, from (@link geo.action}.
-	 * @property {string} [owner] A name of an owning process that can be used to
-	 *      locate or filter actions.
-	 * @property {string} [name] A human-readable name that can be used to locate
-	 *      or filter actions.
-	 * @property {string|object} input The name of an input that is used for the
-	 *      action, or an object with input names as keys and boolean values of
-	 *      inputs that are required to occur or required to not occur to trigger
-	 *      the action.  Input names include `left`, `right`, `middle` (for mouse
-	 *      buttons), `wheel` (the mouse wheel), `pan` (touch pan), `rotate` (touch
-	 *      rotate).
-	 * @property {string|object} [modifiers] The name of a modifier key or an
-	 *      object with modifiers as the keys and boolean values.  The listed
-	 *      modifiers must be set or unset depending on the boolean value.
-	 *      Modifiers include `shift`, `ctrl`, `alt`, and `meta`.
-	 * @property {boolean|string} [selectionRectangle] If truthy, a selection
-	 *      rectangle is shown during the action.  If a string, the name of an
-	 *      event that is triggered when the selection is complete.
-	 */
-
-	/**
-	 * The current action state a map interactor.
-	 *
-	 * @typedef geo.actionState
-	 * @type {object}
-	 * @property {string} action Name of the action that is being handled.
-	 * @property {geo.actionRecord} actionRecord The action record which triggered
-	 *      the current action.
-	 * @property {string} [origAction] The name of an action that triggered this
-	 *      action.
-	 * @property {geo.mouseState} origin The mouse state at the start of the
-	 *      action.
-	 * @property {number} initialZoom The zoom level at the start of the action.
-	 * @property {number} initialRotation The map's rotation in radians at the
-	 *      start of the action.
-	 * @property {number} initialEventRotation The rotation reported by the
-	 *      event that triggered this action.  For example, this could be the
-	 *      angle between two multi-touch points.
-	 * @property {object} delta The total movement of during the action in gcs
-	 *      coordinates.
-	 * @property {number} delta.x The horizontal movement during the action.
-	 * @property {number} delta.y The vertical movement during the action.
-	 * @property {boolean} boundDocumentHandlers `true` if the mouse is down and
-	 *      being tracked.
-	 * @property {Date} [start] The time when the action started.
-	 * @property {function} [handler] A function to call on every animation from
-	 *      while the action is occurring.
-	 * @property {geo.mouseState} [momentum] The mouse location when a momentum
-	 *      action starts.
-	 * @property {boolean} [zoomrotateAllowRotation] Truthy if enough movement has
-	 *      occurred that rotations are allowed.
-	 * @property {boolean} [zoomrotateAllowZoom] Truthy if enough movement has
-	 *      occurred that zooms are allowed.
-	 * @property {boolean} [zoomrotateAllowPan] Truthy if enough movement has
-	 *      occurred that pans are allowed.
-	 * @property {number} [lastRotationDelta] When rotating, the last amount that
-	 *      was rotated from the start of the action.  This is used to debounce
-	 *      jitter on touch events.
-	 * @property {geo.geoPosition} [initialEventGeo] The position of the mouse
-	 *      when significant movement first occurred.
-	 */
-
-	/**
-	 * A color value.  Although opacity can be specified, it is not always used.
-	 * When a string is specified, any of the following forms can be used:
-	 *   - CSS color name
-	 *   - `#rrggbb` The color specified in hexadecmial with each channel on a
-	 *     scale between 0 and 255 (`ff`).  Case insensitive.
-	 *   - `#rrggbbaa` The color and opacity specified in hexadecmial with each
-	 *     channel on a scale between 0 and 255 (`ff`).  Case insensitive.
-	 *   - `#rgb` The color specified in hexadecmial with each channel on a scale
-	 *     between 0 and 15 (`f`).  Case insensitive.
-	 *   - `#rgba` The color and opacity specified in hexadecmial with each channel
-	 *      on a scale between 0 and 15 (`f`).  Case insensitive.
-	 *   - `rgb(R, G, B)`, `rgb(R, G, B, A)`, `rgba(R, G, B)`, `rgba(R, G, B, A)`
-	 *     The color with the values of each color channel specified as numeric
-	 *     values between 0 and 255 or as percent (between 0 and 100) if a percent
-	 *     `%` follows the number.  The alpha (opacity) channel is optional and can
-	 *     either be a number between 0 and 1 or a percent.  White space may appear
-	 *     before and after numbers, and between the number and a percent symbol.
-	 *     Commas are not required.  A slash may be used as a separator before the
-	 *     alpha value instead of a comma.  The numbers conform to the CSS number
-	 *     specification, and can be signed floating-point values, possibly with
-	 *     exponents.
-	 *   - `hsl(H, S, L)`, `hsl(H, S, L, A)`, `hsla(H, S, L)`, `hsla(H, S, L, A)`
-	 *     Hue, saturation, and lightness with optional alpha (opacity).  Hue is a
-	 *     number between 0 and 360 and is interpretted as degrees unless an angle
-	 *     unit is specified.  CSS units of `deg`, `grad`, `rad`, and `turn` are
-	 *     supported.  Saturation and lightness are percentages between 0 and 100
-	 *     and *must* be followed by a percent `%` symbol.  The alpha (opacity)
-	 *     channel is optional and is specified as with `rgba(R, G, B, A)`.
-	 *   - `transparent` Black with 0 opacity.
-	 *
-	 * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for
-	 * more details on CSS color values.
-	 *
-	 * @typedef geo.geoColor
-	 * @type {geo.geoColorObject|string}
-	 */
-
-	/**
-	 * A color value represented as an object.  Although opacity can be specified,
-	 * it is not always used.
-	 *
-	 * @typedef {object} geo.geoColorObject
-	 * @property {number} r The red intensity on a scale of [0-1].
-	 * @property {number} g The green intensity on a scale of [0-1].
-	 * @property {number} b The blue intensity on a scale of [0-1].
-	 * @property {number} [a] The opacity on a scale of [0-1].  If unspecified and
-	 *      used, it should be treated as 1.
-	 */
-
-
-/***/ }),
-/* 248 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var feature = __webpack_require__(207);
-
-	/**
-	 * Create a new instance of class vectorFeature
-	 *
-	 * @class geo.vectorFeature
-	 * @extends geo.feature
-	 * @returns {geo.vectorFeature}
-	 */
-	var vectorFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof vectorFeature)) {
-	    return new vectorFeature(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-
-	  arg = arg || {};
-	  feature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      s_style = this.style;
-
-	  /**
-	   * Get or set the accessor for the origin of the vector.  This is the point
-	   * that the vector base resides at.  Defaults to (0, 0, 0).
-	   * @param {geo.accessor|geo.geoPosition} [accessor] The origin accessor
-	   */
-	  this.origin = function (val) {
-	    if (val === undefined) {
-	      return s_style('origin');
-	    } else {
-	      s_style('origin', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Get or set the accessor for the displacement (coordinates) of the vector.
-	   * @param {geo.accessor|geo.geoPosition} [accessor] The accessor
-	   */
-	  this.delta = function (val) {
-	    if (val === undefined) {
-	      return s_style('delta');
-	    } else {
-	      s_style('delta', val);
-	      m_this.dataTime().modified();
-	      m_this.modified();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   * @protected
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-
-	    var defaultStyle = $.extend(
-	      {},
-	      {
-	        strokeColor: 'black',
-	        strokeWidth: 2.0,
-	        strokeOpacity: 1.0,
-	        originStyle: 'none',
-	        endStyle: 'arrow',
-	        origin: {x: 0, y: 0, z: 0},
-	        delta: function (d) { return d; },
-	        scale: null // size scaling factor (null -> renderer decides)
-	      },
-	      arg.style === undefined ? {} : arg.style
-	    );
-
-	    if (arg.origin !== undefined) {
-	      defaultStyle.origin = arg.origin;
-	    }
-
-	    m_this.style(defaultStyle);
-	    m_this.dataTime().modified();
-	  };
-	};
-
-	inherit(vectorFeature, feature);
-	module.exports = vectorFeature;
-
-
-/***/ }),
-/* 249 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = ("0.16.0");
-
-
-/***/ }),
-/* 250 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = ("644aa5f8e129f78eb97c81b3e4838c5bc33637c2");
-
-
-/***/ }),
-/* 251 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var geo_event = __webpack_require__(9);
-	geo_event.d3 = {
-	  rescale: __webpack_require__(229)
-	};
-
-	/**
-	 * @namespace geo.d3
-	 */
-	module.exports = {
-	  graphFeature: __webpack_require__(252),
-	  lineFeature: __webpack_require__(253),
-	  object: __webpack_require__(227),
-	  pathFeature: __webpack_require__(254),
-	  pointFeature: __webpack_require__(255),
-	  quadFeature: __webpack_require__(256),
-	  renderer: __webpack_require__(226),
-	  tileLayer: __webpack_require__(257),
-	  uniqueID: __webpack_require__(228),
-	  vectorFeature: __webpack_require__(258)
-	};
-
-
-/***/ }),
-/* 252 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var graphFeature = __webpack_require__(235);
-
-	/**
-	 * @class geo.d3.graphFeature
-	 * @extends geo.graphFeature
-	 */
-	var d3_graphFeature = function (arg) {
-	  'use strict';
-
-	  var m_this = this;
-
-	  if (!(this instanceof d3_graphFeature)) {
-	    return new d3_graphFeature(arg);
-	  }
-	  graphFeature.call(this, arg);
-
-	  /**
-	  *  Returns a d3 selection for the graph elements
-	  */
-	  this.select = function () {
-	    var renderer = m_this.renderer(),
-	        selection = {},
-	        node = m_this.nodeFeature(),
-	        links = m_this.linkFeatures();
-	    selection.nodes = renderer.select(node._d3id());
-	    selection.links = links.map(function (link) {
-	      return renderer.select(link._d3id());
-	    });
-	    return selection;
-	  };
-
-	  return this;
-	};
-
-	inherit(d3_graphFeature, graphFeature);
-
-	registerFeature('d3', 'graph', d3_graphFeature);
-
-	module.exports = d3_graphFeature;
-
-
-/***/ }),
-/* 253 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var lineFeature = __webpack_require__(206);
-
-	/**
-	 * Create a new instance of class lineFeature.
-	 *
-	 * @class geo.d3.lineFeature
-	 * @extends geo.lineFeature
-	 * @param {geo.lineFeature.spec} arg
-	 * @returns {geo.d3.lineFeature}
-	 */
-	var d3_lineFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof d3_lineFeature)) {
-	    return new d3_lineFeature(arg);
-	  }
-
-	  var d3 = __webpack_require__(226).d3;
-	  var object = __webpack_require__(227);
-	  var timestamp = __webpack_require__(209);
-	  var util = __webpack_require__(83);
-
-	  arg = arg || {};
-	  lineFeature.call(this, arg);
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      m_buildTime = timestamp(),
-	      m_maxIdx = 0,
-	      s_update = this._update;
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.lineFeature.spec} arg The feature specification.
-	   * @returns {this}
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-	    return m_this;
-	  };
-
-	  /**
-	   * Build.  Create the necessary elements to render lines.
-	   *
-	   * @returns {this}
-	   */
-	  this._build = function () {
-	    var data = m_this.data() || [],
-	        s_style = m_this.style(),
-	        m_renderer = m_this.renderer(),
-	        pos_func = m_this.position(),
-	        line, i;
-
-	    s_update.call(m_this);
-	    s_style.fill = function () { return false; };
-
-	    data.forEach(function (item, idx) {
-	      var m_style;
-	      var ln = m_this.line()(item, idx);
-
-	      var style = {}, key;
-	      function wrapStyle(func) {
-	        if (util.isFunction(func)) {
-	          return function () {
-	            return func(ln[0], 0, item, idx);
-	          };
-	        } else {
-	          return func;
-	        }
-	      }
-	      for (key in s_style) {
-	        if (s_style.hasOwnProperty(key)) {
-	          style[key] = wrapStyle(s_style[key]);
-	        }
-	      }
-
-	      line = d3.svg.line()
-	          .x(function (d) { return m_this.featureGcsToDisplay(d).x; })
-	          .y(function (d) { return m_this.featureGcsToDisplay(d).y; })
-	          .interpolate(m_this.style.get('closed')(item, idx) && ln.length > 2 ?
-	                       'linear-closed' : 'linear');
-	      // item is an object representing a single line
-	      // m_this.line()(item) is an array of coordinates
-	      m_style = {
-	        data: [ln.map(function (d, i) { return pos_func(d, i, item, idx); })],
-	        append: 'path',
-	        attributes: {
-	          d: line
-	        },
-	        id: m_this._d3id() + idx,
-	        classes: ['d3LineFeature', 'd3SubLine-' + idx],
-	        visible: m_this.visible,
-	        style: style
-	      };
-
-	      m_renderer._drawFeatures(m_style);
-	    });
-	    for (i = data.length; i < m_maxIdx; i += 1) {
-	      m_renderer._removeFeature(m_this._d3id() + i);
-	    }
-	    m_maxIdx = data.length;
-
-	    m_buildTime.modified();
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update.  Rebuild if necessary.
-	   *
-	   * @returns {this}
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.getMTime() >= m_buildTime.getMTime()) {
-	      m_this._build();
-	    }
-
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(d3_lineFeature, lineFeature);
-
-	// Now register it
-	var capabilities = {};
-	capabilities[lineFeature.capabilities.basic] = true;
-	capabilities[lineFeature.capabilities.multicolor] = false;
-
-	registerFeature('d3', 'line', d3_lineFeature, capabilities);
-
-	module.exports = d3_lineFeature;
-
-
-/***/ }),
-/* 254 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var pathFeature = __webpack_require__(245);
-
-	/**
-	 * Create a new instance of class pathFeature
-	 *
-	 * @class geo.d3.pathFeature
-	 * @extends geo.pathFeature
-	 * @extends geo.d3.object
-	 * @returns {geo.d3.pathFeature}
-	 */
-	var d3_pathFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof d3_pathFeature)) {
-	    return new d3_pathFeature(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-	  var d3 = __webpack_require__(226).d3;
-	  var object = __webpack_require__(227);
-	  var timestamp = __webpack_require__(209);
-
-	  arg = arg || {};
-	  pathFeature.call(this, arg);
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      m_buildTime = timestamp(),
-	      s_update = this._update,
-	      m_style = {};
-
-	  m_style.style = {};
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-	    return m_this;
-	  };
-
-	  /**
-	   * Build
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-	    var data = m_this.data() || [],
-	        s_style = m_this.style(),
-	        tmp, diag;
-	    s_update.call(m_this);
-
-	    diag = function (d) {
-	      var p = {
-	        source: d.source,
-	        target: d.target
-	      };
-	      return d3.svg.diagonal()(p);
-	    };
-	    tmp = [];
-	    data.forEach(function (d, i) {
-	      var src, trg;
-	      if (i < data.length - 1) {
-	        src = d;
-	        trg = data[i + 1];
-	        tmp.push({
-	          source: m_this.featureGcsToDisplay(src),
-	          target: m_this.featureGcsToDisplay(trg)
-	        });
-	      }
-	    });
-	    m_style.data = tmp;
-	    m_style.attributes = {
-	      d: diag
-	    };
-
-	    m_style.id = m_this._d3id();
-	    m_style.append = 'path';
-	    m_style.classes = ['d3PathFeature'];
-	    m_style.style = $.extend({
-	      'fill': function () { return false; },
-	      'fillColor': function () { return { r: 0, g: 0, b: 0 }; }
-	    }, s_style);
-	    m_style.visible = m_this.visible;
-
-	    m_this.renderer()._drawFeatures(m_style);
-
-	    m_buildTime.modified();
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update
-	   *
-	   * @override
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) {
-	      m_this._build();
-	    }
-
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(d3_pathFeature, pathFeature);
-
-	registerFeature('d3', 'path', d3_pathFeature);
-
-	module.exports = d3_pathFeature;
-
-
-/***/ }),
-/* 255 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var pointFeature = __webpack_require__(212);
-
-	/**
-	 *
-	 * Create a new instance of pointFeature
-	 *
-	 * @class geo.d3.pointFeature
-	 * @extends geo.pointFeature
-	 * @extends geo.d3.object
-	 * @returns {geo.d3.pointFeature}
-	 */
-	var d3_pointFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof d3_pointFeature)) {
-	    return new d3_pointFeature(arg);
-	  }
-
-	  var d3_object = __webpack_require__(227);
-	  var timestamp = __webpack_require__(209);
-
-	  arg = arg || {};
-	  pointFeature.call(this, arg);
-	  d3_object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_buildTime = timestamp(),
-	      m_style = {};
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-	    return m_this;
-	  };
-
-	  /**
-	   * Build
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-	    var data = m_this.data(),
-	        s_style = m_this.style.get(),
-	        m_renderer = m_this.renderer(),
-	        pos_func = m_this.position();
-
-	    // call super-method
-	    s_update.call(m_this);
-
-	    // default to empty data array
-	    if (!data) { data = []; }
-
-	    // fill in d3 renderer style object defaults
-	    m_style.id = m_this._d3id();
-	    m_style.data = data;
-	    m_style.append = 'circle';
-	    m_style.attributes = {
-	      r: m_renderer._convertScale(s_style.radius),
-	      cx: function (d) {
-	        return m_this.featureGcsToDisplay(pos_func(d)).x;
-	      },
-	      cy: function (d) {
-	        return m_this.featureGcsToDisplay(pos_func(d)).y;
-	      }
-	    };
-	    m_style.style = s_style;
-	    m_style.classes = ['d3PointFeature'];
-	    m_style.visible = m_this.visible;
-
-	    // pass to renderer to draw
-	    m_this.renderer()._drawFeatures(m_style);
-
-	    // update time stamps
-	    m_buildTime.modified();
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update
-	   *
-	   * @override
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.getMTime() >= m_buildTime.getMTime()) {
-	      m_this._build();
-	    }
-
-	    return m_this;
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(d3_pointFeature, pointFeature);
-
-	// Now register it
-	registerFeature('d3', 'point', d3_pointFeature);
-
-	module.exports = d3_pointFeature;
-
-
-/***/ }),
-/* 256 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var quadFeature = __webpack_require__(223);
-
-	/**
-	 * Create a new instance of class quadFeature.
-	 *
-	 * @class geo.d3.quadFeature
-	 * @param {geo.quadFeature.spec} arg Options object.
-	 * @extends geo.quadFeature
-	 * @returns {geo.d3.quadFeature}
-	 */
-	var d3_quadFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof d3_quadFeature)) {
-	    return new d3_quadFeature(arg);
-	  }
-
-	  var $ = __webpack_require__(1);
-	  var d3 = __webpack_require__(226).d3;
-	  var object = __webpack_require__(227);
-
-	  quadFeature.call(this, arg);
-	  object.call(this);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_quads;
-
-	  /**
-	   * Build this feature.
-	   */
-	  this._build = function () {
-	    if (!this.position()) {
-	      return;
-	    }
-	    var renderer = this.renderer(),
-	        map = renderer.layer().map();
-
-	    m_quads = this._generateQuads();
-
-	    var data = [];
-	    $.each(m_quads.clrQuads, function (idx, quad) {
-	      data.push({type: 'clr', quad: quad, zIndex: quad.pos[2]});
-	    });
-	    $.each(m_quads.imgQuads, function (idx, quad) {
-	      if (quad.image) {
-	        data.push({type: 'img', quad: quad, zIndex: quad.pos[2]});
-	      }
-	    });
-
-	    var feature = {
-	      id: this._d3id(),
-	      data: data,
-	      dataIndex: function (d) {
-	        return d.quad.quadId;
-	      },
-	      append: function (d) {
-	        var ns = this.namespaceURI,
-	            element = d.type === 'clr' ? 'polygon' : 'image';
-	        return (ns ? document.createElementNS(ns, element) :
-	                document.createElement(element));
-	      },
-	      attributes: {
-	        fill: function (d) {
-	          if (d.type === 'clr') {
-	            return d3.rgb(255 * d.quad.color.r, 255 * d.quad.color.g,
-	                          255 * d.quad.color.b);
-	          }
-	          /* set some styles here */
-	          if (d.quad.opacity !== 1) {
-	            d3.select(this).style('opacity', d.quad.opacity);
-	          }
-	        },
-	        height: function (d) {
-	          return d.type === 'clr' ? undefined : 1;
-	        },
-	        points: function (d) {
-	          if (d.type === 'clr' && !d.points) {
-	            var points = [], i;
-	            for (i = 0; i < d.quad.pos.length; i += 3) {
-	              var p = {
-	                x: d.quad.pos[i],
-	                y: d.quad.pos[i + 1],
-	                z: d.quad.pos[i + 2]
-	              };
-	              /* We don't use 'p = m_this.featureGcsToDisplay(p);' because the
-	               * quads have already been converted to the map's gcs (no longer
-	               * the feature's gcs or map's ingcs). */
-	              p = map.gcsToDisplay(p, null);
-	              p = renderer.baseToLocal(p);
-	              points.push('' + p.x + ',' + p.y);
-	            }
-	            d.points = (points[0] + ' ' + points[1] + ' ' + points[3] + ' ' +
-	                        points[2]);
-	          }
-	          return d.type === 'clr' ? d.points : undefined;
-	        },
-	        preserveAspectRatio: function (d) {
-	          return d.type === 'clr' ? undefined : 'none';
-	        },
-	        reference: function (d) {
-	          return d.quad.reference;
-	        },
-	        stroke: false,
-	        transform: function (d) {
-	          if (d.type === 'img' && d.quad.image && !d.svgTransform) {
-	            var pos = [], area, maxarea = -1, maxv, i, imgscale,
-	                imgw = d.quad.image.width, imgh = d.quad.image.height;
-	            for (i = 0; i < d.quad.pos.length; i += 3) {
-	              var p = {
-	                x: d.quad.pos[i],
-	                y: d.quad.pos[i + 1],
-	                z: d.quad.pos[i + 2]
-	              };
-	              /* We don't use 'p = m_this.featureGcsToDisplay(p);' because the
-	               * quads have already been converted to the map's gcs (no longer
-	               * the feature's gcs or map's ingcs). */
-	              p = map.gcsToDisplay(p, null);
-	              p = renderer.baseToLocal(p);
-	              pos.push(p);
-	            }
-	            /* We can only fit three corners of the quad to the image, but we
-	             * get to pick which three.  We choose to always include the
-	             * largest of the triangles formed by a set of three vertices.  The
-	             * image is always rendered as a parallelogram, so it may be larger
-	             * than desired, and, for convex quads, miss some of the intended
-	             * area. */
-	            for (i = 0; i < 4; i += 1) {
-	              area = Math.abs(
-	                pos[(i + 1) % 4].x * (pos[(i + 2) % 4].y - pos[(i + 3) % 4].y) +
-	                pos[(i + 2) % 4].x * (pos[(i + 3) % 4].y - pos[(i + 1) % 4].y) +
-	                pos[(i + 3) % 4].x * (pos[(i + 1) % 4].y - pos[(i + 2) % 4].y)) / 2;
-	              if (area > maxarea) {
-	                maxarea = area;
-	                maxv = i;
-	              }
-	            }
-	            d.svgTransform = [
-	              maxv === 3 || maxv === 2 ? pos[1].x - pos[0].x : pos[3].x - pos[2].x,
-	              maxv === 3 || maxv === 2 ? pos[1].y - pos[0].y : pos[3].y - pos[2].y,
-	              maxv === 0 || maxv === 2 ? pos[1].x - pos[3].x : pos[0].x - pos[2].x,
-	              maxv === 0 || maxv === 2 ? pos[1].y - pos[3].y : pos[0].y - pos[2].y,
-	              maxv === 2 ? pos[3].x + pos[0].x - pos[1].x : pos[2].x,
-	              maxv === 2 ? pos[3].y + pos[0].y - pos[1].y : pos[2].y
-	            ];
-	            if (Math.abs(d.svgTransform[1] / imgw) < 1e-6 &&
-	                Math.abs(d.svgTransform[2] / imgh) < 1e-6) {
-	              imgscale = d.svgTransform[0] / imgw;
-	              d.svgTransform[4] = Math.round(d.svgTransform[4] / imgscale) * imgscale;
-	              imgscale = d.svgTransform[3] / imgh;
-	              d.svgTransform[5] = Math.round(d.svgTransform[5] / imgscale) * imgscale;
-	            }
-	          }
-	          return ((d.type !== 'img' || !d.quad.image) ? undefined :
-	                  'matrix(' + d.svgTransform.join(' ') + ')');
-	        },
-	        width: function (d) {
-	          return d.type === 'clr' ? undefined : 1;
-	        },
-	        x: function (d) {
-	          return d.type === 'clr' ? undefined : 0;
-	        },
-	        'xlink:href': function (d) {
-	          return ((d.type === 'clr' || !d.quad.image) ? undefined :
-	                  d.quad.image.src);
-	        },
-	        y: function (d) {
-	          return d.type === 'clr' ? undefined : 0;
-	        }
-	      },
-	      style: {
-	        fillOpacity: function (d) {
-	          return d.type === 'clr' ? d.quad.opacity : undefined;
-	        }
-	      },
-	      onlyRenderNew: !this.style('previewColor') && !this.style('previewImage'),
-	      sortByZ: true,
-	      visible: m_this.visible,
-	      classes: ['d3QuadFeature']
-	    };
-	    renderer._drawFeatures(feature);
-
-	    this.buildTime().modified();
-	  };
-
-	  /**
-	   * Update the feature.
-	   *
-	   * @returns {this}
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
-	        m_this.buildTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize.
-	   */
-	  this._init = function () {
-	    s_init.call(m_this, arg);
-	  };
-
-	  /**
-	   * Destroy.
-	   */
-	  this._exit = function () {
-	    s_exit.call(m_this);
-	  };
-
-	  m_this._init(arg);
-	  return this;
-	};
-
-	inherit(d3_quadFeature, quadFeature);
-
-	// Now register it
-	var capabilities = {};
-	capabilities[quadFeature.capabilities.color] = true;
-	capabilities[quadFeature.capabilities.image] = true;
-	capabilities[quadFeature.capabilities.imageCrop] = false;
-	capabilities[quadFeature.capabilities.imageFixedScale] = false;
-	capabilities[quadFeature.capabilities.imageFull] = false;
-	capabilities[quadFeature.capabilities.canvas] = false;
-	capabilities[quadFeature.capabilities.video] = false;
-
-	registerFeature('d3', 'quad', d3_quadFeature, capabilities);
-	module.exports = d3_quadFeature;
-
-
-/***/ }),
-/* 257 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var registerLayerAdjustment = __webpack_require__(201).registerLayerAdjustment;
-
-	var d3_tileLayer = function () {
-	  'use strict';
-	  var m_this = this,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      m_quadFeature,
-	      m_nextTileId = 0,
-	      m_tiles = [];
-
-	  this._drawTile = function (tile) {
-	    if (!m_quadFeature) {
-	      return;
-	    }
-	    var bounds = m_this._tileBounds(tile),
-	        level = tile.index.level || 0,
-	        to = this._tileOffset(level),
-	        quad = {};
-	    quad.ul = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.ll = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ur = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.lr = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ul.z = quad.ll.z = quad.ur.z = quad.lr.z = level * 1e-5;
-	    m_nextTileId += 1;
-	    quad.id = m_nextTileId;
-	    tile.quadId = quad.id;
-	    quad.image = tile.image;
-	    quad.reference = tile.toString();
-	    m_tiles.push(quad);
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	    m_this.draw();
-	  };
-
-	  /* Remove the tile feature. */
-	  this._remove = function (tile) {
-	    if (tile.quadId !== undefined && m_quadFeature) {
-	      for (var i = 0; i < m_tiles.length; i += 1) {
-	        if (m_tiles[i].id === tile.quadId) {
-	          m_tiles.splice(i, 1);
-	          break;
-	        }
-	      }
-	      m_quadFeature.data(m_tiles);
-	      m_quadFeature._update();
-	      m_this.draw();
-	    }
-	  };
-
-	  /**
-	   * Clean up the layer.
-	   */
-	  this._exit = function () {
-	    m_this.deleteFeature(m_quadFeature);
-	    m_quadFeature = null;
-	    m_tiles = [];
-	    s_exit.apply(m_this, arguments);
-	  };
-
-	  /* Initialize the tile layer.  This creates a series of sublayers so that
-	   * the different layers will stack in the proper order.
-	   */
-	  this._init = function () {
-	    s_init.apply(m_this, arguments);
-	    m_quadFeature = this.createFeature('quad', {
-	      previewColor: m_this._options.previewColor,
-	      previewImage: m_this._options.previewImage
-	    });
-	    m_quadFeature.geoTrigger = undefined;
-	    m_quadFeature.gcs(m_this._options.gcs || m_this.map().gcs());
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	  };
-
-	  this._getSubLayer = function () {};
-	  this._updateSubLayers = undefined;
-	};
-
-	registerLayerAdjustment('d3', 'tile', d3_tileLayer);
-	module.exports = d3_tileLayer;
-
-
-/***/ }),
-/* 258 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var vectorFeature = __webpack_require__(248);
-
-	/**
-	 * Create a new instance of vectorFeature
-	 *
-	 * @class geo.d3.vectorFeature
-	 * @extends geo.vectorFeature
-	 * @extends geo.d3.object
-	 * @returns {geo.d3.vectorFeature}
-	 */
-	var d3_vectorFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof d3_vectorFeature)) {
-	    return new d3_vectorFeature(arg);
-	  }
-
-	  var object = __webpack_require__(227);
-	  var timestamp = __webpack_require__(209);
-	  var d3 = __webpack_require__(226).d3;
-
-	  arg = arg || {};
-	  vectorFeature.call(this, arg);
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      s_update = this._update,
-	      m_buildTime = timestamp(),
-	      m_style = {};
-
-	  /**
-	   * Generate a unique ID for a marker definition
-	   * @private
-	   * @param {object} d Unused datum (for d3 compat)
-	   * @param {number} i The marker index
-	   * @param {string} position The marker's vector position (head or tail)
-	   */
-	  function markerID(d, i, position) {
-	    return m_this._d3id() + '_marker_' + i + '_' + position;
-	  }
-
-	  /**
-	   * Add marker styles for vector arrows.
-	   * @private
-	   * @param {object[]} data The vector data array
-	   * @param {function} stroke The stroke accessor
-	   * @param {function} opacity The opacity accessor
-	   * @param {function} originStyle The marker style for the vector head
-	   * @param {function} endStyle The marker style for the vector tail
-	   */
-	  function updateMarkers(data, stroke, opacity, originStyle, endStyle) {
-
-	    var markerConfigs = {
-	      'arrow': {
-	        attrs: {'class': 'geo-vector-arrow geo-vector-marker', 'viewBox': '0 0 10 10', 'refX': '1', 'refY': '5', 'markerHeight': '5', 'markerWidth': '5', 'orient': 'auto'},
-	        path: 'M 0 0 L 10 5 L 0 10 z'
-	      },
-	      'point': {
-	        attrs: {'class': 'geo-vector-point geo-vector-marker', 'viewBox': '0 0 12 12', 'refX': '6', 'refY': '6', 'markerHeight': '8', 'markerWidth': '8', 'orient': 'auto'},
-	        path: 'M 6 3 A 3 3 0 1 1 5.99999 3 Z'
-	      },
-	      'bar': {
-	        attrs: {'class': 'geo-vector-bar geo-vector-marker', 'viewBox': '0 0 10 10', 'refX': '0', 'refY': '5', 'markerHeight': '6', 'markerWidth': '6', 'orient': 'auto'},
-	        path: 'M 0 0 L 2 0 L 2 10 L 0 10 z'
-	      },
-	      'wedge': {
-	        attrs: {'class': 'geo-vector-wedge geo-vector-marker', 'viewBox': '0 0 10 10', 'refX': '10', 'refY': '5', 'markerHeight': '5', 'markerWidth': '5', 'orient': 'auto'},
-	        path: 'M 0 0 L 1 0 L 10 5 L 1 10 L 0 10 L 9 5 L 0 0'
-	      }
-	    };
-
-	    //this allows for multiple VectorFeatures in a layer
-	    var markerGroup = m_this.renderer()._definitions()
-	      .selectAll('g.marker-group#' + m_this._d3id())
-	      .data(data.length ? [1] : []);
-
-	    markerGroup
-	      .enter()
-	      .append('g')
-	      .attr('id', m_this._d3id)
-	      .attr('class', 'marker-group');
-
-	    markerGroup.exit().remove();
-
-	    var markers = data.reduce(function (markers, d, i) {
-	      var head = markerConfigs[endStyle(d, i)];
-	      var tail = markerConfigs[originStyle(d, i)];
-	      if (head) {
-	        markers.push({
-	          data: d,
-	          dataIndex: i,
-	          head: true
-	        });
-	      }
-	      if (tail) {
-	        markers.push({
-	          data: d,
-	          dataIndex: i,
-	          head: false
-	        });
-	      }
-	      return markers;
-	    }, []);
-
-	    var sel = markerGroup
-	      .selectAll('marker.geo-vector-marker')
-	      .data(markers);
-
-	    sel.enter()
-	      .append('marker')
-	      .append('path');
-
-	    var renderer = m_this.renderer();
-
-	    sel
-	      .each(function (d) {
-	        var marker = d3.select(this);
-	        var markerData = d.head ? markerConfigs[endStyle(d.data, d.dataIndex)] : markerConfigs[originStyle(d.data, d.dataIndex)];
-	        Object.keys(markerData.attrs).map(function (attrName) {
-	          marker.attr(attrName, markerData.attrs[attrName]);
-	        });
-	      })
-	      .attr('id', function (d) {
-	        return markerID(d.data, d.dataIndex, d.head ? 'head' : 'tail');
-	      })
-	      .style('stroke', function (d) {
-	        return renderer._convertColor(stroke)(d.data, d.dataIndex);
-	      })
-	      .style('fill', function (d) {
-	        return renderer._convertColor(stroke)(d.data, d.dataIndex);
-	      })
-	      .style('opacity', function (d) {
-	        return opacity(d.data, d.dataIndex);
-	      })
-	      .select('path')
-	      .attr('d', function (d) {
-	        return d.head ? markerConfigs[endStyle(d.data, d.dataIndex)].path : markerConfigs[originStyle(d.data, d.dataIndex)].path;
-	      });
-
-	    sel.exit().remove();
-	  }
-
-	  /**
-	   * Initialize
-	   * @protected
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-	    return m_this;
-	  };
-
-	  /**
-	   * Build
-	   * @protected
-	   */
-	  this._build = function () {
-	    var data = m_this.data(),
-	        s_style = m_this.style.get(),
-	        m_renderer = m_this.renderer(),
-	        orig_func = m_this.origin(),
-	        size_func = m_this.delta(),
-	        cache = [],
-	        scale = m_this.style('scale'),
-	        max = Number.NEGATIVE_INFINITY;
-
-	    // call super-method
-	    s_update.call(m_this);
-
-	    // default to empty data array
-	    if (!data) { data = []; }
-
-	    // cache the georeferencing
-	    cache = data.map(function (d, i) {
-	      var origin = m_this.featureGcsToDisplay(orig_func(d, i)),
-	          delta = size_func(d, i);
-	      max = Math.max(max, delta.x * delta.x + delta.y * delta.y);
-	      return {
-	        x1: origin.x,
-	        y1: origin.y,
-	        dx: delta.x,
-	        dy: -delta.y
-	      };
-	    });
-
-	    max = Math.sqrt(max);
-	    if (!scale) {
-	      scale = 75 / max;
-	    }
-
-	    function getScale() {
-	      return scale / m_renderer.scaleFactor();
-	    }
-
-	    // fill in d3 renderer style object defaults
-	    m_style.id = m_this._d3id();
-	    m_style.data = data;
-	    m_style.append = 'line';
-	    m_style.attributes = {
-	      x1: function (d, i) {
-	        return cache[i].x1;
-	      },
-	      y1: function (d, i) {
-	        return cache[i].y1;
-	      },
-	      x2: function (d, i) {
-	        return cache[i].x1 + getScale() * cache[i].dx;
-	      },
-	      y2: function (d, i) {
-	        return cache[i].y1 + getScale() * cache[i].dy;
-	      },
-	      'marker-start': function (d, i) {
-	        return 'url(#' + markerID(d, i, 'tail') + ')';
-	      },
-	      'marker-end': function (d, i) {
-	        return 'url(#' + markerID(d, i, 'head') + ')';
-	      }
-	    };
-	    m_style.style = {
-	      stroke: function () { return true; },
-	      strokeColor: s_style.strokeColor,
-	      strokeWidth: s_style.strokeWidth,
-	      strokeOpacity: s_style.strokeOpacity,
-	      originStyle: s_style.originStyle,
-	      endStyle: s_style.endStyle
-	    };
-	    m_style.classes = ['d3VectorFeature'];
-	    m_style.visible = m_this.visible;
-
-	    // Add markers to the defition list
-	    updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity, s_style.originStyle, s_style.endStyle);
-
-	    // pass to renderer to draw
-	    m_this.renderer()._drawFeatures(m_style);
-
-	    // update time stamps
-	    m_buildTime.modified();
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update
-	   * @protected
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.getMTime() >= m_buildTime.getMTime()) {
-	      m_this._build();
-	    } else {
-	      updateMarkers(
-	        m_style.data,
-	        m_style.style.strokeColor,
-	        m_style.style.strokeOpacity,
-	        m_style.style.originStyle,
-	        m_style.style.endStyle
-	      );
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Exit
-	   * @protected
-	   */
-	  this._exit = function () {
-	    s_exit.call(m_this);
-	    m_style = {};
-	    updateMarkers([], null, null, null, null);
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(d3_vectorFeature, vectorFeature);
-
-	// Now register it
-	registerFeature('d3', 'vector', d3_vectorFeature);
-	module.exports = d3_vectorFeature;
-
-
-/***/ }),
-/* 259 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/**
-	 * @namespace geo.gl
-	 */
-	module.exports = {
-	  choroplethFeature: __webpack_require__(260),
-	  contourFeature: __webpack_require__(261),
-	  lineFeature: __webpack_require__(263),
-	  pointFeature: __webpack_require__(264),
-	  polygonFeature: __webpack_require__(265),
-	  quadFeature: __webpack_require__(267),
-	  tileLayer: __webpack_require__(268),
-	  vglRenderer: __webpack_require__(200)
-	};
-
-
-/***/ }),
-/* 260 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var choroplethFeature = __webpack_require__(225);
-
-	/**
-	 * Create a new instance of choroplethFeature
-	 *
-	 * @class geo.gl.choroplethFeature
-	 * @extends geo.choroplethFeature
-	 * @returns {geo.gl.choroplethFeature}
-	 */
-	var gl_choroplethFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof gl_choroplethFeature)) {
-	    return new gl_choroplethFeature(arg);
-	  }
-	  arg = arg || {};
-	  choroplethFeature.call(this, arg);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_gl_polygons = null,
-	      s_exit = this._exit,
-	      s_init = this._init,
-	      s_draw = this.draw,
-	      s_update = this._update;
-
-	  /* Create the choropleth.  This calls the base class to generate the contours,
-	   * into the various gl uniforms and buffers.
-	   */
-	  function createGLChoropleth() {
-	    return m_this.createChoropleth();
-	  }
-
-	  this.draw = function () {
-	    m_this._update();
-	    if (m_gl_polygons) {
-	      for (var idx = 0; idx < m_gl_polygons.length; idx += 1) {
-	        m_gl_polygons[idx].draw();
-	      }
-	    }
-	    s_draw();
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    s_init.call(m_this, arg);
-	  };
-
-	  /**
-	   * Build
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-	    m_this.buildTime().modified();
-	    return (m_gl_polygons = createGLChoropleth());
-	  };
-
-	  /**
-	   * Update
-	   *
-	   * @override
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
-	        m_this.updateTime().getMTime() <= m_this.getMTime()) {
-	      m_this._wipePolygons();
-	      m_this._build();
-	    }
-	    m_this.updateTime().modified();
-	  };
-
-	  /**
-	   * Destroy Polygon Sub-Features
-	   */
-	  this._wipePolygons = function () {
-	    if (m_gl_polygons) {
-	      m_gl_polygons.map(function (polygon) {
-	        return polygon._exit();
-	      });
-	    }
-	    m_gl_polygons = null;
-	  };
-
-	  /**
-	   * Destroy
-	   */
-	  this._exit = function () {
-	    m_this._wipePolygons();
-	    s_exit();
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(gl_choroplethFeature, choroplethFeature);
-
-	// Now register it
-	registerFeature('vgl', 'choropleth', gl_choroplethFeature);
-
-	module.exports = gl_choroplethFeature;
-
-
-/***/ }),
-/* 261 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var contourFeature = __webpack_require__(231);
-
-	/**
-	 * Create a new instance of contourFeature
-	 *
-	 * @class geo.gl.contourFeature
-	 * @extends geo.contourFeature
-	 * @returns {geo.gl.contourFeature}
-	 */
-	var gl_contourFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof gl_contourFeature)) {
-	    return new gl_contourFeature(arg);
-	  }
-	  arg = arg || {};
-	  contourFeature.call(this, arg);
-
-	  var vgl = __webpack_require__(86);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var object = __webpack_require__(262);
-
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_textureUnit = 7,
-	      m_actor = null,
-	      m_mapper = null,
-	      m_material = null,
-	      m_texture = null,
-	      m_minColorUniform = null,
-	      m_maxColorUniform = null,
-	      m_stepsUniform = null,
-	      m_steppedUniform = null,
-	      m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
-	      s_init = this._init,
-	      s_update = this._update;
-
-	  function createVertexShader() {
-	    var vertexShaderSource = [
-	          '#ifdef GL_ES',
-	          '  precision highp float;',
-	          '#endif',
-	          'attribute vec3 pos;',
-	          'attribute float value;',
-	          'attribute float opacity;',
-	          'uniform mat4 modelViewMatrix;',
-	          'uniform mat4 projectionMatrix;',
-	          'varying float valueVar;',
-	          'varying float opacityVar;',
-
-	          'void main(void)',
-	          '{',
-	      /* Don't use z values; something is rotten in one of our matrices */
-	          '  vec4 scrPos = projectionMatrix * modelViewMatrix * vec4(pos.xy, 0, 1);',
-	          '  if (scrPos.w != 0.0) {',
-	          '    scrPos = scrPos / scrPos.w;',
-	          '  }',
-	          '  valueVar = value;',
-	          '  opacityVar = opacity;',
-	          '  gl_Position = scrPos;',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.VERTEX_SHADER);
-	    shader.setShaderSource(vertexShaderSource);
-	    return shader;
-	  }
-
-	  function createFragmentShader() {
-	    var fragmentShaderSource = [
-	          '#ifdef GL_ES',
-	          '  precision highp float;',
-	          '#endif',
-	          'uniform vec4 minColor;',
-	          'uniform vec4 maxColor;',
-	          'uniform float steps;',
-	          'uniform bool stepped;',
-	          'uniform sampler2D sampler2d;',
-	          'varying float valueVar;',
-	          'varying float opacityVar;',
-	          'void main () {',
-	          '  vec4 clr;',
-	          '  if (valueVar < 0.0) {',
-	          '    clr = minColor;',
-	          '  } else if (valueVar > steps) {',
-	          '    clr = maxColor;',
-	          '  } else {',
-	          '    float step;',
-	          '    if (stepped) {',
-	          '      step = floor(valueVar) + 0.5;',
-	          '      if (step > steps) {',
-	          '        step = steps - 0.5;',
-	          '      }',
-	          '    } else {',
-	          '      step = valueVar;',
-	          '    }',
-	          '    clr = texture2D(sampler2d, vec2(step / steps, 0.0));',
-	          '  }',
-	          '  gl_FragColor = vec4(clr.rgb, clr.a * opacityVar);',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER);
-	    shader.setShaderSource(fragmentShaderSource);
-	    return shader;
-	  }
-
-	  /* Create the contours.  This calls the base class to generate the geometry,
-	   * color map, and other parameters.  The generated geoemtry is then loaded
-	   * into the various gl uniforms and buffers.
-	   */
-	  function createGLContours() {
-	    var contour = m_this.createContours(),
-	        numPts = contour.elements.length,
-	        colorTable = [],
-	        i, i3, j, j3,
-	        posBuf, opacityBuf, valueBuf, indicesBuf,
-	        geom = m_mapper.geometryData();
-
-	    m_minColorUniform.set([contour.minColor.r, contour.minColor.g,
-	                           contour.minColor.b, contour.minColor.a]);
-	    m_maxColorUniform.set([contour.maxColor.r, contour.maxColor.g,
-	                           contour.maxColor.b, contour.maxColor.a]);
-	    m_stepsUniform.set(contour.colorMap.length);
-	    m_steppedUniform.set(contour.stepped);
-	    for (i = 0; i < contour.colorMap.length; i += 1) {
-	      colorTable.push(contour.colorMap[i].r * 255);
-	      colorTable.push(contour.colorMap[i].g * 255);
-	      colorTable.push(contour.colorMap[i].b * 255);
-	      colorTable.push(contour.colorMap[i].a * 255);
-	    }
-	    m_texture.setColorTable(colorTable);
-	    contour.pos = transform.transformCoordinates(
-	        m_this.gcs(), m_this.layer().map().gcs(), contour.pos, 3);
-	    posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3);
-	    opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts);
-	    valueBuf = util.getGeomBuffer(geom, 'value', numPts);
-	    for (i = i3 = 0; i < numPts; i += 1, i3 += 3) {
-	      j = contour.elements[i];
-	      j3 = j * 3;
-	      posBuf[i3] = contour.pos[j3];
-	      posBuf[i3 + 1] = contour.pos[j3 + 1];
-	      posBuf[i3 + 2] = contour.pos[j3 + 2];
-	      opacityBuf[i] = contour.opacity[j];
-	      valueBuf[i] = contour.value[j];
-	    }
-	    indicesBuf = geom.primitive(0).indices();
-	    if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== numPts) {
-	      indicesBuf = new Uint16Array(numPts);
-	      geom.primitive(0).setIndices(indicesBuf);
-	    }
-	    geom.boundsDirty(true);
-	    m_mapper.modified();
-	    m_mapper.boundsDirtyTimestamp().modified();
-	  }
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function (arg) {
-	    var blend = vgl.blend(),
-	        prog = vgl.shaderProgram(),
-	        mat = vgl.material(),
-	        tex = vgl.lookupTable(),
-	        geom = vgl.geometryData(),
-	        modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	        samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'),
-	        vertexShader = createVertexShader(),
-	        fragmentShader = createFragmentShader(),
-	        posAttr = vgl.vertexAttribute('pos'),
-	        valueAttr = vgl.vertexAttribute('value'),
-	        opacityAttr = vgl.vertexAttribute('opacity'),
-	        sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}),
-	        sourceValues = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.One, {'name': 'value'}),
-	        sourceOpacity = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'opacity'}),
-	        primitive = new vgl.triangles();
-
-	    s_init.call(m_this, arg);
-	    m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});
-
-	    prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
-	    prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One);
-	    prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two);
-
-	    prog.addUniform(modelViewUniform);
-	    prog.addUniform(projectionUniform);
-	    m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor');
-	    prog.addUniform(m_minColorUniform);
-	    m_maxColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'maxColor');
-	    prog.addUniform(m_maxColorUniform);
-	    /* steps is always an integer, but it is more efficient if we use a float
-	     */
-	    m_stepsUniform = new vgl.uniform(vgl.GL.FLOAT, 'steps');
-	    prog.addUniform(m_stepsUniform);
-	    m_steppedUniform = new vgl.uniform(vgl.GL.BOOL, 'stepped');
-	    prog.addUniform(m_steppedUniform);
-
-	    prog.addShader(fragmentShader);
-	    prog.addShader(vertexShader);
-
-	    prog.addUniform(samplerUniform);
-	    tex.setTextureUnit(m_textureUnit);
-	    samplerUniform.set(m_textureUnit);
-
-	    m_material = mat;
-	    m_material.addAttribute(prog);
-	    m_material.addAttribute(blend);
-	    m_texture = tex;
-	    m_material.addAttribute(m_texture);
-
-	    m_actor = vgl.actor();
-	    m_actor.setMaterial(m_material);
-	    m_actor.setMapper(m_mapper);
-
-	    geom.addSource(sourcePositions);
-	    geom.addSource(sourceValues);
-	    geom.addSource(sourceOpacity);
-	    geom.addPrimitive(primitive);
-	    m_mapper.setGeometryData(geom);
-	  };
-
-	  /**
-	   * Build
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-	    if (m_actor) {
-	      m_this.renderer().contextRenderer().removeActor(m_actor);
-	    }
-
-	    createGLContours();
-
-	    m_this.renderer().contextRenderer().addActor(m_actor);
-	    m_this.buildTime().modified();
-	  };
-
-	  /**
-	   * Update
-	   *
-	   * @override
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
-	        m_this.updateTime().getMTime() <= m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    m_actor.setVisible(m_this.visible());
-	    m_actor.material().setBinNumber(m_this.bin());
-	    m_this.updateTime().modified();
-	  };
-
-	  /**
-	   * Destroy
-	   */
-	  this._exit = function () {
-	    m_this.renderer().contextRenderer().removeActor(m_actor);
-	    s_exit();
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(gl_contourFeature, contourFeature);
-
-	// Now register it
-	registerFeature('vgl', 'contour', gl_contourFeature);
-
-	module.exports = gl_contourFeature;
-
-
-/***/ }),
-/* 262 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/**
-	 * VGL specific subclass of object which rerenders when the object is drawn.
-	 *
-	 * @class
-	 * @alias geo.gl.object
-	 * @extends geo.sceneObject
-	 * @param {object} arg Options for the object.
-	 * @returns {geo.gl.object}
-	 */
-	var gl_object = function (arg) {
-	  'use strict';
-
-	  var object = __webpack_require__(203);
-
-	  // this is used to extend other geojs classes, so only generate
-	  // a new object when that is not the case... like if this === window
-	  if (!(this instanceof object)) {
-	    return new gl_object(arg);
-	  }
-
-	  var m_this = this,
-	      s_draw = this.draw;
-
-	  /**
-	   * Redraw the object.
-	   *
-	   * @returns {this}
-	   */
-	  this.draw = function () {
-	    m_this._update({mayDelay: true});
-	    m_this.renderer()._render();
-	    s_draw();
-	    return m_this;
-	  };
-
-	  return this;
-	};
-
-	module.exports = gl_object;
-
-
-/***/ }),
-/* 263 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var lineFeature = __webpack_require__(206);
-
-	var MAX_MITER_LIMIT = 100;
-
-	/* Flags are passed to the vertex shader in a float.  Since a 32-bit float has
-	 * 24 bits of mantissa, including the sign bit, a maximum of 23 bits of flags
-	 * can be passed in a float without loss or complication. */
-	/* vertex flags specify which direction a vertex needs to be offset */
-	var flagsVertex = {  // uses 2 bits
-	  corner: 0,
-	  near: 1,
-	  far: 3
-	};
-	var flagsLineCap = {  // uses 3 bits with flagsLineJoin
-	  butt: 0,
-	  square: 1,
-	  round: 2
-	};
-	var flagsLineJoin = {  // uses 3 bits with flagsLineCap
-	  passthrough: 3,
-	  miter: 4,
-	  bevel: 5,
-	  round: 6,
-	  'miter-clip': 7
-	};
-	var flagsNearLineShift = 2, flagsFarLineShift = 5;
-	var flagsNearOffsetShift = 8;  // uses 11 bits
-	/* Fixed flags */
-	var flagsDebug = {  // uses 1 bit
-	  normal: 0,
-	  debug: 1
-	};
-
-	/**
-	 * Create a new instance of lineFeature.
-	 *
-	 * @class geo.gl.lineFeature
-	 * @extends geo.lineFeature
-	 * @param {geo.lineFeature.spec} arg
-	 * @returns {geo.gl.lineFeature}
-	 */
-	var gl_lineFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof gl_lineFeature)) {
-	    return new gl_lineFeature(arg);
-	  }
-	  arg = arg || {};
-	  lineFeature.call(this, arg);
-
-	  var vgl = __webpack_require__(86);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var object = __webpack_require__(262);
-
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_actor,
-	      m_mapper,
-	      m_material,
-	      m_pixelWidthUnif,
-	      m_aspectUniform,
-	      m_miterLimitUniform,
-	      m_antialiasingUniform,
-	      m_flagsUniform,
-	      m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
-	      m_geometry,
-	      s_init = this._init,
-	      s_update = this._update;
-
-	  /**
-	   * Create the vertex shader for lines.
-	   *
-	   * @returns {vgl.shader}
-	   */
-	  function createVertexShader() {
-	    var vertexShaderSource = [
-	          '#ifdef GL_ES',
-	          '  precision highp float;',
-	          '#endif',
-	          'attribute vec3 pos;',
-	          'attribute vec3 prev;',
-	          'attribute vec3 next;',
-	          'attribute vec3 far;',
-	          'attribute float flags;',
-
-	          'attribute vec3 strokeColor;',
-	          'attribute float strokeOpacity;',
-	          'attribute float strokeWidth;',
-
-	          'uniform mat4 modelViewMatrix;',
-	          'uniform mat4 projectionMatrix;',
-	          'uniform float pixelWidth;',
-	          'uniform float aspect;',
-	          'uniform float miterLimit;',
-	          'uniform float antialiasing;',
-
-	          'varying vec4 strokeColorVar;',
-	          'varying vec4 subpos;',  /* px, py, length - px, width */
-	          'varying vec4 info;',  /* near mode, far mode, offset */
-	          'varying vec4 angles;', /* near angle cos, sin, far angle cos, sin */
-
-	          'const float PI = 3.14159265358979323846264;',
-
-	          'vec4 viewCoord(vec3 c) {',
-	          '  vec4 result = projectionMatrix * modelViewMatrix * vec4(c.xyz, 1);',
-	          '  if (result.w != 0.0)  result = result / result.w;',
-	          '  return result;',
-	          '}',
-
-	          'void main(void)',
-	          '{',
-	          /* If any vertex has been deliberately set to a negative opacity,
-	           * skip doing computations on it. */
-	          '  if (strokeOpacity < 0.0) {',
-	          '    gl_Position = vec4(2, 2, 0, 1);',
-	          '    return;',
-	          '  }',
-	          /* convert coordinates.  We have four values, since we need to
-	           * calculate the angles between the lines formed by prev-pos and
-	           * pos-next, and between pos-next and next-far, plus know the angle
-	           *   (prev)---(pos)---(next)---(far) => A---B---C---D */
-	          '  vec4 A = viewCoord(prev);',
-	          '  vec4 B = viewCoord(pos);',
-	          '  vec4 C = viewCoord(next);',
-	          '  vec4 D = viewCoord(far);',
-	          // calculate line segment vector and angle
-	          '  vec2 deltaCB = C.xy - B.xy;',
-	          '  if (deltaCB == vec2(0.0, 0.0)) {',
-	          '    gl_Position = vec4(2, 2, 0, 1);',
-	          '    return;',
-	          '  }',
-	          '  float angleCB = atan(deltaCB.y / aspect, deltaCB.x);',
-	          // values we need to pass along
-	          '  strokeColorVar = vec4(strokeColor, strokeOpacity);',
-	          // extract values from our flags field
-	          '  int vertex = int(mod(flags, 4.0));',
-	          '  int nearMode = int(mod(floor(flags / 4.0), 8.0));',
-	          '  int farMode = int(mod(floor(flags / 32.0), 8.0));',
-	          // we use 11 bits of the flags for the offset, where -1023 to 1023
-	          // maps to -1 to 1.  The 11 bits are a signed value, so simply
-	          // selecting the bits will result in an unsigned values that may be
-	          // greater than 1, in which case we have to subtract appropriately.
-	          '  float offset = mod(floor(flags / 256.0), 2048.0) / 1023.0;',
-	          '  if (offset > 1.0)  offset -= 2048.0 / 1023.0;',
-	          // by default, offset by the width and don't extend lines.  Later,
-	          // calculate line extensions based on end cap and end join modes
-	          '  float yOffset = strokeWidth + antialiasing;',
-	          '  if (vertex == 0 || vertex == 2)  yOffset *= -1.0;',
-	          '  yOffset += strokeWidth * offset;',
-	          '  float xOffset = 0.0;',
-	          // end caps
-	          '  if (nearMode == 0) {',
-	          '    xOffset = antialiasing;',
-	          '  } else if (nearMode == 1 || nearMode == 2) {',
-	          '    xOffset = strokeWidth + antialiasing;',
-	          '  }',
-
-	          // If joining lines, calculate the angles in screen space formed by
-	          // the near end (A-B-C) and far end (B-C-D), and determine how much
-	          // space is needed for the particular join.
-	          //   This could be changed: if the lines are not a uniform width and
-	          // offset, then the functional join angle is not simply half the
-	          // angle between the two lines, but rather half the angle of the
-	          // inside edge of the the two lines.
-	          '  float cosABC, sinABC, cosBCD, sinBCD;',  // of half angles
-	          // handle near end
-	          '  if (nearMode >= 4) {',
-	          '    float angleBA = atan((B.y - A.y) / aspect, B.x - A.x);',
-	          '    if (A.xy == B.xy)  angleBA = angleCB;',
-	          '    float angleABC = angleCB - angleBA;',
-	          // ensure angle is in the range [-PI, PI], then take the half angle
-	          '    angleABC = (mod(angleABC + PI, 2.0 * PI) - PI) / 2.0;',
-	          '    cosABC = cos(angleABC);  sinABC = sin(angleABC);',
-	          // if this angle is close to flat, pass-through the join
-	          '    if (nearMode >= 4 && cosABC > 0.999999) {',
-	          '      nearMode = 3;',
-	          '    }',
-	          // miter, miter-clip
-	          '    if (nearMode == 4 || nearMode == 7) {',
-	          '      if (cosABC == 0.0 || 1.0 / cosABC > miterLimit) {',
-	          '        if (nearMode == 4) {',
-	          '          nearMode = 5;',
-	          '        } else {',
-	          '          xOffset = miterLimit * strokeWidth * (1.0 - offset * sign(sinABC)) + antialiasing;',
-	          '        }',
-	          '      } else {',
-	          // we add an extra 1.0 to the xOffset to make sure that fragment
-	          // shader is doing the clipping
-	          '        xOffset = abs(sinABC / cosABC) * strokeWidth * (1.0 - offset * sign(sinABC)) + antialiasing + 1.0;',
-	          '        nearMode = 4;',
-	          '      }',
-	          '    }',
-	          // bevel or round join
-	          '    if (nearMode == 5 || nearMode == 6) {',
-	          '      xOffset = strokeWidth * (1.0 - offset * sign(sinABC)) + antialiasing;',
-	          '    }',
-	          '  }',
-
-	          // handle far end
-	          '  if (farMode >= 4) {',
-	          '    float angleDC = atan((D.y - C.y) / aspect, D.x - C.x);',
-	          '    if (D.xy == C.xy)  angleDC = angleCB;',
-	          '    float angleBCD = angleDC - angleCB;',
-	          // ensure angle is in the range [-PI, PI], then take the half angle
-	          '    angleBCD = (mod(angleBCD + PI, 2.0 * PI) - PI) / 2.0;',
-	          '    cosBCD = cos(angleBCD);  sinBCD = sin(angleBCD);',
-	          // if this angle is close to flat, pass-through the join
-	          '    if (farMode >= 4 && cosBCD > 0.999999) {',
-	          '      farMode = 3;',
-	          '    }',
-	          // miter, miter-clip
-	          '    if (farMode == 4 || farMode == 7) {',
-	          '      if (cosBCD == 0.0 || 1.0 / cosBCD > miterLimit) {',
-	          '        if (farMode == 4)  farMode = 5;',
-	          '      } else {',
-	          '        farMode = 4;',
-	          '      }',
-	          '    }',
-	          '  }',
-
-	          // compute the location of a vertex to include everything that might
-	          // need to be rendered
-	          '  xOffset *= -1.0;',
-	          '  gl_Position = vec4(',
-	          '    B.x + (xOffset * cos(angleCB) - yOffset * sin(angleCB)) * pixelWidth,',
-	          '    B.y + (xOffset * sin(angleCB) + yOffset * cos(angleCB)) * pixelWidth * aspect,',
-	          '    B.z, 1);',
-	          // store other values needed to determine which pixels to plot.
-	          '  float lineLength = length(vec2(deltaCB.x, deltaCB.y / aspect)) / pixelWidth;',
-
-	          '  if (vertex == 0 || vertex == 1) {',
-	          '    subpos = vec4(xOffset, yOffset, lineLength - xOffset, strokeWidth);',
-	          '    info = vec4(float(nearMode), float(farMode), offset, 0.0);',
-	          '    angles = vec4(cosABC, sinABC, cosBCD, sinBCD);',
-	          '  } else {',
-	          '    subpos = vec4(lineLength - xOffset, -yOffset, xOffset, strokeWidth);',
-	          '    info = vec4(float(farMode), float(nearMode), -offset, 0.0);',
-	          '    angles = vec4(cosBCD, -sinBCD, cosABC, -sinABC);',
-	          '  }',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.VERTEX_SHADER);
-	    shader.setShaderSource(vertexShaderSource);
-	    return shader;
-	  }
-
-	  /**
-	   * Create the fragment shader for lines.
-	   *
-	   * @param {boolean} [allowDebug] If truthy, include code that can render
-	   *    in debug mode.  This is mildly less efficient, even if debugging is
-	   *    not turned on.
-	   * @returns {vgl.shader}
-	   */
-	  function createFragmentShader(allowDebug) {
-	    var fragmentShaderSource = [
-	          '#ifdef GL_ES',
-	          '  precision highp float;',
-	          '#endif',
-	          'varying vec4 strokeColorVar;',
-	          'varying vec4 subpos;',
-	          'varying vec4 info;',
-	          'varying vec4 angles;',
-	          'uniform float antialiasing;',
-	          'uniform float miterLimit;',
-	          'uniform float fixedFlags;',
-	          'void main () {',
-	          '  vec4 color = strokeColorVar;',
-	          allowDebug ? '  bool debug = bool(mod(fixedFlags, 2.0));' : '',
-	          '  float opacity = 1.0;',
-	          '  int nearMode = int(info.x);',
-	          '  int farMode = int(info.y);',
-	          '  float cosABC = angles.x;',
-	          '  float sinABC = angles.y;',
-	          '  float cosBCD = angles.z;',
-	          '  float sinBCD = angles.w;',
-	          // never render on the opposite side of a miter.  This uses a bit of
-	          // slop, via pow(smoothstep()) instead of step(), since there are
-	          // precision issues in this calculation.  This doesn't wholy solve
-	          // the precision issue; sometimes pixels are missed or double
-	          // rendered along the inside seam of a miter.
-	          '  if (nearMode >= 4) {',
-	          '    float dist = cosABC * subpos.x - sinABC * subpos.y;',
-	          '    opacity = min(opacity, pow(smoothstep(-0.02, 0.02, dist), 0.5));',
-	          '    if (opacity == 0.0) {',
-	          allowDebug ? 'if (debug) {color.r=255.0/255.0;gl_FragColor=color;return;}' : '',
-	          '      discard;',
-	          '    }',
-	          '  }',
-	          '  if (farMode >= 4) {',
-	          '    float dist = cosBCD * subpos.z - sinBCD * subpos.y;',
-	          '    opacity = min(opacity, pow(smoothstep(-0.02, 0.02, dist), 0.5));',
-	          '    if (opacity == 0.0) {',
-	          allowDebug ? 'if (debug) {color.r=254.0/255.0;gl_FragColor=color;return;}' : '',
-	          '      discard;',
-	          '    }',
-	          '  }',
-	          // butt or square cap
-	          '  if ((nearMode == 0 || nearMode == 1) && subpos.x < antialiasing) {',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, subpos.x + subpos.w * float(nearMode)));',
-	          '  }',
-	          '  if ((farMode == 0 || farMode == 1) && subpos.z < antialiasing) {',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, subpos.z + subpos.w * float(farMode)));',
-	          '  }',
-	          // round cap
-	          '  if (nearMode == 2 && subpos.x <= 0.0) {',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, subpos.w - sqrt(pow(subpos.x, 2.0) + pow(subpos.y - info.z * subpos.w, 2.0))));',
-	          '  }',
-	          '  if (farMode == 2 && subpos.z <= 0.0) {',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, subpos.w - sqrt(pow(subpos.z, 2.0) + pow(subpos.y - info.z * subpos.w, 2.0))));',
-	          '  }',
-	          // bevel and clip joins
-	          '  if ((nearMode == 5 || nearMode == 7) && subpos.x < antialiasing) {',
-	          '    float dist = (sinABC * subpos.x + cosABC * subpos.y) * sign(sinABC);',
-	          '    float w = subpos.w * (1.0 - info.z * sign(sinABC));',
-	          '    float maxDist;',
-	          '    if (nearMode == 5)  maxDist = cosABC * w;',
-	          '    else                maxDist = miterLimit * w;',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, maxDist + dist));',
-	          '  }',
-	          '  if ((farMode == 5 || farMode == 7) && subpos.z < antialiasing) {',
-	          '    float dist = (sinBCD * subpos.z + cosBCD * subpos.y) * sign(sinBCD);',
-	          '    float w = subpos.w * (1.0 - info.z * sign(sinBCD));',
-	          '    float maxDist;',
-	          '    if (farMode == 5)  maxDist = cosBCD * w;',
-	          '    else               maxDist = miterLimit * w;',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, maxDist + dist));',
-	          '  }',
-	          // round join
-	          '  if (nearMode == 6 && subpos.x <= 0.0) {',
-	          '    float w = subpos.w * (1.0 - info.z * sign(sinABC));',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, w - sqrt(pow(subpos.x, 2.0) + pow(subpos.y, 2.0))));',
-	          '  }',
-	          '  if (farMode == 6 && subpos.z <= 0.0) {',
-	          '    float w = subpos.w * (1.0 - info.z * sign(sinBCD));',
-	          '    opacity = min(opacity, smoothstep(-antialiasing, antialiasing, w - sqrt(pow(subpos.z, 2.0) + pow(subpos.y, 2.0))));',
-	          '  }',
-	          // antialias along main edges
-	          '  if (antialiasing > 0.0) {',
-	          '    if (subpos.y > subpos.w * (1.0 + info.z) - antialiasing) {',
-	          '      opacity = min(opacity, smoothstep(antialiasing, -antialiasing, subpos.y - subpos.w * (1.0 + info.z)));',
-	          '    }',
-	          '    if (subpos.y < subpos.w * (-1.0 + info.z) + antialiasing) {',
-	          '      opacity = min(opacity, smoothstep(-antialiasing, antialiasing, subpos.y - subpos.w * (-1.0 + info.z)));',
-	          '    }',
-	          '  }',
-	          '  if (opacity == 0.0) {',
-	          allowDebug ? 'if (debug) {color.r=253.0/255.0;gl_FragColor=color;return;}' : '',
-	          '    discard;',
-	          '  }',
-	          '  color.a *= opacity;',
-	          '  gl_FragColor = color;',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER);
-	    shader.setShaderSource(fragmentShaderSource);
-	    return shader;
-	  }
-
-	 /**
-	   * Create and style the data needed to render the lines.
-	   *
-	   * @param {boolean} onlyStyle if true, use the existing geoemtry and just
-	   *    recalculate the style.
-	   */
-	  function createGLLines(onlyStyle) {
-	    var data = m_this.data(),
-	        d, i, j, k, v, v2, lidx,
-	        numSegments = 0, len,
-	        lineItemList, lineItem, lineItemData,
-	        vert = [{}, {}], v1 = vert[1],
-	        pos, posIdx3, firstpos, firstPosIdx3,
-	        strokeWidthFunc = m_this.style.get('strokeWidth'), strokeWidthVal,
-	        strokeColorFunc = m_this.style.get('strokeColor'), strokeColorVal,
-	        strokeOpacityFunc = m_this.style.get('strokeOpacity'), strokeOpacityVal,
-	        lineCapFunc = m_this.style.get('lineCap'), lineCapVal,
-	        lineJoinFunc = m_this.style.get('lineJoin'), lineJoinVal,
-	        strokeOffsetFunc = m_this.style.get('strokeOffset'), strokeOffsetVal,
-	        miterLimit = m_this.style.get('miterLimit')(data),
-	        antialiasing = m_this.style.get('antialiasing')(data) || 0,
-	        order = m_this.featureVertices(),
-	        posBuf, prevBuf, nextBuf, farBuf, flagsBuf, indicesBuf,
-	        fixedFlags = (flagsDebug[m_this.style.get('debug')(data) ? 'debug' : 'normal'] || 0),
-	        strokeWidthBuf, strokeColorBuf, strokeOpacityBuf,
-	        dest, dest3,
-	        geom = m_mapper.geometryData(),
-	        closedFunc = m_this.style.get('closed'), closedVal, closed = [],
-	        updateFlags = true;
-
-	    closedVal = util.isFunction(m_this.style('closed')) ? undefined : (closedFunc() || false);
-	    lineCapVal = util.isFunction(m_this.style('lineCap')) ? undefined : (lineCapFunc() || 'butt');
-	    lineJoinVal = util.isFunction(m_this.style('lineJoin')) ? undefined : (lineJoinFunc() || 'miter');
-	    strokeColorVal = util.isFunction(m_this.style('strokeColor')) ? undefined : strokeColorFunc();
-	    strokeOffsetVal = util.isFunction(m_this.style('strokeOffset')) ? undefined : (strokeOffsetFunc() || 0);
-	    strokeOpacityVal = util.isFunction(m_this.style('strokeOpacity')) ? undefined : strokeOpacityFunc();
-	    strokeWidthVal = util.isFunction(m_this.style('strokeWidth')) ? undefined : strokeWidthFunc();
-
-	    if (miterLimit !== undefined) {
-	      /* We impose a limit no matter what, since otherwise the growth is
-	       * unbounded.  Values less than 1 make no sense, since we are using the
-	       * SVG definition of miter length. */
-	      m_miterLimitUniform.set(Math.max(1, Math.min(MAX_MITER_LIMIT, miterLimit)));
-	    }
-	    m_flagsUniform.set(fixedFlags);
-	    m_antialiasingUniform.set(antialiasing);
-
-	    if (!onlyStyle) {
-	      var position = [],
-	          posFunc = m_this.position();
-	      lineItemList = new Array(data.length);
-	      for (i = 0; i < data.length; i += 1) {
-	        d = data[i];
-	        lineItem = m_this.line()(d, i);
-	        lineItemList[i] = lineItem;
-	        if (lineItem.length < 2) {
-	          continue;
-	        }
-	        numSegments += lineItem.length - 1;
-	        for (j = 0; j < lineItem.length; j += 1) {
-	          pos = posFunc(lineItem[j], j, d, i);
-	          position.push(pos.x);
-	          position.push(pos.y);
-	          position.push(pos.z || 0.0);
-	          if (!j) {
-	            firstpos = pos;
-	          }
-	        }
-	        if (lineItem.length > 2 && (closedVal === undefined ? closedFunc(d, i) : closedVal)) {
-	          /* line is closed */
-	          if (pos.x !== firstpos.x || pos.y !== firstpos.y ||
-	              pos.z !== firstpos.z) {
-	            numSegments += 1;
-	            closed[i] = 2;  /* first and last points are distinct */
-	          } else {
-	            closed[i] = 1;  /* first point is repeated as last point */
-	          }
-	        }
-	      }
-
-	      position = transform.transformCoordinates(
-	        m_this.gcs(), m_this.layer().map().gcs(), position, 3);
-	      len = numSegments * order.length;
-	      posBuf = util.getGeomBuffer(geom, 'pos', len * 3);
-	      prevBuf = util.getGeomBuffer(geom, 'prev', len * 3);
-	      nextBuf = util.getGeomBuffer(geom, 'next', len * 3);
-	      farBuf = util.getGeomBuffer(geom, 'far', len * 3);
-
-	      indicesBuf = geom.primitive(0).indices();
-	      if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== len) {
-	        indicesBuf = new Uint16Array(len);
-	        geom.primitive(0).setIndices(indicesBuf);
-	      }
-	      // save some information to be reused when we update only style
-	      m_geometry = {
-	        numSegments: numSegments,
-	        closed: closed,
-	        lineItemList: lineItemList,
-	        lineCapVal: lineCapVal,
-	        lineJoinVal: lineJoinVal,
-	        strokeOffsetVal: strokeOffsetVal
-	      };
-	    } else {
-	      numSegments = m_geometry.numSegments;
-	      closed = m_geometry.closed;
-	      lineItemList = m_geometry.lineItemList;
-	      len = numSegments * order.length;
-	      updateFlags = (
-	        (lineCapVal !== m_geometry.lineCapVal || lineCapVal === undefined) ||
-	        (lineJoinVal !== m_geometry.lineJoinVal || lineJoinVal === undefined) ||
-	        (strokeOffsetVal !== m_geometry.strokeOffsetVal || strokeOffsetVal === undefined)
-	      );
-	    }
-
-	    flagsBuf = util.getGeomBuffer(geom, 'flags', len);
-	    strokeWidthBuf = util.getGeomBuffer(geom, 'strokeWidth', len);
-	    strokeColorBuf = util.getGeomBuffer(geom, 'strokeColor', len * 3);
-	    strokeOpacityBuf = util.getGeomBuffer(geom, 'strokeOpacity', len);
-
-	    for (i = posIdx3 = dest = dest3 = 0; i < data.length; i += 1) {
-	      lineItem = lineItemList[i];
-	      if (lineItem.length < 2) {
-	        continue;
-	      }
-	      d = data[i];
-	      firstPosIdx3 = posIdx3;
-	      for (j = 0; j < lineItem.length + (closed[i] === 2 ? 1 : 0); j += 1, posIdx3 += 3) {
-	        lidx = j;
-	        if (j === lineItem.length) {
-	          lidx = 0;
-	          posIdx3 -= 3;
-	        }
-	        lineItemData = lineItem[lidx];
-	        /* swap entries in vert so that vert[0] is the first vertex, and
-	         * vert[1] will be reused for the second vertex */
-	        if (j) {
-	          v1 = vert[0];
-	          vert[0] = vert[1];
-	          vert[1] = v1;
-	        }
-	        if (!onlyStyle) {
-	          v1.pos = j === lidx ? posIdx3 : firstPosIdx3;
-	          v1.prev = lidx ? posIdx3 - 3 : (closed[i] ?
-	              firstPosIdx3 + (lineItem.length - 3 + closed[i]) * 3 : posIdx3);
-	          v1.next = j + 1 < lineItem.length ? posIdx3 + 3 : (closed[i] ?
-	              (j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closed[i] * 3) :
-	              posIdx3);
-	        }
-	        v1.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
-	        v1.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
-	        v1.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
-	        if (updateFlags) {
-	          v1.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
-	          if (v1.strokeOffset) {
-	            /* we use 11 bits to store the offset, and we want to store values
-	             * from -1 to 1, so multiply our values by 1023, and use some bit
-	             * manipulation to ensure that it is packed properly */
-	            v1.posStrokeOffset = Math.round(2048 + 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
-	            v1.negStrokeOffset = Math.round(2048 - 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
-	          } else {
-	            v1.posStrokeOffset = v1.negStrokeOffset = 0;
-	          }
-	          if (!closed[i] && (!j || j === lineItem.length - 1)) {
-	            v1.flags = flagsLineCap[lineCapVal === undefined ? lineCapFunc(lineItemData, lidx, d, i) : lineCapVal] || flagsLineCap.butt;
-	          } else {
-	            v1.flags = flagsLineJoin[lineJoinVal === undefined ? lineJoinFunc(lineItemData, lidx, d, i) : lineJoinVal] || flagsLineJoin.miter;
-	          }
-	        }
-
-	        if (j) {
-	          for (k = 0; k < order.length; k += 1, dest += 1, dest3 += 3) {
-	            v = vert[order[k][0]];
-	            v2 = vert[1 - order[k][0]];
-	            if (!onlyStyle) {
-	              posBuf[dest3] = position[v.pos];
-	              posBuf[dest3 + 1] = position[v.pos + 1];
-	              posBuf[dest3 + 2] = position[v.pos + 2];
-	            }
-	            if (!order[k][0]) {
-	              if (!onlyStyle) {
-	                prevBuf[dest3] = position[v.prev];
-	                prevBuf[dest3 + 1] = position[v.prev + 1];
-	                prevBuf[dest3 + 2] = position[v.prev + 2];
-	                nextBuf[dest3] = position[v.next];
-	                nextBuf[dest3 + 1] = position[v.next + 1];
-	                nextBuf[dest3 + 2] = position[v.next + 2];
-	                farBuf[dest3] = position[v2.next];
-	                farBuf[dest3 + 1] = position[v2.next + 1];
-	                farBuf[dest3 + 2] = position[v2.next + 2];
-	              }
-	              if (updateFlags) {
-	                flagsBuf[dest] = (flagsVertex[order[k][1]] |
-	                  (v.flags << flagsNearLineShift) |
-	                  (v2.flags << flagsFarLineShift) |
-	                  (v.negStrokeOffset << flagsNearOffsetShift));
-	              }
-	            } else {
-	              if (!onlyStyle) {
-	                prevBuf[dest3] = position[v.next];
-	                prevBuf[dest3 + 1] = position[v.next + 1];
-	                prevBuf[dest3 + 2] = position[v.next + 2];
-	                nextBuf[dest3] = position[v.prev];
-	                nextBuf[dest3 + 1] = position[v.prev + 1];
-	                nextBuf[dest3 + 2] = position[v.prev + 2];
-	                farBuf[dest3] = position[v2.prev];
-	                farBuf[dest3 + 1] = position[v2.prev + 1];
-	                farBuf[dest3 + 2] = position[v2.prev + 2];
-	              }
-	              if (updateFlags) {
-	                flagsBuf[dest] = (flagsVertex[order[k][1]] |
-	                  (v.flags << flagsNearLineShift) |
-	                  (v2.flags << flagsFarLineShift) |
-	                  (v.posStrokeOffset << flagsNearOffsetShift));
-	              }
-	            }
-	            strokeWidthBuf[dest] = v.strokeWidth;
-	            strokeColorBuf[dest3] = v.strokeColor.r;
-	            strokeColorBuf[dest3 + 1] = v.strokeColor.g;
-	            strokeColorBuf[dest3 + 2] = v.strokeColor.b;
-	            strokeOpacityBuf[dest] = v.strokeOpacity;
-	          }
-	        }
-	      }
-	    }
-
-	    m_mapper.modified();
-	    if (!onlyStyle) {
-	      geom.boundsDirty(true);
-	      m_mapper.boundsDirtyTimestamp().modified();
-	    }
-	  }
-
-	  /**
-	   * Return the arrangement of vertices used for each line segment.  Each line
-	   * is rendered by two triangles.  This reports how the vertices of those
-	   * triangles are arranged.  Each entry is a triple: the line-end number, the
-	   * vertex use, and the side of the line that the vertex is on.
-	   *
-	   * @returns {array[]}
-	   */
-	  this.featureVertices = function () {
-	    return [[0, 'corner', -1], [0, 'near', 1], [1, 'far', -1],
-	            [1, 'corner', 1], [1, 'near', -1], [0, 'far', 1]];
-	  };
-
-	  /**
-	   * Return the number of vertices used for each line segment.
-	   *
-	   * @returns {number}
-	   */
-	  this.verticesPerFeature = function () {
-	    return m_this.featureVertices().length;
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.lineFeature.spec} arg The feature specification.
-	   * @returns {this}
-	   */
-	  this._init = function (arg) {
-	    var prog = vgl.shaderProgram(),
-	        vs = createVertexShader(),
-	        fs = createFragmentShader(((arg || {}).style || {}).debug !== undefined),
-	        // Vertex attributes
-	        posAttr = vgl.vertexAttribute('pos'),
-	        prvAttr = vgl.vertexAttribute('prev'),
-	        nxtAttr = vgl.vertexAttribute('next'),
-	        farAttr = vgl.vertexAttribute('far'),
-	        flagsAttr = vgl.vertexAttribute('flags'),
-	        strkWidthAttr = vgl.vertexAttribute('strokeWidth'),
-	        strkColorAttr = vgl.vertexAttribute('strokeColor'),
-	        strkOpacityAttr = vgl.vertexAttribute('strokeOpacity'),
-	        // Shader uniforms
-	        mviUnif = new vgl.modelViewUniform('modelViewMatrix'),
-	        prjUnif = new vgl.projectionUniform('projectionMatrix'),
-	        geom = vgl.geometryData(),
-	        // Sources
-	        posData = vgl.sourceDataP3fv({name: 'pos'}),
-	        prvPosData = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Four, {name: 'prev'}),
-	        nxtPosData = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Five, {name: 'next'}),
-	        farPosData = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Six, {name: 'far'}),
-	        flagsData = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Seven, {name: 'flags'}),
-	        strkWidthData = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.One, {name: 'strokeWidth'}),
-	        strkColorData = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Two, {name: 'strokeColor'}),
-	        strkOpacityData = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Three, {name: 'strokeOpacity'}),
-	        // Primitive indices
-	        triangles = vgl.triangles();
-
-	    m_pixelWidthUnif = new vgl.floatUniform('pixelWidth',
-	                          1.0 / m_this.renderer().width());
-	    m_aspectUniform = new vgl.floatUniform('aspect',
-	        m_this.renderer().width() / m_this.renderer().height());
-	    m_miterLimitUniform = new vgl.floatUniform('miterLimit', 10);
-	    m_antialiasingUniform = new vgl.floatUniform('antialiasing', 0);
-	    m_flagsUniform = new vgl.floatUniform('fixedFlags', 0);
-
-	    s_init.call(m_this, arg);
-	    m_material = vgl.material();
-	    m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});
-
-	    prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
-	    prog.addVertexAttribute(strkWidthAttr, vgl.vertexAttributeKeysIndexed.One);
-	    prog.addVertexAttribute(strkColorAttr, vgl.vertexAttributeKeysIndexed.Two);
-	    prog.addVertexAttribute(strkOpacityAttr, vgl.vertexAttributeKeysIndexed.Three);
-	    prog.addVertexAttribute(prvAttr, vgl.vertexAttributeKeysIndexed.Four);
-	    prog.addVertexAttribute(nxtAttr, vgl.vertexAttributeKeysIndexed.Five);
-	    prog.addVertexAttribute(farAttr, vgl.vertexAttributeKeysIndexed.Six);
-	    prog.addVertexAttribute(flagsAttr, vgl.vertexAttributeKeysIndexed.Seven);
-
-	    prog.addUniform(mviUnif);
-	    prog.addUniform(prjUnif);
-	    prog.addUniform(m_pixelWidthUnif);
-	    prog.addUniform(m_aspectUniform);
-	    prog.addUniform(m_miterLimitUniform);
-	    prog.addUniform(m_antialiasingUniform);
-	    prog.addUniform(m_flagsUniform);
-
-	    prog.addShader(fs);
-	    prog.addShader(vs);
-
-	    m_material.addAttribute(prog);
-	    m_material.addAttribute(vgl.blend());
-
-	    m_actor = vgl.actor();
-	    m_actor.setMaterial(m_material);
-	    m_actor.setMapper(m_mapper);
-
-	    geom.addSource(posData);
-	    geom.addSource(prvPosData);
-	    geom.addSource(nxtPosData);
-	    geom.addSource(farPosData);
-	    geom.addSource(strkWidthData);
-	    geom.addSource(strkColorData);
-	    geom.addSource(strkOpacityData);
-	    geom.addSource(flagsData);
-	    geom.addPrimitive(triangles);
-	    m_mapper.setGeometryData(geom);
-	    return m_this;
-	  };
-
-	  /**
-	   * Return list of vgl actorss used for rendering.
-	   *
-	   * @returns {vgl.actor[]}
-	   */
-	  this.actors = function () {
-	    if (!m_actor) {
-	      return [];
-	    }
-	    return [m_actor];
-	  };
-
-	  /**
-	   * Build.  Create the necessary elements to render lines.
-	   *
-	   * There are several optimizations to do less work when possible.  If only
-	   * styles have changed, the geometry is not re-transformed.  If styles use
-	   * static values (rather than functions), they are only calculated once.  If
-	   * styles have not changed that would affect flags (lineCap, lineJoin, and
-	   * strokeOffset), the vertex flags are not recomputed -- this helps, as it is
-	   * a slow step due to most javascript interpreters not optimizing bit
-	   * operations.
-	   *
-	   * @returns {this}
-	   */
-	  this._build = function () {
-	    createGLLines(m_this.dataTime().getMTime() < m_this.buildTime().getMTime() && m_geometry);
-
-	    if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
-	      m_this.renderer().contextRenderer().addActor(m_actor);
-	    }
-	    m_this.buildTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update.  Rebuild if necessary.
-	   *
-	   * @returns {this}
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-
-	    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
-	        m_this.updateTime().getMTime() <= m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    m_pixelWidthUnif.set(1.0 / m_this.renderer().width());
-	    m_aspectUniform.set(m_this.renderer().width() /
-	                        m_this.renderer().height());
-	    m_actor.setVisible(m_this.visible());
-	    m_actor.material().setBinNumber(m_this.bin());
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Destroy.  Free used resources.
-	   */
-	  this._exit = function () {
-	    m_this.renderer().contextRenderer().removeActor(m_actor);
-	    m_actor = null;
-	    s_exit();
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(gl_lineFeature, lineFeature);
-
-	var capabilities = {};
-	capabilities[lineFeature.capabilities.basic] = true;
-	capabilities[lineFeature.capabilities.multicolor] = true;
-
-	// Now register it
-	registerFeature('vgl', 'line', gl_lineFeature, capabilities);
-
-	module.exports = gl_lineFeature;
-
-
-/***/ }),
-/* 264 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var pointFeature = __webpack_require__(212);
-
-	/**
-	 * Create a new instance of pointFeature
-	 *
-	 * @class geo.gl.pointFeature
-	 * @extends geo.pointFeature
-	 * @returns {geo.gl.pointFeature}
-	 */
-	var gl_pointFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof gl_pointFeature)) {
-	    return new gl_pointFeature(arg);
-	  }
-	  arg = arg || {};
-	  pointFeature.call(this, arg);
-
-	  var vgl = __webpack_require__(86);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var object = __webpack_require__(262);
-
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_actor = null,
-	      m_mapper = null,
-	      m_pixelWidthUniform = null,
-	      m_aspectUniform = null,
-	      m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
-	      /* If you are drawing very large points, you will often get better
-	       * performance using a different primitiveShape.  The 'sprite' shape uses
-	       * the least memory, but has hardware-specific limitations to its size.
-	       * 'triangle' seems to be fastest on low-powered hardware, but 'square'
-	       * visits fewer fragments. */
-	      m_primitiveShape = 'sprite', // arg can change this, below
-	      s_init = this._init,
-	      s_update = this._update,
-	      s_updateStyleFromArray = this.updateStyleFromArray,
-	      vertexShaderSource = null,
-	      fragmentShaderSource = null;
-
-	  if (arg.primitiveShape === 'triangle' ||
-	      arg.primitiveShape === 'square' ||
-	      arg.primitiveShape === 'sprite') {
-	    m_primitiveShape = arg.primitiveShape;
-	  }
-
-	  vertexShaderSource = [
-	    '#ifdef GL_ES',
-	    '  precision highp float;',
-	    '#endif',
-	    'attribute vec3 pos;',
-	    'attribute float radius;',
-	    'attribute vec3 fillColor;',
-	    'attribute vec3 strokeColor;',
-	    'attribute float fillOpacity;',
-	    'attribute float strokeWidth;',
-	    'attribute float strokeOpacity;',
-	    'attribute float fill;',
-	    'attribute float stroke;',
-	    'uniform float pixelWidth;',
-	    'uniform float aspect;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying vec4 fillColorVar;',
-	    'varying vec4 strokeColorVar;',
-	    'varying float radiusVar;',
-	    'varying float strokeWidthVar;',
-	    'varying float fillVar;',
-	    'varying float strokeVar;'
-	  ];
-
-	  if (m_primitiveShape !== 'sprite') {
-	    vertexShaderSource = vertexShaderSource.concat([
-	      'attribute vec2 unit;',
-	      'varying vec3 unitVar;'
-	    ]);
-	  }
-
-	  vertexShaderSource.push.apply(vertexShaderSource, [
-	    'void main(void)',
-	    '{',
-	    '  strokeWidthVar = strokeWidth;',
-	    '  // No stroke or fill implies nothing to draw',
-	    '  if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) {',
-	    '    strokeVar = 0.0;',
-	    '    strokeWidthVar = 0.0;',
-	    '  }',
-	    '  else',
-	    '    strokeVar = 1.0;',
-	    '  if (fill < 1.0 || radius <= 0.0 || fillOpacity <= 0.0)',
-	    '    fillVar = 0.0;',
-	    '  else',
-	    '    fillVar = 1.0;',
-	    /* If the point has no visible pixels, skip doing computations on it. */
-	    '  if (fillVar == 0.0 && strokeVar == 0.0) {',
-	    '    gl_Position = vec4(2, 2, 0, 1);',
-	    '    return;',
-	    '  }',
-	    '  fillColorVar = vec4 (fillColor, fillOpacity);',
-	    '  strokeColorVar = vec4 (strokeColor, strokeOpacity);',
-	    '  radiusVar = radius;'
-	  ]);
-
-	  if (m_primitiveShape === 'sprite') {
-	    vertexShaderSource.push.apply(vertexShaderSource, [
-	      '  gl_Position = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;',
-	      '  gl_PointSize = 2.0 * (radius + strokeWidthVar); ',
-	      '}'
-	    ]);
-	  } else {
-	    vertexShaderSource.push.apply(vertexShaderSource, [
-	      '  unitVar = vec3 (unit, 1.0);',
-	      '  vec4 p = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;',
-	      '  if (p.w != 0.0) {',
-	      '    p = p / p.w;',
-	      '  }',
-	      '  p += (radius + strokeWidthVar) * ',
-	      '       vec4 (unit.x * pixelWidth, unit.y * pixelWidth * aspect, 0.0, 1.0);',
-	      '  gl_Position = vec4(p.xyz, 1.0);',
-	      '}'
-	    ]);
-	  }
-	  vertexShaderSource = vertexShaderSource.join('\n');
-
-	  fragmentShaderSource = [
-	    '#ifdef GL_ES',
-	    '  precision highp float;',
-	    '#endif',
-	    'uniform float aspect;',
-	    'varying vec4 fillColorVar;',
-	    'varying vec4 strokeColorVar;',
-	    'varying float radiusVar;',
-	    'varying float strokeWidthVar;',
-	    'varying float fillVar;',
-	    'varying float strokeVar;'
-	  ];
-
-	  if (m_primitiveShape !== 'sprite') {
-	    fragmentShaderSource.push('varying vec3 unitVar;');
-	  }
-
-	  fragmentShaderSource.push.apply(fragmentShaderSource, [
-	    'void main () {',
-	    '  vec4 strokeColor, fillColor;',
-	    '  float endStep;',
-	    '  // No stroke or fill implies nothing to draw',
-	    '  if (fillVar == 0.0 && strokeVar == 0.0)',
-	    '    discard;'
-	  ]);
-
-	  if (m_primitiveShape === 'sprite') {
-	    fragmentShaderSource.push(
-	      '  float rad = 2.0 * length (gl_PointCoord - vec2(0.5));');
-	  } else {
-	    fragmentShaderSource.push(
-	      '  float rad = length (unitVar.xy);');
-	  }
-
-	  fragmentShaderSource.push.apply(fragmentShaderSource, [
-	    '  if (rad > 1.0)',
-	    '    discard;',
-	    '  // If there is no stroke, the fill region should transition to nothing',
-	    '  if (strokeVar == 0.0) {',
-	    '    strokeColor = vec4 (fillColorVar.rgb, 0.0);',
-	    '    endStep = 1.0;',
-	    '  } else {',
-	    '    strokeColor = strokeColorVar;',
-	    '    endStep = radiusVar / (radiusVar + strokeWidthVar);',
-	    '  }',
-	    '  // Likewise, if there is no fill, the stroke should transition to nothing',
-	    '  if (fillVar == 0.0)',
-	    '    fillColor = vec4 (strokeColor.rgb, 0.0);',
-	    '  else',
-	    '    fillColor = fillColorVar;',
-	    '  // Distance to antialias over',
-	    '  float antialiasDist = 3.0 / (2.0 * radiusVar);',
-	    '  if (rad < endStep) {',
-	    '    float step = smoothstep (endStep - antialiasDist, endStep, rad);',
-	    '    gl_FragColor = mix (fillColor, strokeColor, step);',
-	    '  } else {',
-	    '    float step = smoothstep (1.0 - antialiasDist, 1.0, rad);',
-	    '    gl_FragColor = mix (strokeColor, vec4 (strokeColor.rgb, 0.0), step);',
-	    '  }',
-	    '}'
-	  ]);
-
-	  fragmentShaderSource = fragmentShaderSource.join('\n');
-
-	  function createVertexShader() {
-	    var shader = new vgl.shader(vgl.GL.VERTEX_SHADER);
-	    shader.setShaderSource(vertexShaderSource);
-	    return shader;
-	  }
-
-	  function createFragmentShader() {
-	    var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER);
-	    shader.setShaderSource(fragmentShaderSource);
-	    return shader;
-	  }
-
-	  function pointPolygon(x, y, w, h) {
-	    var verts;
-	    switch (m_primitiveShape) {
-	      case 'triangle':
-	        /* Use an equilateral triangle.  While this has 30% more area than a
-	         * square, the reduction in vertices should help more than the
-	         * processing the additional fragments. */
-	        verts = [
-	          x, y - h * 2,
-	          x - w * Math.sqrt(3.0), y + h,
-	          x + w * Math.sqrt(3.0), y + h
-	        ];
-	        break;
-	      case 'sprite':
-	        /* Point sprite uses only one vertex per point. */
-	        verts = [x, y];
-	        break;
-	      default: // "square"
-	        /* Use a surrounding square split diagonally into two triangles. */
-	        verts = [
-	          x - w, y + h,
-	          x - w, y - h,
-	          x + w, y + h,
-	          x - w, y - h,
-	          x + w, y - h,
-	          x + w, y + h
-	        ];
-	        break;
-	    }
-	    return verts;
-	  }
-
-	  function createGLPoints() {
-	    // unit and associated data is not used when drawing sprite
-	    var i, j, numPts = m_this.data().length,
-	        unit = pointPolygon(0, 0, 1, 1),
-	        position = new Array(numPts * 3), posBuf, posVal, posFunc,
-	        unitBuf, indices,
-	        radius, radiusVal, radFunc,
-	        stroke, strokeVal, strokeFunc,
-	        strokeWidth, strokeWidthVal, strokeWidthFunc,
-	        strokeOpacity, strokeOpacityVal, strokeOpacityFunc,
-	        strokeColor, strokeColorVal, strokeColorFunc,
-	        fill, fillVal, fillFunc,
-	        fillOpacity, fillOpacityVal, fillOpacityFunc,
-	        fillColor, fillColorVal, fillColorFunc,
-	        vpf = m_this.verticesPerFeature(),
-	        data = m_this.data(),
-	        item, ivpf, ivpf3, iunit, i3,
-	        geom = m_mapper.geometryData(), nonzeroZ;
-
-	    posFunc = m_this.position();
-	    radFunc = m_this.style.get('radius');
-	    strokeFunc = m_this.style.get('stroke');
-	    strokeWidthFunc = m_this.style.get('strokeWidth');
-	    strokeOpacityFunc = m_this.style.get('strokeOpacity');
-	    strokeColorFunc = m_this.style.get('strokeColor');
-	    fillFunc = m_this.style.get('fill');
-	    fillOpacityFunc = m_this.style.get('fillOpacity');
-	    fillColorFunc = m_this.style.get('fillColor');
-
-	    /* It is more efficient to do a transform on a single array rather than on
-	     * an array of arrays or an array of objects. */
-	    for (i = i3 = 0; i < numPts; i += 1, i3 += 3) {
-	      posVal = posFunc(data[i], i);
-	      position[i3] = posVal.x;
-	      position[i3 + 1] = posVal.y;
-	      position[i3 + 2] = posVal.z || 0;
-	      nonzeroZ = nonzeroZ || position[i3 + 2];
-	    }
-	    position = transform.transformCoordinates(
-	                  m_this.gcs(), m_this.layer().map().gcs(),
-	                  position, 3);
-	    /* Some transforms modify the z-coordinate.  If we started with all zero z
-	     * coordinates, don't modify them.  This could be changed if the
-	     * z-coordinate space of the gl cube is scaled appropriately. */
-	    if (!nonzeroZ && m_this.gcs() !== m_this.layer().map().gcs()) {
-	      for (i = i3 = 0; i < numPts; i += 1, i3 += 3) {
-	        position[i3 + 2] = 0;
-	      }
-	    }
-
-	    posBuf = util.getGeomBuffer(geom, 'pos', vpf * numPts * 3);
-
-	    if (m_primitiveShape !== 'sprite') {
-	      unitBuf = util.getGeomBuffer(geom, 'unit', vpf * numPts * 2);
-	    }
-
-	    radius = util.getGeomBuffer(geom, 'radius', vpf * numPts);
-	    stroke = util.getGeomBuffer(geom, 'stroke', vpf * numPts);
-	    strokeWidth = util.getGeomBuffer(geom, 'strokeWidth', vpf * numPts);
-	    strokeOpacity = util.getGeomBuffer(geom, 'strokeOpacity', vpf * numPts);
-	    strokeColor = util.getGeomBuffer(geom, 'strokeColor', vpf * numPts * 3);
-	    fill = util.getGeomBuffer(geom, 'fill', vpf * numPts);
-	    fillOpacity = util.getGeomBuffer(geom, 'fillOpacity', vpf * numPts);
-	    fillColor = util.getGeomBuffer(geom, 'fillColor', vpf * numPts * 3);
-	    indices = geom.primitive(0).indices();
-	    if (!(indices instanceof Uint16Array) || indices.length !== vpf * numPts) {
-	      indices = new Uint16Array(vpf * numPts);
-	      geom.primitive(0).setIndices(indices);
-	    }
-
-	    for (i = ivpf = ivpf3 = iunit = i3 = 0; i < numPts; i += 1, i3 += 3) {
-	      item = data[i];
-	      if (m_primitiveShape !== 'sprite') {
-	        for (j = 0; j < unit.length; j += 1, iunit += 1) {
-	          unitBuf[iunit] = unit[j];
-	        }
-	      }
-	      /* We can ignore the indicies (they will all be zero) */
-	      radiusVal = radFunc(item, i);
-	      strokeVal = strokeFunc(item, i) ? 1.0 : 0.0;
-	      strokeWidthVal = strokeWidthFunc(item, i);
-	      strokeOpacityVal = strokeOpacityFunc(item, i);
-	      strokeColorVal = strokeColorFunc(item, i);
-	      fillVal = fillFunc(item, i) ? 1.0 : 0.0;
-	      fillOpacityVal = fillOpacityFunc(item, i);
-	      fillColorVal = fillColorFunc(item, i);
-	      for (j = 0; j < vpf; j += 1, ivpf += 1, ivpf3 += 3) {
-	        posBuf[ivpf3] = position[i3];
-	        posBuf[ivpf3 + 1] = position[i3 + 1];
-	        posBuf[ivpf3 + 2] = position[i3 + 2];
-	        radius[ivpf] = radiusVal;
-	        stroke[ivpf] = strokeVal;
-	        strokeWidth[ivpf] = strokeWidthVal;
-	        strokeOpacity[ivpf] = strokeOpacityVal;
-	        strokeColor[ivpf3] = strokeColorVal.r;
-	        strokeColor[ivpf3 + 1] = strokeColorVal.g;
-	        strokeColor[ivpf3 + 2] = strokeColorVal.b;
-	        fill[ivpf] = fillVal;
-	        fillOpacity[ivpf] = fillOpacityVal;
-	        fillColor[ivpf3] = fillColorVal.r;
-	        fillColor[ivpf3 + 1] = fillColorVal.g;
-	        fillColor[ivpf3 + 2] = fillColorVal.b;
-	      }
-	    }
-
-	    geom.boundsDirty(true);
-	    m_mapper.modified();
-	    m_mapper.boundsDirtyTimestamp().modified();
-	  }
-
-	  /**
-	   * Return list of actors
-	   *
-	   * @returns {vgl.actor[]}
-	   */
-	  this.actors = function () {
-	    if (!m_actor) {
-	      return [];
-	    }
-	    return [m_actor];
-	  };
-
-	  /**
-	   * Return the number of vertices used for each point.
-	   *
-	   * @returns {Number}
-	   */
-	  this.verticesPerFeature = function () {
-	    var unit = pointPolygon(0, 0, 1, 1);
-	    return unit.length / 2;
-	  };
-
-	  this.updateStyleFromArray = function (keyOrObject, styleArray, refresh) {
-	    var bufferedKeys = {
-	      fill: 'bool',
-	      fillColor: 3,
-	      fillOpacity: 1,
-	      radius: 1,
-	      stroke: 'bool',
-	      strokeColor: 3,
-	      strokeOpacity: 1,
-	      strokeWidth: 1
-	    };
-	    var needsRefresh, needsRender;
-	    if (typeof keyOrObject === 'string') {
-	      var obj = {};
-	      obj[keyOrObject] = styleArray;
-	      keyOrObject = obj;
-	    }
-	    $.each(keyOrObject, function (key, styleArray) {
-	      if (m_this.visible() && m_actor && bufferedKeys[key] && !needsRefresh && !m_this.clustering()) {
-	        var vpf, mapper, buffer, numPts, value, i, j, v, bpv;
-	        bpv = bufferedKeys[key] === 'bool' ? 1 : bufferedKeys[key];
-	        numPts = m_this.data().length;
-	        mapper = m_actor.mapper();
-	        buffer = mapper.getSourceBuffer(key);
-	        vpf = m_this.verticesPerFeature();
-	        if (!buffer || !numPts || numPts * vpf * bpv !== buffer.length) {
-	          needsRefresh = true;
-	        } else {
-	          switch (bufferedKeys[key]) {
-	            case 1:
-	              for (i = 0, v = 0; i < numPts; i += 1) {
-	                value = styleArray[i];
-	                for (j = 0; j < vpf; j += 1, v += 1) {
-	                  buffer[v] = value;
-	                }
-	              }
-	              break;
-	            case 3:
-	              for (i = 0, v = 0; i < numPts; i += 1) {
-	                value = styleArray[i];
-	                for (j = 0; j < vpf; j += 1, v += 3) {
-	                  buffer[v] = value.r;
-	                  buffer[v + 1] = value.g;
-	                  buffer[v + 2] = value.b;
-	                }
-	              }
-	              break;
-	            case 'bool':
-	              for (i = 0, v = 0; i < numPts; i += 1) {
-	                value = styleArray[i] ? 1.0 : 0.0;
-	                for (j = 0; j < vpf; j += 1, v += 1) {
-	                  buffer[v] = value;
-	                }
-	              }
-	              break;
-	          }
-	          mapper.updateSourceBuffer(key);
-	          /* This could probably be even faster than calling _render after
-	           * updating the buffer, if the context's buffer was bound and
-	           * updated.  This would requiring knowing the webgl context and
-	           * probably the source to buffer mapping. */
-	          needsRender = true;
-	        }
-	      } else {
-	        needsRefresh = true;
-	      }
-	      s_updateStyleFromArray(key, styleArray, false);
-	    });
-	    if (m_this.visible() && needsRefresh) {
-	      m_this.draw();
-	    } else if (needsRender) {
-	      m_this.renderer()._render();
-	    }
-	  };
-
-	  /**
-	   * Initialize
-	   */
-	  this._init = function () {
-	    var prog = vgl.shaderProgram(),
-	        vertexShader = createVertexShader(),
-	        fragmentShader = createFragmentShader(),
-	        posAttr = vgl.vertexAttribute('pos'),
-	        unitAttr = vgl.vertexAttribute('unit'),
-	        radAttr = vgl.vertexAttribute('radius'),
-	        strokeWidthAttr = vgl.vertexAttribute('strokeWidth'),
-	        fillColorAttr = vgl.vertexAttribute('fillColor'),
-	        fillAttr = vgl.vertexAttribute('fill'),
-	        strokeColorAttr = vgl.vertexAttribute('strokeColor'),
-	        strokeAttr = vgl.vertexAttribute('stroke'),
-	        fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
-	        strokeOpacityAttr = vgl.vertexAttribute('strokeOpacity'),
-	        modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	        mat = vgl.material(),
-	        blend = vgl.blend(),
-	        geom = vgl.geometryData(),
-	        sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}),
-	        sourceUnits = vgl.sourceDataAnyfv(
-	            2, vgl.vertexAttributeKeysIndexed.One, {'name': 'unit'}),
-	        sourceRadius = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'radius'}),
-	        sourceStrokeWidth = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Three, {'name': 'strokeWidth'}),
-	        sourceFillColor = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Four, {'name': 'fillColor'}),
-	        sourceFill = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Five, {'name': 'fill'}),
-	        sourceStrokeColor = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Six, {'name': 'strokeColor'}),
-	        sourceStroke = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Seven, {'name': 'stroke'}),
-	        sourceAlpha = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Eight, {'name': 'fillOpacity'}),
-	        sourceStrokeOpacity = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Nine, {'name': 'strokeOpacity'}),
-	        primitive = new vgl.triangles();
-
-	    if (m_primitiveShape === 'sprite') {
-	      primitive = new vgl.points();
-	    }
-
-	    m_pixelWidthUniform = new vgl.floatUniform('pixelWidth',
-	                            2.0 / m_this.renderer().width());
-	    m_aspectUniform = new vgl.floatUniform('aspect',
-	                        m_this.renderer().width() / m_this.renderer().height());
-
-	    s_init.call(m_this, arg);
-	    m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});
-
-	    // TODO: Right now this is ugly but we will fix it.
-	    prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
-	    if (m_primitiveShape !== 'sprite') {
-	      prog.addVertexAttribute(unitAttr, vgl.vertexAttributeKeysIndexed.One);
-	    }
-
-	    prog.addVertexAttribute(radAttr, vgl.vertexAttributeKeysIndexed.Two);
-	    prog.addVertexAttribute(strokeWidthAttr, vgl.vertexAttributeKeysIndexed.Three);
-	    prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Four);
-	    prog.addVertexAttribute(fillAttr, vgl.vertexAttributeKeysIndexed.Five);
-	    prog.addVertexAttribute(strokeColorAttr, vgl.vertexAttributeKeysIndexed.Six);
-	    prog.addVertexAttribute(strokeAttr, vgl.vertexAttributeKeysIndexed.Seven);
-	    prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Eight);
-	    prog.addVertexAttribute(strokeOpacityAttr, vgl.vertexAttributeKeysIndexed.Nine);
-
-	    prog.addUniform(m_pixelWidthUniform);
-	    prog.addUniform(m_aspectUniform);
-	    prog.addUniform(modelViewUniform);
-	    prog.addUniform(projectionUniform);
-
-	    prog.addShader(fragmentShader);
-	    prog.addShader(vertexShader);
-
-	    mat.addAttribute(prog);
-	    mat.addAttribute(blend);
-
-	    m_actor = vgl.actor();
-	    m_actor.setMaterial(mat);
-	    m_actor.setMapper(m_mapper);
-
-	    geom.addSource(sourcePositions);
-	    geom.addSource(sourceUnits);
-	    geom.addSource(sourceRadius);
-	    geom.addSource(sourceStrokeWidth);
-	    geom.addSource(sourceFillColor);
-	    geom.addSource(sourceFill);
-	    geom.addSource(sourceStrokeColor);
-	    geom.addSource(sourceStroke);
-	    geom.addSource(sourceAlpha);
-	    geom.addSource(sourceStrokeOpacity);
-	    geom.addPrimitive(primitive);
-	    m_mapper.setGeometryData(geom);
-	  };
-
-	  /**
-	   * Build
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-
-	    if (m_actor) {
-	      m_this.renderer().contextRenderer().removeActor(m_actor);
-	    }
-
-	    createGLPoints();
-
-	    m_this.renderer().contextRenderer().addActor(m_actor);
-	    m_this.renderer().contextRenderer().render();
-	    m_this.buildTime().modified();
-	  };
-
-	  /**
-	   * Update
-	   *
-	   * @override
-	   */
-	  this._update = function () {
-
-	    s_update.call(m_this);
-
-	    // For now build if the data or style changes. In the future we may
-	    // we able to partially update the data using dynamic gl buffers.
-	    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
-	        m_this.updateTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    // Update uniforms
-	    m_pixelWidthUniform.set(2.0 / m_this.renderer().width());
-	    m_aspectUniform.set(m_this.renderer().width() /
-	                        m_this.renderer().height());
-
-	    m_actor.setVisible(m_this.visible());
-	    m_actor.material().setBinNumber(m_this.bin());
-
-	    m_this.updateTime().modified();
-	  };
-
-	  /**
-	   * Destroy
-	   */
-	  this._exit = function () {
-	    m_this.renderer().contextRenderer().removeActor(m_actor);
-	    m_actor = null;
-	    s_exit();
-	  };
-
-	  m_this._init();
-	  return this;
-	};
-
-	inherit(gl_pointFeature, pointFeature);
-
-	// Now register it
-	registerFeature('vgl', 'point', gl_pointFeature);
-
-	module.exports = gl_pointFeature;
-
-
-/***/ }),
-/* 265 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var polygonFeature = __webpack_require__(217);
-
-	/**
-	 * Create a new instance of gl.polygonFeature.
-	 *
-	 * @class
-	 * @alias geo.gl.polygonFeature
-	 * @extends geo.polygonFeature
-	 * @param {geo.polygonFeature.spec} arg
-	 * @returns {geo.gl.polygonFeature}
-	 */
-	var gl_polygonFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof gl_polygonFeature)) {
-	    return new gl_polygonFeature(arg);
-	  }
-	  arg = arg || {};
-	  polygonFeature.call(this, arg);
-
-	  var vgl = __webpack_require__(86);
-	  var earcut = __webpack_require__(266);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var object = __webpack_require__(262);
-
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_actor = vgl.actor(),
-	      m_mapper = vgl.mapper(),
-	      m_material = vgl.material(),
-	      m_geometry,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_builtOnce,
-	      m_updateAnimFrameRef;
-
-	  function createVertexShader() {
-	    var vertexShaderSource = [
-	          'attribute vec3 pos;',
-	          'attribute vec3 fillColor;',
-	          'attribute float fillOpacity;',
-	          'uniform mat4 modelViewMatrix;',
-	          'uniform mat4 projectionMatrix;',
-	          'varying vec4 fillColorVar;',
-
-	          'void main(void)',
-	          '{',
-	          '  vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);',
-	          '  if (clipPos.w != 0.0) {',
-	          '    clipPos = clipPos/clipPos.w;',
-	          '  }',
-	          '  fillColorVar = vec4(fillColor, fillOpacity);',
-	          '  gl_Position = clipPos;',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.VERTEX_SHADER);
-	    shader.setShaderSource(vertexShaderSource);
-	    return shader;
-	  }
-
-	  function createFragmentShader() {
-	    var fragmentShaderSource = [
-	          '#ifdef GL_ES',
-	          '  precision highp float;',
-	          '#endif',
-	          'varying vec4 fillColorVar;',
-	          'void main () {',
-	          '  gl_FragColor = fillColorVar;',
-	          '}'
-	        ].join('\n'),
-	        shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER);
-	    shader.setShaderSource(fragmentShaderSource);
-	    return shader;
-	  }
-
-	  /**
-	   * Create and style the triangles needed to render the polygons.
-	   *
-	   * There are several optimizations to do less work when possible.  If only
-	   * styles have changed, the triangulation is not recomputed, nor is the
-	   * geometry re-transformed.  If styles use static values (rather than
-	   * functions), they are only calculated once.  If a polygon reports that it
-	   * has a uniform style, then styles are only calculated once for that polygon
-	   * (the uniform property may be different per polygon or per update).
-	   * Array.map is slower in Chrome that using a loop, so loops are used in
-	   * places that would be conceptually served by maps.
-	   *
-	   * @param {boolean} onlyStyle if true, use the existing geoemtry and just
-	   *    recalculate the style.
-	   */
-	  function createGLPolygons(onlyStyle) {
-	    var posBuf, posFunc, polyFunc,
-	        fillColor, fillColorFunc, fillColorVal,
-	        fillOpacity, fillOpacityFunc, fillOpacityVal,
-	        fillFunc, fillVal,
-	        uniformPolyFunc, uniform,
-	        indices,
-	        items = [],
-	        target_gcs = m_this.gcs(),
-	        map_gcs = m_this.layer().map().gcs(),
-	        numPts = 0,
-	        geom = m_mapper.geometryData(),
-	        color, opacity, fill, d, d3, vertices, i, j, k, n,
-	        record, item, itemIndex, original;
-
-	    fillColorFunc = m_this.style.get('fillColor');
-	    fillColorVal = util.isFunction(m_this.style('fillColor')) ? undefined : fillColorFunc();
-	    fillOpacityFunc = m_this.style.get('fillOpacity');
-	    fillOpacityVal = util.isFunction(m_this.style('fillOpacity')) ? undefined : fillOpacityFunc();
-	    fillFunc = m_this.style.get('fill');
-	    fillVal = util.isFunction(m_this.style('fill')) ? undefined : fillFunc();
-	    uniformPolyFunc = m_this.style.get('uniformPolygon');
-
-	    if (!onlyStyle) {
-	      posFunc = m_this.style.get('position');
-	      polyFunc = m_this.style.get('polygon');
-	      m_this.data().forEach(function (item, itemIndex) {
-	        var polygon, outer, geometry, c;
-
-	        polygon = polyFunc(item, itemIndex);
-	        if (!polygon) {
-	          return;
-	        }
-	        outer = polygon.outer || (Array.isArray(polygon) ? polygon : []);
-	        if (outer.length < 3) {
-	          return;
-	        }
-
-	        /* expand to an earcut polygon geometry.  We had been using a map call,
-	         * but using loops is much faster in Chrome (4 versus 33 ms for one
-	         * test). */
-	        geometry = new Array(outer.length * 3);
-	        for (i = d3 = 0; i < outer.length; i += 1, d3 += 3) {
-	          c = posFunc(outer[i], i, item, itemIndex);
-	          geometry[d3] = c.x;
-	          geometry[d3 + 1] = c.y;
-	          geometry[d3 + 2] = c.z || 0;
-	        }
-	        geometry = {vertices: geometry, dimensions: 3, holes: []};
-	        original = outer;
-
-	        if (polygon.inner) {
-	          polygon.inner.forEach(function (hole) {
-	            if (hole.length < 3) {
-	              return;
-	            }
-	            original = original.concat(hole);
-	            geometry.holes.push(d3 / 3);
-	            for (i = 0; i < hole.length; i += 1, d3 += 3) {
-	              c = posFunc(hole[i], i, item, itemIndex);
-	              geometry.vertices[d3] = c.x;
-	              geometry.vertices[d3 + 1] = c.y;
-	              geometry.vertices[d3 + 2] = c.z || 0;
-	            }
-	          });
-	        }
-
-	        // transform to map gcs
-	        geometry.vertices = transform.transformCoordinates(
-	          target_gcs,
-	          map_gcs,
-	          geometry.vertices,
-	          geometry.dimensions
-	        );
-
-	        record = {
-	          // triangulate
-	          triangles: earcut(geometry.vertices, geometry.holes, geometry.dimensions),
-	          vertices: geometry.vertices,
-	          original: original,
-	          item: item,
-	          itemIndex: itemIndex
-	        };
-	        if (record.triangles.length) {
-	          items.push(record);
-	          numPts += record.triangles.length;
-	        }
-	      });
-	      posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3);
-	      indices = geom.primitive(0).indices();
-	      if (!(indices instanceof Uint16Array) || indices.length !== numPts) {
-	        indices = new Uint16Array(numPts);
-	        geom.primitive(0).setIndices(indices);
-	      }
-	      m_geometry = {items: items, numPts: numPts};
-	    } else {
-	      items = m_geometry.items;
-	      numPts = m_geometry.numPts;
-	    }
-	    fillColor = util.getGeomBuffer(geom, 'fillColor', numPts * 3);
-	    fillOpacity = util.getGeomBuffer(geom, 'fillOpacity', numPts);
-	    d = d3 = 0;
-	    color = fillColorVal;
-	    fill = fillVal;
-	    for (k = 0; k < items.length; k += 1) {
-	      n = items[k].triangles.length;
-	      vertices = items[k].vertices;
-	      item = items[k].item;
-	      itemIndex = items[k].itemIndex;
-	      original = items[k].original;
-	      uniform = uniformPolyFunc(item, itemIndex);
-	      opacity = fillOpacityVal;
-	      if (uniform) {
-	        if (fillColorVal === undefined) {
-	          color = fillColorFunc(vertices[0], 0, item, itemIndex);
-	        }
-	        if (fillOpacityVal === undefined) {
-	          opacity = fillOpacityFunc(vertices[0], 0, item, itemIndex);
-	        }
-	      }
-	      if (fillVal === undefined) {
-	        fill = fillFunc(item, itemIndex);
-	      }
-	      if (!fill) {
-	        opacity = 0;
-	      }
-	      if (uniform && onlyStyle && items[k].uniform && items[k].color &&
-	          color.r === items[k].color.r && color.g === items[k].color.g &&
-	          color.b === items[k].color.b && opacity === items[k].opacity) {
-	        d += n;
-	        d3 += n * 3;
-	        continue;
-	      }
-	      for (i = 0; i < n; i += 1, d += 1, d3 += 3) {
-	        if (onlyStyle && uniform) {
-	          fillColor[d3] = color.r;
-	          fillColor[d3 + 1] = color.g;
-	          fillColor[d3 + 2] = color.b;
-	          fillOpacity[d] = opacity;
-	        } else {
-	          j = items[k].triangles[i] * 3;
-	          if (!onlyStyle) {
-	            posBuf[d3] = vertices[j];
-	            posBuf[d3 + 1] = vertices[j + 1];
-	            posBuf[d3 + 2] = vertices[j + 2];
-	            indices[d] = i;
-	          }
-	          if (!uniform && fillColorVal === undefined) {
-	            color = fillColorFunc(original[j], j, item, itemIndex);
-	          }
-	          fillColor[d3] = color.r;
-	          fillColor[d3 + 1] = color.g;
-	          fillColor[d3 + 2] = color.b;
-	          if (!uniform && fillOpacityVal === undefined) {
-	            opacity = fillOpacityFunc(original[j], j, item, itemIndex);
-	          }
-	          fillOpacity[d] = opacity;
-	        }
-	      }
-	      if (uniform || items[k].uniform) {
-	        items[k].uniform = uniform;
-	        items[k].color = color;
-	        items[k].opacity = opacity;
-	      }
-	    }
-	    m_mapper.modified();
-	    if (!onlyStyle) {
-	      geom.boundsDirty(true);
-	      m_mapper.boundsDirtyTimestamp().modified();
-	    }
-	  }
-
-	  /**
-	   * Initialize.
-	   *
-	   * @param {geo.polygonFeature.spec} arg An object with options for the
-	   *    feature.
-	   */
-	  this._init = function (arg) {
-	    var prog = vgl.shaderProgram(),
-	        posAttr = vgl.vertexAttribute('pos'),
-	        fillColorAttr = vgl.vertexAttribute('fillColor'),
-	        fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
-	        modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
-	        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
-	        vertexShader = createVertexShader(),
-	        fragmentShader = createFragmentShader(),
-	        blend = vgl.blend(),
-	        geom = vgl.geometryData(),
-	        sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}),
-	        sourceFillColor = vgl.sourceDataAnyfv(
-	            3, vgl.vertexAttributeKeysIndexed.Two, {'name': 'fillColor'}),
-	        sourceFillOpacity = vgl.sourceDataAnyfv(
-	            1, vgl.vertexAttributeKeysIndexed.Three, {'name': 'fillOpacity'}),
-	        trianglePrimitive = vgl.triangles();
-
-	    prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
-	    prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Two);
-	    prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Three);
-
-	    prog.addUniform(modelViewUniform);
-	    prog.addUniform(projectionUniform);
-
-	    prog.addShader(fragmentShader);
-	    prog.addShader(vertexShader);
-
-	    m_material.addAttribute(prog);
-	    m_material.addAttribute(blend);
-
-	    m_actor.setMaterial(m_material);
-	    m_actor.setMapper(m_mapper);
-
-	    geom.addSource(sourcePositions);
-	    geom.addSource(sourceFillColor);
-	    geom.addSource(sourceFillOpacity);
-	    geom.addPrimitive(trianglePrimitive);
-	    m_mapper.setGeometryData(geom);
-
-	    s_init.call(m_this, arg);
-	  };
-
-	  /**
-	   * Build.
-	   *
-	   * @override
-	   */
-	  this._build = function () {
-	    createGLPolygons(m_this.dataTime().getMTime() < m_this.buildTime().getMTime() && m_geometry);
-
-	    if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
-	      m_this.renderer().contextRenderer().addActor(m_actor);
-	      m_builtOnce = true;
-	    }
-	    m_this.buildTime().modified();
-	  };
-
-	  /**
-	   * Update.
-	   *
-	   * @param {object} [opts] Update options.
-	   * @param {boolean} [opts.mayDelay] If truthy, wait until the next animation
-	   *    frame for the update.
-	   */
-	  this._update = function (opts) {
-	    if (opts && opts.mayDelay && m_builtOnce) {
-	      m_updateAnimFrameRef = m_this.layer().map().scheduleAnimationFrame(m_this._update);
-	      return;
-	    }
-	    if (m_updateAnimFrameRef) {
-	      m_this.layer().map().scheduleAnimationFrame(m_this._update, 'remove');
-	      m_updateAnimFrameRef = null;
-	    }
-	    s_update.call(m_this);
-
-	    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
-	        m_this.updateTime().getMTime() <= m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    m_actor.setVisible(m_this.visible());
-	    m_actor.material().setBinNumber(m_this.bin());
-	    m_this.updateTime().modified();
-	  };
-
-	  /**
-	   * Destroy.
-	   */
-	  this._exit = function () {
-	    m_this.renderer().contextRenderer().removeActor(m_actor);
-	    s_exit();
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(gl_polygonFeature, polygonFeature);
-
-	// Now register it
-	registerFeature('vgl', 'polygon', gl_polygonFeature);
-	module.exports = gl_polygonFeature;
-
-
-/***/ }),
-/* 266 */
-/***/ (function(module, exports) {
-
-	'use strict';
-
-	module.exports = earcut;
-	module.exports.default = earcut;
-
-	function earcut(data, holeIndices, dim) {
-
-	    dim = dim || 2;
-
-	    var hasHoles = holeIndices && holeIndices.length,
-	        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
-	        outerNode = linkedList(data, 0, outerLen, dim, true),
-	        triangles = [];
-
-	    if (!outerNode) return triangles;
-
-	    var minX, minY, maxX, maxY, x, y, invSize;
-
-	    if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
-
-	    // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
-	    if (data.length > 80 * dim) {
-	        minX = maxX = data[0];
-	        minY = maxY = data[1];
-
-	        for (var i = dim; i < outerLen; i += dim) {
-	            x = data[i];
-	            y = data[i + 1];
-	            if (x < minX) minX = x;
-	            if (y < minY) minY = y;
-	            if (x > maxX) maxX = x;
-	            if (y > maxY) maxY = y;
-	        }
-
-	        // minX, minY and invSize are later used to transform coords into integers for z-order calculation
-	        invSize = Math.max(maxX - minX, maxY - minY);
-	        invSize = invSize !== 0 ? 1 / invSize : 0;
-	    }
-
-	    earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
-
-	    return triangles;
-	}
-
-	// create a circular doubly linked list from polygon points in the specified winding order
-	function linkedList(data, start, end, dim, clockwise) {
-	    var i, last;
-
-	    if (clockwise === (signedArea(data, start, end, dim) > 0)) {
-	        for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
-	    } else {
-	        for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
-	    }
-
-	    if (last && equals(last, last.next)) {
-	        removeNode(last);
-	        last = last.next;
-	    }
-
-	    return last;
-	}
-
-	// eliminate colinear or duplicate points
-	function filterPoints(start, end) {
-	    if (!start) return start;
-	    if (!end) end = start;
-
-	    var p = start,
-	        again;
-	    do {
-	        again = false;
-
-	        if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
-	            removeNode(p);
-	            p = end = p.prev;
-	            if (p === p.next) break;
-	            again = true;
-
-	        } else {
-	            p = p.next;
-	        }
-	    } while (again || p !== end);
-
-	    return end;
-	}
-
-	// main ear slicing loop which triangulates a polygon (given as a linked list)
-	function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
-	    if (!ear) return;
-
-	    // interlink polygon nodes in z-order
-	    if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
-
-	    var stop = ear,
-	        prev, next;
-
-	    // iterate through ears, slicing them one by one
-	    while (ear.prev !== ear.next) {
-	        prev = ear.prev;
-	        next = ear.next;
-
-	        if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
-	            // cut off the triangle
-	            triangles.push(prev.i / dim);
-	            triangles.push(ear.i / dim);
-	            triangles.push(next.i / dim);
-
-	            removeNode(ear);
-
-	            // skipping the next vertice leads to less sliver triangles
-	            ear = next.next;
-	            stop = next.next;
-
-	            continue;
-	        }
-
-	        ear = next;
-
-	        // if we looped through the whole remaining polygon and can't find any more ears
-	        if (ear === stop) {
-	            // try filtering points and slicing again
-	            if (!pass) {
-	                earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
-
-	            // if this didn't work, try curing all small self-intersections locally
-	            } else if (pass === 1) {
-	                ear = cureLocalIntersections(ear, triangles, dim);
-	                earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
-
-	            // as a last resort, try splitting the remaining polygon into two
-	            } else if (pass === 2) {
-	                splitEarcut(ear, triangles, dim, minX, minY, invSize);
-	            }
-
-	            break;
-	        }
-	    }
-	}
-
-	// check whether a polygon node forms a valid ear with adjacent nodes
-	function isEar(ear) {
-	    var a = ear.prev,
-	        b = ear,
-	        c = ear.next;
-
-	    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
-
-	    // now make sure we don't have other points inside the potential ear
-	    var p = ear.next.next;
-
-	    while (p !== ear.prev) {
-	        if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-	            area(p.prev, p, p.next) >= 0) return false;
-	        p = p.next;
-	    }
-
-	    return true;
-	}
-
-	function isEarHashed(ear, minX, minY, invSize) {
-	    var a = ear.prev,
-	        b = ear,
-	        c = ear.next;
-
-	    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
-
-	    // triangle bbox; min & max are calculated like this for speed
-	    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
-	        minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
-	        maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
-	        maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
-
-	    // z-order range for the current triangle bbox;
-	    var minZ = zOrder(minTX, minTY, minX, minY, invSize),
-	        maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
-
-	    // first look for points inside the triangle in increasing z-order
-	    var p = ear.nextZ;
-
-	    while (p && p.z <= maxZ) {
-	        if (p !== ear.prev && p !== ear.next &&
-	            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-	            area(p.prev, p, p.next) >= 0) return false;
-	        p = p.nextZ;
-	    }
-
-	    // then look for points in decreasing z-order
-	    p = ear.prevZ;
-
-	    while (p && p.z >= minZ) {
-	        if (p !== ear.prev && p !== ear.next &&
-	            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-	            area(p.prev, p, p.next) >= 0) return false;
-	        p = p.prevZ;
-	    }
-
-	    return true;
-	}
-
-	// go through all polygon nodes and cure small local self-intersections
-	function cureLocalIntersections(start, triangles, dim) {
-	    var p = start;
-	    do {
-	        var a = p.prev,
-	            b = p.next.next;
-
-	        if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
-
-	            triangles.push(a.i / dim);
-	            triangles.push(p.i / dim);
-	            triangles.push(b.i / dim);
-
-	            // remove two nodes involved
-	            removeNode(p);
-	            removeNode(p.next);
-
-	            p = start = b;
-	        }
-	        p = p.next;
-	    } while (p !== start);
-
-	    return p;
-	}
-
-	// try splitting polygon into two and triangulate them independently
-	function splitEarcut(start, triangles, dim, minX, minY, invSize) {
-	    // look for a valid diagonal that divides the polygon into two
-	    var a = start;
-	    do {
-	        var b = a.next.next;
-	        while (b !== a.prev) {
-	            if (a.i !== b.i && isValidDiagonal(a, b)) {
-	                // split the polygon in two by the diagonal
-	                var c = splitPolygon(a, b);
-
-	                // filter colinear points around the cuts
-	                a = filterPoints(a, a.next);
-	                c = filterPoints(c, c.next);
-
-	                // run earcut on each half
-	                earcutLinked(a, triangles, dim, minX, minY, invSize);
-	                earcutLinked(c, triangles, dim, minX, minY, invSize);
-	                return;
-	            }
-	            b = b.next;
-	        }
-	        a = a.next;
-	    } while (a !== start);
-	}
-
-	// link every hole into the outer loop, producing a single-ring polygon without holes
-	function eliminateHoles(data, holeIndices, outerNode, dim) {
-	    var queue = [],
-	        i, len, start, end, list;
-
-	    for (i = 0, len = holeIndices.length; i < len; i++) {
-	        start = holeIndices[i] * dim;
-	        end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
-	        list = linkedList(data, start, end, dim, false);
-	        if (list === list.next) list.steiner = true;
-	        queue.push(getLeftmost(list));
-	    }
-
-	    queue.sort(compareX);
-
-	    // process holes from left to right
-	    for (i = 0; i < queue.length; i++) {
-	        eliminateHole(queue[i], outerNode);
-	        outerNode = filterPoints(outerNode, outerNode.next);
-	    }
-
-	    return outerNode;
-	}
-
-	function compareX(a, b) {
-	    return a.x - b.x;
-	}
-
-	// find a bridge between vertices that connects hole with an outer ring and and link it
-	function eliminateHole(hole, outerNode) {
-	    outerNode = findHoleBridge(hole, outerNode);
-	    if (outerNode) {
-	        var b = splitPolygon(outerNode, hole);
-	        filterPoints(b, b.next);
-	    }
-	}
-
-	// David Eberly's algorithm for finding a bridge between hole and outer polygon
-	function findHoleBridge(hole, outerNode) {
-	    var p = outerNode,
-	        hx = hole.x,
-	        hy = hole.y,
-	        qx = -Infinity,
-	        m;
-
-	    // find a segment intersected by a ray from the hole's leftmost point to the left;
-	    // segment's endpoint with lesser x will be potential connection point
-	    do {
-	        if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
-	            var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
-	            if (x <= hx && x > qx) {
-	                qx = x;
-	                if (x === hx) {
-	                    if (hy === p.y) return p;
-	                    if (hy === p.next.y) return p.next;
-	                }
-	                m = p.x < p.next.x ? p : p.next;
-	            }
-	        }
-	        p = p.next;
-	    } while (p !== outerNode);
-
-	    if (!m) return null;
-
-	    if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint
-
-	    // look for points inside the triangle of hole point, segment intersection and endpoint;
-	    // if there are no points found, we have a valid connection;
-	    // otherwise choose the point of the minimum angle with the ray as connection point
-
-	    var stop = m,
-	        mx = m.x,
-	        my = m.y,
-	        tanMin = Infinity,
-	        tan;
-
-	    p = m.next;
-
-	    while (p !== stop) {
-	        if (hx >= p.x && p.x >= mx && hx !== p.x &&
-	                pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
-
-	            tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
-
-	            if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
-	                m = p;
-	                tanMin = tan;
-	            }
-	        }
-
-	        p = p.next;
-	    }
-
-	    return m;
-	}
-
-	// interlink polygon nodes in z-order
-	function indexCurve(start, minX, minY, invSize) {
-	    var p = start;
-	    do {
-	        if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
-	        p.prevZ = p.prev;
-	        p.nextZ = p.next;
-	        p = p.next;
-	    } while (p !== start);
-
-	    p.prevZ.nextZ = null;
-	    p.prevZ = null;
-
-	    sortLinked(p);
-	}
-
-	// Simon Tatham's linked list merge sort algorithm
-	// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
-	function sortLinked(list) {
-	    var i, p, q, e, tail, numMerges, pSize, qSize,
-	        inSize = 1;
-
-	    do {
-	        p = list;
-	        list = null;
-	        tail = null;
-	        numMerges = 0;
-
-	        while (p) {
-	            numMerges++;
-	            q = p;
-	            pSize = 0;
-	            for (i = 0; i < inSize; i++) {
-	                pSize++;
-	                q = q.nextZ;
-	                if (!q) break;
-	            }
-	            qSize = inSize;
-
-	            while (pSize > 0 || (qSize > 0 && q)) {
-
-	                if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
-	                    e = p;
-	                    p = p.nextZ;
-	                    pSize--;
-	                } else {
-	                    e = q;
-	                    q = q.nextZ;
-	                    qSize--;
-	                }
-
-	                if (tail) tail.nextZ = e;
-	                else list = e;
-
-	                e.prevZ = tail;
-	                tail = e;
-	            }
-
-	            p = q;
-	        }
-
-	        tail.nextZ = null;
-	        inSize *= 2;
-
-	    } while (numMerges > 1);
-
-	    return list;
-	}
-
-	// z-order of a point given coords and inverse of the longer side of data bbox
-	function zOrder(x, y, minX, minY, invSize) {
-	    // coords are transformed into non-negative 15-bit integer range
-	    x = 32767 * (x - minX) * invSize;
-	    y = 32767 * (y - minY) * invSize;
-
-	    x = (x | (x << 8)) & 0x00FF00FF;
-	    x = (x | (x << 4)) & 0x0F0F0F0F;
-	    x = (x | (x << 2)) & 0x33333333;
-	    x = (x | (x << 1)) & 0x55555555;
-
-	    y = (y | (y << 8)) & 0x00FF00FF;
-	    y = (y | (y << 4)) & 0x0F0F0F0F;
-	    y = (y | (y << 2)) & 0x33333333;
-	    y = (y | (y << 1)) & 0x55555555;
-
-	    return x | (y << 1);
-	}
-
-	// find the leftmost node of a polygon ring
-	function getLeftmost(start) {
-	    var p = start,
-	        leftmost = start;
-	    do {
-	        if (p.x < leftmost.x) leftmost = p;
-	        p = p.next;
-	    } while (p !== start);
-
-	    return leftmost;
-	}
-
-	// check if a point lies within a convex triangle
-	function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
-	    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
-	           (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
-	           (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
-	}
-
-	// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
-	function isValidDiagonal(a, b) {
-	    return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
-	           locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
-	}
-
-	// signed area of a triangle
-	function area(p, q, r) {
-	    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
-	}
-
-	// check if two points are equal
-	function equals(p1, p2) {
-	    return p1.x === p2.x && p1.y === p2.y;
-	}
-
-	// check if two segments intersect
-	function intersects(p1, q1, p2, q2) {
-	    if ((equals(p1, q1) && equals(p2, q2)) ||
-	        (equals(p1, q2) && equals(p2, q1))) return true;
-	    return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
-	           area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
-	}
-
-	// check if a polygon diagonal intersects any polygon segments
-	function intersectsPolygon(a, b) {
-	    var p = a;
-	    do {
-	        if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
-	                intersects(p, p.next, a, b)) return true;
-	        p = p.next;
-	    } while (p !== a);
-
-	    return false;
-	}
-
-	// check if a polygon diagonal is locally inside the polygon
-	function locallyInside(a, b) {
-	    return area(a.prev, a, a.next) < 0 ?
-	        area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
-	        area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
-	}
-
-	// check if the middle point of a polygon diagonal is inside the polygon
-	function middleInside(a, b) {
-	    var p = a,
-	        inside = false,
-	        px = (a.x + b.x) / 2,
-	        py = (a.y + b.y) / 2;
-	    do {
-	        if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
-	                (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
-	            inside = !inside;
-	        p = p.next;
-	    } while (p !== a);
-
-	    return inside;
-	}
-
-	// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
-	// if one belongs to the outer ring and another to a hole, it merges it into a single ring
-	function splitPolygon(a, b) {
-	    var a2 = new Node(a.i, a.x, a.y),
-	        b2 = new Node(b.i, b.x, b.y),
-	        an = a.next,
-	        bp = b.prev;
-
-	    a.next = b;
-	    b.prev = a;
-
-	    a2.next = an;
-	    an.prev = a2;
-
-	    b2.next = a2;
-	    a2.prev = b2;
-
-	    bp.next = b2;
-	    b2.prev = bp;
-
-	    return b2;
-	}
-
-	// create a node and optionally link it with previous one (in a circular doubly linked list)
-	function insertNode(i, x, y, last) {
-	    var p = new Node(i, x, y);
-
-	    if (!last) {
-	        p.prev = p;
-	        p.next = p;
-
-	    } else {
-	        p.next = last.next;
-	        p.prev = last;
-	        last.next.prev = p;
-	        last.next = p;
-	    }
-	    return p;
-	}
-
-	function removeNode(p) {
-	    p.next.prev = p.prev;
-	    p.prev.next = p.next;
-
-	    if (p.prevZ) p.prevZ.nextZ = p.nextZ;
-	    if (p.nextZ) p.nextZ.prevZ = p.prevZ;
-	}
-
-	function Node(i, x, y) {
-	    // vertice index in coordinates array
-	    this.i = i;
-
-	    // vertex coordinates
-	    this.x = x;
-	    this.y = y;
-
-	    // previous and next vertice nodes in a polygon ring
-	    this.prev = null;
-	    this.next = null;
-
-	    // z-order curve value
-	    this.z = null;
-
-	    // previous and next nodes in z-order
-	    this.prevZ = null;
-	    this.nextZ = null;
-
-	    // indicates whether this is a steiner point
-	    this.steiner = false;
-	}
-
-	// return a percentage difference between the polygon area and its triangulation area;
-	// used to verify correctness of triangulation
-	earcut.deviation = function (data, holeIndices, dim, triangles) {
-	    var hasHoles = holeIndices && holeIndices.length;
-	    var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
-
-	    var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
-	    if (hasHoles) {
-	        for (var i = 0, len = holeIndices.length; i < len; i++) {
-	            var start = holeIndices[i] * dim;
-	            var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
-	            polygonArea -= Math.abs(signedArea(data, start, end, dim));
-	        }
-	    }
-
-	    var trianglesArea = 0;
-	    for (i = 0; i < triangles.length; i += 3) {
-	        var a = triangles[i] * dim;
-	        var b = triangles[i + 1] * dim;
-	        var c = triangles[i + 2] * dim;
-	        trianglesArea += Math.abs(
-	            (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
-	            (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
-	    }
-
-	    return polygonArea === 0 && trianglesArea === 0 ? 0 :
-	        Math.abs((trianglesArea - polygonArea) / polygonArea);
-	};
-
-	function signedArea(data, start, end, dim) {
-	    var sum = 0;
-	    for (var i = start, j = end - dim; i < end; i += dim) {
-	        sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
-	        j = i;
-	    }
-	    return sum;
-	}
-
-	// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
-	earcut.flatten = function (data) {
-	    var dim = data[0][0].length,
-	        result = {vertices: [], holes: [], dimensions: dim},
-	        holeIndex = 0;
-
-	    for (var i = 0; i < data.length; i++) {
-	        for (var j = 0; j < data[i].length; j++) {
-	            for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
-	        }
-	        if (i > 0) {
-	            holeIndex += data[i - 1].length;
-	            result.holes.push(holeIndex);
-	        }
-	    }
-	    return result;
-	};
-
-
-/***/ }),
-/* 267 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var quadFeature = __webpack_require__(223);
-
-	/**
-	 * Create a new instance of class quadFeature.
-	 *
-	 * @class geo.gl.quadFeature
-	 * @param {geo.quadFeature.spec} arg Options object.
-	 * @extends geo.quadFeature
-	 * @returns {geo.gl.quadFeature}
-	 */
-	var gl_quadFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof gl_quadFeature)) {
-	    return new gl_quadFeature(arg);
-	  }
-	  quadFeature.call(this, arg);
-
-	  var $ = __webpack_require__(1);
-	  var vgl = __webpack_require__(86);
-	  var object = __webpack_require__(262);
-
-	  object.call(this);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_modelViewUniform,
-	      m_actor_image, m_actor_color, m_glBuffers = {}, m_imgposbuf,
-	      m_clrposbuf, m_clrModelViewUniform,
-	      m_glCompileTimestamp = vgl.timestamp(),
-	      m_glColorCompileTimestamp = vgl.timestamp(),
-	      m_quads;
-	  var fragmentShaderImageSource = [
-	    'varying highp vec2 iTextureCoord;',
-	    'uniform sampler2D sampler2d;',
-	    'uniform mediump float opacity;',
-	    'uniform highp vec2 crop;',
-	    'void main(void) {',
-	    '  mediump vec4 color = texture2D(sampler2d, iTextureCoord);',
-	    '  if ((crop.s < 1.0 && iTextureCoord.s > crop.s) || (crop.t < 1.0 && 1.0 - iTextureCoord.t > crop.t)) {',
-	    '    discard;',
-	    '  }',
-	    '  color.w *= opacity;',
-	    '  gl_FragColor = color;',
-	    '}'].join('\n');
-	  var vertexShaderImageSource = [
-	    'attribute vec3 vertexPosition;',
-	    'attribute vec2 textureCoord;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying highp vec2 iTextureCoord;',
-	    'void main(void) {',
-	    '  gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    '  iTextureCoord = textureCoord;',
-	    '}'].join('\n');
-	  var vertexShaderColorSource = [
-	    'attribute vec3 vertexPosition;',
-	    'uniform vec3 vertexColor;',
-	    'uniform mat4 modelViewMatrix;',
-	    'uniform mat4 projectionMatrix;',
-	    'varying mediump vec3 iVertexColor;',
-	    'void main(void) {',
-	    '  gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);',
-	    '  iVertexColor = vertexColor;',
-	    '}'].join('\n');
-
-	  /**
-	   * Allocate buffers that we need to control for image quads.  This mimics
-	   * the actions from vgl.mapper to some degree.
-	   *
-	   * @private
-	   * @param {vgl.renderState} renderState An object that contains the context
-	   *   used for drawing.
-	   */
-	  function setupDrawObjects(renderState) {
-	    var context = renderState.m_context,
-	        newbuf = false;
-
-	    if (m_quads.imgQuads.length) {
-	      if (!m_imgposbuf || m_imgposbuf.length < m_quads.imgQuads.length * 12 ||
-	          !m_glBuffers.imgQuadsPosition) {
-	        if (m_glBuffers.imgQuadsPosition) {
-	          context.deleteBuffer(m_glBuffers.imgQuadsPosition);
-	        }
-	        m_glBuffers.imgQuadsPosition = context.createBuffer();
-	        m_imgposbuf = new Float32Array(Math.max(
-	            128, m_quads.imgQuads.length * 2) * 12);
-	        newbuf = true;
-	      }
-	      $.each(m_quads.imgQuads, function (idx, quad) {
-	        for (var i = 0; i < 12; i += 1) {
-	          m_imgposbuf[idx * 12 + i] = quad.pos[i] - m_quads.origin[i % 3];
-	        }
-	      });
-	      context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.imgQuadsPosition);
-	      if (newbuf) {
-	        context.bufferData(vgl.GL.ARRAY_BUFFER, m_imgposbuf, vgl.GL.DYNAMIC_DRAW);
-	      } else {
-	        context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0, m_imgposbuf);
-	      }
-	    }
-	    m_glCompileTimestamp.modified();
-	  }
-
-	  /**
-	   * Allocate buffers that we need to control for color quads.  This mimics
-	   * the actions from vgl.mapper to some degree.
-	   *
-	   * @private
-	   * @param {vgl.renderState} renderState An object that contains the context
-	   *   used for drawing.
-	   */
-	  function setupColorDrawObjects(renderState) {
-	    var context = renderState.m_context,
-	        newbuf = false;
-
-	    if (m_quads.clrQuads.length) {
-	      if (!m_clrposbuf || m_clrposbuf.length < m_quads.clrQuads.length * 12 ||
-	          !m_glBuffers.clrQuadsPosition) {
-	        if (m_glBuffers.clrQuadsPosition) {
-	          context.deleteBuffer(m_glBuffers.clrQuadsPosition);
-	        }
-	        m_glBuffers.clrQuadsPosition = context.createBuffer();
-	        m_clrposbuf = new Float32Array(Math.max(
-	            128, m_quads.clrQuads.length * 2) * 12);
-	        newbuf = true;
-	      }
-	      $.each(m_quads.clrQuads, function (idx, quad) {
-	        for (var i = 0; i < 12; i += 1) {
-	          m_clrposbuf[idx * 12 + i] = quad.pos[i] - m_quads.origin[i % 3];
-	        }
-	      });
-	      context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.clrQuadsPosition);
-	      if (newbuf) {
-	        context.bufferData(vgl.GL.ARRAY_BUFFER, m_clrposbuf, vgl.GL.DYNAMIC_DRAW);
-	      } else {
-	        context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0, m_clrposbuf);
-	      }
-	    }
-	    m_glColorCompileTimestamp.modified();
-	  }
-
-	  /**
-	   * Build this feature.
-	   */
-	  this._build = function () {
-	    var mapper, mat, prog, srctex, unicrop, geom, context;
-
-	    if (!m_this.position()) {
-	      return;
-	    }
-	    m_quads = this._generateQuads();
-	    /* Create an actor to render image quads */
-	    if (m_quads.imgQuads.length && !m_actor_image) {
-	      m_this.visible(false);
-	      mapper = new vgl.mapper({dynamicDraw: true});
-	      m_actor_image = new vgl.actor();
-	      /* This is similar to vgl.utils.createTextureMaterial */
-	      m_actor_image.setMapper(mapper);
-	      mat = new vgl.material();
-	      prog = new vgl.shaderProgram();
-	      prog.addVertexAttribute(new vgl.vertexAttribute('vertexPosition'),
-	                              vgl.vertexAttributeKeys.Position);
-	      prog.addVertexAttribute(new vgl.vertexAttribute('textureCoord'),
-	                              vgl.vertexAttributeKeys.TextureCoordinate);
-	      m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix',
-	        m_quads.origin);
-	      prog.addUniform(m_modelViewUniform);
-	      prog.addUniform(new vgl.projectionUniform('projectionMatrix'));
-	      prog.addUniform(new vgl.floatUniform('opacity', 1.0));
-	      unicrop = new vgl.uniform(vgl.GL.FLOAT_VEC2, 'crop');
-	      unicrop.set([1.0, 1.0]);
-	      prog.addUniform(unicrop);
-	      context = m_this.renderer()._glContext();
-	      prog.addShader(vgl.getCachedShader(
-	          vgl.GL.VERTEX_SHADER, context, vertexShaderImageSource));
-	      prog.addShader(vgl.getCachedShader(
-	          vgl.GL.FRAGMENT_SHADER, context, fragmentShaderImageSource));
-	      mat.addAttribute(prog);
-	      mat.addAttribute(new vgl.blend());
-	      /* This is similar to vgl.planeSource */
-	      geom = new vgl.geometryData();
-	      m_imgposbuf = undefined;
-	      srctex = new vgl.sourceDataT2fv();
-	      srctex.pushBack([0, 0, 1, 0, 0, 1, 1, 1]);
-	      geom.addSource(srctex);
-	      /* We deliberately do not add a primitive to our geometry -- we take care
-	       * of that ourselves. */
-
-	      mapper.setGeometryData(geom);
-	      m_actor_image.setMaterial(mat);
-
-	      mapper.s_render = mapper.render;
-	      mapper.render = m_this._renderImageQuads;
-	      m_this.renderer().contextRenderer().addActor(m_actor_image);
-	      m_this.visible(true);
-	    }
-	    /* Create an actor to render color quads */
-	    if (m_quads.clrQuads.length && !m_actor_color) {
-	      m_this.visible(false);
-	      mapper = new vgl.mapper({dynamicDraw: true});
-	      m_actor_color = new vgl.actor();
-	      /* This is similar to vgl.utils.createTextureMaterial */
-	      m_actor_color.setMapper(mapper);
-	      mat = new vgl.material();
-	      prog = new vgl.shaderProgram();
-	      prog.addVertexAttribute(new vgl.vertexAttribute('vertexPosition'),
-	                              vgl.vertexAttributeKeys.Position);
-	      m_clrModelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix',
-	        m_quads.origin);
-	      prog.addUniform(m_clrModelViewUniform);
-	      prog.addUniform(new vgl.projectionUniform('projectionMatrix'));
-	      prog.addUniform(new vgl.floatUniform('opacity', 1.0));
-	      prog.addUniform(new vgl.uniform(vgl.GL.FLOAT_VEC3, 'vertexColor'));
-	      context = m_this.renderer()._glContext();
-	      prog.addShader(vgl.getCachedShader(
-	          vgl.GL.VERTEX_SHADER, context, vertexShaderColorSource));
-	      prog.addShader(vgl.utils.createFragmentShader(context));
-	      mat.addAttribute(prog);
-	      mat.addAttribute(new vgl.blend());
-	      /* This is similar to vgl.planeSource */
-	      geom = new vgl.geometryData();
-	      m_clrposbuf = undefined;
-	      /* We deliberately do not add a primitive to our geometry -- we take care
-	       * of that ourselves. */
-
-	      mapper.setGeometryData(geom);
-	      m_actor_color.setMaterial(mat);
-
-	      mapper.s_render = mapper.render;
-	      mapper.render = m_this._renderColorQuads;
-	      m_this.renderer().contextRenderer().addActor(m_actor_color);
-	      m_this.visible(true);
-	    }
-	    if (m_modelViewUniform) {
-	      m_modelViewUniform.setOrigin(m_quads.origin);
-	    }
-	    if (m_clrModelViewUniform) {
-	      m_clrModelViewUniform.setOrigin(m_quads.origin);
-	    }
-	    m_this._updateTextures();
-	    m_this.buildTime().modified();
-	  };
-
-	  /**
-	   * Check all of the image quads.  If any do not have the correct texture,
-	   * update them. */
-	  this._updateTextures = function () {
-	    var texture;
-
-	    $.each(m_quads.imgQuads, function (idx, quad) {
-	      if (!quad.image) {
-	        return;
-	      }
-	      if (quad.image._texture) {
-	        quad.texture = quad.image._texture;
-	      } else {
-	        texture = new vgl.texture();
-	        texture.setImage(quad.image);
-	        quad.texture = quad.image._texture = texture;
-	      }
-	    });
-	  };
-
-	  /**
-	   * Render all of the color quads using a single mapper.
-	   *
-	   * @param {vgl.renderState} renderState An object that contains the context
-	   *   used for drawing.
-	   */
-	  this._renderColorQuads = function (renderState) {
-	    if (!m_quads.clrQuads.length) {
-	      return;
-	    }
-	    var mapper = this;
-	    if (mapper.getMTime() > m_glColorCompileTimestamp.getMTime() ||
-	        m_this.dataTime().getMTime() > m_glColorCompileTimestamp.getMTime() ||
-	        renderState.m_contextChanged || !m_clrposbuf ||
-	        m_quads.clrQuads.length * 12 > m_clrposbuf.length) {
-	      setupColorDrawObjects(renderState);
-	    }
-	    mapper.s_render(renderState, true);
-
-	    var context = renderState.m_context, opacity = 1, color;
-
-	    context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.clrQuadsPosition);
-	    $.each(m_quads.clrQuads, function (idx, quad) {
-	      if (quad.opacity !== opacity) {
-	        opacity = quad.opacity;
-	        context.uniform1fv(renderState.m_material.shaderProgram(
-	            ).uniformLocation('opacity'), new Float32Array([opacity]));
-	      }
-	      if (!color || color.r !== quad.color.r || color.g !== quad.color.g ||
-	          color.b !== quad.color.b) {
-	        color = quad.color;
-	        context.uniform3fv(renderState.m_material.shaderProgram(
-	            ).uniformLocation('vertexColor'), new Float32Array([
-	              color.r, color.g, color.b]));
-	      }
-
-	      context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.clrQuadsPosition);
-	      context.vertexAttribPointer(vgl.vertexAttributeKeys.Position, 3,
-	                                  vgl.GL.FLOAT, false, 12, idx * 12 * 4);
-	      context.enableVertexAttribArray(vgl.vertexAttributeKeys.Position);
-
-	      context.drawArrays(vgl.GL.TRIANGLE_STRIP, 0, 4);
-	    });
-	    context.bindBuffer(vgl.GL.ARRAY_BUFFER, null);
-	    mapper.undoBindVertexData(renderState);
-	  };
-
-	  /**
-	   * Render all of the image quads using a single mapper.
-	   *
-	   * @param {vgl.renderState} renderState An object that contains the context
-	   *   used for drawing.
-	   */
-	  this._renderImageQuads = function (renderState) {
-	    if (!m_quads.imgQuads.length) {
-	      return;
-	    }
-	    var mapper = this;
-	    if (mapper.getMTime() > m_glCompileTimestamp.getMTime() ||
-	        m_this.dataTime().getMTime() > m_glCompileTimestamp.getMTime() ||
-	        renderState.m_contextChanged || !m_imgposbuf ||
-	        m_quads.imgQuads.length * 12 > m_imgposbuf.length) {
-	      setupDrawObjects(renderState);
-	    }
-	    mapper.s_render(renderState, true);
-
-	    var context = renderState.m_context,
-	        opacity = 1,
-	        crop = {x: 1, y: 1}, quadcrop;
-
-	    context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.imgQuadsPosition);
-	    $.each(m_quads.imgQuads, function (idx, quad) {
-	      if (!quad.image) {
-	        return;
-	      }
-	      quad.texture.bind(renderState);
-
-	      if (quad.opacity !== opacity) {
-	        opacity = quad.opacity;
-	        context.uniform1fv(renderState.m_material.shaderProgram(
-	            ).uniformLocation('opacity'), new Float32Array([opacity]));
-	      }
-	      quadcrop = quad.crop || {x: 1, y: 1};
-	      if (!crop || quadcrop.x !== crop.x || quadcrop.y !== crop.y) {
-	        crop = quadcrop;
-	        context.uniform2fv(renderState.m_material.shaderProgram(
-	            ).uniformLocation('crop'), new Float32Array([crop.x, crop.y]));
-	      }
-	      context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_glBuffers.imgQuadsPosition);
-	      context.vertexAttribPointer(vgl.vertexAttributeKeys.Position, 3,
-	                                  vgl.GL.FLOAT, false, 12, idx * 12 * 4);
-	      context.enableVertexAttribArray(vgl.vertexAttributeKeys.Position);
-
-	      context.drawArrays(vgl.GL.TRIANGLE_STRIP, 0, 4);
-	      quad.texture.undoBind(renderState);
-	    });
-	    context.bindBuffer(vgl.GL.ARRAY_BUFFER, null);
-	    mapper.undoBindVertexData(renderState);
-	  };
-
-	  /**
-	   * Update.
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
-	        m_this.updateTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-	    if (m_actor_color) {
-	      m_actor_color.setVisible(m_this.visible(undefined, true));
-	      m_actor_color.material().setBinNumber(m_this.bin());
-	    }
-	    if (m_actor_image) {
-	      m_actor_image.setVisible(m_this.visible(undefined, true));
-	      m_actor_image.material().setBinNumber(m_this.bin());
-	    }
-	    m_this.updateTime().modified();
-	  };
-
-	  /**
-	   * Initialize.
-	   */
-	  this._init = function () {
-	    s_init.call(m_this, arg);
-	  };
-
-	  /**
-	   * Destroy.
-	   */
-	  this._exit = function () {
-	    if (m_actor_image) {
-	      m_this.renderer().contextRenderer().removeActor(m_actor_image);
-	      m_actor_image = null;
-	    }
-	    if (m_actor_color) {
-	      m_this.renderer().contextRenderer().removeActor(m_actor_color);
-	      m_actor_color = null;
-	    }
-	    s_exit.call(m_this);
-	  };
-
-	  m_this._init(arg);
-	  return this;
-	};
-
-	inherit(gl_quadFeature, quadFeature);
-
-	// Now register it
-	var capabilities = {};
-	capabilities[quadFeature.capabilities.color] = true;
-	capabilities[quadFeature.capabilities.image] = true;
-	capabilities[quadFeature.capabilities.imageCrop] = true;
-	capabilities[quadFeature.capabilities.imageFixedScale] = false;
-	capabilities[quadFeature.capabilities.imageFull] = true;
-	capabilities[quadFeature.capabilities.canvas] = false;
-	capabilities[quadFeature.capabilities.video] = false;
-
-	registerFeature('vgl', 'quad', gl_quadFeature, capabilities);
-	module.exports = gl_quadFeature;
-
-
-/***/ }),
-/* 268 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var registerLayerAdjustment = __webpack_require__(201).registerLayerAdjustment;
-
-	var gl_tileLayer = function () {
-	  'use strict';
-	  var m_this = this,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      m_quadFeature,
-	      m_nextTileId = 0,
-	      m_tiles = [];
-
-	  /* Add a tile to the list of quads */
-	  this._drawTile = function (tile) {
-	    if (!m_quadFeature) {
-	      return;
-	    }
-	    var bounds = this._tileBounds(tile),
-	        level = tile.index.level || 0,
-	        to = this._tileOffset(level),
-	        crop = this.tileCropFromBounds(tile),
-	        quad = {};
-	    if (crop) {
-	      quad.crop = {
-	        x: crop.x / m_this._options.tileWidth,
-	        y: crop.y / m_this._options.tileHeight
-	      };
-	    }
-	    quad.ul = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.ll = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ur = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.lr = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ul.z = quad.ll.z = quad.ur.z = quad.lr.z = level * 1e-5;
-	    m_nextTileId += 1;
-	    quad.id = m_nextTileId;
-	    tile.quadId = quad.id;
-	    quad.image = tile.image;
-	    m_tiles.push(quad);
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	    m_this.draw();
-	  };
-
-	  /* Remove the tile feature. */
-	  this._remove = function (tile) {
-	    if (tile.quadId !== undefined && m_quadFeature) {
-	      for (var i = 0; i < m_tiles.length; i += 1) {
-	        if (m_tiles[i].id === tile.quadId) {
-	          m_tiles.splice(i, 1);
-	          break;
-	        }
-	      }
-	      m_quadFeature.data(m_tiles);
-	      m_quadFeature._update();
-	      m_this.draw();
-	    }
-	  };
-
-	  /**
-	   * Clean up the layer.
-	   */
-	  this._exit = function () {
-	    m_this.deleteFeature(m_quadFeature);
-	    m_quadFeature = null;
-	    m_tiles = [];
-	    s_exit.apply(m_this, arguments);
-	  };
-
-	  /**
-	   * Initialize after the layer is added to the map.
-	   */
-	  this._init = function () {
-	    s_init.apply(m_this, arguments);
-	    m_quadFeature = this.createFeature('quad', {
-	      previewColor: m_this._options.previewColor,
-	      previewImage: m_this._options.previewImage
-	    });
-	    m_quadFeature.geoTrigger = undefined;
-	    m_quadFeature.gcs(m_this._options.gcs || m_this.map().gcs());
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	  };
-
-	  /* These functions don't need to do anything. */
-	  this._getSubLayer = function () {};
-	  this._updateSubLayers = undefined;
-	};
-
-	registerLayerAdjustment('vgl', 'tile', gl_tileLayer);
-
-	module.exports = gl_tileLayer;
-
-
-/***/ }),
-/* 269 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/**
-	 * @namespace geo.canvas
-	 */
-	module.exports = {
-	  canvasRenderer: __webpack_require__(270),
-	  heatmapFeature: __webpack_require__(271),
-	  lineFeature: __webpack_require__(273),
-	  pixelmapFeature: __webpack_require__(274),
-	  quadFeature: __webpack_require__(275),
-	  textFeature: __webpack_require__(276),
-	  tileLayer: __webpack_require__(296)
-	};
-
-
-/***/ }),
-/* 270 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerRenderer = __webpack_require__(201).registerRenderer;
-	var renderer = __webpack_require__(202);
-
-	/**
-	 * Create a new instance of class canvasRenderer.
-	 *
-	 * @class geo.canvas.renderer
-	 * @extends geo.renderer
-	 * @param {object} arg Options for the renderer.
-	 * @param {geo.layer} [arg.layer] Layer associated with the renderer.
-	 * @param {HTMLElement} [arg.canvas] Canvas element associated with the
-	 *   renderer.
-	 * @returns {geo.canvas.canvasRenderer}
-	 */
-	var canvasRenderer = function (arg) {
-	  'use strict';
-
-	  var $ = __webpack_require__(1);
-
-	  if (!(this instanceof canvasRenderer)) {
-	    return new canvasRenderer(arg);
-	  }
-	  arg = arg || {};
-	  renderer.call(this, arg);
-
-	  var m_this = this,
-	      m_clearCanvas = true,
-	      s_init = this._init,
-	      s_exit = this._exit;
-
-	  /**
-	   * Set the clear canvas flag.  If truthy, the canvas is erased at the start
-	   * of the render cycle.  If falsy, the old data is kept.
-	   *
-	   * @param {boolean} arg Truthy to clear the canvas when rendering is started.
-	   */
-	  this.clearCanvas = function (arg) {
-	    m_clearCanvas = arg;
-	  };
-
-	  /**
-	   * Get API used by the renderer.
-	   *
-	   * @returns {string} 'canvas'.
-	   */
-	  this.api = function () {
-	    return 'canvas';
-	  };
-
-	  /**
-	   * Initialize.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    if (m_this.initialized()) {
-	      return m_this;
-	    }
-
-	    s_init.call(m_this);
-
-	    var canvas = $(document.createElement('canvas'));
-	    m_this.context2d = canvas[0].getContext('2d');
-
-	    canvas.addClass('canvas-canvas');
-	    $(m_this.layer().node().get(0)).append(canvas);
-
-	    m_this.canvas(canvas);
-	    /* Initialize the size of the renderer */
-	    var map = m_this.layer().map(),
-	        mapSize = map.size();
-	    m_this._resize(0, 0, mapSize.width, mapSize.height);
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Handle resize event.
-	   *
-	   * @param {number} x Ignored.
-	   * @param {number} y Ignored.
-	   * @param {number} w New width in pixels.
-	   * @param {number} h New height in pixels.
-	   * @returns {this}
-	   */
-	  this._resize = function (x, y, w, h) {
-	    m_this.canvas().attr('width', w);
-	    m_this.canvas().attr('height', h);
-	    m_this._render();
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Render.
-	   *
-	   * @returns {this}
-	   */
-	  this._render = function () {
-	    m_this.layer().map().scheduleAnimationFrame(this._renderFrame);
-	    return m_this;
-	  };
-
-	  /**
-	   * Render during an animation frame callback.
-	   */
-	  this._renderFrame = function () {
-	    var layer = m_this.layer(),
-	        map = layer.map(),
-	        camera = map.camera(),
-	        viewport = camera._viewport,
-	        features = layer.features(),
-	        i;
-
-	    for (i = 0; i < features.length; i += 1) {
-	      if (features[i]._delayRender()) {
-	        // reschedule the render for the next animation frame
-	        m_this._render();
-	        // exit this render loop so it doesn't occur
-	        return;
-	      }
-	    }
-
-	    // Clear the canvas.
-	    if (m_clearCanvas) {
-	      m_this.context2d.setTransform(1, 0, 0, 1, 0, 0);
-	      m_this.context2d.clearRect(0, 0, viewport.width, viewport.height);
-	    }
-
-	    for (i = 0; i < features.length; i += 1) {
-	      if (features[i].visible()) {
-	        features[i]._renderOnCanvas(m_this.context2d, map);
-	      }
-	    }
-	  };
-
-	  /**
-	   * Exit.
-	   */
-	  this._exit = function () {
-	    m_this.canvas().remove();
-	    s_exit();
-	  };
-
-	  return this;
-	};
-
-	inherit(canvasRenderer, renderer);
-
-	registerRenderer('canvas', canvasRenderer);
-
-	(function () {
-	  'use strict';
-
-	  var checkedCanvas;
-
-	  /**
-	   * Report if the canvas renderer is supported.
-	   *
-	   * @returns {boolean} true if available.
-	   */
-	  canvasRenderer.supported = function () {
-	    if (checkedCanvas === undefined) {
-	      /* This is extracted from what Modernizr uses. */
-	      var canvas; // eslint-disable-line no-unused-vars
-	      try {
-	        canvas = document.createElement('canvas');
-	        checkedCanvas = !!(canvas.getContext && canvas.getContext('2d'));
-	      } catch (e) {
-	        checkedCanvas = false;
-	      }
-	      canvas = undefined;
-	    }
-	    return checkedCanvas;
-	  };
-
-	  /**
-	   * If the canvas renderer is not supported, supply the name of a renderer that
-	   * should be used instead.  This asks for the null renderer.
-	   *
-	   * @returns {null} `null` for the null renderer.
-	   */
-	  canvasRenderer.fallback = function () {
-	    return null;
-	  };
-	})();
-
-	module.exports = canvasRenderer;
-
-
-/***/ }),
-/* 271 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var heatmapFeature = __webpack_require__(236);
-	var timestamp = __webpack_require__(209);
-
-	/**
-	 * Create a new instance of class heatmapFeature
-	 * Inspired from
-	 *    https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js
-	 *
-	 * @class geo.canvas.heatmapFeature
-	 * @param {Object} arg Options object
-	 * @extends geo.heatmapFeature
-	 * @returns {canvas_heatmapFeature}
-	 */
-	var canvas_heatmapFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof canvas_heatmapFeature)) {
-	    return new canvas_heatmapFeature(arg);
-	  }
-	  heatmapFeature.call(this, arg);
-	  var object = __webpack_require__(272);
-
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var geo_event = __webpack_require__(9);
-
-	  var m_this = this,
-	      m_typedBuffer,
-	      m_typedClampedBuffer,
-	      m_typedBufferData,
-	      m_heatMapPosition,
-	      m_heatMapTransform,
-	      s_exit = this._exit,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_renderTime = timestamp();
-
-	  /**
-	   * Meta functions for converting from geojs styles to canvas.
-	   * @private
-	   */
-	  this._convertColor = function (c) {
-	    var color;
-	    if (c.hasOwnProperty('r') &&
-	      c.hasOwnProperty('g') &&
-	      c.hasOwnProperty('b') &&
-	      c.hasOwnProperty('a')) {
-	      color = 'rgba(' + 255 * c.r + ',' + 255 * c.g + ','
-	                    + 255 * c.b + ',' + c.a + ')';
-	    }
-	    return color;
-	  };
-
-	  /**
-	   * Compute gradient (color lookup table)
-	   * @protected
-	   */
-	  this._computeGradient = function () {
-	    var canvas, stop, context2d, gradient, colors;
-
-	    colors = m_this.style('color');
-	    if (!m_this._grad || m_this._gradColors !== colors) {
-	      canvas = document.createElement('canvas');
-	      context2d = canvas.getContext('2d');
-	      gradient = context2d.createLinearGradient(0, 0, 0, 256);
-
-	      canvas.width = 1;
-	      canvas.height = 256;
-
-	      for (stop in colors) {
-	        gradient.addColorStop(stop, m_this._convertColor(colors[stop]));
-	      }
-
-	      context2d.fillStyle = gradient;
-	      context2d.fillRect(0, 0, 1, 256);
-	      m_this._grad = context2d.getImageData(0, 0, 1, 256).data;
-	      m_this._gradColors = colors;
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Create circle for each data point
-	   * @protected
-	   */
-	  this._createCircle = function () {
-	    var circle, ctx, r, r2, blur, gaussian;
-	    r = m_this.style('radius');
-	    blur = m_this.style('blurRadius');
-	    gaussian = m_this.style('gaussian');
-	    if (!m_this._circle || m_this._circle.gaussian !== gaussian ||
-	        m_this._circle.radius !== r || m_this._circle.blurRadius !== blur) {
-	      circle = m_this._circle = document.createElement('canvas');
-	      ctx = circle.getContext('2d');
-	      r2 = blur + r;
-	      circle.width = circle.height = r2 * 2;
-	      if (!gaussian) {
-	        ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2;
-	        ctx.shadowBlur = blur;
-	        ctx.shadowColor = 'black';
-	        ctx.beginPath();
-	        ctx.arc(-r2, -r2, r, 0, Math.PI * 2, true);
-	        ctx.closePath();
-	        ctx.fill();
-	      } else {
-	        /* This approximates a gaussian distribution by using a 10-step
-	         * piecewise linear radial gradient.  Strictly, it should not stop at
-	         * the radius, but should be attenuated further.  The scale has been
-	         * selected such that the values at the radius are around 1/256th of
-	         * the maximum, and therefore would not be visible using an 8-bit alpha
-	         * channel for the summation.  The values for opacity were generated by
-	         * the python expression:
-	         *   from scipy.stats import norm
-	         *   for r in [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]:
-	         *     opacity = norm.pdf(r, scale=0.3) / norm.pdf(0, scale=0.3)
-	         * Usng a 10-interval approximation is accurate to within 0.5% of the
-	         * actual Gaussian magnitude.  Switching to a 20-interval approximation
-	         * would get within 0.1%, at which point there is more error from using
-	         * a Gaussian truncated at the radius than from the approximation.
-	         */
-	        var grad = ctx.createRadialGradient(r2, r2, 0, r2, r2, r2);
-	        grad.addColorStop(0.0, 'rgba(255,255,255,1)');
-	        grad.addColorStop(0.1, 'rgba(255,255,255,0.946)');
-	        grad.addColorStop(0.2, 'rgba(255,255,255,0.801)');
-	        grad.addColorStop(0.3, 'rgba(255,255,255,0.607)');
-	        grad.addColorStop(0.4, 'rgba(255,255,255,0.411)');
-	        grad.addColorStop(0.5, 'rgba(255,255,255,0.249)');
-	        grad.addColorStop(0.6, 'rgba(255,255,255,0.135)');
-	        grad.addColorStop(0.7, 'rgba(255,255,255,0.066)');
-	        grad.addColorStop(0.8, 'rgba(255,255,255,0.029)');
-	        grad.addColorStop(0.9, 'rgba(255,255,255,0.011)');
-	        grad.addColorStop(1.0, 'rgba(255,255,255,0)');
-	        ctx.fillStyle = grad;
-	        ctx.fillRect(0, 0, r2 * 2, r2 * 2);
-	      }
-	      circle.radius = r;
-	      circle.blurRadius = blur;
-	      circle.gaussian = gaussian;
-	      m_this._circle = circle;
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Compute color for each pixel on the screen
-	   * @protected
-	   */
-	  this._colorize = function (pixels, gradient) {
-	    var grad = new Uint32Array(gradient.buffer),
-	        pixlen = pixels.length,
-	        i, j, k;
-	    if (!m_typedBuffer || m_typedBuffer.length !== pixlen) {
-	      m_typedBuffer = new ArrayBuffer(pixlen);
-	      m_typedClampedBuffer = new Uint8ClampedArray(m_typedBuffer);
-	      m_typedBufferData = new Uint32Array(m_typedBuffer);
-	    }
-	    for (i = 3, k = 0; i < pixlen; i += 4, k += 1) {
-	      // Get opacity from the temporary canvas image and look up the final
-	      // value from gradient
-	      j = pixels[i];
-	      if (j) {
-	        m_typedBufferData[k] = grad[j];
-	      }
-	    }
-	    pixels.set(m_typedClampedBuffer);
-	  };
-
-	  /**
-	   * Render individual data points on the canvas.
-	   * @protected
-	   * @param {object} context2d the canvas context to draw in.
-	   * @param {object} map the parent map object.
-	   * @param {Array} data the main data array.
-	   * @param {number} radius the sum of radius and blurRadius.
-	   */
-	  this._renderPoints = function (context2d, map, data, radius) {
-	    var position = m_this.gcsPosition(),
-	        intensityFunc = m_this.intensity(),
-	        minIntensity = m_this.minIntensity(),
-	        rangeIntensity = (m_this.maxIntensity() - minIntensity) || 1,
-	        idx, pos, intensity;
-
-	    for (idx = data.length - 1; idx >= 0; idx -= 1) {
-	      pos = map.worldToDisplay(position[idx]);
-	      intensity = (intensityFunc(data[idx]) - minIntensity) / rangeIntensity;
-	      if (intensity <= 0) {
-	        continue;
-	      }
-	      // Small values are not visible because globalAlpha < .01
-	      // cannot be read from imageData
-	      context2d.globalAlpha = intensity < 0.01 ? 0.01 : (intensity > 1 ? 1 : intensity);
-	      context2d.drawImage(m_this._circle, pos.x - radius, pos.y - radius);
-	    }
-	  };
-
-	  /**
-	   * Render data points on the canvas by binning.
-	   * @protected
-	   * @param {object} context2d the canvas context to draw in.
-	   * @param {object} map the parent map object.
-	   * @param {Array} data the main data array.
-	   * @param {number} radius the sum of radius and blurRadius.
-	   * @param {number} binSize size of the bins in pixels.
-	   */
-	  this._renderBinnedData = function (context2d, map, data, radius, binSize) {
-	    var position = m_this.gcsPosition(),
-	        intensityFunc = m_this.intensity(),
-	        minIntensity = m_this.minIntensity(),
-	        rangeIntensity = (m_this.maxIntensity() - minIntensity) || 1,
-	        viewport = map.camera()._viewport,
-	        bins = [],
-	        rw = Math.ceil(radius / binSize),
-	        maxx = Math.ceil(viewport.width / binSize) + rw * 2 + 2,
-	        maxy = Math.ceil(viewport.height / binSize) + rw * 2 + 2,
-	        datalen = data.length,
-	        idx, pos, intensity, x, y, binrow, offsetx, offsety;
-
-	    /* We create bins of size (binSize) pixels on a side.  We only track bins
-	     * that are on the viewport or within the radius of it, plus one extra bin
-	     * width. */
-	    for (idx = 0; idx < datalen; idx += 1) {
-	      pos = map.worldToDisplay(position[idx]);
-	      /* To make the results look more stable, we use the first data point as a
-	       * hard-reference to where the bins should line up.  Otherwise, as we pan
-	       * points would shift which bin they are in and the display would ripple
-	       * oddly. */
-	      if (isNaN(pos.x) || isNaN(pos.y)) {
-	        continue;
-	      }
-	      if (offsetx === undefined) {
-	        offsetx = ((pos.x % binSize) + binSize) % binSize;
-	        offsety = ((pos.y % binSize) + binSize) % binSize;
-	      }
-	      /* We handle points that are in the viewport, plus the radius on either
-	       * side, as they will add into the visual effect, plus one additional bin
-	       * to account for the offset alignment. */
-	      x = Math.floor((pos.x - offsetx) / binSize) + rw + 1;
-	      if (x < 0 || x >= maxx) {
-	        continue;
-	      }
-	      y = Math.floor((pos.y - offsety) / binSize) + rw + 1;
-	      if (y < 0 || y >= maxy) {
-	        continue;
-	      }
-	      intensity = (intensityFunc(data[idx]) - minIntensity) / rangeIntensity;
-	      if (intensity <= 0) {
-	        continue;
-	      }
-	      if (intensity > 1) {
-	        intensity = 1;
-	      }
-	      /* bins is an array of arrays.  The subarrays would be conceptually
-	       * better represented as an array of dicts, but having a sparse array is
-	       * uses much less memory and is faster.  Each bin uses four array entries
-	       * that are (weight, intensity, x, y).  The weight is the sum of the
-	       * intensities for all points in the bin.  The intensity is the geometric
-	       * sum of the intensities to approximate what happens to the unbinned
-	       * data on the alpha channel of the canvas.  The x and y coordinates are
-	       * weighted by the intensity of each point. */
-	      bins[y] = bins[y] || [];
-	      x *= 4;
-	      binrow = bins[y];
-	      if (!binrow[x]) {
-	        binrow[x] = binrow[x + 1] = intensity;
-	        binrow[x + 2] = pos.x * intensity;
-	        binrow[x + 3] = pos.y * intensity;
-	      } else {
-	        binrow[x] += intensity;  // weight
-	        binrow[x + 1] += (1 - binrow[x + 1]) * intensity;
-	        binrow[x + 2] += pos.x * intensity;
-	        binrow[x + 3] += pos.y * intensity;
-	      }
-	    }
-	    /* For each bin, render a point on the canvas. */
-	    for (y = bins.length - 1; y >= 0; y -= 1) {
-	      binrow = bins[y];
-	      if (binrow) {
-	        for (x = binrow.length - 4; x >= 0; x -= 4) {
-	          if (binrow[x]) {
-	            intensity = binrow[x + 1];
-	            context2d.globalAlpha = intensity < 0.01 ? 0.01 : (intensity > 1 ? 1 : intensity);
-	            /* The position is eighted by the intensities, so we have to divide
-	             * it to get the necessary position */
-	            context2d.drawImage(
-	              m_this._circle,
-	              binrow[x + 2] / binrow[x] - radius,
-	              binrow[x + 3] / binrow[x] - radius);
-	          }
-	        }
-	      }
-	    }
-	  };
-
-	  /**
-	   * Render the data on the canvas, then colorize the resulting opacity map.
-	   * @protected
-	   * @param {object} context2d the canvas context to draw in.
-	   * @param {object} map the parent map object.
-	   */
-	  this._renderOnCanvas = function (context2d, map) {
-
-	    if (m_renderTime.getMTime() < m_this.buildTime().getMTime()) {
-	      var data = m_this.data() || [],
-	          radius = m_this.style('radius') + m_this.style('blurRadius'),
-	          binned = m_this.binned(),
-	          canvas, pixelArray,
-	          layer = m_this.layer(),
-	          viewport = map.camera()._viewport;
-
-	      /* Determine if we should bin the data */
-	      if (binned === true || binned === 'auto') {
-	        binned = Math.max(Math.floor(radius / 8), 3);
-	        if (m_this.binned() === 'auto') {
-	          var numbins = (Math.ceil((viewport.width + radius * 2) / binned) *
-	                         Math.ceil((viewport.height + radius * 2) / binned));
-	          if (numbins >= data.length) {
-	            binned = 0;
-	          }
-	        }
-	      }
-	      if (binned < 1 || isNaN(binned)) {
-	        binned = false;
-	      }
-	      /* Store what we did, in case this is ever useful elsewhere */
-	      m_this._binned = binned;
-
-	      context2d.setTransform(1, 0, 0, 1, 0, 0);
-	      context2d.clearRect(0, 0, viewport.width, viewport.height);
-	      m_heatMapTransform = '';
-	      map.scheduleAnimationFrame(m_this._setTransform, false);
-	      layer.canvas().css({transform: ''});
-
-	      m_this._createCircle();
-	      m_this._computeGradient();
-	      if (!binned) {
-	        m_this._renderPoints(context2d, map, data, radius);
-	      } else {
-	        m_this._renderBinnedData(context2d, map, data, radius, binned);
-	      }
-	      canvas = layer.canvas()[0];
-	      pixelArray = context2d.getImageData(0, 0, canvas.width, canvas.height);
-	      m_this._colorize(pixelArray.data, m_this._grad);
-	      context2d.putImageData(pixelArray, 0, 0);
-
-	      m_heatMapPosition = {
-	        zoom: map.zoom(),
-	        gcsOrigin: map.displayToGcs({x: 0, y: 0}, null),
-	        rotation: map.rotation(),
-	        lastScale: undefined,
-	        lastOrigin: {x: 0, y: 0},
-	        lastRotation: undefined
-	      };
-	      m_renderTime.modified();
-	      layer.renderer().clearCanvas(false);
-	    }
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Initialize
-	   * @protected
-	   */
-	  this._init = function () {
-	    s_init.call(m_this, arg);
-
-	    m_this.geoOn(geo_event.pan, m_this._animatePan);
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Update
-	   * @protected
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
-	        m_this.updateTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-	    m_this.updateTime().modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Update the css transform for the layer as part of an animation frame.
-	   * @protected
-	   */
-	  this._setTransform = function () {
-	    if (m_this.layer() && m_this.layer().canvas() && m_this.layer().canvas()[0]) {
-	      m_this.layer().canvas()[0].style.transform = m_heatMapTransform;
-	    }
-	  };
-
-	  /**
-	   * Animate pan (and zoom)
-	   * @protected
-	   */
-	  this._animatePan = function (e) {
-
-	    if (!m_heatMapPosition) {
-	      return;
-	    }
-	    var map = m_this.layer().map(),
-	        zoom = map.zoom(),
-	        scale = Math.pow(2, (zoom - m_heatMapPosition.zoom)),
-	        origin = map.gcsToDisplay(m_heatMapPosition.gcsOrigin, null),
-	        rotation = map.rotation();
-
-	    if (m_heatMapPosition.lastScale === scale &&
-	        m_heatMapPosition.lastOrigin.x === origin.x &&
-	        m_heatMapPosition.lastOrigin.y === origin.y &&
-	        m_heatMapPosition.lastRotation === rotation) {
-	      return;
-	    }
-
-	    var transform = '' +
-	        ' translate(' + origin.x + 'px' + ',' + origin.y + 'px' + ')' +
-	        ' scale(' + scale + ')' +
-	        ' rotate(' + ((rotation - m_heatMapPosition.rotation) * 180 / Math.PI) + 'deg)';
-
-	    map.scheduleAnimationFrame(m_this._setTransform);
-
-	    m_heatMapTransform = transform;
-	    m_heatMapPosition.lastScale = scale;
-	    m_heatMapPosition.lastOrigin.x = origin.x;
-	    m_heatMapPosition.lastOrigin.y = origin.y;
-	    m_heatMapPosition.lastRotation = rotation;
-
-	    if (m_heatMapPosition.timeout) {
-	      window.clearTimeout(m_heatMapPosition.timeout);
-	      m_heatMapPosition.timeout = undefined;
-	    }
-	    /* This conditional can change if we compute the heatmap beyond the visable
-	     * viewport so that we don't have to update on pans as often.  If we are
-	     * close to where the heatmap was originally computed, don't bother
-	     * updating it. */
-	    if (parseFloat(scale.toFixed(4)) !== 1 ||
-	        parseFloat((rotation - m_heatMapPosition.rotation).toFixed(4)) !== 0 ||
-	        parseFloat(origin.x.toFixed(1)) !== 0 ||
-	        parseFloat(origin.y.toFixed(1)) !== 0) {
-	      m_heatMapPosition.timeout = window.setTimeout(function () {
-	        m_heatMapPosition.timeout = undefined;
-	        m_this.buildTime().modified();
-	        m_this.layer().draw();
-	      }, m_this.updateDelay());
-	    }
-	  };
-
-	  /**
-	   * Destroy
-	   * @protected
-	   */
-	  this._exit = function () {
-	    s_exit.call(m_this);
-	  };
-
-	  m_this._init(arg);
-	  return this;
-	};
-
-	inherit(canvas_heatmapFeature, heatmapFeature);
-
-	// Now register it
-	registerFeature('canvas', 'heatmap', canvas_heatmapFeature);
-	module.exports = canvas_heatmapFeature;
-
-
-/***/ }),
-/* 272 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-
-	/**
-	 * Canvas specific subclass of object which rerenders when the object is drawn.
-	 * @class
-	 * @alias geo.canvas.object
-	 * @extends geo.sceneObject
-	 * @param {object} arg Options for the object.
-	 * @returns {geo.canvas.object}
-	 */
-	var canvas_object = function (arg) {
-	  'use strict';
-
-	  var object = __webpack_require__(203);
-
-	  // this is used to extend other geojs classes, so only generate
-	  // a new object when that is not the case... like if this === window
-	  if (!(this instanceof object)) {
-	    return new canvas_object(arg);
-	  }
-	  sceneObject.call(this);
-
-	  var m_this = this,
-	      s_draw = this.draw,
-	      m_canvasProperties = {};
-
-	  /**
-	   * This must be overridden by any feature that needs to render.
-	   */
-	  this._renderOnCanvas = function () {
-	  };
-
-	  /**
-	   * If this returns true, the render will be skipped and tried again on the
-	   * next animation frame.
-	   *
-	   * @returns {boolean} Truthy to delay rendering.
-	   */
-	  this._delayRender = function () {
-	    return false;
-	  };
-
-	  /**
-	   * Check if a property has already been set on a canvas's context.  If so,
-	   * don't set it again.  Some browsers are much slower if the properties are
-	   * set, even if no change is made.
-	   *
-	   * @param {CanvasRenderingContext2D} [context] The canvas context to modify.
-	   *    If `undefined`, clear the internal property buffer.
-	   * @param {string} [key] The property to set on the canvas.
-	   * @param {object} [value] The value for the property.
-	   * @returns {this} The current object.
-	   */
-	  this._canvasProperty = function (context, key, value) {
-	    if (!context || !key) {
-	      m_canvasProperties = {};
-	      return m_this;
-	    }
-	    if (m_canvasProperties[key] !== value) {
-	      m_canvasProperties[key] = value;
-	      context[key] = value;
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * Redraw the object.
-	   *
-	   * @returns {this} The current object.
-	   */
-	  this.draw = function () {
-	    m_this._update();
-	    m_this.renderer()._render();
-	    s_draw();
-	    return m_this;
-	  };
-
-	  return this;
-	};
-
-	inherit(canvas_object, sceneObject);
-	module.exports = canvas_object;
-
-
-/***/ }),
-/* 273 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var lineFeature = __webpack_require__(206);
-
-	/**
-	 * Create a new instance of class lineFeature.
-	 *
-	 * @class geo.canvas.lineFeature
-	 * @extends geo.lineFeature
-	 * @param {geo.lineFeature.spec} arg
-	 * @returns {geo.canvas.lineFeature}
-	 */
-	var canvas_lineFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof canvas_lineFeature)) {
-	    return new canvas_lineFeature(arg);
-	  }
-
-	  var object = __webpack_require__(272);
-
-	  arg = arg || {};
-	  lineFeature.call(this, arg);
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this;
-
-	  /**
-	   * Render the data on the canvas.
-	   *
-	   * @protected
-	   * @param {object} context2d the canvas context to draw in.
-	   * @param {geo.map} map the parent map object.
-	   */
-	  this._renderOnCanvas = function (context2d, map) {
-	    var data = m_this.data(),
-	        posFunc = m_this.position(),
-	        lineFunc = m_this.line(),
-	        strokeWidthFunc = m_this.style.get('strokeWidth'),
-	        strokeColorFunc = m_this.style.get('strokeColor'),
-	        strokeOpacityFunc = m_this.style.get('strokeOpacity'),
-	        lineCapFunc = m_this.style.get('lineCap'),
-	        lineJoinFunc = m_this.style.get('lineJoin'),
-	        miterLimit = m_this.style.get('miterLimit')(data),
-	        closedFunc = m_this.style.get('closed'),
-	        last = {}, cur = {}, temp, line, pos, firstPos, j;
-
-	    data.forEach(function (d, i) {
-	      line = lineFunc(d, i);
-	      if (line.length < 2) {
-	        return;
-	      }
-	      cur.closed = closedFunc(d, i);
-	      cur.width = strokeWidthFunc(line[0], 0, d, i);
-	      cur.color = strokeColorFunc(line[0], 0, d, i);
-	      cur.opacity = strokeOpacityFunc(line[0], 0, d, i);
-	      cur.linecap = lineCapFunc(line[0], 0, d, i);
-	      cur.linejoin = lineJoinFunc(line[0], 0, d, i);
-	      cur.strokeStyle = 'rgba(' + (cur.color.g * 255) + ', ' +
-	          (cur.color.g * 255) + ', ' + (cur.color.b * 255) + ', ' +
-	          (cur.opacity !== undefined ? cur.opacity : 1) + ')';
-	      if (last.strokeStyle !== cur.strokeStyle || last.width !== cur.width ||
-	          last.linecap !== cur.linecap || last.linejoin !== cur.linejoin ||
-	          last.miterlimit !== cur.miterlimit) {
-	        if (last.strokeStyle !== undefined) {
-	          context2d.stroke();
-	        }
-	        context2d.beginPath();
-	        context2d.strokeStyle = cur.strokeStyle;
-	        context2d.lineWidth = cur.width;
-	        context2d.lineCap = cur.linecap;
-	        context2d.lineJoin = cur.linejoin;
-	        context2d.miterLimit = miterLimit;
-	      }
-	      for (j = 0; j < line.length; j += 1) {
-	        pos = m_this.featureGcsToDisplay(posFunc(line[j], j, d, i));
-	        if (!j) {
-	          firstPos = pos;
-	          context2d.moveTo(pos.x, pos.y);
-	        } else {
-	          context2d.lineTo(pos.x, pos.y);
-	        }
-	      }
-	      if (cur.closed) {
-	        context2d.lineTo(firstPos.x, firstPos.y);
-	      }
-	      temp = last;
-	      last = cur;
-	      cur = temp;
-	    });
-	    if (last.strokeStyle !== undefined) {
-	      context2d.stroke();
-	    }
-	  };
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(canvas_lineFeature, lineFeature);
-
-	// Now register it
-	var capabilities = {};
-	capabilities[lineFeature.capabilities.basic] = true;
-	capabilities[lineFeature.capabilities.multicolor] = false;
-
-	registerFeature('canvas', 'line', canvas_lineFeature, capabilities);
-
-	module.exports = canvas_lineFeature;
-
-
-/***/ }),
-/* 274 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var pixelmapFeature = __webpack_require__(246);
-
-	/**
-	 * Create a new instance of class pixelmapFeature
-	 *
-	 * @class geo.canvas.pixelmapFeature
-	 * @param {Object} arg Options object
-	 * @extends geo.pixelmapFeature
-	 * @returns {canvas_pixelmapFeature}
-	 */
-	var canvas_pixelmapFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof canvas_pixelmapFeature)) {
-	    return new canvas_pixelmapFeature(arg);
-	  }
-	  pixelmapFeature.call(this, arg);
-
-	  var object = __webpack_require__(272);
-	  object.call(this);
-
-	  this._init(arg);
-	  return this;
-	};
-
-	inherit(canvas_pixelmapFeature, pixelmapFeature);
-
-	// Now register it
-	registerFeature('canvas', 'pixelmap', canvas_pixelmapFeature);
-	module.exports = canvas_pixelmapFeature;
-
-
-/***/ }),
-/* 275 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var quadFeature = __webpack_require__(223);
-	var util = __webpack_require__(83);
-
-	/**
-	 * Create a new instance of class quadFeature.
-	 *
-	 * @class geo.canvas.quadFeature
-	 * @param {geo.quadFeature.spec} arg Options object.
-	 * @extends geo.quadFeature
-	 * @returns {geo.canvas.quadFeature}
-	 */
-	var canvas_quadFeature = function (arg) {
-	  'use strict';
-
-	  if (!(this instanceof canvas_quadFeature)) {
-	    return new canvas_quadFeature(arg);
-	  }
-	  quadFeature.call(this, arg);
-
-	  var object = __webpack_require__(272);
-	  object.call(this);
-
-	  var $ = __webpack_require__(1);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      s_init = this._init,
-	      s_update = this._update,
-	      m_quads;
-
-	  /**
-	   * Build this feature.
-	   */
-	  this._build = function () {
-	    if (!m_this.position()) {
-	      return;
-	    }
-	    m_quads = this._generateQuads();
-
-	    if (m_quads.imgQuads) {
-	      m_quads.imgQuads.sort(function (a, b) {
-	        return a.pos[2] - b.pos[2];
-	      });
-	    }
-	    m_this.buildTime().modified();
-	  };
-
-	  /**
-	   * When any quad may have changed, ask for a animation frame callback so we
-	   * can update the quad on the next animation cycle.
-	   *
-	   * This is called when a video qaud may have changed play state.
-	   * @param {object} quad The quad record that triggered this.
-	   * @param {jQuery.Event} [evt] The event that triggered this.
-	   */
-	  this._checkQuadUpdate = function (quad, evt) {
-	    m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
-	  };
-
-	  /**
-	   * Check if any video quads are changing or need rerendering.  If any are
-	   * changing (because they are seeking), defer rendering and check again.  If
-	   * any need rendering, schedule it.
-	   */
-	  this._checkIfChanged = function () {
-	    if (!m_quads || !m_quads.vidQuads || !m_quads.vidQuads.length) {
-	      return;
-	    }
-	    var render = false, changing = false;
-
-	    $.each(m_quads.vidQuads, function (idx, quad) {
-	      if (quad.video && quad.video.HAVE_CURRENT_DATA !== undefined) {
-	        if (!quad.video.seeking && quad.video.readyState >= quad.video.HAVE_CURRENT_DATA) {
-	          render = true;
-	        }
-	        if (!quad.video.paused || quad.video.seeking) {
-	          changing = true;
-	        }
-	      }
-	    });
-	    if (render) {
-	      m_this.renderer()._render();
-	    }
-	    if (changing) {
-	      m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
-	    }
-	  };
-
-	  /**
-	   * Render all of the color quads.
-	   *
-	   * @param {CanvasRenderingContext2D} context2d The rendering context.
-	   * @param {geo.map} map The current renderer's parent map.
-	   */
-	  this._renderColorQuads = function (context2d, map) {
-	    if (!m_quads.clrQuads || !m_quads.clrQuads.length) {
-	      return;
-	    }
-	    var oldAlpha = context2d.globalAlpha;
-	    var opacity = oldAlpha;
-	    $.each(m_quads.clrQuads, function (idx, quad) {
-	      var p0 = map.gcsToDisplay({x: quad.pos[0], y: quad.pos[1]}, null),
-	          p1 = map.gcsToDisplay({x: quad.pos[3], y: quad.pos[4]}, null),
-	          p2 = map.gcsToDisplay({x: quad.pos[6], y: quad.pos[7]}, null),
-	          p3 = map.gcsToDisplay({x: quad.pos[9], y: quad.pos[10]}, null);
-	      if (quad.opacity !== opacity) {
-	        opacity = quad.opacity;
-	        context2d.globalAlpha = opacity;
-	      }
-	      context2d.fillStyle = util.convertColorToHex(quad.color, true);
-	      context2d.beginPath();
-	      context2d.moveTo(p0.x, p0.y);
-	      context2d.lineTo(p1.x, p1.y);
-	      context2d.lineTo(p3.x, p3.y);
-	      context2d.lineTo(p2.x, p2.y);
-	      context2d.closePath();
-	      context2d.fill();
-	    });
-	    if (opacity !== oldAlpha) {
-	      context2d.globalAlpha = oldAlpha;
-	    }
-	  };
-
-	  /**
-	   * Render all of the image and video quads.
-	   *
-	   * @param {CanvasRenderingContext2D} context2d The rendering context.
-	   * @param {geo.map} map The current renderer's parent map.
-	   */
-	  this._renderImageAndVideoQuads = function (context2d, map) {
-	    if ((!m_quads.imgQuads || !m_quads.imgQuads.length) &&
-	        (!m_quads.vidQuads || !m_quads.vidQuads.length)) {
-	      return;
-	    }
-
-	    var oldAlpha = context2d.globalAlpha;
-	    var opacity = oldAlpha;
-	    $.each([m_quads.imgQuads, m_quads.vidQuads], function (listidx, quadlist) {
-	      if (!quadlist) {
-	        return;
-	      }
-	      $.each(quadlist, function (idx, quad) {
-	        var src, w, h;
-	        if (quad.image) {
-	          src = quad.image;
-	          w = src.width;
-	          h = src.height;
-	        } else if (quad.video) {
-	          src = quad.video;
-	          w = src.videoWidth;
-	          h = src.videoHeight;
-	          if (src.seeking) {
-	            return;
-	          }
-	        }
-	        if (!src || !w || !h || quad.opacity <= 0) {
-	          return;
-	        }
-	        // Canvas transform is affine, so quad has to be a parallelogram
-	        // Also, canvas has no way to render z.
-	        var p0 = map.gcsToDisplay({x: quad.pos[0], y: quad.pos[1]}, null),
-	            p2 = map.gcsToDisplay({x: quad.pos[6], y: quad.pos[7]}, null),
-	            p3 = map.gcsToDisplay({x: quad.pos[9], y: quad.pos[10]}, null);
-	        context2d.setTransform((p3.x - p2.x) / w, (p3.y - p2.y) / w,
-	                               (p0.x - p2.x) / h, (p0.y - p2.y) / h,
-	                               p2.x, p2.y);
-	        if (quad.opacity !== opacity) {
-	          opacity = quad.opacity;
-	          context2d.globalAlpha = opacity;
-	        }
-	        if (!quad.crop) {
-	          context2d.drawImage(src, 0, 0);
-	        } else {
-	          context2d.drawImage(src, 0, 0, quad.crop.x, quad.crop.y, 0, 0,
-	                              quad.crop.x, quad.crop.y);
-	        }
-	      });
-	    });
-	    if (opacity !== oldAlpha) {
-	      context2d.globalAlpha = oldAlpha;
-	    }
-	    context2d.setTransform(1, 0, 0, 1, 0, 0);
-	  };
-
-	  /**
-	   * If this returns true, the render will be skipped and tried again on the
-	   * next animation frame.
-	   *
-	   * @returns {boolean} Truthy to delay rendering.
-	   */
-	  this._delayRender = function () {
-	    var delay = false;
-	    if (m_quads && m_quads.vidQuads && m_quads.vidQuads.length) {
-	      $.each(m_quads.vidQuads, function (idx, quad) {
-	        if (quad.video && quad.video.HAVE_CURRENT_DATA !== undefined) {
-	          delay |= (quad.video.seeking && quad.delayRenderWhenSeeking);
-	        }
-	      });
-	    }
-	    return delay;
-	  };
-
-	  /**
-	   * Render all of the quads.
-	   *
-	   * @param {CanvasRenderingContext2D} context The rendering context.
-	   * @param {geo.map} map The current renderer's parent map.
-	   */
-	  this._renderOnCanvas = function (context, map) {
-	    if (m_quads) {
-	      this._renderImageAndVideoQuads(context, map);
-	      this._renderColorQuads(context, map);
-	    }
-	  };
-
-	  /**
-	   * Update.
-	   */
-	  this._update = function () {
-	    s_update.call(m_this);
-	    if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
-	        m_this.updateTime().getMTime() < m_this.getMTime()) {
-	      m_this._build();
-	    }
-
-	    m_this.updateTime().modified();
-	    m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
-	  };
-
-	  /**
-	   * Initialize.
-	   */
-	  this._init = function () {
-	    s_init.call(m_this, arg);
-	  };
-
-	  /**
-	   * Destroy.
-	   */
-	  this._exit = function () {
-
-	    s_exit.call(m_this);
-	  };
-
-	  m_this._init(arg);
-	  return this;
-	};
-
-	inherit(canvas_quadFeature, quadFeature);
-
-	// Now register it
-	var capabilities = {};
-	capabilities[quadFeature.capabilities.color] = true;
-	capabilities[quadFeature.capabilities.image] = true;
-	capabilities[quadFeature.capabilities.imageCrop] = true;
-	capabilities[quadFeature.capabilities.imageFixedScale] = true;
-	capabilities[quadFeature.capabilities.imageFull] = false;
-	capabilities[quadFeature.capabilities.canvas] = true;
-	capabilities[quadFeature.capabilities.video] = true;
-
-	registerFeature('canvas', 'quad', canvas_quadFeature, capabilities);
-	module.exports = canvas_quadFeature;
-
-
-/***/ }),
-/* 276 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var registerFeature = __webpack_require__(201).registerFeature;
-	var textFeature = __webpack_require__(218);
-	var util = __webpack_require__(83);
-	var mat3 = __webpack_require__(277);
-	var vec3 = __webpack_require__(138);
-
-	/**
-	 * Create a new instance of class canvas.textFeature.
-	 *
-	 * @class
-	 * @alias geo.canvas.textFeature
-	 * @extends geo.textFeature
-	 * @extends geo.canvas.object
-	 *
-	 * @param {geo.textFeature.spec} [arg] Options for the feature.
-	 * @returns {geo.canvas.textFeature} The created feature.
-	 */
-	var canvas_textFeature = function (arg) {
-	  'use strict';
-	  if (!(this instanceof canvas_textFeature)) {
-	    return new canvas_textFeature(arg);
-	  }
-
-	  var object = __webpack_require__(272);
-
-	  arg = arg || {};
-	  textFeature.call(this, arg);
-	  object.call(this);
-
-	  /**
-	   * @private
-	   */
-	  var m_this = this,
-	      m_defaultFont = 'bold 16px sans-serif',
-	      /* This regexp parses css font specifications into style, variant,
-	       * weight, stretch, size, line height, and family.  It is based on a
-	       * regexp here: https://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font,
-	       * but has been modified to fix some issues and handle font stretch. */
-	      m_cssFontRegExp = new RegExp(
-	        '^\\s*' +
-	        '(?=(?:(?:[-a-z0-9]+\\s+){0,3}(italic|oblique))?)' +
-	        '(?=(?:(?:[-a-z0-9]+\\s+){0,3}(small-caps))?)' +
-	        '(?=(?:(?:[-a-z0-9]+\\s+){0,3}(bold(?:er)?|lighter|[1-9]00))?)' +
-	        '(?=(?:(?:[-a-z0-9]+\\s+){0,3}((?:ultra-|extra-|semi-)?(?:condensed|expanded)))?)' +
-	        '(?:(?:normal|\\1|\\2|\\3|\\4)\\s+){0,4}' +
-	        '((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))' +
-	        '(?:/(normal|[.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])))?\\s+' +
-	        '([-,\\"\\sa-z]+?)\\s*$', 'i');
-
-	  /**
-	   * Get the font for a specific data item.  This falls back to the default
-	   * font if the value is unset or doesn't contain sufficient information.
-	   *
-	   * @param {boolean} useSubValues If truthy, check all font styles (such as
-	   *    `fontSize`, `lineHeight`, etc., and override the code `font` style
-	   *    with those values.  If falsy, only use `font`.
-	   * @param {object} d The current data element.
-	   * @param {number} i The index of the current data element.
-	   * @returns {string} The font style.
-	   */
-	  this.getFontFromStyles = function (useSubValues, d, i) {
-	    var font = m_this.style.get('font')(d, i) || m_defaultFont;
-	    if (useSubValues) {
-	      var parts = m_cssFontRegExp.exec(font);
-	      if (parts === null) {
-	        parts = m_cssFontRegExp.exec(m_defaultFont);
-	      }
-	      parts[1] = m_this.style.get('fontStyle')(d, i) || parts[1];
-	      parts[2] = m_this.style.get('fontVariant')(d, i) || parts[2];
-	      parts[3] = m_this.style.get('fontWeight')(d, i) || parts[3];
-	      parts[4] = m_this.style.get('fontStretch')(d, i) || parts[4];
-	      parts[5] = m_this.style.get('fontSize')(d, i) || parts[5] || '16px';
-	      parts[6] = m_this.style.get('lineHeight')(d, i) || parts[6];
-	      parts[7] = m_this.style.get('fontFamily')(d, i) || parts[7] || 'sans-serif';
-	      font = (parts[1] || '') + ' ' + (parts[2] || '') + ' ' +
-	             (parts[3] || '') + ' ' + (parts[4] || '') + ' ' +
-	             (parts[5] || '') + (parts[6] ? '/' + parts[6] : '') + ' ' +
-	             parts[7];
-	      font = font.trim().replace(/\s\s+/g, ' ');
-	    }
-	    return font;
-	  };
-
-	  /**
-	   * Render the data on the canvas.
-	   *
-	   * This does not currently support multiline text or word wrapping, since
-	   * canvas doesn't implement that directly.  To support these, each text item
-	   * would need to be split on line breaks, and have the width of the text
-	   * calculated with context2d.measureText to determine word wrapping.  This
-	   * would also need to calculate the effective line height from the font
-	   * specification.
-	   *
-	   * @protected
-	   * @param {CanvasRenderingContext2D} context2d The canvas context to draw in.
-	   * @param {geo.map} map The parent map object.
-	   */
-	  this._renderOnCanvas = function (context2d, map) {
-	    var data = m_this.data(),
-	        posFunc = m_this.style.get('position'),
-	        textFunc = m_this.style.get('text'),
-	        mapRotation = map.rotation(),
-	        mapZoom = map.zoom(),
-	        fontFromSubValues, text, pos, visible, color, blur, stroke, width,
-	        rotation, rotateWithMap, scale, offset,
-	        transform, lastTransform = util.mat3AsArray();
-
-	    /* If any of the font styles other than `font` have values, then we need to
-	     * construct a single font value from the subvalues.  Otherwise, we can
-	     * skip it. */
-	    fontFromSubValues = [
-	      'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize',
-	      'lineHeight', 'fontFamily'
-	    ].some(function (key) {
-	      return m_this.style(key) !== null && m_this.style(key) !== undefined;
-	    });
-	    /* Clear the canvas property buffer */
-	    m_this._canvasProperty();
-	    data.forEach(function (d, i) {
-	      visible = m_this.style.get('visible')(d, i);
-	      if (!visible && visible !== undefined) {
-	        return;
-	      }
-	      color = util.convertColorAndOpacity(
-	        m_this.style.get('color')(d, i), m_this.style.get('textOpacity')(d, i));
-	      stroke = util.convertColorAndOpacity(
-	        m_this.style.get('textStrokeColor')(d, i), m_this.style.get('textOpacity')(d, i), {r: 0, g: 0, b: 0, a: 0});
-	      if (color.a === 0 && stroke.a === 0) {
-	        return;
-	      }
-	      m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToRGBA(color));
-	      // TODO: get the position position without transform.  If it is outside
-	      // of the map to an extent that there is no chance of text showing,
-	      // skip further processing.
-	      pos = m_this.featureGcsToDisplay(posFunc(d, i));
-	      text = textFunc(d, i);
-	      m_this._canvasProperty(context2d, 'font', m_this.getFontFromStyles(fontFromSubValues, d, i));
-	      m_this._canvasProperty(context2d, 'textAlign', m_this.style.get('textAlign')(d, i) || 'center');
-	      m_this._canvasProperty(context2d, 'textBaseline', m_this.style.get('textBaseline')(d, i) || 'middle');
-	      /* rotation, scale, and offset */
-	      rotation = m_this.style.get('rotation')(d, i) || 0;
-	      rotateWithMap = m_this.style.get('rotateWithMap')(d, i) && mapRotation;
-	      scale = m_this.style.get('textScaled')(d, i);
-	      scale = util.isNonNullFinite(scale) ? Math.pow(2, mapZoom - scale) : null;
-	      offset = m_this.style.get('offset')(d, i);
-	      transform = util.mat3AsArray();
-	      if (rotation || rotateWithMap || (scale && scale !== 1) || (offset && (offset.x || offset.y))) {
-	        mat3.translate(transform, transform, [pos.x, pos.y]);
-	        if (rotateWithMap && mapRotation) {
-	          mat3.rotate(transform, transform, mapRotation);
-	        }
-	        mat3.translate(transform, transform, [
-	          offset && offset.x ? +offset.x : 0,
-	          offset && offset.y ? +offset.y : 0]);
-	        if (rotation) {
-	          mat3.rotate(transform, transform, rotation);
-	        }
-	        if (scale && scale !== 1) {
-	          mat3.scale(transform, transform, [scale, scale]);
-	        }
-	        mat3.translate(transform, transform, [-pos.x, -pos.y]);
-	      }
-	      if (lastTransform[0] !== transform[0] || lastTransform[1] !== transform[1] ||
-	          lastTransform[3] !== transform[3] || lastTransform[4] !== transform[4] ||
-	          lastTransform[6] !== transform[6] || lastTransform[7] !== transform[7]) {
-	        context2d.setTransform(transform[0], transform[1], transform[3], transform[4], transform[6], transform[7]);
-	        mat3.copy(lastTransform, transform);
-	      }
-	      /* shadow */
-	      color = util.convertColorAndOpacity(
-	        m_this.style.get('shadowColor')(d, i), undefined, {r: 0, g: 0, b: 0, a: 0});
-	      if (color.a) {
-	        offset = m_this.style.get('shadowOffset')(d, i);
-	        blur = m_this.style.get('shadowBlur')(d, i);
-	      }
-	      if (color.a && ((offset && (offset.x || offset.y)) || blur)) {
-	        m_this._canvasProperty(context2d, 'shadowColor', util.convertColorToRGBA(color));
-	        if (offset && (rotation || rotateWithMap) && m_this.style.get('shadowRotate')(d, i)) {
-	          transform = [+offset.x, +offset.y, 0];
-	          vec3.rotateZ(transform, transform, [0, 0, 0],
-	                       rotation + (rotateWithMap ? mapRotation : 0));
-	          offset = {x: transform[0], y: transform[1]};
-	        }
-	        m_this._canvasProperty(context2d, 'shadowOffsetX', offset && offset.x ? +offset.x : 0);
-	        m_this._canvasProperty(context2d, 'shadowOffsetY', offset && offset.y ? +offset.y : 0);
-	        m_this._canvasProperty(context2d, 'shadowBlur', blur || 0);
-	      } else {
-	        m_this._canvasProperty(context2d, 'shadowColor', 'rgba(0,0,0,0)');
-	      }
-	      /* draw the text */
-	      if (stroke.a) {
-	        width = m_this.style.get('textStrokeWidth')(d, i);
-	        if (isFinite(width) && width > 0) {
-	          m_this._canvasProperty(context2d, 'strokeStyle', util.convertColorToRGBA(stroke));
-	          m_this._canvasProperty(context2d, 'lineWidth', width);
-	          context2d.strokeText(text, pos.x, pos.y);
-	          m_this._canvasProperty(context2d, 'shadowColor', 'rgba(0,0,0,0)');
-	        }
-	      }
-	      context2d.fillText(text, pos.x, pos.y);
-	    });
-	    m_this._canvasProperty(context2d, 'globalAlpha', 1);
-	    context2d.setTransform(1, 0, 0, 1, 0, 0);
-	  };
-
-	  return this;
-	};
-
-	inherit(canvas_textFeature, textFeature);
-
-	// Now register it
-	var capabilities = {};
-
-	registerFeature('canvas', 'text', canvas_textFeature, capabilities);
-
-	module.exports = canvas_textFeature;
-
-
-/***/ }),
-/* 277 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	module.exports = {
-	  adjoint: __webpack_require__(278)
-	  , clone: __webpack_require__(279)
-	  , copy: __webpack_require__(280)
-	  , create: __webpack_require__(281)
-	  , determinant: __webpack_require__(282)
-	  , frob: __webpack_require__(283)
-	  , fromMat2: __webpack_require__(284)
-	  , fromMat4: __webpack_require__(285)
-	  , fromQuat: __webpack_require__(286)
-	  , identity: __webpack_require__(287)
-	  , invert: __webpack_require__(288)
-	  , multiply: __webpack_require__(289)
-	  , normalFromMat4: __webpack_require__(290)
-	  , rotate: __webpack_require__(291)
-	  , scale: __webpack_require__(292)
-	  , str: __webpack_require__(293)
-	  , translate: __webpack_require__(294)
-	  , transpose: __webpack_require__(295)
-	}
-
-
-/***/ }),
-/* 278 */
-/***/ (function(module, exports) {
-
-	module.exports = adjoint
-
-	/**
-	 * Calculates the adjugate of a mat3
-	 *
-	 * @alias mat3.adjoint
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the source matrix
-	 * @returns {mat3} out
-	 */
-	function adjoint(out, a) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-
-	  out[0] = (a11 * a22 - a12 * a21)
-	  out[1] = (a02 * a21 - a01 * a22)
-	  out[2] = (a01 * a12 - a02 * a11)
-	  out[3] = (a12 * a20 - a10 * a22)
-	  out[4] = (a00 * a22 - a02 * a20)
-	  out[5] = (a02 * a10 - a00 * a12)
-	  out[6] = (a10 * a21 - a11 * a20)
-	  out[7] = (a01 * a20 - a00 * a21)
-	  out[8] = (a00 * a11 - a01 * a10)
-
-	  return out
-	}
-
-
-/***/ }),
-/* 279 */
-/***/ (function(module, exports) {
-
-	module.exports = clone
-
-	/**
-	 * Creates a new mat3 initialized with values from an existing matrix
-	 *
-	 * @alias mat3.clone
-	 * @param {mat3} a matrix to clone
-	 * @returns {mat3} a new 3x3 matrix
-	 */
-	function clone(a) {
-	  var out = new Float32Array(9)
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = a[2]
-	  out[3] = a[3]
-	  out[4] = a[4]
-	  out[5] = a[5]
-	  out[6] = a[6]
-	  out[7] = a[7]
-	  out[8] = a[8]
-	  return out
-	}
-
-
-/***/ }),
-/* 280 */
-/***/ (function(module, exports) {
-
-	module.exports = copy
-
-	/**
-	 * Copy the values from one mat3 to another
-	 *
-	 * @alias mat3.copy
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the source matrix
-	 * @returns {mat3} out
-	 */
-	function copy(out, a) {
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = a[2]
-	  out[3] = a[3]
-	  out[4] = a[4]
-	  out[5] = a[5]
-	  out[6] = a[6]
-	  out[7] = a[7]
-	  out[8] = a[8]
-	  return out
-	}
-
-
-/***/ }),
-/* 281 */
-/***/ (function(module, exports) {
-
-	module.exports = create
-
-	/**
-	 * Creates a new identity mat3
-	 *
-	 * @alias mat3.create
-	 * @returns {mat3} a new 3x3 matrix
-	 */
-	function create() {
-	  var out = new Float32Array(9)
-	  out[0] = 1
-	  out[1] = 0
-	  out[2] = 0
-	  out[3] = 0
-	  out[4] = 1
-	  out[5] = 0
-	  out[6] = 0
-	  out[7] = 0
-	  out[8] = 1
-	  return out
-	}
-
-
-/***/ }),
-/* 282 */
-/***/ (function(module, exports) {
-
-	module.exports = determinant
-
-	/**
-	 * Calculates the determinant of a mat3
-	 *
-	 * @alias mat3.determinant
-	 * @param {mat3} a the source matrix
-	 * @returns {Number} determinant of a
-	 */
-	function determinant(a) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-
-	  return a00 * (a22 * a11 - a12 * a21)
-	       + a01 * (a12 * a20 - a22 * a10)
-	       + a02 * (a21 * a10 - a11 * a20)
-	}
-
-
-/***/ }),
-/* 283 */
-/***/ (function(module, exports) {
-
-	module.exports = frob
-
-	/**
-	 * Returns Frobenius norm of a mat3
-	 *
-	 * @alias mat3.frob
-	 * @param {mat3} a the matrix to calculate Frobenius norm of
-	 * @returns {Number} Frobenius norm
-	 */
-	function frob(a) {
-	  return Math.sqrt(
-	      a[0]*a[0]
-	    + a[1]*a[1]
-	    + a[2]*a[2]
-	    + a[3]*a[3]
-	    + a[4]*a[4]
-	    + a[5]*a[5]
-	    + a[6]*a[6]
-	    + a[7]*a[7]
-	    + a[8]*a[8]
-	  )
-	}
-
-
-/***/ }),
-/* 284 */
-/***/ (function(module, exports) {
-
-	module.exports = fromMat2d
-
-	/**
-	 * Copies the values from a mat2d into a mat3
-	 *
-	 * @alias mat3.fromMat2d
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat2d} a the matrix to copy
-	 * @returns {mat3} out
-	 **/
-	function fromMat2d(out, a) {
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = 0
-
-	  out[3] = a[2]
-	  out[4] = a[3]
-	  out[5] = 0
-
-	  out[6] = a[4]
-	  out[7] = a[5]
-	  out[8] = 1
-
-	  return out
-	}
-
-
-/***/ }),
-/* 285 */
-/***/ (function(module, exports) {
-
-	module.exports = fromMat4
-
-	/**
-	 * Copies the upper-left 3x3 values into the given mat3.
-	 *
-	 * @alias mat3.fromMat4
-	 * @param {mat3} out the receiving 3x3 matrix
-	 * @param {mat4} a   the source 4x4 matrix
-	 * @returns {mat3} out
-	 */
-	function fromMat4(out, a) {
-	  out[0] = a[0]
-	  out[1] = a[1]
-	  out[2] = a[2]
-	  out[3] = a[4]
-	  out[4] = a[5]
-	  out[5] = a[6]
-	  out[6] = a[8]
-	  out[7] = a[9]
-	  out[8] = a[10]
-	  return out
-	}
-
-
-/***/ }),
-/* 286 */
-/***/ (function(module, exports) {
-
-	module.exports = fromQuat
-
-	/**
-	* Calculates a 3x3 matrix from the given quaternion
-	*
-	* @alias mat3.fromQuat
-	* @param {mat3} out mat3 receiving operation result
-	* @param {quat} q Quaternion to create matrix from
-	*
-	* @returns {mat3} out
-	*/
-	function fromQuat(out, q) {
-	  var x = q[0]
-	  var y = q[1]
-	  var z = q[2]
-	  var w = q[3]
-
-	  var x2 = x + x
-	  var y2 = y + y
-	  var z2 = z + z
-
-	  var xx = x * x2
-	  var yx = y * x2
-	  var yy = y * y2
-	  var zx = z * x2
-	  var zy = z * y2
-	  var zz = z * z2
-	  var wx = w * x2
-	  var wy = w * y2
-	  var wz = w * z2
-
-	  out[0] = 1 - yy - zz
-	  out[3] = yx - wz
-	  out[6] = zx + wy
-
-	  out[1] = yx + wz
-	  out[4] = 1 - xx - zz
-	  out[7] = zy - wx
-
-	  out[2] = zx - wy
-	  out[5] = zy + wx
-	  out[8] = 1 - xx - yy
-
-	  return out
-	}
-
-
-/***/ }),
-/* 287 */
-/***/ (function(module, exports) {
-
-	module.exports = identity
-
-	/**
-	 * Set a mat3 to the identity matrix
-	 *
-	 * @alias mat3.identity
-	 * @param {mat3} out the receiving matrix
-	 * @returns {mat3} out
-	 */
-	function identity(out) {
-	  out[0] = 1
-	  out[1] = 0
-	  out[2] = 0
-	  out[3] = 0
-	  out[4] = 1
-	  out[5] = 0
-	  out[6] = 0
-	  out[7] = 0
-	  out[8] = 1
-	  return out
-	}
-
-
-/***/ }),
-/* 288 */
-/***/ (function(module, exports) {
-
-	module.exports = invert
-
-	/**
-	 * Inverts a mat3
-	 *
-	 * @alias mat3.invert
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the source matrix
-	 * @returns {mat3} out
-	 */
-	function invert(out, a) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-
-	  var b01 = a22 * a11 - a12 * a21
-	  var b11 = -a22 * a10 + a12 * a20
-	  var b21 = a21 * a10 - a11 * a20
-
-	  // Calculate the determinant
-	  var det = a00 * b01 + a01 * b11 + a02 * b21
-
-	  if (!det) return null
-	  det = 1.0 / det
-
-	  out[0] = b01 * det
-	  out[1] = (-a22 * a01 + a02 * a21) * det
-	  out[2] = (a12 * a01 - a02 * a11) * det
-	  out[3] = b11 * det
-	  out[4] = (a22 * a00 - a02 * a20) * det
-	  out[5] = (-a12 * a00 + a02 * a10) * det
-	  out[6] = b21 * det
-	  out[7] = (-a21 * a00 + a01 * a20) * det
-	  out[8] = (a11 * a00 - a01 * a10) * det
-
-	  return out
-	}
-
-
-/***/ }),
-/* 289 */
-/***/ (function(module, exports) {
-
-	module.exports = multiply
-
-	/**
-	 * Multiplies two mat3's
-	 *
-	 * @alias mat3.multiply
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the first operand
-	 * @param {mat3} b the second operand
-	 * @returns {mat3} out
-	 */
-	function multiply(out, a, b) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-
-	  var b00 = b[0], b01 = b[1], b02 = b[2]
-	  var b10 = b[3], b11 = b[4], b12 = b[5]
-	  var b20 = b[6], b21 = b[7], b22 = b[8]
-
-	  out[0] = b00 * a00 + b01 * a10 + b02 * a20
-	  out[1] = b00 * a01 + b01 * a11 + b02 * a21
-	  out[2] = b00 * a02 + b01 * a12 + b02 * a22
-
-	  out[3] = b10 * a00 + b11 * a10 + b12 * a20
-	  out[4] = b10 * a01 + b11 * a11 + b12 * a21
-	  out[5] = b10 * a02 + b11 * a12 + b12 * a22
-
-	  out[6] = b20 * a00 + b21 * a10 + b22 * a20
-	  out[7] = b20 * a01 + b21 * a11 + b22 * a21
-	  out[8] = b20 * a02 + b21 * a12 + b22 * a22
-
-	  return out
-	}
-
-
-/***/ }),
-/* 290 */
-/***/ (function(module, exports) {
-
-	module.exports = normalFromMat4
-
-	/**
-	* Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
-	*
-	* @alias mat3.normalFromMat4
-	* @param {mat3} out mat3 receiving operation result
-	* @param {mat4} a Mat4 to derive the normal matrix from
-	*
-	* @returns {mat3} out
-	*/
-	function normalFromMat4(out, a) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
-	  var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]
-	  var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]
-	  var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]
-
-	  var b00 = a00 * a11 - a01 * a10
-	  var b01 = a00 * a12 - a02 * a10
-	  var b02 = a00 * a13 - a03 * a10
-	  var b03 = a01 * a12 - a02 * a11
-	  var b04 = a01 * a13 - a03 * a11
-	  var b05 = a02 * a13 - a03 * a12
-	  var b06 = a20 * a31 - a21 * a30
-	  var b07 = a20 * a32 - a22 * a30
-	  var b08 = a20 * a33 - a23 * a30
-	  var b09 = a21 * a32 - a22 * a31
-	  var b10 = a21 * a33 - a23 * a31
-	  var b11 = a22 * a33 - a23 * a32
-
-	  // Calculate the determinant
-	  var det = b00 * b11
-	          - b01 * b10
-	          + b02 * b09
-	          + b03 * b08
-	          - b04 * b07
-	          + b05 * b06
-
-	  if (!det) return null
-	  det = 1.0 / det
-
-	  out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det
-	  out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det
-	  out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det
-
-	  out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det
-	  out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det
-	  out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det
-
-	  out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det
-	  out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det
-	  out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det
-
-	  return out
-	}
-
-
-/***/ }),
-/* 291 */
-/***/ (function(module, exports) {
-
-	module.exports = rotate
-
-	/**
-	 * Rotates a mat3 by the given angle
-	 *
-	 * @alias mat3.rotate
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the matrix to rotate
-	 * @param {Number} rad the angle to rotate the matrix by
-	 * @returns {mat3} out
-	 */
-	function rotate(out, a, rad) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-
-	  var s = Math.sin(rad)
-	  var c = Math.cos(rad)
-
-	  out[0] = c * a00 + s * a10
-	  out[1] = c * a01 + s * a11
-	  out[2] = c * a02 + s * a12
-
-	  out[3] = c * a10 - s * a00
-	  out[4] = c * a11 - s * a01
-	  out[5] = c * a12 - s * a02
-
-	  out[6] = a20
-	  out[7] = a21
-	  out[8] = a22
-
-	  return out
-	}
-
-
-/***/ }),
-/* 292 */
-/***/ (function(module, exports) {
-
-	module.exports = scale
-
-	/**
-	 * Scales the mat3 by the dimensions in the given vec2
-	 *
-	 * @alias mat3.scale
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the matrix to rotate
-	 * @param {vec2} v the vec2 to scale the matrix by
-	 * @returns {mat3} out
-	 **/
-	function scale(out, a, v) {
-	  var x = v[0]
-	  var y = v[1]
-
-	  out[0] = x * a[0]
-	  out[1] = x * a[1]
-	  out[2] = x * a[2]
-
-	  out[3] = y * a[3]
-	  out[4] = y * a[4]
-	  out[5] = y * a[5]
-
-	  out[6] = a[6]
-	  out[7] = a[7]
-	  out[8] = a[8]
-
-	  return out
-	}
-
-
-/***/ }),
-/* 293 */
-/***/ (function(module, exports) {
-
-	module.exports = str
-
-	/**
-	 * Returns a string representation of a mat3
-	 *
-	 * @alias mat3.str
-	 * @param {mat3} mat matrix to represent as a string
-	 * @returns {String} string representation of the matrix
-	 */
-	function str(a) {
-	  return 'mat3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' +
-	                   a[3] + ', ' + a[4] + ', ' + a[5] + ', ' +
-	                   a[6] + ', ' + a[7] + ', ' + a[8] + ')'
-	}
-
-
-/***/ }),
-/* 294 */
-/***/ (function(module, exports) {
-
-	module.exports = translate
-
-	/**
-	 * Translate a mat3 by the given vector
-	 *
-	 * @alias mat3.translate
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the matrix to translate
-	 * @param {vec2} v vector to translate by
-	 * @returns {mat3} out
-	 */
-	function translate(out, a, v) {
-	  var a00 = a[0], a01 = a[1], a02 = a[2]
-	  var a10 = a[3], a11 = a[4], a12 = a[5]
-	  var a20 = a[6], a21 = a[7], a22 = a[8]
-	  var x = v[0], y = v[1]
-
-	  out[0] = a00
-	  out[1] = a01
-	  out[2] = a02
-
-	  out[3] = a10
-	  out[4] = a11
-	  out[5] = a12
-
-	  out[6] = x * a00 + y * a10 + a20
-	  out[7] = x * a01 + y * a11 + a21
-	  out[8] = x * a02 + y * a12 + a22
-
-	  return out
-	}
-
-
-/***/ }),
-/* 295 */
-/***/ (function(module, exports) {
-
-	module.exports = transpose
-
-	/**
-	 * Transpose the values of a mat3
-	 *
-	 * @alias mat3.transpose
-	 * @param {mat3} out the receiving matrix
-	 * @param {mat3} a the source matrix
-	 * @returns {mat3} out
-	 */
-	function transpose(out, a) {
-	  // If we are transposing ourselves we can skip a few steps but have to cache some values
-	  if (out === a) {
-	    var a01 = a[1], a02 = a[2], a12 = a[5]
-	    out[1] = a[3]
-	    out[2] = a[6]
-	    out[3] = a01
-	    out[5] = a[7]
-	    out[6] = a02
-	    out[7] = a12
-	  } else {
-	    out[0] = a[0]
-	    out[1] = a[3]
-	    out[2] = a[6]
-	    out[3] = a[1]
-	    out[4] = a[4]
-	    out[5] = a[7]
-	    out[6] = a[2]
-	    out[7] = a[5]
-	    out[8] = a[8]
-	  }
-
-	  return out
-	}
-
-
-/***/ }),
-/* 296 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var registerLayerAdjustment = __webpack_require__(201).registerLayerAdjustment;
-
-	var canvas_tileLayer = function () {
-	  'use strict';
-	  var m_this = this,
-	      s_init = this._init,
-	      s_exit = this._exit,
-	      m_quadFeature,
-	      m_nextTileId = 0,
-	      m_tiles = [];
-
-	  /* Add a tile to the list of quads */
-	  this._drawTile = function (tile) {
-	    if (!m_quadFeature) {
-	      return;
-	    }
-	    var bounds = this._tileBounds(tile),
-	        level = tile.index.level || 0,
-	        to = this._tileOffset(level),
-	        crop = this.tileCropFromBounds(tile),
-	        quad = {};
-	    if (crop) {
-	      quad.crop = crop;
-	    }
-	    quad.ul = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.ll = this.fromLocal(this.fromLevel({
-	      x: bounds.left - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ur = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.top - to.y
-	    }, level), 0);
-	    quad.lr = this.fromLocal(this.fromLevel({
-	      x: bounds.right - to.x, y: bounds.bottom - to.y
-	    }, level), 0);
-	    quad.ul.z = quad.ll.z = quad.ur.z = quad.lr.z = level * 1e-5;
-	    m_nextTileId += 1;
-	    quad.id = m_nextTileId;
-	    tile.quadId = quad.id;
-	    quad.image = tile.image;
-	    m_tiles.push(quad);
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	    m_this.draw();
-	  };
-
-	  /* Remove the tile feature. */
-	  this._remove = function (tile) {
-	    if (tile.quadId !== undefined && m_quadFeature) {
-	      for (var i = 0; i < m_tiles.length; i += 1) {
-	        if (m_tiles[i].id === tile.quadId) {
-	          m_tiles.splice(i, 1);
-	          break;
-	        }
-	      }
-	      m_quadFeature.data(m_tiles);
-	      m_quadFeature._update();
-	      m_this.draw();
-	    }
-	  };
-
-	  /**
-	   * Clean up the layer.
-	   */
-	  this._exit = function () {
-	    m_this.deleteFeature(m_quadFeature);
-	    m_quadFeature = null;
-	    m_tiles = [];
-	    s_exit.apply(m_this, arguments);
-	  };
-
-	  /**
-	   * Initialize after the layer is added to the map.
-	   */
-	  this._init = function () {
-	    s_init.apply(m_this, arguments);
-	    m_quadFeature = this.createFeature('quad', {
-	      previewColor: m_this._options.previewColor,
-	      previewImage: m_this._options.previewImage
-	    });
-	    m_quadFeature.geoTrigger = undefined;
-	    m_quadFeature.gcs(m_this._options.gcs || m_this.map().gcs());
-	    m_quadFeature.data(m_tiles);
-	    m_quadFeature._update();
-	  };
-
-	  /* These functions don't need to do anything. */
-	  this._getSubLayer = function () {};
-	  this._updateSubLayers = undefined;
-	};
-
-	registerLayerAdjustment('canvas', 'tile', canvas_tileLayer);
-	module.exports = canvas_tileLayer;
-
-
-/***/ }),
-/* 297 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	/**
-	 * @namespace geo.gui
-	 */
-	module.exports = {
-	  colorLegendWidget: __webpack_require__(298),
-	  domWidget: __webpack_require__(299),
-	  legendWidget: __webpack_require__(303),
-	  scaleWidget: __webpack_require__(305),
-	  sliderWidget: __webpack_require__(308),
-	  svgWidget: __webpack_require__(304),
-	  uiLayer: __webpack_require__(241),
-	  widget: __webpack_require__(300)
-	};
-
-
-/***/ }),
-/* 298 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var d3 = __webpack_require__(226).d3;
-	var domWidget = __webpack_require__(299);
-	var inherit = __webpack_require__(8);
-	var registerWidget = __webpack_require__(201).registerWidget;
-	var util = __webpack_require__(83);
-	var uniqueID = __webpack_require__(228);
-
-	__webpack_require__(301);
-
-	/**
-	 * @typedef {object} geo.gui.colorLegendWidget.category
-	 * @property {string} name The text label of the legend.
-	 * @property {string} type The type of the legend, either discrete or continuous.
-	 * @property {string} scale The scale of the legend. For discrete type,
-	 * linear, log, sqrt, pow, ordinal, and quantile is supported.
-	 * For continuous type, linear, log, sqrt, and pow is supported.
-	 * @property {number[]|string[]} domain Only for ordinal scale legend, string
-	 * values are acceptable. For ordinal legend, the number in the domain array
-	 * should be the same number of colors. For quantile scale legend, the domain
-	 * should be an array of all values. For other scales, the domain needs to be
-	 * an array of two number for marking the upper bound and lower bound.
-	 * This domain property will be used with d3 scale object internally.
-	 * @property {geo.geoColor[]} colors The colors of the legend.
-	 * All valid svg color can be used. For discrete type, multiple values
-	 * are accepted. For continuous type, an array of two values is supported.
-	 * @property {number} [base] The base of log when log scale is used.
-	 * default to 10.
-	 * @property {number} [exponent] The exponent of power when power scale is used.
-	 * default to 1.
-	 */
-
-	/**
-	 * A UI widget that enables display discrete colors or two-color continuous
-	 *  transition legend.
-	 *
-	 * @class
-	 * @alias geo.gui.colorLegendWidget
-	 * @extends geo.gui.domWidget
-	 * @param {object} [arg] Widget options.
-	 * @param {geo.gui.widget.position} [arg.position] Position setting relatively to the map
-	 * container.
-	 * @param {geo.gui.colorLegendWidget.category[]} [arg.categories] An array
-	 * of category definitions for the initial color legends
-	 * @param {number} [arg.width=300] The width of the widget in pixels.
-	 * @param {number} [arg.ticks=6] The maximum number of ticks on the axis of a legend, default is 6.
-	 * @returns {geo.gui.colorLegendWidget}
-	 */
-	var colorLegendWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof colorLegendWidget)) {
-	    return new colorLegendWidget(arg);
-	  }
-
-	  domWidget.call(this, arg);
-
-	  var m_this = this,
-	      m_categories = [],
-	      m_width = arg.width || 300,
-	      m_ticks = arg.ticks || 6,
-	      s_init = this._init;
-	  // get the widget container ready
-	  this._init = function () {
-	    s_init();
-	    var canvas = m_this.canvas();
-	    d3.select(canvas)
-	      .attr('class', 'color-legend-container');
-
-	    m_this.popup = d3.select(canvas).append('div')
-	      .attr('class', 'color-legend-popup');
-
-	    if (arg.categories) {
-	      this.categories(arg.categories);
-	    }
-	  };
-
-	  /**
-	   * Clear the DOM container and create legends.
-	   */
-	  this._draw = function () {
-	    d3.select(m_this.canvas()).selectAll('div.legends').remove();
-
-	    if (!m_categories.length) {
-	      d3.select(m_this.canvas()).style('display', 'none');
-	      return;
-	    } else {
-	      d3.select(m_this.canvas()).style('display', 'block');
-	    }
-
-	    var container = d3.select(m_this.canvas())
-	      .append('div')
-	      .attr('class', 'legends');
-
-	    var width = m_width;
-	    var margin = 20;
-
-	    m_categories.forEach(function (category, index) {
-	      var legendContainer = container
-	        .append('div')
-	        .attr('class', 'legend');
-
-	      legendContainer
-	        .append('div')
-	        .attr('class', 'title')
-	        .text(category.name);
-
-	      var legendSvg = legendContainer
-	        .append('svg')
-	        .attr({
-	          'class': 'svg',
-	          'width': width,
-	          'height': '40px',
-	          'viewBox': -margin + ' 0 ' + width + ' 40'
-	        });
-
-	      if (category.type === 'discrete') {
-	        m_this._drawDiscrete(legendSvg, width - 2 * margin, category);
-	      } else if (category.type === 'continuous') {
-	        m_this._drawContinous(legendSvg, width - 2 * margin, category);
-	      }
-	    });
-
-	  };
-
-	  /**
-	   * Set or get categories.
-	   * @param {geo.gui.colorLegendWidget.category[]} [categories] If `undefined`,
-	   * return the current legend categories array. If an array is provided,
-	   * remove current legends and recreate with the new categories.
-	   * @returns {geo.gui.colorLegendWidget.category[]|this}
-	   * The current list of categories or the current class instance.
-	   */
-	  this.categories = function (categories) {
-	    if (categories === undefined) {
-	      return m_categories;
-	    }
-	    m_categories = this._prepareCategories(categories);
-	    this._draw();
-	    return this;
-	  };
-
-	  /**
-	   * Add additional categories.
-	   * @param {geo.gui.colorLegendWidget.category[]} categories Append additional
-	   * legend categories to the end the of the current list of legends.
-	   * @returns {this} The current class instance.
-	   */
-	  this.addCategories = function (categories) {
-	    m_categories = m_categories.concat(this._prepareCategories(categories));
-	    this._draw();
-	    return this;
-	  };
-
-	  /**
-	   * Remove categories.
-	   *
-	   * @param {geo.gui.colorLegendWidget.category[]} categories If a category
-	   * object exists in the current legend categories, that category will be
-	   * removed.
-	   * @returns {this} The current class instance.
-	   */
-	  this.removeCategories = function (categories) {
-	    m_categories = m_categories.filter(function (category) {
-	      return categories.indexOf(category) === -1;
-	    });
-	    this._draw();
-	    return this;
-	  };
-
-	  /**
-	   * This function normalize color input string with the utility function. It modifies the original object.
-	   * @param {geo.gui.colorLegendWidget.category[]} categories The categories
-	   * @returns {geo.gui.colorLegendWidget.category[]} prepared categories
-	   */
-	  this._prepareCategories = function (categories) {
-	    categories.forEach(function (category) {
-	      category.color = category.colors.map(function (color) {
-	        return util.convertColorToHex(color, true);
-	      });
-	    });
-	    return categories;
-	  };
-
-	  /**
-	   * Draw an individual discrete type legend.
-	   * @param {Element} svg svg element that the legend will be drawn
-	   * @param {number} width width of the svg element in pixel
-	   * @param {geo.gui.colorLegendWidget.category} category The discrete type legend category
-	   */
-	  this._drawDiscrete = function (svg, width, category) {
-	    if (['linear', 'log', 'sqrt', 'pow', 'quantile', 'ordinal'].indexOf(category.scale) === -1) {
-	      throw new Error('unsupported scale');
-	    }
-	    var valueRange, valueScale, colorScale, axisScale, axis, steps, ticks;
-	    if (category.scale === 'ordinal') {
-	      colorScale = d3.scale.ordinal()
-	        .domain(category.domain)
-	        .range(category.colors);
-	      m_this._renderDiscreteColors(
-	        svg, category.domain, colorScale, width, function (d) { return d; });
-
-	      axisScale = d3.scale.ordinal()
-	        .domain(category.domain)
-	        .rangeRoundBands([0, width]);
-	      axis = d3.svg.axis()
-	        .scale(axisScale)
-	        .tickValues(function () {
-	          var skip = Math.ceil(axisScale.domain().length / m_ticks);
-	          return axisScale.domain()
-	            .filter(function (d, i) { return i % skip === 0; });
-	        });
-	      m_this._renderAxis(svg, axis);
-
-	    } else if (category.scale === 'quantile') {
-	      valueRange = [0, category.colors.length];
-	      steps = util.range(0, category.colors.length - 1);
-	      valueScale = d3.scale.quantile().domain(category.domain).range(steps);
-	      colorScale = d3.scale.quantize().domain(valueRange).range(category.colors);
-	      m_this._renderDiscreteColors(svg, steps, colorScale, width, function (d) {
-	        return valueScale.invertExtent(d).join(' - ');
-	      });
-
-	      var axisDomain = [valueScale.invertExtent(0)[0]];
-	      axisDomain = axisDomain.concat(steps.map(
-	        function (step) { return valueScale.invertExtent(step)[1]; }));
-
-	      ticks = steps.slice();
-	      ticks.push(category.colors.length);
-	      axisScale = d3.scale.ordinal()
-	        .domain(axisDomain)
-	        .rangePoints([0, width]);
-	      axis = createAxis(axisScale);
-	      m_this._renderAxis(svg, axis);
-
-	    } else if (['linear', 'log', 'sqrt', 'pow'].indexOf(category.scale) !== -1) {
-	      valueRange = [0, category.colors.length];
-	      valueScale = d3.scale[category.scale]()
-	        .domain(category.domain).range(valueRange).nice();
-	      colorScale = d3.scale.quantize().domain(valueRange).range(category.colors);
-	      steps = util.range(0, category.colors.length - 1);
-	      var precision = Math.max.apply(null, category.domain
-	        .map(function (number) { return getPrecision(number); }));
-	      m_this._renderDiscreteColors(svg, steps, colorScale, width, function (d) {
-	        return m_this._popupFormatter(valueScale.invert(d), precision)
-	          + ' - ' + m_this._popupFormatter(valueScale.invert(d + 1), precision);
-	      });
-
-	      ticks = steps.slice();
-	      ticks.push(category.colors.length);
-	      axisScale = d3.scale.ordinal()
-	        .domain(ticks.map(function (tick) {
-	          return valueScale.invert(tick);
-	        }))
-	        .rangePoints([0, width]);
-	      axis = createAxis(axisScale);
-	      m_this._renderAxis(svg, axis);
-	    }
-
-	    /**
-	     * Render the d3 axis object based on the axis d3 Scale.
-	     * @param {object} axisScale d3 scale object
-	     * @returns {object} d3 axis object
-	     */
-	    function createAxis(axisScale) {
-	      return d3.svg.axis()
-	        .scale(axisScale)
-	        .tickFormat(d3.format('.2s'))
-	        .tickValues(function () {
-	          var skip = Math.ceil(axisScale.domain().length / m_ticks);
-	          return axisScale.domain().filter(function (d, i) { return i % skip === 0; });
-	        });
-	    }
-	  };
-
-	  /**
-	   * Render colors for discrete type with d3.
-	   * @param {Element} svg svg element that the legend will be drawn
-	   * @param {number[]} steps discrete input scale domain for d3 scale
-	   * @param {object} colorScale d3 scale for transform input into color
-	   * @param {number} width width of the svg element in pixel
-	   * @param {function} getValue function that transforms raw domain into desired discrete range
-	   */
-	  this._renderDiscreteColors = function (svg, steps, colorScale, width, getValue) {
-	    svg.selectAll('rect')
-	      .data(steps)
-	      .enter()
-	      .append('rect')
-	      .attr('width', width / steps.length)
-	      .attr('height', '20px')
-	      .attr('fill', function (d) {
-	        return colorScale(d);
-	      })
-	      .attr('transform', function (d, i) {
-	        return 'translate(' + i * width / steps.length + ' ,0)';
-	      })
-	      .on('mousemove', function (d) {
-	        m_this._showPopup(getValue(d));
-	      })
-	      .on('mouseout', m_this._hidePopup);
-	  };
-
-	  /**
-	   * Draw an individual continous type legend.
-	   * @param {Element} svg svg element that the legend will be drawn
-	   * @param {number} width width of the svg element in pixel
-	   * @param {geo.gui.colorLegendWidget.category} category The continuous type legend category
-	   */
-	  this._drawContinous = function (svg, width, category) {
-	    var axisScale, axis;
-	    if (['linear', 'log', 'sqrt', 'pow'].indexOf(category.scale) === -1) {
-	      throw new Error('unsupported scale');
-	    }
-	    axisScale = d3.scale[category.scale]().domain(category.domain).range([0, width]).nice();
-	    if (category.scale === 'log' && category.base) {
-	      axisScale.base(category.base);
-	    }
-	    if (category.scale === 'pow' && category.exponent) {
-	      axisScale.exponent(category.exponent);
-	    }
-	    var id = uniqueID();
-	    var precision = Math.max.apply(null, category.domain
-	      .map(function (number) { return getPrecision(number); }));
-
-	    var gradient = svg
-	      .append('defs')
-	      .append('linearGradient')
-	      .attr('id', 'gradient' + id);
-	    gradient.append('stop')
-	      .attr('offset', '0%')
-	      .attr('stop-color', category.colors[0]);
-	    gradient.append('stop')
-	      .attr('offset', '100%')
-	      .attr('stop-color', category.colors[1]);
-	    svg.append('rect')
-	      .attr('fill', 'url(#gradient' + id + ')')
-	      .attr('width', width)
-	      .attr('height', '20px')
-	      .on('mousemove', function () {
-	        var value = axisScale.invert(d3.mouse(this)[0]);
-	        var text = m_this._popupFormatter(value, precision);
-	        m_this._showPopup(text);
-	      })
-	      .on('mouseout', m_this._hidePopup);
-
-	    axis = d3.svg.axis()
-	      .scale(axisScale)
-	      .ticks(m_ticks, '.2s');
-
-	    this._renderAxis(svg, axis);
-	  };
-
-	  /**
-	   * Actually render the axis with d3.
-	   * @param {Element} svg svg element that the axis will be drawn
-	   * @param {object} axis d3 axis object
-	   */
-	  this._renderAxis = function (svg, axis) {
-	    svg.append('g')
-	      .attr('class', 'axis x')
-	      .attr('transform', 'translate(0, 20)')
-	      .call(function (g) {
-	        g.call(axis);
-	      });
-	  };
-
-	  /**
-	   * Formatter of number that tries to maximize the precision
-	   * while making the output shorter.
-	   * @param {number} number to be formatted
-	   * @param {number} precision maximum number of decimal places that are kept
-	   * @returns {string} formatted string output
-	   */
-	  this._popupFormatter = function (number, precision) {
-	    number = parseFloat(number.toFixed(8));
-	    precision = Math.min(precision, getPrecision(number));
-	    precision = Math.min(precision, Math.max(3, 7 - Math.trunc(number).toString().length));
-	    return d3.format('.' + precision + 'f')(number);
-	  };
-
-	  /**
-	   * Show the popup based on current mouse event.
-	   * @param {string} text content to be shown in the popup
-	   */
-	  this._showPopup = function (text) {
-	    // The cursor location relative to the container
-	    var offset = d3.mouse(m_this.canvas());
-	    m_this.popup
-	      .text(text);
-	    var containerWidth = m_this.canvas().clientWidth;
-	    var popupWidth = m_this.popup[0][0].clientWidth;
-	    m_this.popup
-	      .style({
-	        // If the popup will be longer or almost longer than the container
-	        'left': offset[0] - (offset[0] +
-	          popupWidth - containerWidth > -10 ? popupWidth : 0) + 'px',
-	        'top': (offset[1] - 22) + 'px'
-	      })
-	      .transition()
-	      .duration(200)
-	      .style('opacity', 1);
-	  };
-
-	  /**
-	   * Hide the popup.
-	   */
-	  this._hidePopup = function () {
-	    m_this.popup.transition()
-	      .duration(200)
-	      .style('opacity', 0);
-	  };
-
-	  return this;
-	};
-
-	/**
-	 * Get the number of decimals of a number.
-	 * @param {number} number the number input
-	 * @returns {number} the number of decimal
-	 */
-	function getPrecision(number) {
-	  if (!isFinite(number)) return 0;
-	  var e = 1, p = 0;
-	  while (Math.round(number * e) / e !== number) {
-	    if (!isFinite(number * e)) { return 0; }
-	    e *= 10;
-	    p++;
-	  }
-	  return p;
-	}
-
-	inherit(colorLegendWidget, domWidget);
-
-	registerWidget('dom', 'colorLegend', colorLegendWidget);
-	module.exports = colorLegendWidget;
-
-
-/***/ }),
-/* 299 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var widget = __webpack_require__(300);
-	var inherit = __webpack_require__(8);
-	var registerWidget = __webpack_require__(201).registerWidget;
-
-	/**
-	 * Create a new instance of class domWidget.
-	 *
-	 * @class
-	 * @alias geo.gui.domWidget
-	 * @extends geo.gui.widget
-	 * @param {object} arg
-	 * @param {geo.widget} [parent] A parent widget for this widget.
-	 * @param {string} [el='div'] The type of DOM element to create.
-	 * @returns {geo.domWidget}
-	 */
-	var domWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof domWidget)) {
-	    return new domWidget(arg);
-	  }
-
-	  widget.call(this, arg);
-
-	  var m_this = this,
-	      m_default_canvas = 'div';
-
-	  /**
-	   * Initializes DOM Widget.
-	   * Sets the canvas for the widget, does parent/child relationship management,
-	   * appends it to it's parent and handles any positioning logic.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    if (arg.hasOwnProperty('parent')) {
-	      arg.parent.addChild(m_this);
-	    }
-
-	    m_this._createCanvas();
-	    m_this._appendCanvasToParent();
-
-	    m_this.canvas().addEventListener('mousedown', function (e) {
-	      e.stopPropagation();
-	    });
-
-	    m_this.reposition();
-	    return m_this;
-	  };
-
-	  /**
-	   * Creates the widget canvas.  This is a DOM element (`arg.el` or a div).
-	   */
-	  this._createCanvas = function () {
-	    m_this.canvas(document.createElement(arg.el || m_default_canvas));
-	  };
-
-	  return this;
-	};
-
-	inherit(domWidget, widget);
-
-	registerWidget('dom', 'dom', domWidget);
-	module.exports = domWidget;
-
-
-/***/ }),
-/* 300 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var inherit = __webpack_require__(8);
-	var sceneObject = __webpack_require__(208);
-	var $ = __webpack_require__(1);
-
-	/**
-	 * @typedef {object} geo.gui.widget.position
-	 * @property {string|number} [top] The position to the top of the container.
-	 *   A string css position or a number in pixels.
-	 * @property {string|number} [right] The position to the right of the
-	 *   container.  A string css position or a number in pixels.
-	 * @property {string|number} [bottom] The position to the bottom of the
-	 *   container.  A string css position or a number in pixels.
-	 * @property {string|number} [left] The position to the left of the container.
-	 * @property {string|number} [top] The position to the top of the container.
-	 * @property {*} [...] Additional css properties that affect position are
-	 *   allowed.  See the css specification for details.
-	 */
-
-	/**
-	 * Create a new instance of class widget.
-	 *
-	 * @class
-	 * @alias geo.gui.widget
-	 * @param {object} [arg] Options for the widget.
-	 * @param {geo.layer} [arg.layer] Layer associated with the widget.
-	 * @param {geo.gui.widget.position} [arg.position] Location of the widget.
-	 * @param {geo.gui.widget} [arg.parent] Optional parent widget.
-	 * @extends {geo.sceneObject}
-	 * @returns {geo.gui.widget}
-	 */
-	var widget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof widget)) {
-	    return new widget(arg);
-	  }
-	  arg = arg || {};
-	  sceneObject.call(this, arg);
-
-	  var geo_event = __webpack_require__(9);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_layer = arg.layer,
-	      m_canvas = null,
-	      m_position = arg.position === undefined ? { left: 0, top: 0 } : arg.position;
-
-	  if (arg.parent !== undefined && !(arg.parent instanceof widget)) {
-	    throw new Error('Parent must be of type geo.gui.widget');
-	  } else if (arg.parent) {
-	    m_this.parent(arg.parent);
-	  }
-
-	  /**
-	   * Initialize the widget.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    m_this.modified();
-	    return m_this;
-	  };
-
-	  /**
-	   * Clean up the widget.
-	   */
-	  this._exit = function () {
-	    m_this.children().forEach(function (child) {
-	      m_this.removeChild(child);
-	      child._exit();
-	    });
-
-	    m_this.layer().geoOff(geo_event.pan, m_this.repositionEvent);
-	    if (m_this.parentCanvas().removeChild && m_this.canvas()) {
-	      try {
-	        m_this.parentCanvas().removeChild(m_this.canvas());
-	      } catch (err) {
-	        // fail gracefully if the canvas is not a child of the parentCanvas
-	      }
-	    }
-	    s_exit();
-	  };
-
-	  /**
-	   * Return the layer associated with this widget.
-	   *
-	   * @returns {geo.layer}
-	   */
-	  this.layer = function () {
-	    return m_layer || (m_this.parent() && m_this.parent().layer());
-	  };
-
-	  /**
-	   * Create the canvas this widget will operate on.
-	   */
-	  this._createCanvas = function () {
-	    throw new Error('Must be defined in derived classes');
-	  };
-
-	  /**
-	   * Get/Set the canvas for the widget.
-	   *
-	   * @param {HTMLElement} [val] If specified, set the canvas, otherwise get
-	   *    the canvas.
-	   * @returns {HTMLElement|this} If getting the canvas, return the current
-	   *    value; otherwise, return this widget.
-	   */
-	  this.canvas = function (val) {
-	    if (val === undefined) {
-	      return m_canvas;
-	    }
-	    m_canvas = val;
-	    return m_this;
-	  };
-
-	  /**
-	   * Appends the canvas to the parent canvas.
-	   * The widget determines how to append itself to a parent, the parent can
-	   * either be another widget, or the UI Layer.
-	   */
-	  this._appendCanvasToParent = function () {
-	    m_this.parentCanvas().appendChild(m_this.canvas());
-	  };
-
-	  /**
-	   * Get the parent canvas (top level widgets define their layer as their
-	   * parent canvas).
-	   *
-	   * @returns {HTMLElement} The canvas of the widget's parent.
-	   */
-	  this.parentCanvas = function () {
-	    if (!m_this.parent()) {
-	      return m_this.layer().canvas();
-	    }
-	    return m_this.parent().canvas();
-	  };
-
-	  /**
-	   * Get or set the CSS positioning that a widget should be placed at.
-	   *
-	   * @param {geo.gui.widget.position} [pos] If unspecified, return the current
-	   *    position.  Otherwise, set the current position.
-	   * @param {boolean} [actualValue] If getting the position, if this is truthy,
-	   *    always return the stored value, not a value adjusted for display.
-	   * @returns {geo.gui.widget.position|this} Either the position or the widget
-	   *    instance.  If this is the position and `actualValue` is falsy,
-	   *    positions that specify an explicit `x` and `y` parameter will be
-	   *    converted to a value that can be used by the display css.
-	   */
-	  this.position = function (pos, actualValue) {
-	    if (pos !== undefined) {
-	      this.layer().geoOff(geo_event.pan, m_this.repositionEvent);
-	      var clearPosition = {};
-	      for (var attr in m_position) {
-	        if (m_position.hasOwnProperty(attr)) {
-	          clearPosition[attr] = null;
-	        }
-	      }
-	      m_position = pos;
-	      if (m_position.hasOwnProperty('x') && m_position.hasOwnProperty('y')) {
-	        this.layer().geoOn(geo_event.pan, m_this.repositionEvent);
-	      }
-	      this.reposition($.extend(clearPosition, m_this.position()));
-	      return this;
-	    }
-	    if (m_position.hasOwnProperty('x') && m_position.hasOwnProperty('y') && !actualValue) {
-	      var position = m_this.layer().map().gcsToDisplay(m_position);
-
-	      return {
-	        left: position.x,
-	        top: position.y,
-	        right: null,
-	        bottom: null
-	      };
-	    }
-
-	    return m_position;
-	  };
-
-	  /**
-	   * Repositions a widget.
-	   *
-	   * @param {geo.gui.widget.position} [position] The new position for the
-	   *    widget.  `undefined` uses the stored position value.
-	   * @returns {this}
-	   */
-	  this.reposition = function (position) {
-	    position = position || m_this.position();
-	    if (m_this.canvas() && m_this.canvas().style) {
-	      m_this.canvas().style.position = 'absolute';
-
-	      for (var cssAttr in position) {
-	        if (position.hasOwnProperty(cssAttr)) {
-	          // if the property is a number, add px to it, otherwise set it to the
-	          // specified value.  Setting a property to null clears it.  Setting to
-	          // undefined doesn't alter it.
-	          if (/^\s*(-|\+)?(\d+(\.\d*)?|\d*\.\d+)([eE](-|\+)?\d+)?\s*$/.test(position[cssAttr])) {
-	            // tris ensures that the number is a float with no more than 3
-	            // decimal places (Chrome does this automatically, but doing so
-	            // explicitly makes testing more consistent).  It will be an
-	            // integer when possible.
-	            m_this.canvas().style[cssAttr] = parseFloat(parseFloat(position[cssAttr]).toFixed(3)) + 'px';
-	          } else {
-	            m_this.canvas().style[cssAttr] = position[cssAttr];
-	          }
-	        }
-	      }
-	    }
-	    return m_this;
-	  };
-
-	  /**
-	   * If the position is based on map coordinates, this gets called when the
-	   * map is panned to resposition the widget.
-	   *
-	   * @returns {this}
-	   */
-	  this.repositionEvent = function () {
-	    return m_this.reposition();
-	  };
-
-	  /**
-	   * Report if the top left of widget (or its current x, y position) is within
-	   * the viewport.
-	   *
-	   * @returns {boolean} True if the widget is within the viewport.
-	   */
-	  this.isInViewport = function () {
-	    var position = m_this.position();
-	    var layer = m_this.layer();
-
-	    return ((position.left >= 0 && position.top >= 0) &&
-	            (position.left <= layer.width() && position.top <= layer.height()));
-	  };
-
-	  if (m_position.hasOwnProperty('x') && m_position.hasOwnProperty('y')) {
-	    this.layer().geoOn(geo_event.pan, m_this.repositionEvent);
-	  }
-	};
-	inherit(widget, sceneObject);
-	module.exports = widget;
-
-
-/***/ }),
-/* 301 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	// style-loader: Adds some css to the DOM by adding a <style> tag
-
-	// load the styles
-	var content = __webpack_require__(302);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(6)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/stylus-loader/index.js!./colorLegendWidget.styl", function() {
-				var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/stylus-loader/index.js!./colorLegendWidget.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
-
-/***/ }),
-/* 302 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	exports = module.exports = __webpack_require__(5)();
-	// imports
-
-
-	// module
-	exports.push([module.id, ".color-legend-container{display:none;padding:10px;border:1.5px solid #000;border-radius:3px;transition:background .25s linear;background-color:hsla(0,0%,100%,.75)}.color-legend-container:hover{background-color:#fff}.color-legend-container .legends .legend{margin-bottom:10px}.color-legend-container .legends .legend .title{text-align:center}.color-legend-container .legends .legend svg.svg{display:block}.color-legend-container .legends .legend svg.svg .axis.x line,.color-legend-container .legends .legend svg.svg .axis.x path.domain{fill:none;stroke:#000;stroke-width:.7}.color-legend-container .legends .legend svg.svg .axis.x text{font-size:12px}.color-legend-container .color-legend-popup{position:absolute;background:#fff;height:22px;font-size:14px;border:1px solid #000;padding:0 5px;pointer-events:none;white-space:nowrap;z-index:100000;opacity:0}", ""]);
-
-	// exports
-
-
-/***/ }),
-/* 303 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var svgWidget = __webpack_require__(304);
-	var inherit = __webpack_require__(8);
-	var registerWidget = __webpack_require__(201).registerWidget;
-
-	/**
-	 * Create a new instance of class legendWidget
-	 *
-	 * @class geo.gui.legendWidget
-	 * @extends geo.gui.svgWidget
-	 * @returns {geo.gui.legendWidget}
-	 */
-	var legendWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof legendWidget)) {
-	    return new legendWidget(arg);
-	  }
-	  svgWidget.call(this, arg);
-
-	  var d3 = __webpack_require__(226).d3;
-	  var geo_event = __webpack_require__(9);
-
-	  /** @private */
-	  var m_this = this,
-	      m_categories = [],
-	      m_top = null,
-	      m_group = null,
-	      m_border = null,
-	      m_spacing = 20, // distance in pixels between lines
-	      m_padding = 12; // padding in pixels inside the border
-
-	  /**
-	   * Get or set the category array associated with
-	   * the legend.  Each element of this array is
-	   * an object: ::
-	   *     {
-	   *         name: string,
-	   *         style: object,
-	   *         type: 'point' | 'line' | ...
-	   *     }
-	   *
-	   * The style property can contain the following feature styles:
-	   *     * fill: bool
-	   *     * fillColor: object | string
-	   *     * fillOpacity: number
-	   *     * stroke: bool
-	   *     * strokeColor: object | string
-	   *     * strokeWidth: number
-	   *     * strokeOpacity: number
-	   *
-	   * The type controls how the element is displayed, point as a circle,
-	   * line as a line segment.  Any other value will display as a rounded
-	   * rectangle.
-	   *
-	   * @param {object[]?} categories The categories to display
-	   */
-	  this.categories = function (arg) {
-	    if (arg === undefined) {
-	      return m_categories.slice();
-	    }
-	    m_categories = arg.slice().map(function (d) {
-	      if (d.type === 'line') {
-	        d.style.fill = false;
-	        d.style.stroke = true;
-	      }
-	      return d;
-	    });
-	    m_this.draw();
-	    return m_this;
-	  };
-
-	  /**
-	   * Get the widget's size
-	   * @return {{width: number, height: number}} The size in pixels
-	   */
-	  this.size = function () {
-	    var width = 1, height;
-	    var test = d3.select(m_this.canvas()).append('text')
-	          .style('opacity', 1e-6);
-
-	    m_categories.forEach(function (d) {
-	      test.text(d.name);
-	      width = Math.max(width, test.node().getBBox().width);
-	    });
-	    test.remove();
-
-	    height = m_spacing * (m_categories.length + 1);
-	    return {
-	      width: width + 50,
-	      height: height
-	    };
-	  };
-
-	  /**
-	   * Redraw the legend
-	   */
-	  this.draw = function () {
-
-	    m_this._init();
-	    function applyColor(selection) {
-	      selection.style('fill', function (d) {
-	        if (d.style.fill || d.style.fill === undefined) {
-	          return d.style.fillColor;
-	        } else {
-	          return 'none';
-	        }
-	      })
-	        .style('fill-opacity', function (d) {
-	          if (d.style.fillOpacity === undefined) {
-	            return 1;
-	          }
-	          return d.style.fillOpacity;
-	        })
-	        .style('stroke', function (d) {
-	          if (d.style.stroke || d.style.stroke === undefined) {
-	            return d.style.strokeColor;
-	          } else {
-	            return 'none';
-	          }
-	        })
-	        .style('stroke-opacity', function (d) {
-	          if (d.style.strokeOpacity === undefined) {
-	            return 1;
-	          }
-	          return d.style.strokeOpacity;
-	        })
-	        .style('stroke-width', function (d) {
-	          if (d.style.strokeWidth === undefined) {
-	            return 1.5;
-	          }
-	          return d.style.strokeWidth;
-	        });
-	    }
-
-	    m_border.attr('height', m_this.size().height + 2 * m_padding)
-	      .style('display', null);
-
-	    var scale = m_this._scale();
-
-	    var labels = m_group.selectAll('g.geo-label')
-	          .data(m_categories, function (d) { return d.name; });
-
-	    var g = labels.enter().append('g')
-	          .attr('class', 'geo-label')
-	          .attr('transform', function (d, i) {
-	            return 'translate(0,' + scale.y(i) + ')';
-	          });
-
-	    applyColor(g.filter(function (d) {
-	      return d.type !== 'point' && d.type !== 'line';
-	    }).append('rect')
-	               .attr('x', 0)
-	               .attr('y', -6)
-	               .attr('rx', 5)
-	               .attr('ry', 5)
-	               .attr('width', 40)
-	               .attr('height', 12)
-	              );
-
-	    applyColor(g.filter(function (d) {
-	      return d.type === 'point';
-	    }).append('circle')
-	               .attr('cx', 20)
-	               .attr('cy', 0)
-	               .attr('r', 6)
-	              );
-
-	    applyColor(g.filter(function (d) {
-	      return d.type === 'line';
-	    }).append('line')
-	               .attr('x1', 0)
-	               .attr('y1', 0)
-	               .attr('x2', 40)
-	               .attr('y2', 0)
-	              );
-
-	    g.append('text')
-	      .attr('x', '50px')
-	      .attr('y', 0)
-	      .attr('dy', '0.3em')
-	      .text(function (d) {
-	        return d.name;
-	      });
-
-	    m_this.reposition();
-
-	    return m_this;
-	  };
-
-	  /**
-	   * Get scales for the x and y axis for the current size.
-	   * @private
-	   */
-	  this._scale = function () {
-	    return {
-	      x: d3.scale.linear()
-	        .domain([0, 1])
-	        .range([0, m_this.size().width]),
-	      y: d3.scale.linear()
-	        .domain([0, m_categories.length - 1])
-	        .range([m_padding / 2, m_this.size().height - m_padding / 2])
-	    };
-	  };
-
-	  /**
-	   * Private initialization.  Creates the widget's DOM container and internal
-	   * variables.
-	   * @private
-	   */
-	  this._init = function () {
-	    // adding categories redraws the entire thing by calling _init, see
-	    // the m_top.remove() line below
-	    if (!m_top) {
-	      m_this._createCanvas();
-	      m_this._appendCanvasToParent();
-	    }
-
-	    // total size = interior size + 2 * padding + 2 * width of the border
-	    var w = m_this.size().width + 2 * m_padding + 4,
-	        h = m_this.size().height + 2 * m_padding + 4;
-
-	    // @todo - removing after creating to maintain the appendChild structure
-	    if (m_top) {
-	      m_top.remove();
-	    }
-
-	    d3.select(m_this.canvas()).attr('width', w).attr('height', h);
-
-	    m_top = d3.select(m_this.canvas()).append('g');
-	    m_group = m_top
-	      .append('g')
-	      .attr('transform', 'translate(' + [m_padding + 2, m_padding + 2] + ')');
-	    m_border = m_group.append('rect')
-	      .attr('x', -m_padding)
-	      .attr('y', -m_padding)
-	      .attr('width', w - 4)
-	      .attr('height', h - 4)
-	      .attr('rx', 3)
-	      .attr('ry', 3)
-	      .style({
-	        'stroke': 'black',
-	        'stroke-width': '1.5px',
-	        'fill': 'white',
-	        'fill-opacity': 0.75,
-	        'display': 'none'
-	      });
-	    m_group.on('mousedown', function () {
-	      d3.event.stopPropagation();
-	    });
-	    m_group.on('mouseover', function () {
-	      m_border.transition()
-	        .duration(250)
-	        .style('fill-opacity', 1);
-	    });
-	    m_group.on('mouseout', function () {
-	      m_border.transition()
-	        .duration(250)
-	        .style('fill-opacity', 0.75);
-	    });
-
-	    m_this.reposition();
-	  };
-
-	  this.geoOn(geo_event.resize, function () {
-	    m_this.draw();
-	  });
-
-	};
-
-	inherit(legendWidget, svgWidget);
-
-	registerWidget('dom', 'legend', legendWidget);
-	module.exports = legendWidget;
-
-
-/***/ }),
-/* 304 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var widget = __webpack_require__(300);
-	var inherit = __webpack_require__(8);
-	var registerWidget = __webpack_require__(201).registerWidget;
-
-	/**
-	 * Create a new instance of class geo.gui.svgWidget.
-	 *
-	 * Due to the nature of d3 creating DOM elements as it inserts them, calls to
-	 * appendChild don't appear in this widget.
-	 *
-	 * The canvas of an svgWidget always refers to the actual svg element.
-	 * The parentCanvas can refer to another widget's svg element, dom element, or
-	 * the UI layer's dom element.
-	 * See {@link geo.gui.widget#parentCanvas}.
-	 *
-	 * @class
-	 * @alias geo.gui.svgWidget
-	 * @extends geo.gui.widget
-	 * @param {object} arg
-	 * @param {geo.widget} [parent] A parent widget for this widget.
-	 * @returns {geo.gui.svgWidget}
-	 */
-	var svgWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof svgWidget)) {
-	    return new svgWidget(arg);
-	  }
-
-	  widget.call(this, arg);
-
-	  var d3Renderer = __webpack_require__(226);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_renderer = null;
-
-	  /**
-	   * Initializes SVG Widget.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    var d3Parent;
-	    if (arg.hasOwnProperty('parent')) {
-	      arg.parent.addChild(m_this);
-
-	      // Tell the renderer there is an SVG element as a parent
-	      d3Parent = arg.parent.canvas();
-	    }
-
-	    m_this._createCanvas(d3Parent);
-
-	    m_this.canvas().addEventListener('mousedown', function (e) {
-	      e.stopPropagation();
-	    });
-
-	    m_this.reposition();
-	    return m_this;
-	  };
-
-	  /**
-	   * Clean up the widget.
-	   */
-	  this._exit = function () {
-	    if (m_renderer) {
-	      m_renderer._exit();
-	    }
-	    s_exit();
-	  };
-
-	  /**
-	   * Creates the canvas for the svg widget.
-	   * This directly uses the {@link geo.d3.d3Renderer} as a helper to do all of
-	   * the heavy lifting.
-	   *
-	   * @param {d3Selector} d3Parent The canvas's parent element.
-	   */
-	  this._createCanvas = function (d3Parent) {
-	    var rendererOpts = {
-	      layer: m_this.layer(),
-	      widget: true
-	    };
-
-	    if (d3Parent) {
-	      rendererOpts.d3Parent = d3Parent;
-	    }
-
-	    m_renderer = d3Renderer(rendererOpts);
-
-	    // svg widgets manage their own sizes, so make the resize handler a no-op
-	    m_renderer._resize = function () {};
-
-	    m_this.canvas(m_renderer.canvas()[0][0]);
-	  };
-
-	  return this;
-	};
-
-	inherit(svgWidget, widget);
-
-	registerWidget('dom', 'svg', svgWidget);
-	module.exports = svgWidget;
-
-
-/***/ }),
-/* 305 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var $ = __webpack_require__(1);
-	var inherit = __webpack_require__(8);
-	var svgWidget = __webpack_require__(304);
-	var registerWidget = __webpack_require__(201).registerWidget;
-
-	__webpack_require__(306);
-
-	/**
-	 * Scale widget specification.
-	 *
-	 * @typedef {object} geo.gui.scaleWidget.spec
-	 * @param {number} [scale=1] A scale applied to the map gcs units to convert to
-	 *   the scale units.
-	 * @param {number} [maxWidth=200] The maximum width of the scale in pixels.
-	 *   For horizontal scales (orientation is `top` or `bottom`) this is the
-	 *   maximum length of the scale bar.  For vertical scales, this is the width
-	 *   available for the scale text.
-	 * @param {number} [maxHeight] The maximum height of the scale in pixels.
-	 *   For vertical scales (orientation is `left` or `right`) this is the
-	 *   maximum length of the scale bar.  For horizontal scales, this is the
-	 *   height available for the scale text.  Default is 200 for vertical scales,
-	 *   20 for horizontal scales.
-	 * @param {string} [orientation='bottom'] One of `left`, `right`, `top`, or
-	 *   `bottom`.  The scale text is placed in that location relative to the scale
-	 *   bar.
-	 * @param {number} [strokeWidth=2] The width of the ticks and scale bar in
-	 *   pixels.
-	 * @param {number} [tickLength=10] The length of the end ticks in pixels.
-	 * @param {string|geo.gui.scaleWidget.unit[]} [units='si'] One of either 'si'
-	 *   or 'miles' or an array of units in ascending order.  See the `UnitsTable`
-	 *   for examples.
-	 * @param {Function} [distance] The function used to compute the length of the
-	 *   scale bar.  This defaults to `transform.sphericalDistance` for all maps
-	 *   except those with a gcs of `'+proj=longlat +axis=enu'`, where
-	 *   `math.sqrt(util.distance2dSquared(pt1, pt2))` is used instead.
-	 */
-
-	/**
-	 * Scale widget unit specification.
-	 *
-	 * @typedef {object} geo.gui.scaleWidget.unit
-	 * @param {string} unit Display name for the unit.
-	 * @param {number} scale Scale for 1 unit in the current system.
-	 * @param {number} [minimum=1] Minimum value where this applies after scaling.
-	 *   This can be used to handle singular and plural words (e.g., `[{units:
-	 *   'meter', scale: 1}, {units: 'meters', scale: 1, minimum: 1.5}]`)
-	 * @param {number} [basis=10] The basis for the multiples value.
-	 * @param {object[]} [multiples] A list of objects in ascending value order that
-	 *   determine what round values are displayed.
-	 * @param {number} multiples.multiple The value that is selected for display.
-	 * @param {number} multiples.digit The number of significant digits in
-	 *   `mutliple`.
-	 */
-
-	/**
-	 * Create a new instance of class geo.gui.scaleWidget.
-	 *
-	 * @class
-	 * @alias geo.gui.scaleWidget
-	 * @extends {geo.gui.svgWidget}
-	 * @param {geo.gui.scaleWidget.spec} arg
-	 * @returns {geo.gui.scaleWidget}
-	 */
-	var scaleWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof scaleWidget)) {
-	    return new scaleWidget(arg);
-	  }
-	  svgWidget.call(this, arg);
-
-	  var geo_event = __webpack_require__(9);
-	  var transform = __webpack_require__(11);
-	  var util = __webpack_require__(83);
-	  var d3 = __webpack_require__(226).d3;
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_options = $.extend({}, {
-	        scale: 1,
-	        maxWidth: 200,
-	        maxHeight: arg.orientation === 'left' || arg.orientation === 'right' ? 200 : 20,
-	        orientation: 'bottom',
-	        strokeWidth: 2,
-	        tickLength: 10,
-	        units: 'si',
-	        distance: function (pt1, pt2, gcs) {
-	          if (gcs === '+proj=longlat +axis=enu') {
-	            return Math.sqrt(util.distance2dSquared(pt1, pt2));
-	          }
-	          /* We can use either the spherical distance or the Vincenty distance
-	           * here in much the same way.
-	          return transform.vincentyDistance(pt1, pt2, gcs).distance;
-	           */
-	          return transform.sphericalDistance(pt1, pt2, gcs);
-	        }
-	      }, arg);
-
-	  /**
-	   * Initialize the scale widget.
-	   *
-	   * @returns {this}
-	   */
-	  this._init = function () {
-	    m_this._createCanvas();
-	    m_this._appendCanvasToParent();
-	    m_this.reposition();
-
-	    d3.select(m_this.canvas()).attr({
-	      width: m_options.maxWidth,
-	      height: m_options.maxHeight
-	    });
-	    // Update the scale on pan
-	    m_this.geoOn(geo_event.pan, m_this._update);
-	    m_this._render();
-	    return m_this;
-	  };
-
-	  /**
-	   * Clean up after the widget.
-	   */
-	  this._exit = function () {
-	    m_this.geoOff(geo_event.pan, m_this._update);
-	    s_exit();
-	  };
-
-	  /**
-	   * Return true if the scale is vertically oriented.
-	   *
-	   * @returns {boolean} `true` if the scale is vertical, `false` if horizontal.
-	   */
-	  this._vertical = function () {
-	    return m_options.orientation === 'left' || m_options.orientation === 'right';
-	  };
-
-	  /**
-	   * Given a maximum value, return a value that is no larger than it but at a
-	   * round number of a set of units.
-	   *
-	   * @param {number} maxValue The maximum value to return.  The returned value
-	   *    will never be smaller than 3/5 of this value.
-	   * @param {number} pixels A number that is scaled by the ratio of the
-	   *    returned value to `maxValue`.
-	   * @param {string|geo.gui.scaleWidget.unit[]} [units] The units to use.  If
-	   *    not specified, the instance's option units value is used.
-	   * @returns {object} An object with `html`, `value`, and `pixels` values
-	   *    representing the calculated value.
-	   */
-	  this._scaleValue = function (maxValue, pixels, units) {
-	    units = (scaleWidget.unitsTable[units] || units ||
-	             scaleWidget.unitsTable[m_options.units] || m_options.units);
-	    var multiples = [
-	      {multiple: 1, digits: 1},
-	      {multiple: 1.5, digits: 2},
-	      {multiple: 2, digits: 1},
-	      {multiple: 3, digits: 1},
-	      {multiple: 5, digits: 1},
-	      {multiple: 8, digits: 1}];
-	    var unit = units[0],
-	        multiple, power, value;
-	    units.forEach(function (unitEntry) {
-	      if (maxValue >= unitEntry.scale * (unitEntry.minimum || 1)) {
-	        unit = unitEntry;
-	      }
-	    });
-	    power = Math.floor(Math.log(maxValue / unit.scale) / Math.log(unit.basis || 10));
-	    multiples = unit.multiples || multiples;
-	    multiples.forEach(function (mul) {
-	      var mulValue = unit.scale * mul.multiple * Math.pow(10, power);
-	      if (mulValue <= maxValue) {
-	        multiple = mul;
-	        value = mulValue;
-	      }
-	    });
-	    return {
-	      html: (multiple.multiple * Math.pow(10, power)).toFixed(
-	        Math.max(0, -power + multiple.digits - 1)) + ' ' + unit.unit,
-	      value: value,
-	      pixels: value / maxValue * pixels,
-	      power: power,
-	      multiple: multiple,
-	      unitRecord: unit,
-	      originalValue: maxValue,
-	      originalPixels: pixels
-	    };
-	  };
-
-	  /**
-	   * Create and draw the scale based on the current display distance at the
-	   * location of the scale.
-	   */
-	  this._render = function () {
-	    var svg = d3.select(m_this.canvas()),
-	        map = m_this.layer().map(),
-	        width = m_options.maxWidth,
-	        height = m_options.maxHeight,
-	        sw = m_options.strokeWidth,
-	        sw2 = sw * 0.5,
-	        tl = m_options.tickLength,
-	        vert = m_this._vertical(),
-	        pixels, pt1, pt2, dist, value, pts;
-
-	    pixels = (vert ? m_options.maxHeight : m_options.maxWidth) - sw;
-	    /* Calculate the distance that the maximum length scale bar can occupy at
-	     * the location that the scale bar will be drawn. */
-	    // svg.attr({width: width, height: height});
-	    pt1 = $(svg[0][0]).offset();
-	    pt1 = {
-	      x: pt1.left + (m_options.orientation === 'left' ? width - sw2 : sw2),
-	      y: pt1.top + (m_options.orientation === 'top' ? height - sw2 : sw2)
-	    };
-	    pt2 = {x: pt1.x + (vert ? 0 : pixels), y: pt1.y + (vert ? pixels : 0)};
-	    dist = m_options.distance(map.displayToGcs(pt1, null), map.displayToGcs(pt2, null), map.gcs()) * m_options.scale;
-	    if (dist <= 0 || !isFinite(dist)) {
-	      console.warn('The distance calculated for the scale is invalid: ' + dist);
-	      return;
-	    }
-	    value = m_this._scaleValue(dist, pixels);
-	    if (vert) {
-	      height = value.pixels + sw;
-	    } else {
-	      width = value.pixels + sw;
-	    }
-	    svg.attr({width: width, height: height});
-	    if (svg.select('polyline').empty()) {
-	      svg.append('polyline').classed('geojs-scale-widget-bar', true).attr({
-	        fill: 'none',
-	        'stroke-width': sw
-	      });
-	    }
-	    if (svg.select('text').empty()) {
-	      svg.append('text').classed('geojs-scale-widget-text', true);
-	    }
-	    switch (m_options.orientation) {
-	      case 'bottom':
-	        pts = [[sw2, tl], [sw2, sw2], [width - sw2, sw2], [width - sw2, tl]];
-	        svg.select('text').attr({
-	          x: width / 2,
-	          y: sw * 2,
-	          'text-anchor': 'middle',
-	          'alignment-baseline': 'hanging'
-	        });
-	        break;
-	      case 'top':
-	        pts = [[sw2, height - tl], [sw2, height - sw2], [width - sw2, height - sw2], [width - sw2, height - tl]];
-	        svg.select('text').attr({
-	          x: width / 2,
-	          y: height - sw * 2,
-	          'text-anchor': 'middle',
-	          'alignment-baseline': 'baseline'
-	        });
-	        break;
-	      case 'left':
-	        pts = [[width - tl, sw2], [width - sw2, sw2], [width - sw2, height - sw2], [width - tl, height - sw2]];
-	        svg.select('text').attr({
-	          x: width - sw * 2,
-	          y: height / 2,
-	          'text-anchor': 'end',
-	          'alignment-baseline': 'middle'
-	        });
-	        break;
-	      case 'right':
-	        pts = [[tl, sw2], [sw2, sw2], [sw2, height - sw2], [tl, height - sw2]];
-	        svg.select('text').attr({
-	          x: sw * 2,
-	          y: height / 2,
-	          'text-anchor': 'start',
-	          'alignment-baseline': 'middle'
-	        });
-	        break;
-	    }
-	    svg.select('polyline').attr('points', pts.map(function (pt) { return pt.join(','); }).join(' '));
-	    svg.select('text').html(value.html);
-	  };
-
-	  /**
-	   * Update the widget upon panning.
-	   */
-	  this._update = function () {
-	    this._render();
-	  };
-
-	  /**
-	   * Set or get options.
-	   *
-	   * @param {string|object} [arg1] If `undefined`, return the options object.
-	   *    If a string, either set or return the option of that name.  If an
-	   *    object, update the options with the object's values.
-	   * @param {object} [arg2] If `arg1` is a string and this is defined, set
-	   *    the option to this value.
-	   * @returns {object|this} If options are set, return the annotation,
-	   *    otherwise return the requested option or the set of options.
-	   */
-	  this.options = function (arg1, arg2) {
-	    if (arg1 === undefined) {
-	      var result = $.extend({}, m_options);
-	      result.position = m_this.position(undefined, true);
-	      return result;
-	    }
-	    if (typeof arg1 === 'string' && arg2 === undefined) {
-	      return arg1 === 'position' ? m_this.position(undefined, true) : m_options[arg1];
-	    }
-	    if (arg2 === undefined) {
-	      m_options = $.extend(true, m_options, arg1);
-	    } else {
-	      m_options[arg1] = arg2;
-	    }
-	    if (arg1.position || arg1 === 'position') {
-	      m_this.position(arg1.position || arg2);
-	    }
-	    m_this._render();
-	    return m_this;
-	  };
-	};
-
-	inherit(scaleWidget, svgWidget);
-
-	/* The unitsTable has predefined unit sets.  Each entry is an array that must
-	 * be in ascending order. */
-	scaleWidget.unitsTable = {
-	  si: [
-	    {unit: 'nm', scale: 1e-9},
-	    {unit: '&mu;m', scale: 1e-6},
-	    {unit: 'mm', scale: 0.001},
-	    {unit: 'm', scale: 1},
-	    {unit: 'km', scale: 1000}
-	  ],
-	  miles: [
-	    {unit: 'in', scale: 0.0254}, // applies to < 1 in
-	    {
-	      /* By specifying inches a second time, the first entry will apply to
-	       * values less than 1 inch, and those will be rounded by powers of 10
-	       * using the default rules.  This entry will round values differently,
-	       * so one will see 1, 1.5, 2, 3, 6, 9 rather than the default which would
-	       * be 1, 1.5, 2, 3, 5, 8, 10. */
-	      unit: 'in',
-	      scale: 0.0254,
-	      basis: 12,
-	      multiples: [
-	        {multiple: 1, digits: 1},
-	        {multiple: 1.5, digits: 2},
-	        {multiple: 2, digits: 1},
-	        {multiple: 3, digits: 1},
-	        {multiple: 6, digits: 1},
-	        {multiple: 9, digits: 1}
-	      ]
-	    },
-	    {unit: 'ft', scale: 0.3048},
-	    {unit: 'mi', scale: 1609.344}
-	  ]
-	};
-
-	registerWidget('dom', 'scale', scaleWidget);
-	module.exports = scaleWidget;
-
-
-/***/ }),
-/* 306 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	// style-loader: Adds some css to the DOM by adding a <style> tag
-
-	// load the styles
-	var content = __webpack_require__(307);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(6)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/stylus-loader/index.js!./scaleWidget.styl", function() {
-				var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/stylus-loader/index.js!./scaleWidget.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
-
-/***/ }),
-/* 307 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	exports = module.exports = __webpack_require__(5)();
-	// imports
-
-
-	// module
-	exports.push([module.id, ".geojs-scale-widget-bar{stroke:#000}.geojs-scale-widget-text{font-weight:700;font-size:16px;font-family:serif}", ""]);
-
-	// exports
-
-
-/***/ }),
-/* 308 */
-/***/ (function(module, exports, __webpack_require__) {
-
-	var svgWidget = __webpack_require__(304);
-	var inherit = __webpack_require__(8);
-	var registerWidget = __webpack_require__(201).registerWidget;
-
-	/**
-	 * Create a new instance of class sliderWidget
-	 *
-	 * @class geo.gui.sliderWidget
-	 * @extends {geo.gui.svgWidget}
-	 * @returns {geo.gui.sliderWidget}
-	 */
-	var sliderWidget = function (arg) {
-	  'use strict';
-	  if (!(this instanceof sliderWidget)) {
-	    return new sliderWidget(arg);
-	  }
-	  svgWidget.call(this, arg);
-
-	  var d3 = __webpack_require__(226).d3;
-	  var geo_event = __webpack_require__(9);
-
-	  var m_this = this,
-	      s_exit = this._exit,
-	      m_xscale,
-	      m_yscale,
-	      m_plus,
-	      m_minus,
-	      m_nub,
-	      m_width = arg.width || 20, // Size of the widget in pixels
-	      m_height = arg.height || 160,  // slider height + 3 * width
-	      m_nubSize = arg.width ? arg.width * 0.5 : 10,
-	      m_plusIcon,
-	      m_minusIcon,
-	      m_group,
-	      m_lowContrast,
-	      m_highlightDur = 100;
-
-	  /* http://icomoon.io */
-	  /* CC BY 3.0 http://creativecommons.org/licenses/by/3.0/ */
-	  m_plusIcon = 'M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM564.326 564.326v206.182h-104.653v-206.182h-206.234v-104.653h206.182v-206.234h104.704v206.182h206.182v104.704h-206.182z';
-	  m_minusIcon = 'M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM770.56 459.674v104.704h-517.12v-104.704h517.12z';
-
-	  // Define off-white gray colors for low contrast ui (unselected).
-	  m_lowContrast = {
-	    white: '#f4f4f4',
-	    black: '#505050'
-	  };
-
-	  /**
-	   * Add an icon from a path string.  Returns a d3 group element.
-	   *
-	   * @function
-	   * @argument {String} icon svg path string
-	   * @argument {Array} base where to append the element (d3 selection)
-	   * @argument {Number} cx Center x-coordinate
-	   * @argument {Number} cy Center y-coordinate
-	   * @argument {Number} size Icon size in pixels
-	   * @returns {object}
-	   * @private
-	   */
-	  function put_icon(icon, base, cx, cy, size) {
-	    var g = base.append('g');
-
-	    // the scale factor
-	    var s = size / 1024;
-
-	    g.append('g')
-	      .append('g')
-	        .attr(
-	          'transform',
-	          'translate(' + cx + ',' + cy + ') scale(' + s + ') translate(-512,-512)'
-	      )
-	      .append('path')
-	        .attr('d', icon)
-	        .attr('class', 'geo-glyphicon');
-
-	    return g;
-	  }
-
-	  this.size = function () {
-	    return {width: m_width, height: m_height};
-	  };
-
-	  /**
-	   * Initialize the slider widget in the map.
-	   *
-	   * @function
-	   * @returns {geo.gui.sliderWidget}
-	   * @private
-	   */
-	  this._init = function () {
-	    m_this._createCanvas();
-	    m_this._appendCanvasToParent();
-
-	    m_this.reposition();
-
-	    var svg = d3.select(m_this.canvas()),
-	        map = m_this.layer().map();
-
-	    svg.attr('width', m_width).attr('height', m_height);
-
-	    // create d3 scales for positioning
-	    // TODO: make customizable and responsive
-	    m_xscale = d3.scale.linear().domain([-4, 4]).range([0, m_width]);
-	    m_yscale = d3.scale.linear().domain([0, 1]).range([m_width * 1.5, m_height - m_width * 1.5]);
-
-	    // Create the main group element
-	    svg = svg.append('g').classed('geo-ui-slider', true);
-	    m_group = svg;
-
-	    // Create + zoom button
-	    m_plus = svg.append('g');
-	    m_plus.append('circle')
-	      .datum({
-	        fill: 'white',
-	        stroke: null
-	      })
-	      .classed('geo-zoom-in', true)
-	      .attr('cx', m_xscale(0))
-	      .attr('cy', m_yscale(0.0) - m_width + 2)
-	      .attr('r', (m_width - 2) / 2)
-	      .style({
-	        'cursor': 'pointer'
-	      })
-	      .on('click', function () {
-	        var z = map.zoom();
-	        map.transition({
-	          zoom: z + 1,
-	          ease: d3.ease('cubic-in-out'),
-	          duration: 500
-	        });
-	      })
-	      .on('mousedown', function () {
-	        d3.event.stopPropagation();
-	      });
-
-	    put_icon(
-	      m_plusIcon,
-	      m_plus,
-	      m_xscale(0),
-	      m_yscale(0) - m_width + 2,
-	      m_width + 4
-	    ).style('cursor', 'pointer')
-	      .style('pointer-events', 'none')
-	      .select('path')
-	      .datum({
-	        fill: 'black',
-	        stroke: null
-	      });
-
-	    // Create the - zoom button
-	    m_minus = svg.append('g');
-	    m_minus.append('circle')
-	      .datum({
-	        fill: 'white',
-	        stroke: null
-	      })
-	      .classed('geo-zoom-out', true)
-	      .attr('cx', m_xscale(0))
-	      .attr('cy', m_yscale(1.0) + m_width - 2)
-	      .attr('r', (m_width - 2) / 2)
-	      .style({
-	        'cursor': 'pointer'
-	      })
-	      .on('click', function () {
-	        var z = map.zoom();
-	        map.transition({
-	          zoom: z - 1,
-	          ease: d3.ease('cubic-in-out'),
-	          duration: 500
-	        });
-	      })
-	      .on('mousedown', function () {
-	        d3.event.stopPropagation();
-	      });
-
-	    put_icon(
-	      m_minusIcon,
-	      m_minus,
-	      m_xscale(0),
-	      m_yscale(1) + m_width - 2,
-	      m_width + 4
-	    ).style('cursor', 'pointer')
-	      .style('pointer-events', 'none')
-	      .select('path')
-	      .datum({
-	        fill: 'black',
-	        stroke: null
-	      });
-
-	    // Respond to a mouse event on the widget
-	    function respond(evt, trans) {
-	      var z = m_yscale.invert(d3.mouse(svg.node())[1]),
-	          zrange = map.zoomRange();
-	      z = (1 - z) * (zrange.max - zrange.min) + zrange.min;
-	      if (trans) {
-	        map.transition({
-	          zoom: z,
-	          ease: d3.ease('cubic-in-out'),
-	          duration: 500,
-	          done: m_this._update()
-	        });
-	      } else {
-	        map.zoom(z);
-	        m_this._update();
-	      }
-	      evt.stopPropagation();
-	    }
-
-	    // Create the track
-	    svg.append('rect')
-	      .datum({
-	        fill: 'white',
-	        stroke: 'black'
-	      })
-	      .classed('geo-zoom-track', true)
-	      .attr('x', m_xscale(0) - m_width / 6)
-	      .attr('y', m_yscale(0))
-	      .attr('rx', m_width / 10)
-	      .attr('ry', m_width / 10)
-	      .attr('width', m_width / 3)
-	      .attr('height', m_height - m_width * 3)
-	      .style({
-	        'cursor': 'pointer'
-	      })
-	      .on('click', function () {
-	        respond(d3.event, true);
-	      });
-
-	    // Create the nub
-	    m_nub = svg.append('rect')
-	      .datum({
-	        fill: 'black',
-	        stroke: null
-	      })
-	      .classed('geo-zoom-nub', true)
-	      .attr('x', m_xscale(-4))
-	      .attr('y', m_yscale(0.5) - m_nubSize / 2)
-	      .attr('rx', 3)
-	      .attr('ry', 3)
-	      .attr('width', m_width)
-	      .attr('height', m_nubSize)
-	      .style({
-	        'cursor': 'pointer'
-	      })
-	      .on('mousedown', function () {
-	        d3.select(document).on('mousemove.geo.slider', function () {
-	          respond(d3.event);
-	        });
-	        d3.select(document).on('mouseup.geo.slider', function () {
-	          respond(d3.event);
-	          d3.select(document).on('.geo.slider', null);
-	        });
-	        d3.event.stopPropagation();
-	      });
-
-	    var mouseOver = function () {
-	      d3.select(this).attr('filter', 'url(#geo-highlight)');
-	      m_group.selectAll('rect,path,circle').transition()
-	        .duration(m_highlightDur)
-	        .style('fill', function (d) {
-	          return d.fill || null;
-	        })
-	        .style('stroke', function (d) {
-	          return d.stroke || null;
-	        });
-
-	    };
-
-	    var mouseOut = function () {
-	      d3.select(this).attr('filter', null);
-	      m_group.selectAll('circle,rect,path').transition()
-	        .duration(m_highlightDur)
-	        .style('fill', function (d) {
-	          return m_lowContrast[d.fill] || null;
-	        })
-	        .style('stroke', function (d) {
-	          return m_lowContrast[d.stroke] || null;
-	        });
-	    };
-
-	    m_group.selectAll('*')
-	      .on('mouseover', mouseOver)
-	      .on('mouseout', mouseOut);
-
-	    // Update the nub position on zoom
-	    m_this.geoOn(geo_event.zoom, m_this._update);
-
-	    mouseOut();
-	    m_this._update();
-	  };
-
-	  /**
-	   * Removes the slider element from the map and unbinds all handlers.
-	   *
-	   * @function
-	   * @returns {geo.gui.sliderWidget}
-	   * @private
-	   */
-	  this._exit = function () {
-	    m_this.geoOff(geo_event.zoom, m_this._update);
-	    m_group.remove();
-	    s_exit();
-	  };
-
-	  /**
-	   * Update the slider widget state in reponse to map changes.  I.e. zoom
-	   * range changes.
-	   *
-	   * @function
-	   * @returns {geo.gui.sliderWidget}
-	   * @private
-	   */
-	  this._update = function (obj) {
-	    var map = m_this.layer().map(),
-	        zoomRange = map.zoomRange(),
-	        zoom = map.zoom(),
-	        zoomScale = d3.scale.linear();
-
-	    obj = obj || {};
-	    zoom = obj.value || zoom;
-	    zoomScale.domain([zoomRange.min, zoomRange.max])
-	      .range([1, 0])
-	      .clamp(true);
-
-	    m_nub.attr('y', m_yscale(zoomScale(zoom)) - m_nubSize / 2);
-	  };
-	};
-
-	inherit(sliderWidget, svgWidget);
-
-	registerWidget('dom', 'slider', sliderWidget);
-	module.exports = sliderWidget;
-
-
-/***/ })
-/******/ ])
-});
-;
\ No newline at end of file