(function (root, factory) {
    if (typeof exports === "object") {
        // CommonJS-like
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD.
        define([], factory);
    } else {
        // Browser globals
        root.WorldWind = factory();
    }
}(this, function () {

/**
 * @license almond 0.3.0 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/almond for details
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
    var main, req, makeMap, handlers,
        defined = {},
        waiting = {},
        config = {},
        defining = {},
        hasOwn = Object.prototype.hasOwnProperty,
        aps = [].slice,
        jsSuffixRegExp = /\.js$/;

    function hasProp(obj, prop) {
        return hasOwn.call(obj, prop);
    }

    /**
     * Given a relative module name, like ./something, normalize it to
     * a real name that can be mapped to a path.
     * @param {String} name the relative name
     * @param {String} baseName a real name that the name arg is relative
     * to.
     * @returns {String} normalized name
     */
    function normalize(name, baseName) {
        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
            foundI, foundStarMap, starI, i, j, part,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name && name.charAt(0) === ".") {
            //If have a base name, try to normalize against it,
            //otherwise, assume it is a top-level require that will
            //be relative to baseUrl in the end.
            if (baseName) {
                //Convert baseName to array, and lop off the last part,
                //so that . matches that "directory" and not name of the baseName's
                //module. For instance, baseName of "one/two/three", maps to
                //"one/two/three.js", but we want the directory, "one/two" for
                //this normalization.
                baseParts = baseParts.slice(0, baseParts.length - 1);
                name = name.split('/');
                lastIndex = name.length - 1;

                // Node .js allowance:
                if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
                    name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
                }

                name = baseParts.concat(name);

                //start trimDots
                for (i = 0; i < name.length; i += 1) {
                    part = name[i];
                    if (part === ".") {
                        name.splice(i, 1);
                        i -= 1;
                    } else if (part === "..") {
                        if (i === 1 && (name[2] === '..' || name[0] === '..')) {
                            //End of the line. Keep at least one non-dot
                            //path segment at the front so it can be mapped
                            //correctly to disk. Otherwise, there is likely
                            //no path mapping for a path starting with '..'.
                            //This can still fail, but catches the most reasonable
                            //uses of ..
                            break;
                        } else if (i > 0) {
                            name.splice(i - 1, 2);
                            i -= 2;
                        }
                    }
                }
                //end trimDots

                name = name.join("/");
            } else if (name.indexOf('./') === 0) {
                // No baseName, so this is ID is resolved relative
                // to baseUrl, pull off the leading dot.
                name = name.substring(2);
            }
        }

        //Apply map config if available.
        if ((baseParts || starMap) && map) {
            nameParts = name.split('/');

            for (i = nameParts.length; i > 0; i -= 1) {
                nameSegment = nameParts.slice(0, i).join("/");

                if (baseParts) {
                    //Find the longest baseName segment match in the config.
                    //So, do joins on the biggest to smallest lengths of baseParts.
                    for (j = baseParts.length; j > 0; j -= 1) {
                        mapValue = map[baseParts.slice(0, j).join('/')];

                        //baseName segment has  config, find if it has one for
                        //this name.
                        if (mapValue) {
                            mapValue = mapValue[nameSegment];
                            if (mapValue) {
                                //Match, update name to the new value.
                                foundMap = mapValue;
                                foundI = i;
                                break;
                            }
                        }
                    }
                }

                if (foundMap) {
                    break;
                }

                //Check for a star map match, but just hold on to it,
                //if there is a shorter segment match later in a matching
                //config, then favor over this star map.
                if (!foundStarMap && starMap && starMap[nameSegment]) {
                    foundStarMap = starMap[nameSegment];
                    starI = i;
                }
            }

            if (!foundMap && foundStarMap) {
                foundMap = foundStarMap;
                foundI = starI;
            }

            if (foundMap) {
                nameParts.splice(0, foundI, foundMap);
                name = nameParts.join('/');
            }
        }

        return name;
    }

    function makeRequire(relName, forceSync) {
        return function () {
            //A version of a require function that passes a moduleName
            //value for items that may need to
            //look up paths relative to the moduleName
            var args = aps.call(arguments, 0);

            //If first arg is not require('string'), and there is only
            //one arg, it is the array form without a callback. Insert
            //a null so that the following concat is correct.
            if (typeof args[0] !== 'string' && args.length === 1) {
                args.push(null);
            }
            return req.apply(undef, args.concat([relName, forceSync]));
        };
    }

    function makeNormalize(relName) {
        return function (name) {
            return normalize(name, relName);
        };
    }

    function makeLoad(depName) {
        return function (value) {
            defined[depName] = value;
        };
    }

    function callDep(name) {
        if (hasProp(waiting, name)) {
            var args = waiting[name];
            delete waiting[name];
            defining[name] = true;
            main.apply(undef, args);
        }

        if (!hasProp(defined, name) && !hasProp(defining, name)) {
            throw new Error('No ' + name);
        }
        return defined[name];
    }

    //Turns a plugin!resource to [plugin, resource]
    //with the plugin being undefined if the name
    //did not have a plugin prefix.
    function splitPrefix(name) {
        var prefix,
            index = name ? name.indexOf('!') : -1;
        if (index > -1) {
            prefix = name.substring(0, index);
            name = name.substring(index + 1, name.length);
        }
        return [prefix, name];
    }

    /**
     * Makes a name map, normalizing the name, and using a plugin
     * for normalization if necessary. Grabs a ref to plugin
     * too, as an optimization.
     */
    makeMap = function (name, relName) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0];

        name = parts[1];

        if (prefix) {
            prefix = normalize(prefix, relName);
            plugin = callDep(prefix);
        }

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relName));
            } else {
                name = normalize(name, relName);
            }
        } else {
            name = normalize(name, relName);
            parts = splitPrefix(name);
            prefix = parts[0];
            name = parts[1];
            if (prefix) {
                plugin = callDep(prefix);
            }
        }

        //Using ridiculous property names for space reasons
        return {
            f: prefix ? prefix + '!' + name : name, //fullName
            n: name,
            pr: prefix,
            p: plugin
        };
    };

    function makeConfig(name) {
        return function () {
            return (config && config.config && config.config[name]) || {};
        };
    }

    handlers = {
        require: function (name) {
            return makeRequire(name);
        },
        exports: function (name) {
            var e = defined[name];
            if (typeof e !== 'undefined') {
                return e;
            } else {
                return (defined[name] = {});
            }
        },
        module: function (name) {
            return {
                id: name,
                uri: '',
                exports: defined[name],
                config: makeConfig(name)
            };
        }
    };

    main = function (name, deps, callback, relName) {
        var cjsModule, depName, ret, map, i,
            args = [],
            callbackType = typeof callback,
            usingExports;

        //Use name if no relName
        relName = relName || name;

        //Call the callback to define the module, if necessary.
        if (callbackType === 'undefined' || callbackType === 'function') {
            //Pull out the defined dependencies and pass the ordered
            //values to the callback.
            //Default to [require, exports, module] if no deps
            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
            for (i = 0; i < deps.length; i += 1) {
                map = makeMap(deps[i], relName);
                depName = map.f;

                //Fast path CommonJS standard dependencies.
                if (depName === "require") {
                    args[i] = handlers.require(name);
                } else if (depName === "exports") {
                    //CommonJS module spec 1.1
                    args[i] = handlers.exports(name);
                    usingExports = true;
                } else if (depName === "module") {
                    //CommonJS module spec 1.1
                    cjsModule = args[i] = handlers.module(name);
                } else if (hasProp(defined, depName) ||
                           hasProp(waiting, depName) ||
                           hasProp(defining, depName)) {
                    args[i] = callDep(depName);
                } else if (map.p) {
                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
                    args[i] = defined[depName];
                } else {
                    throw new Error(name + ' missing ' + depName);
                }
            }

            ret = callback ? callback.apply(defined[name], args) : undefined;

            if (name) {
                //If setting exports via "module" is in play,
                //favor that over return value and exports. After that,
                //favor a non-undefined return value over exports use.
                if (cjsModule && cjsModule.exports !== undef &&
                        cjsModule.exports !== defined[name]) {
                    defined[name] = cjsModule.exports;
                } else if (ret !== undef || !usingExports) {
                    //Use the return value from the function.
                    defined[name] = ret;
                }
            }
        } else if (name) {
            //May just be an object definition for the module. Only
            //worry about defining if have a module name.
            defined[name] = callback;
        }
    };

    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
        if (typeof deps === "string") {
            if (handlers[deps]) {
                //callback in this case is really relName
                return handlers[deps](callback);
            }
            //Just return the module wanted. In this scenario, the
            //deps arg is the module name, and second arg (if passed)
            //is just the relName.
            //Normalize module name, if it contains . or ..
            return callDep(makeMap(deps, callback).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            if (config.deps) {
                req(config.deps, config.callback);
            }
            if (!callback) {
                return;
            }

            if (callback.splice) {
                //callback is an array, which means it is a dependency list.
                //Adjust args if there are dependencies
                deps = callback;
                callback = relName;
                relName = null;
            } else {
                deps = undef;
            }
        }

        //Support require(['a'])
        callback = callback || function () {};

        //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }

        return req;
    };

    /**
     * Just drops the config on the floor, but returns req in case
     * the config return value is used.
     */
    req.config = function (cfg) {
        return req(cfg);
    };

    /**
     * Expose module registry for debugging and tooling
     */
    requirejs._defined = defined;

    define = function (name, deps, callback) {

        //This module may not have dependencies
        if (!deps.splice) {
            //deps is not an array, so probably means
            //an object literal or factory function for
            //the value. Adjust args.
            callback = deps;
            deps = [];
        }

        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
            waiting[name] = [name, deps, callback];
        }
    };

    define.amd = {
        jQuery: true
    };
}());

define("../tools/almond", function(){});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AbstractError
 */
define('error/AbstractError',[],function () {
    "use strict";

    /**
     * Constructs an error with a specified name and message.
     * @alias AbstractError
     * @constructor
     * @abstract
     * @classdesc Provides an abstract base class for error classes. This class is not meant to be instantiated
     * directly but used only by subclasses.
     * @param {String} name The error's name.
     * @param {String} message The message.
     */
    var AbstractError = function (name, message) {
        this.name = name;
        this.message = message;
    };

    /**
     * Returns the message and stack trace associated with this error.
     * @returns {String} The message and stack trace associated with this error.
     */
    AbstractError.prototype.toString = function () {
        var str = this.name + ': ' + this.message;

        if (this.stack) {
            str += '\n' + this.stack.toString();
        }

        return str;
    };

    return AbstractError;

});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('geom/Angle',[], function () {
    "use strict";
    /**
     * Provides constants and functions for working with angles.
     * @exports Angle
     */

    var Angle = {
        /**
         * Conversion factor for degrees to radians.
         * @constant
         */
        DEGREES_TO_RADIANS: Math.PI / 180.0,
        /**
         * Conversion factor for radians to degrees.
         * @constant
         */
        RADIANS_TO_DEGREES: 180.0 / Math.PI,
        /**
         * 2 pi.
         * @constant
         */
        TWO_PI: 2 * Math.PI,
        /**
         * pi / 2
         * @constant
         */
        HALF_PI: Math.PI / 2,

        /**
         * Normalizes a specified value to be within the range of [-180, 180] degrees.
         * @param {Number} degrees The value to normalize, in degrees.
         * @returns {Number} The specified value normalized to [-180, 180] degrees.
         */
        normalizedDegrees: function (degrees) {
            var angle = degrees % 360;

            return angle > 180 ? angle - 360 : angle < -180 ? 360 + angle : angle;
        },

        /**
         * Normalizes a specified value to be within the range of [-90, 90] degrees.
         * @param {Number} degrees The value to normalize, in degrees.
         * @returns {Number} The specified value normalized to the normal range of latitude.
         */
        normalizedDegreesLatitude: function (degrees) {
            var lat = degrees % 180;

            return lat > 90 ? 180 - lat : lat < -90 ? -180 - lat : lat;
        },

        /**
         * Normalizes a specified value to be within the range of [-180, 180] degrees.
         * @param {Number} degrees The value to normalize, in degrees.
         * @returns {Number} The specified value normalized to the normal range of longitude.
         */
        normalizedDegreesLongitude: function (degrees) {
            var lon = degrees % 360;

            return lon > 180 ? lon - 360 : lon < -180 ? 360 + lon : lon;
        },

        /**
         * Normalizes a specified value to be within the range of [-Pi, Pi] radians.
         * @param {Number} radians The value to normalize, in radians.
         * @returns {Number} The specified value normalized to [-Pi, Pi] radians.
         */
        normalizedRadians: function (radians) {
            var angle = radians % this.TWO_PI;

            return angle > Math.PI ? angle - this.TWO_PI : angle < -Math.PI ? this.TWO_PI + angle : angle;
        },

        /**
         * Normalizes a specified value to be within the range of [-Pi/2, Pi/2] radians.
         * @param {Number} radians The value to normalize, in radians.
         * @returns {Number} The specified value normalized to the normal range of latitude.
         */
        normalizedRadiansLatitude: function (radians) {
            var lat = radians % Math.PI;

            return lat > this.HALF_PI ? Math.PI - lat : lat < -this.HALF_PI ? -Math.PI - lat : lat;
        },

        /**
         * Normalizes a specified value to be within the range of [-Pi, Pi] radians.
         * @param {Number} radians The value to normalize, in radians.
         * @returns {Number} The specified value normalized to the normal range of longitude.
         */
        normalizedRadiansLongitude: function (radians) {
            var lon = radians % this.TWO_PI;

            return lon > Math.PI ? lon - this.TWO_PI : lon < -Math.PI ? this.TWO_PI + lon : lon;
        },

        /**
         * Indicates whether a specified value is within the normal range of latitude, [-90, 90].
         * @param {Number} degrees The value to test, in degrees.
         * @returns {Boolean} true if the value is within the normal range of latitude, otherwise false.
         */
        isValidLatitude: function (degrees) {
            return degrees >= -90 && degrees <= 90;
        },

        /**
         * Indicates whether a specified value is within the normal range of longitude, [-180, 180].
         * @param {Number} degrees The value to test, in degrees.
         * @returns {boolean} true if the value is within the normal range of longitude, otherwise false.
         */
        isValidLongitude: function (degrees) {
            return degrees >= -180 && degrees <= 180;
        },

        /**
         * Returns a string representation of a specified value in degrees.
         * @param {Number} degrees The value for which to compute the string.
         * @returns {String} The computed string, which is a decimal degrees value followed by the degree symbol.
         */
        toString: function (degrees) {
            return degrees.toString() + '\u00B0';
        },

        /**
         * Returns a decimal degrees string representation of a specified value in degrees.
         * @param {Number} degrees The value for which to compute the string.
         * @returns {String} The computed string, which is a decimal degrees value followed by the degree symbol.
         */
        toDecimalDegreesString: function (degrees) {
            return degrees.toString() + '\u00B0';
        },

        /**
         * Returns a degrees-minutes-seconds string representation of a specified value in degrees.
         * @param {Number} degrees The value for which to compute the string.
         * @returns {String} The computed string in degrees, minutes and decimal seconds.
         */
        toDMSString: function (degrees) {
            var sign,
                temp,
                d,
                m,
                s;

            sign = degrees < 0 ? -1 : 1;
            temp = sign * degrees;
            d = Math.floor(temp);
            temp = (temp - d) * 60;
            m = Math.floor(temp);
            temp = (temp - m) * 60;
            s = Math.round(temp);

            if (s == 60) {
                m++;
                s = 0;
            }
            if (m == 60) {
                d++;
                m = 0;
            }

            return (sign == -1 ? "-" : "") + d + "\u00B0" + " " + m + "\u2019" + " " + s + "\u201D";
        },

        /**
         * Returns a degrees-minutes string representation of a specified value in degrees.
         * @param {Number} degrees The value for which to compute the string.
         * @returns {String} The computed string in degrees and decimal minutes.
         */
        toDMString: function (degrees) {
            var sign,
                temp,
                d,
                m,
                s,
                mf;

            sign = degrees < 0 ? -1 : 1;
            temp = sign * degrees;
            d = Math.floor(temp);
            temp = (temp - d) * 60;
            m = Math.floor(temp);
            temp = (temp - m) * 60;
            s = Math.round(temp);

            if (s == 60) {
                m++;
                s = 0;
            }
            if (m == 60) {
                d++;
                m = 0;
            }

            mf = s == 0 ? m : m + s / 60;

            return (sign == -1 ? "-" : "") + d + "\u00B0" + " " + mf + "\u2019";
        }
    };

    return Angle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/Logger',[],function () {
    "use strict";
    /**
     * Logs selected message types to the console.
     * @exports Logger
     */

    var Logger = {
        /**
         * Log no messages.
         * @constant
         */
        LEVEL_NONE: 0,
        /**
         * Log messages marked as severe.
         * @constant
         */
        LEVEL_SEVERE: 1,
        /**
         * Log messages marked as warnings and messages marked as severe.
         * @constant
         */
        LEVEL_WARNING: 2,
        /**
         * Log messages marked as information, messages marked as warnings and messages marked as severe.
         * @constant
         */
        LEVEL_INFO: 3,

        /**
         * Set the logging level used by subsequent invocations of the logger.
         * @param {Number} level The logging level, one of Logger.LEVEL_NONE, Logger.LEVEL_SEVERE, Logger.LEVEL_WARNING,
         * or Logger.LEVEL_INFO.
         */
        setLoggingLevel: function (level) {
            loggingLevel = level;
        },

        /**
         * Indicates the current logging level.
         * @returns {Number} The current logging level.
         */
        getLoggingLevel: function () {
            return loggingLevel;
        },

        /**
         * Logs a specified message at a specified level.
         * @param {Number} level The logging level of the message. If the current logging level allows this message to be
         * logged it is written to the console.
         * @param {String} message The message to log. Nothing is logged if the message is null or undefined.
         */
        log: function (level, message) {
            if (message && level > 0 && level <= loggingLevel) {
                if (level === Logger.LEVEL_SEVERE) {
                    console.error(message);
                } else if (level === Logger.LEVEL_WARNING) {
                    console.warn(message);
                } else if (level === Logger.LEVEL_INFO) {
                    console.info(message);
                } else {
                    console.log(message);
                }
            }
        },

        // Intentionally not documented.
        makeMessage: function (className, functionName, message) {
            var msg = this.messageTable[message] ? this.messageTable[message] : message;

            return className + "." + functionName + ": " + msg;
        },

        // Intentionally not documented.
        logMessage: function (level, className, functionName, message) {
            var msg = this.makeMessage(className, functionName, message);
            this.log(level, msg);

            return msg;
        },

        // Intentionally not documented.
        messageTable: { // KEEP THIS TABLE IN ALPHABETICAL ORDER
            abstractInvocation: "The function called is abstract and must be overridden in a subclass.",
            indexOutOfRange: "The specified index is out of range.",
            invalidHeight: "The specified height is zero or negative.",
            invalidWidth: "The specified width is zero or negative.",
            invalidSize: "The specified size is zero or negative.",
            missingAltitudeMode: "The specified altitude mode is null or undefined.",
            missingAttributeName: "The specified DBase attribute file name is null or undefined.",
            missingArray: "The specified array is null, undefined or of insufficient length.",
            missingBoundaries: "The specified boundaries array is null or undefined.",
            missingBuffer: "The specified buffer descriptor is null or undefined.",
            missingColor: "The specified color is null or undefined.",
            missingDc: "The specified draw context is null or undefined.",
            missingDomElement: "The specified DOM element is null or undefined.",
            missingEntry: "The specified entry is null or undefined.",
            missingFont: "The specified font is null or undefined.",
            missingFrustum: "The specified frustum is null or undefined.",
            missingFunction: "The specified function is null or undefined.",
            missingGlContext: "The specified WebGL rendering context is null or undefined.",
            missingGlobe: "The specified globe is null or undefined.",
            missingImage: "The specified image is null or undefined.",
            missingIndices: "The specified indices array is null or undefined.",
            missingKey: "The specified key is null or undefined.",
            missingLevel: "The specified level is null or undefined.",
            missingLine: "The specified line is null or undefined.",
            missingList: "The specified list is null or undefined.",
            missingListener: "The specified listener is null or undefined",
            missingLocation: "The specified location is null or undefined.",
            missingMatrix: "The specified matrix is null or undefined.",
            missingNavigatorState: "The specified navigator state is null or undefined.",
            missingOffset: "The specified offset is null or undefined.",
            missingPath: "The specified path is null or undefined.",
            missingPlacename: "The specified place name is null or undefined.",
            missingPlane: "The specified plane is null or undefined.",
            missingPoint: "The specified point is null or undefined.",
            missingPoints: "The specified points array is null or undefined.",
            missingPosition: "The specified position is null or undefined.",
            missingPositions: "The specified positions array is null or undefined.",
            missingProgram: "The specified program is null or undefined.",
            missingProjection: "The specified projection is null or undefined.",
            missingRectangle: "The specified rectangle is null or undefined.",
            missingRenderable: "The specified renderable is null or undefined.",
            missingResource: "The specified resource is null or undefined.",
            missingResult: "The specified result variable is null or undefined.",
            missingResults: "The specified results array is null or undefined.",
            missingSector: "The specified sector is null or undefined.",
            missingShapeType: "The specified shape type is null or undefined.",
            missingSize: "The specified size is null or undefined.",
            missingText: "The specified text is null or undefined.",
            missingTexture: "The specified texture is null or undefined.",
            missingTile: "The specified tile is null or undefined.",
            missingType: "The specified type is null or undefined.",
            missingUrl: "The specified URL is null or undefined",
            missingVector: "The specified vector is null or undefined.",
            missingVertex: "The specified vertex is null or undefined.",
            missingViewport: "The specified viewport is null or undefined.",
            missingWorldWindow: "The specified WorldWindow is null or undefined.",
            notYetImplemented: "This function is not yet implemented"
        }
    };

    var loggingLevel = 1; // log severe messages by default

    return Logger;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Color
 */
define('util/Color',[
        '../util/Logger'
    ],
    function (Logger) {
        "use strict";

        /**
         * Constructs a color from red, green, blue and alpha values.
         * @alias Color
         * @constructor
         * @classdesc Represents a red, green, blue, alpha, color.
         * @param {Number} red The red component, a number between 0 and 1.
         * @param {Number} green The green component, a number between 0 and 1.
         * @param {Number} blue The blue component, a number between 0 and 1.
         * @param {Number} alpha The alpha component, a number between 0 and 1.
         */
        var Color = function (red, green, blue, alpha) {

            /**
             * This color's red component, a number between 0 and 1.
             * @type {Number}
             */
            this.red = red;

            /**
             * This color's green component, a number between 0 and 1.
             * @type {Number}
             */
            this.green = green;

            /**
             * This color's blue component, a number between 0 and 1.
             * @type {Number}
             */
            this.blue = blue;

            /**
             * This color's alpha component, a number between 0 and 1.
             * @type {Number}
             */
            this.alpha = alpha;
        };

        /**
         * The color white.
         * @type {Color}
         * @constant
         */
        Color.WHITE = new Color(1, 1, 1, 1);

        /**
         * The color black.
         * @type {Color}
         * @constant
         */
        Color.BLACK = new Color(0, 0, 0, 1);

        /**
         * The color red.
         * @type {Color}
         * @constant
         */
        Color.RED = new Color(1, 0, 0, 1);

        /**
         * The color green.
         * @type {Color}
         * @constant
         */
        Color.GREEN = new Color(0, 1, 0, 1);

        /**
         * The color blue.
         * @type {Color}
         * @constant
         */
        Color.BLUE = new Color(0, 0, 1, 1);

        /**
         * The color cyan.
         * @type {Color}
         * @constant
         */
        Color.CYAN = new Color(0, 1, 1, 1);

        /**
         * The color yellow.
         * @type {Color}
         * @constant
         */
        Color.YELLOW = new Color(1, 1, 0, 1);

        /**
         * The color magenta.
         * @type {Color}
         * @constant
         */
        Color.MAGENTA = new Color(1, 0, 1, 1);

        /**
         * A light gray (75% white).
         * @type {Color}
         */
        Color.LIGHT_GRAY = new Color(0.75, 0.75, 0.75, 1);

        /**
         * A medium gray (50% white).
         * @type {Color}
         */
        Color.MEDIUM_GRAY = new Color(0.5, 0.5, 0.5, 1);

        /**
         * A dark gray (25% white).
         * @type {Color}
         */
        Color.DARK_GRAY = new Color(0.25, 0.25, 0.25, 1);

        /**
         * A transparent color.
         * @type {Color}
         */
        Color.TRANSPARENT = new Color(0, 0, 0, 0);

        /**
         * Assigns the components of this color.
         * @param {Number} red The red component, a number between 0 and 1.
         * @param {Number} green The green component, a number between 0 and 1.
         * @param {Number} blue The blue component, a number between 0 and 1.
         * @param {Number} alpha The alpha component, a number between 0 and 1.
         * @returns {Color} This color with the specified components assigned.
         */
        Color.prototype.set = function (red, green, blue, alpha) {
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.alpha = alpha;

            return this;
        };

        /**
         * Copies the components of a specified color to this color.
         * @param {Color} color The color to copy.
         * @returns {Color} This color set to the red, green, blue and alpha values of the specified color.
         * @throws {ArgumentError} If the specified color is null or undefined.
         */
        Color.prototype.copy = function (color) {
            if (!color) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Color", "copy", "missingColor"));
            }

            this.red = color.red;
            this.green = color.green;
            this.blue = color.blue;
            this.alpha = color.alpha;

            return this;
        };

        /**
         * Create a copy of this color.
         * @returns {Color} A new instance containing the color components of this color.
         */
        Color.prototype.clone = function () {
            return new Color(this.red, this.green, this.blue, this.alpha);
        };

        /**
         * Returns this color's components premultiplied by this color's alpha component.
         * @param {Float32Array} array A pre-allocated array in which to return the color components.
         * @returns {Float32Array} This colors premultiplied components as an array, in the order RGBA.
         */
        Color.prototype.premultipliedComponents = function (array) {
            var a = this.alpha;

            array[0] = this.red * a;
            array[1] = this.green * a;
            array[2] = this.blue * a;
            array[3] = a;

            return array;
        };

        /**
         * Construct a color from an array of color components expressed as byte values.
         * @param {Uint8Array} bytes A four-element array containing the red, green, blue and alpha color
         * components each in the range [0, 255];
         * @returns {Color} The constructed color.
         */
        Color.colorFromByteArray = function (bytes) {
            return new Color(bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255);
        };

        /**
         * Construct a color from specified color components expressed as byte values.
         * @param {number} redByte The red component in the range [0, 255].
         * @param {number} greenByte The green component in the range [0, 255].
         * @param {number} blueByte The blue component in the range [0, 255].
         * @param {number} alphaByte The alpha component in the range [0, 255].
         * @returns {Color} The constructed color.
         */
        Color.colorFromBytes = function (redByte, greenByte, blueByte, alphaByte) {
            return new Color(redByte / 255, greenByte / 255, blueByte / 255, alphaByte / 255);
        };

        Color.colorFromHex = function(color) {
            var red = parseInt(color.substring(0, 2), 16);
            var green = parseInt(color.substring(2, 4), 16);
            var blue = parseInt(color.substring(4, 6), 16);
            var alpha = parseInt(color.substring(6, 8), 16);
            return Color.colorFromBytes(red, green, blue, alpha);
        };

        Color.colorFromKmlHex = function(color) {
            var alpha = parseInt(color.substring(0, 2), 16);
            var blue = parseInt(color.substring(2, 4), 16);
            var green = parseInt(color.substring(4, 6), 16);
            var red = parseInt(color.substring(6, 8), 16);
            return Color.colorFromBytes(red, green, blue, alpha);
        };

        /**
         * Computes and sets this color to the next higher RBG color. If the color overflows, this color is set to
         * (1 / 255, 0, 0, *), where * indicates the current alpha value.
         * @returns {Color} This color, set to the next possible color.
         */
        Color.prototype.nextColor = function () {
            var rb = Math.round(this.red * 255),
                gb = Math.round(this.green * 255),
                bb = Math.round(this.blue * 255);

            if (rb < 255) {
                this.red = (rb + 1) / 255;
            } else if (gb < 255) {
                this.red = 0;
                this.green = (gb + 1) / 255;
            } else if (bb < 255) {
                this.red = 0;
                this.green = 0;
                this.blue = (bb + 1) / 255;
            } else {
                this.red = 1 / 255;
                this.green = 0;
                this.blue = 0;
            }

            return this;
        };

        /**
         * Indicates whether this color is equal to a specified color after converting the floating-point component
         * values of each color to byte values.
         * @param {Color} color The color to test,
         * @returns {Boolean} true if the colors are equal, otherwise false.
         */
        Color.prototype.equals = function (color) {
            var rbA = Math.round(this.red * 255),
                gbA = Math.round(this.green * 255),
                bbA = Math.round(this.blue * 255),
                abA = Math.round(this.alpha * 255),
                rbB = Math.round(color.red * 255),
                gbB = Math.round(color.green * 255),
                bbB = Math.round(color.blue * 255),
                abB = Math.round(color.alpha * 255);

            return rbA === rbB && gbA === gbB && bbA === bbB && abA === abB;
        };

        /**
         * Indicates whether this color is equal to another color expressed as an array of bytes.
         * @param {Uint8Array} bytes The red, green, blue and alpha color components.
         * @returns {Boolean} true if the colors are equal, otherwise false.
         */
        Color.prototype.equalsBytes = function (bytes) {
            var rb = Math.round(this.red * 255),
                gb = Math.round(this.green * 255),
                bb = Math.round(this.blue * 255),
                ab = Math.round(this.alpha * 255);

            return rb === bytes[0] && gb === bytes[1] && bb === bytes[2] && ab === bytes[3];
        };

        /**
         * Returns a string representation of this color, indicating the byte values corresponding to this color's
         * floating-point component values.
         * @returns {String}
         */
        Color.prototype.toByteString = function () {
            var rb = Math.round(this.red * 255),
                gb = Math.round(this.green * 255),
                bb = Math.round(this.blue * 255),
                ab = Math.round(this.alpha * 255);

            return "(" + rb + "," + gb + "," + bb + "," + ab + ")";
        };

        /**
         * Create a hex color string that CSS can use. Optionally, inhibit capturing alpha,
         * because some uses reject a four-component color specification.
         * @param {Boolean} isUsingAlpha Enable the use of an alpha component.
         * @returns {string} A color string suitable for CSS.
         */
        Color.prototype.toHexString = function(isUsingAlpha) {
            // Use Math.ceil() to get 0.75 to map to 0xc0. This is important if the display is dithering.
            var redHex = Math.ceil(this.red * 255).toString(16),
                greenHex = Math.ceil(this.green * 255).toString(16),
                blueHex = Math.ceil(this.blue * 255).toString(16),
                alphaHex = Math.ceil(this.alpha * 255).toString(16);

            var result = "#";
            result += (redHex.length < 2) ? ('0' + redHex) : redHex;
            result += (greenHex.length < 2) ? ('0' + greenHex) : greenHex;
            result += (blueHex.length < 2) ? ('0' + blueHex) : blueHex;
            if (isUsingAlpha) {
                result += (alphaHex.length < 2) ? ('0' + alphaHex) : alphaHex;
            }

            return result;
        };

        /**
         * Create a rgba color string that CSS can use.
         * @returns {string} A color string suitable for CSS.
         */
        Color.prototype.toRGBAString = function () {
            var red = Math.floor(this.red * 255),
                green = Math.floor(this.green * 255),
                blue = Math.floor(this.blue * 255);

            return 'rgba(' + red + ' ,' + green + ' ,' + blue + ' ,' + this.alpha + ')';
        };

        return Color;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ArgumentError
 */
define('error/ArgumentError',['../error/AbstractError'],
    function (AbstractError) {
        "use strict";

        /**
         * Constructs an argument error with a specified message.
         * @alias ArgumentError
         * @constructor
         * @classdesc Represents an error associated with invalid function arguments.
         * @augments AbstractError
         * @param {String} message The message.
         */
        var ArgumentError = function (message) {
            AbstractError.call(this, "ArgumentError", message);

            var stack;
            try {
                //noinspection ExceptionCaughtLocallyJS
                throw new Error();
            } catch (e) {
                stack = e.stack;
            }
            this.stack = stack;
        };

        ArgumentError.prototype = Object.create(AbstractError.prototype);

        return ArgumentError;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Font
 */
define('util/Font',[
        '../error/ArgumentError',
        '../util/Color',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              Logger) {
        "use strict";

        /**
         * Construct a font descriptor. See the individual attribute descriptions below for possible parameter values.
         * @param {Number} size The size of font.
         * @param {String} style The style of the font.
         * @param {String} variant The variant of the font.
         * @param {String} weight The weight of the font.
         * @param {String} family The family of the font.
         * @param {String} horizontalAlignment The vertical alignment of the font.
         * @alias Font
         * @constructor
         * @classdesc Holds attributes controlling the style, size and other attributes of {@link Text} shapes and
         * the textual features of {@link Placemark} and other shapes. The values used for these attributes are those
         * defined by the [CSS Font property]{@link http://www.w3schools.com/cssref/pr_font_font.asp}.
         */
        var Font = function (size, style, variant, weight, family, horizontalAlignment) {
            /*
             * All properties of Font are intended to be private and must be accessed via public getters and setters.
             */

            if (!size) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Font", "constructor",
                    "missingSize"));
            }
            else if (size <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Font", "constructor",
                    "invalidSize"));
            }
            else {
                this._size = size;
            }

            this.style = style || "normal";
            this.variant = variant || "normal";
            this.weight = weight || "normal";
            this.family = family || "sans-serif";
            this.horizontalAlignment = horizontalAlignment || "center";
        };

        Object.defineProperties(Font.prototype, {
            /**
             * The font size.
             * @memberof Font.prototype
             * @type Number
             */
            size: {
                get: function () {
                    return this._size;
                },
                set: function (value) {
                    this._fontString = null;
                    this._size = value;
                }
            },
            /**
             * The font style.
             * See [CSS font-style]{@link http://www.w3schools.com/cssref/pr_font_font-style.asp} for defined values.
             * @memberof Font.prototype
             * @type {String}
             * @default "normal"
             */
            style: {
                get: function () {
                    return this._style;
                },
                set: function (value) {
                    this._fontString = null;
                    this._style = value;
                }
            },
            /**
             * The font variant.
             * See [CSS font-variant]{@link http://www.w3schools.com/cssref/pr_font_font-variant.asp} for defined values.
             * @memberof Font.prototype
             * @type {String}
             * @default "normal"
             */
            variant: {
                get: function () {
                    return this._variant;
                },
                set: function (value) {
                    this._fontString = null;
                    this._variant = value;
                }
            },
            /**
             * The font weight.
             * See [CSS font-weight]{@link http://www.w3schools.com/cssref/pr_font_weight.asp} for defined values.
             * @memberof Font.prototype
             * @type {String}
             * @default "normal"
             */
            weight: {
                get: function () {
                    return this._weight;
                },
                set: function (value) {
                    this._fontString = null;
                    this._weight = value;
                }
            },
            /**
             * The font family.
             * See [CSS font-family]{@link http://www.w3schools.com/cssref/pr_font_font-family.asp} for defined values.
             * @memberof Font.prototype
             * @type {String}
             * @default "sans-serif"
             */
            family: {
                get: function () {
                    return this._family;
                },
                set: function (value) {
                    this._fontString = null;
                    this._family = value;
                }
            },
            /**
             * The horizontal alignment of the font.
             * Recognized values are "left", "center" and "right".
             * @memberof Font.prototype
             * @type {String}
             * @default "center"
             */
            horizontalAlignment: {
                get: function () {
                    return this._horizontalAlignment;
                },
                set: function (value) {
                    this._toString = null;
                    this._horizontalAlignment = value;
                }
            },

            /**
             * A string representing this font's style, weight, size and family properties, suitable for
             * passing directly to a 2D canvas context.
             * @memberof Font.prototype
             */
            fontString: {
                get: function () {
                    if (!this._fontString) {
                        this._fontString =
                            this._style + " " +
                            this.variant + " " +
                            this._weight + " " +
                            this._size.toString() + "px " +
                            this._family;
                    }
                    return this._fontString;
                }
            }
        });

        /**
         * Returns a string representation of this object.
         * @returns {String} A string representation of this object.
         */
        Font.prototype.toString = function () {
            if (!this._toString || !this._fontString) {
                this._toString = this.fontString + " " + this.horizontalAlignment;
            }
            return this._toString;
        };

        return Font;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/Insets',['../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs an Insets object that is a representation of the borders of a container.
         * It specifies the space that a container must leave at each of its edges.
         * @alias Insets
         * @param {Number} top The inset from the top.
         * @param {Number} left The inset from the left.
         * @param {Number} bottom The inset from the bottom.
         * @param {Number} right The inset from the right.
         * @constructor
         */
        var Insets = function (top, left, bottom, right) {

            if (arguments.length !== 4) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Insets", "constructor", "invalidArgumentCount"));
            }

            // These are all documented with their property accessors below.
            this._top = top;
            this._left = left;
            this._bottom = bottom;
            this._right = right;
        };

        /**
         * Set top, left, bottom, and right to the specified values.
         * @param {Number} top The inset from the top.
         * @param {Number} left The inset from the left.
         * @param {Number} bottom The inset from the bottom.
         * @param {Number} right The inset from the right.
         */
        Insets.prototype.set = function (top, left, bottom, right) {
            this._top = top;
            this._left = left;
            this._bottom = bottom;
            this._right = right;
        };

        /**
         * Creates a new copy of this insets with identical property values.
         * @returns {Insets} A new insets instance with its property values the same as this one's.
         */
        Insets.prototype.clone = function () {
            return new Insets(this._top, this._left, this._bottom, this._right);
        };

        /**
         * Returns a string representation of this object.
         * @returns {String} A string representation of this object.
         */
        Insets.prototype.toString = function () {
            return this._top + " " + this._left + " " + this._bottom + " " + this._right;
        };

        Object.defineProperties(Insets.prototype, {

            /**
             * Indicates the the inset from the top.
             * @type {Number}
             * @memberof Insets.prototype
             */
            top: {
                get: function () {
                    return this._top;
                },
                set: function (value) {
                    this._top = value;
                }
            },

            /**
             * Indicates the the inset from the left.
             * @type {Number}
             * @memberof Insets.prototype
             */
            left: {
                get: function () {
                    return this._left;
                },
                set: function (value) {
                    this._left = value;
                }
            },

            /**
             * Indicates the the inset from the bottom.
             * @type {Number}
             * @memberof Insets.prototype
             */
            bottom: {
                get: function () {
                    return this._bottom;
                },
                set: function (value) {
                    this._bottom = value;
                }
            },

            /**
             * Indicates the the inset from the right.
             * @type {Number}
             * @memberof Insets.prototype
             */
            right: {
                get: function () {
                    return this._right;
                },
                set: function (value) {
                    this._right = value;
                }
            }

        });

        return Insets;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('geom/Vec3',[
        '../util/Logger',
        '../error/ArgumentError'
    ],
    function (Logger,
              ArgumentError) {
        "use strict";

        /**
         * Constructs a three-component vector.
         * @alias Vec3
         * @classdesc Represents a three-component vector. Access the X component of the vector as v[0], the Y
         * component as v[1] and the Z component as v[2].
         * @augments Float64Array
         * @param {Number} x X component of vector.
         * @param {Number} y Y component of vector.
         * @param {Number} z Z component of vector.
         * @constructor
         */
        var Vec3 = function Vec3(x, y, z) {
            this[0] = x;
            this[1] = y;
            this[2] = z;
        };

        // Vec3 extends Float64Array.
        Vec3.prototype = new Float64Array(3);

        /**
         * A vector corresponding to the origin.
         * @type {Vec3}
         */
        Vec3.ZERO = new Vec3(0, 0, 0);

        /**
         * Computes the average of a specified array of vectors.
         * @param {Vec3[]} vectors The vectors whose average to compute.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed average.
         * @returns {Vec3} The result argument set to the average of the specified array of vectors.
         * @throws {ArgumentError} If the specified array of vectors is null, undefined or empty or the specified
         * result argument is null or undefined.
         */
        Vec3.average = function (vectors, result) {
            if (!vectors || vectors.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "average", "missingArray"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "average", "missingResult"));
            }

            var count = vectors.length,
                vec;

            result[0] = 0;
            result[1] = 0;
            result[2] = 0;

            for (var i = 0, len = vectors.length; i < len; i++) {
                vec = vectors[i];

                result[0] += vec[0] / count;
                result[1] += vec[1] / count;
                result[2] += vec[2] / count;
            }

            return result;
        };

        /**
         * Computes the average of a specified array of points packed into a single array.
         * @param {Float32Array | Float64Array | Number[]} points The points whose average to compute.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed average.
         * @returns {Vec3} The result argument set to the average of the specified array of points.
         * @throws {ArgumentError} If the specified array of points is null, undefined or empty or the result argument
         * is null or undefined.
         */
        Vec3.averageOfBuffer = function (points, result) {
            if (!points || points.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "averageBuffer", "missingArray"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "averageBuffer", "missingResult"));
            }

            var count = points.length / 3;

            result[0] = 0;
            result[1] = 0;
            result[2] = 0;

            for (var i = 0; i < count; i++) {
                result[0] += points[i * 3] / count;
                result[1] += points[i * 3 + 1] / count;
                result[2] += points[i * 3 + 2] / count;
            }

            return result;
        };

        /**
         * Indicates whether three vectors are colinear.
         * @param {Vec3} a The first vector.
         * @param {Vec3} b The second vector.
         * @param {Vec3} c The third vector.
         * @returns {Boolean} true if the vectors are colinear, otherwise false.
         * @throws {ArgumentError} If any of the specified vectors are null or undefined.
         */
        Vec3.areColinear = function (a, b, c) {
            if (!a || !b || !c) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "areColinear", "missingVector"));
            }

           var ab = new Vec3(a[0]-b[0],a[1]-b[1],a[2]-b[2]).normalize(),
               bc = new Vec3(c[0]-b[0],c[1]-b[1],c[2]-b[2]).normalize();

            // ab and bc are considered colinear if their dot product is near +/-1.
            return Math.abs(ab.dot(bc)) > 0.999;
        };

        /**
         * Computes the normal vector of a specified triangle.
         *
         * @param {Vec3} a The triangle's first vertex.
         * @param {Vec3} b The triangle's second vertex.
         * @param {Vec3} c The triangle's third vertex.
         * @returns {Vec3} The triangle's unit-normal vector.
         * @throws {ArgumentError} If any of the specified vectors are null or undefined.
         */
        Vec3.computeTriangleNormal = function (a, b, c) {
            if (!a || !b || !c) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "areColinear", "missingVector"));
            }

            var x = ((b[1] - a[1]) * (c[2] - a[2])) - ((b[2] - a[2]) * (c[1] - a[1])),
                y = ((b[2] - a[2]) * (c[0] - a[0])) - ((b[0] - a[0]) * (c[2] - a[2])),
                z = ((b[0] - a[0]) * (c[1] - a[1])) - ((b[1] - a[1]) * (c[0] - a[0])),
                length = (x * x) + (y * y) + (z * z);

            if (length === 0) {
                return new Vec3(x, y, z);
            }

            length = Math.sqrt(length);

            return new Vec3(x / length, y / length, z / length);
        };

        /**
         * Finds three non-colinear points in an array of coordinates.
         *
         * @param {Number[]} coords The coordinates, in the order x0, y0, z0, x1, y1, z1, ...
         * @param {Number} stride The number of numbers between successive points. 0 indicates that the points
         * are arranged one immediately after the other, as would the value 3.
         * @returns {Vec3[]} Three non-colinear points from the input array of coordinates, or null if three
         * non-colinear points could not be found or the specified coordinates array is null, undefined or
         * contains fewer than three points.
         */
        Vec3.findThreeIndependentVertices = function (coords, stride) {
            var xstride = (stride && stride > 0) ? stride : 3;

            if (!coords || coords.length < 3 * xstride) {
                return null;
            }

            var a = new Vec3(coords[0], coords[1], coords[2]),
                b = null,
                c = null,
                k = xstride;

            for (; k < coords.length; k += xstride) {
                b = new Vec3(coords[k], coords[k + 1], coords[k + 2]);
                if (!(b[0] === a[0] && b[1] === a[1] && b[2] === a[2])) {
                    break;
                }
                b = null;
            }

            if (!b) {
                return null;
            }

            for (k += xstride; k < coords.length; k += xstride) {
                c = new Vec3(coords[k], coords[k + 1], coords[k + 2]);

                // if c is not coincident with a or b, and the vectors ab and bc are not colinear, break and
                // return a, b, c.
                if (!((c[0] === a[0] && c[1] === a[1] && c[2] === a[2])
                    || (c[0] === b[0] && c[1] === b[1] && c[2] === b[2]))) {
                    if (!Vec3.areColinear(a, b, c))
                        break;
                }

                c = null;
            }

            return c ? [a, b, c] : null;
        };

        /**
         * Computes a unit-normal vector for a buffer of coordinate triples. The normal vector is computed from the
         * first three non-colinear points in the buffer.
         *
         * @param {Number[]} coords The coordinates, in the order x0, y0, z0, x1, y1, z1, ...
         * @param {Number} stride The number of numbers between successive points. 0 indicates that the points
         * are arranged one immediately after the other, as would the value 3.
         * @returns {Vec3} The computed unit-length normal vector.
         */
        Vec3.computeBufferNormal = function (coords, stride) {
            var vertices = Vec3.findThreeIndependentVertices(coords, stride);

            return vertices ? Vec3.computeTriangleNormal(vertices[0], vertices[1], vertices[2]) : null;
        };

        /**
         * Assigns the components of this vector.
         * @param {Number} x The X component of the vector.
         * @param {Number} y The Y component of the vector.
         * @param {Number} z The Z component of the vector.
         * @returns {Vec3} This vector with the specified components assigned.
         */
        Vec3.prototype.set = function (x, y, z) {
            this[0] = x;
            this[1] = y;
            this[2] = z;

            return this;
        };

        /**
         * Copies the components of a specified vector to this vector.
         * @param {Vec3} vector The vector to copy.
         * @returns {Vec3} This vector set to the X, Y and Z values of the specified vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.copy = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "copy", "missingVector"));
            }

            this[0] = vector[0];
            this[1] = vector[1];
            this[2] = vector[2];

            return this;
        };

        /**
         * Indicates whether the components of this vector are identical to those of a specified vector.
         * @param {Vec3} vector The vector to test.
         * @returns {Boolean} true if the components of this vector are equal to those of the specified one,
         * otherwise false.
         */
        Vec3.prototype.equals = function (vector) {
            return this[0] === vector[0] && this[1] === vector[1] && this[2] === vector[2];
        };

        /**
         * Adds a specified vector to this vector.
         * @param {Vec3} addend The vector to add.
         * @returns {Vec3} This vector after adding the specified vector to it.
         * @throws {ArgumentError} If the addend is null or undefined.
         */
        Vec3.prototype.add = function (addend) {
            if (!addend) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "add", "missingVector"));
            }

            this[0] += addend[0];
            this[1] += addend[1];
            this[2] += addend[2];

            return this;
        };

        /**
         * Subtracts a specified vector from this vector.
         * @param {Vec3} subtrahend The vector to subtract
         * @returns {Vec3} This vector after subtracting the specified vector from it.
         * @throws {ArgumentError} If the subtrahend is null or undefined.
         */
        Vec3.prototype.subtract = function (subtrahend) {
            if (!subtrahend) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "subtract", "missingVector"));
            }

            this[0] -= subtrahend[0];
            this[1] -= subtrahend[1];
            this[2] -= subtrahend[2];

            return this;
        };

        /**
         * Multiplies this vector by a scalar.
         * @param {Number} scalar The scalar to multiply this vector by.
         * @returns {Vec3} This vector multiplied by the specified scalar.
         */
        Vec3.prototype.multiply = function (scalar) {
            this[0] *= scalar;
            this[1] *= scalar;
            this[2] *= scalar;

            return this;
        };

        /**
         * Divides this vector by a scalar.
         * @param {Number} divisor The scalar to divide this vector by.
         * @returns {Vec3} This vector divided by the specified scalar.
         */
        Vec3.prototype.divide = function (divisor) {
            this[0] /= divisor;
            this[1] /= divisor;
            this[2] /= divisor;

            return this;
        };

        /**
         * Multiplies this vector by a 4x4 matrix. The multiplication is performed with an implicit W component of 1.
         * The resultant W component of the product is then divided through the X, Y, and Z components.
         *
         * @param {Matrix} matrix The matrix to multiply this vector by.
         * @returns {Vec3} This vector multiplied by the specified matrix.
         * @throws ArgumentError If the specified matrix is null or undefined.
         */
        Vec3.prototype.multiplyByMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "multiplyByMatrix", "missingMatrix"));
            }

            var x = matrix[0] * this[0] + matrix[1] * this[1] + matrix[2] * this[2] + matrix[3],
                y = matrix[4] * this[0] + matrix[5] * this[1] + matrix[6] * this[2] + matrix[7],
                z = matrix[8] * this[0] + matrix[9] * this[1] + matrix[10] * this[2] + matrix[11],
                w = matrix[12] * this[0] + matrix[13] * this[1] + matrix[14] * this[2] + matrix[15];

            this[0] = x / w;
            this[1] = y / w;
            this[2] = z / w;

            return this;
        };

        /**
         * Mixes (interpolates) a specified vector with this vector, modifying this vector.
         * @param {Vec3} vector The vector to mix with this one.
         * @param {Number} weight The relative weight of this vector.
         * @returns {Vec3} This vector modified to the mix of itself and the specified vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.mix = function (vector, weight) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "mix", "missingVector"));
            }

            var w0 = 1 - weight,
                w1 = weight;

            this[0] = this[0] * w0 + vector[0] * w1;
            this[1] = this[1] * w0 + vector[1] * w1;
            this[2] = this[2] * w0 + vector[2] * w1;

            return this;
        };

        /**
         * Negates the components of this vector.
         * @returns {Vec3} This vector, negated.
         */
        Vec3.prototype.negate = function () {
            this[0] = -this[0];
            this[1] = -this[1];
            this[2] = -this[2];

            return this;
        };

        /**
         * Computes the scalar dot product of this vector and a specified vector.
         * @param {Vec3} vector The vector to multiply.
         * @returns {Number} The dot product of the two vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.dot = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "dot", "missingVector"));
            }

            return this[0] * vector[0] +
                this[1] * vector[1] +
                this[2] * vector[2];
        };

        /**
         * Computes the cross product of this vector and a specified vector, modifying this vector.
         * @param {Vec3} vector The vector to cross with this vector.
         * @returns {Vec3} This vector set to the cross product of itself and the specified vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.cross = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "cross", "missingVector"));
            }

            var x = this[1] * vector[2] - this[2] * vector[1],
                y = this[2] * vector[0] - this[0] * vector[2],
                z = this[0] * vector[1] - this[1] * vector[0];

            this[0] = x;
            this[1] = y;
            this[2] = z;

            return this;
        };

        /**
         * Computes the squared magnitude of this vector.
         * @returns {Number} The squared magnitude of this vector.
         */
        Vec3.prototype.magnitudeSquared = function () {
            return this.dot(this);
        };

        /**
         * Computes the magnitude of this vector.
         * @returns {Number} The magnitude of this vector.
         */
        Vec3.prototype.magnitude = function () {
            return Math.sqrt(this.magnitudeSquared());
        };

        /**
         * Normalizes this vector to a unit vector.
         * @returns {Vec3} This vector, normalized.
         */
        Vec3.prototype.normalize = function () {
            var magnitude = this.magnitude(),
                magnitudeInverse = 1 / magnitude;

            this[0] *= magnitudeInverse;
            this[1] *= magnitudeInverse;
            this[2] *= magnitudeInverse;

            return this;
        };

        /**
         * Computes the squared distance from this vector to a specified vector.
         * @param {Vec3} vector The vector to compute the distance to.
         * @returns {Number} The squared distance between the vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.distanceToSquared = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "distanceToSquared", "missingVector"));
            }

            var dx = this[0] - vector[0],
                dy = this[1] - vector[1],
                dz = this[2] - vector[2];

            return dx * dx + dy * dy + dz * dz;
        };

        /**
         * Computes the distance from this vector to another vector.
         * @param {Vec3} vector The vector to compute the distance to.
         * @returns {number} The distance between the vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec3.prototype.distanceTo = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec3", "distanceTo", "missingVector"));
            }

            return Math.sqrt(this.distanceToSquared(vector));
        };

        /**
         * Swaps this vector with that vector. This vector's components are set to the values of the specified
         * vector's components, and the specified vector's components are set to the values of this vector's components.
         * @param {Vec3} that The vector to swap.
         * @returns {Vec3} This vector set to the values of the specified vector.
         */
        Vec3.prototype.swap = function (that) {
            var tmp = this[0];
            this[0] = that[0];
            that[0] = tmp;

            tmp = this[1];
            this[1] = that[1];
            that[1] = tmp;

            tmp = this[2];
            this[2] = that[2];
            that[2] = tmp;

            return this;
        };

        /**
         * Returns a string representation of this vector.
         * @returns {String} A string representation of this vector, in the form "(x, y, z)".
         */
        Vec3.prototype.toString = function () {
            return "(" + this[0] + ", " + this[1] + ", " + this[2] + ")";
        };

        return Vec3;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('geom/Vec2',[
        '../util/Logger',
        '../error/ArgumentError',
        '../geom/Vec3'
    ],
    function (Logger,
              ArgumentError,
              Vec3) {
        "use strict";

        /**
         * Constructs a two-component vector.
         * @alias Vec2
         * @classdesc Represents a two-component vector. Access the X component of the vector as v[0] and the Y
         * component as v[1].
         * @augments Float64Array
         * @param {Number} x X component of vector.
         * @param {Number} y Y component of vector.
         * @constructor
         */
        var Vec2 = function Vec2(x, y) {
            this[0] = x;
            this[1] = y;
        };

        // Vec2 inherits from Float64Array.
        Vec2.prototype = new Float64Array(2);

        /**
         * Assigns the components of this vector.
         * @param {Number} x The X component of the vector.
         * @param {Number} y The Y component of the vector.
         * @returns {Vec2} This vector with the specified components assigned.
         */
        Vec2.prototype.set = function (x, y) {
            this[0] = x;
            this[1] = y;

            return this;
        };

        /**
         * Copies the components of a specified vector to this vector.
         * @param {Vec2} vector The vector to copy.
         * @returns {Vec2} This vector set to the values of the specified vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec2.prototype.copy = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "copy", "missingVector"));
            }

            this[0] = vector[0];
            this[1] = vector[1];

            return this;
        };

        /**
         * Indicates whether the X and Y components of this vector are identical to those of a specified vector.
         * @param {Vec2} vector The vector to test.
         * @returns {Boolean} true if this vector's components are equal to those of the specified vector,
         * otherwise false.
         */
        Vec2.prototype.equals = function (vector) {
            return this[0] === vector[0] && this[1] === vector[1];
        };

        /**
         * Computes the average of a specified array of vectors.
         * @param {Vec2[]} vectors The vectors whose average to compute.
         * @param {Vec2} result A pre-allocated Vec2 in which to return the computed average.
         * @returns {Vec2} The result argument set to the average of the specified lists of vectors.
         * @throws {ArgumentError} If the specified array of vectors is null, undefined or empty, or the specified
         * result argument is null or undefined.
         */
        Vec2.average = function (vectors, result) {
            if (!vectors || vectors.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "average", "missingArray"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "average", "missingResult"));
            }

            var count = vectors.length,
                vec;

            result[0] = 0;
            result[1] = 0;

            for (var i = 0, len = vectors.length; i < len; i++) {
                vec = vectors[i];

                result[0] += vec[0] / count;
                result[1] += vec[1] / count;
            }

            return result;
        };

        /**
         * Adds a vector to this vector.
         * @param {Vec2} addend The vector to add to this one.
         * @returns {Vec2} This vector after adding the specified vector to it.
         * @throws {ArgumentError} If the specified addend is null or undefined.
         */
        Vec2.prototype.add = function (addend) {
            if (!addend) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "add", "missingVector"));
            }

            this[0] += addend[0];
            this[1] += addend[1];

            return this;
        };

        /**
         * Subtracts a vector from this vector.
         * @param {Vec2} subtrahend The vector to subtract from this one.
         * @returns {Vec2} This vector after subtracting the specified vector from it.
         * @throws {ArgumentError} If the subtrahend is null or undefined.
         */
        Vec2.prototype.subtract = function (subtrahend) {
            if (!subtrahend) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "subtract", "missingVector"));
            }

            this[0] -= subtrahend[0];
            this[1] -= subtrahend[1];

            return this;
        };

        /**
         * Multiplies this vector by a scalar.
         * @param {Number} scalar The scalar to multiply this vector by.
         * @returns {Vec2} This vector multiplied by the specified scalar.
         */
        Vec2.prototype.multiply = function (scalar) {
            this[0] *= scalar;
            this[1] *= scalar;

            return this;
        };

        /**
         * Divide this vector by a scalar.
         * @param {Number} divisor The scalar to divide this vector by.
         * @returns {Vec2} This vector divided by the specified scalar.
         */
        Vec2.prototype.divide = function (divisor) {
            this[0] /= divisor;
            this[1] /= divisor;

            return this;
        };

        /**
         * Mixes (interpolates) a specified vector with this vector, modifying this vector.
         * @param {Vec2} vector The vector to mix.
         * @param {Number} weight The relative weight of this vector.
         * @returns {Vec2} This vector modified to the mix of itself and the specified vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec2.prototype.mix = function (vector, weight) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "mix", "missingVector"));
            }

            var w0 = 1 - weight,
                w1 = weight;

            this[0] = this[0] * w0 + vector[0] * w1;
            this[1] = this[1] * w0 + vector[1] * w1;

            return this;
        };

        /**
         * Negates this vector.
         * @returns {Vec2} This vector, negated.
         */
        Vec2.prototype.negate = function () {
            this[0] = -this[0];
            this[1] = -this[1];

            return this;
        };

        /**
         * Computes the scalar dot product of this vector and a specified vector.
         * @param {Vec2} vector The vector to multiply.
         * @returns {Number} The scalar dot product of the vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec2.prototype.dot = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "dot", "missingVector"));
            }

            return this[0] * vector[0] + this[1] * vector[1];
        };

        /**
         * Computes the squared magnitude of this vector.
         * @returns {Number} The squared magnitude of this vector.
         */
        Vec2.prototype.magnitudeSquared = function () {
            return this.dot(this);
        };

        /**
         * Computes the magnitude of this vector.
         * @returns {Number} The magnitude of this vector.
         */
        Vec2.prototype.magnitude = function () {
            return Math.sqrt(this.magnitudeSquared());
        };

        /**
         * Normalizes this vector to a unit vector.
         * @returns {Vec2} This vector, normalized.
         */
        Vec2.prototype.normalize = function () {
            var magnitude = this.magnitude(),
                magnitudeInverse = 1 / magnitude;

            this[0] *= magnitudeInverse;
            this[1] *= magnitudeInverse;

            return this;
        };

        /**
         * Computes the squared distance from this vector to a specified vector.
         * @param {Vec2} vector The vector to compute the distance to.
         * @returns {Number} The squared distance between the vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec2.prototype.distanceToSquared = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "distanceToSquared", "missingVector"));
            }

            var dx = this[0] - vector[0],
                dy = this[1] - vector[1];

            return dx * dx + dy * dy;
        };

        /**
         * Computes the distance from this vector to a specified vector.
         * @param {Vec2} vector The vector to compute the distance to.
         * @returns {Number} The distance between the vectors.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Vec2.prototype.distanceTo = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Vec2", "distanceTo", "missingVector"));
            }

            return Math.sqrt(this.distanceToSquared(vector));
        };

        /**
         * Creates a {@link Vec3} using this vector's X and Y components and a Z component of 0.
         * @returns {Vec3} A new vector whose X and Y components are those of this vector and whose Z component is 0.
         */
        Vec2.prototype.toVec3 = function () {
            return new Vec3(this[0], this[1], 0);
        };

        /**
         * Swaps the components of this vector with those of another vector. This vector is set to the values of the
         * specified vector, and the specified vector's components are set to the values of this vector.
         * @param {Vec2} that The vector to swap.
         * @returns {Vec2} This vector set to the values of the specified vector.
         */
        Vec2.prototype.swap = function (that) {
            var tmp = this[0];
            this[0] = that[0];
            that[0] = tmp;

            tmp = this[1];
            this[1] = that[1];
            that[1] = tmp;

            return this;
        };

        /**
         * Returns a string representation of this vector.
         * @returns {String} A string representation of this vector, in the form "(x, y)".
         */
        Vec2.prototype.toString = function () {
            return "(" + this[0] + ", " + this[1] + ")";
        };

        return Vec2;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Offset
 */
define('util/Offset',['../geom/Vec2'
    ],
    function (Vec2) {
        "use strict";

        /**
         * Constructs an offset instance given specified units and offsets.
         * @alias Offset
         * @constructor
         * @classdesc Specifies an offset relative to a rectangle. Used by [Placemark]{@link Placemark} and
         * other shapes.
         * @param {String} xUnits The type of units specified for the X dimension. May be one of the following:
         * <ul>
         *     <li>[WorldWind.OFFSET_FRACTION]{@link WorldWind#OFFSET_FRACTION}</li>
         *     <li>[WorldWind.OFFSET_INSET_PIXELS]{@link WorldWind#OFFSET_INSET_PIXELS}</li>
         *     <li>[WorldWind.OFFSET_PIXELS]{@link WorldWind#OFFSET_PIXELS}</li>
         * </ul>
         * @param {Number} x The offset in the X dimension.
         * @param {String} yUnits The type of units specified for the Y dimension, assuming a lower-left Y origin.
         * May be one of the following:
         * <ul>
         *     <li>[WorldWind.OFFSET_FRACTION]{@link WorldWind#OFFSET_FRACTION}</li>
         *     <li>[WorldWind.OFFSET_INSET_PIXELS]{@link WorldWind#OFFSET_INSET_PIXELS}</li>
         *     <li>[WorldWind.OFFSET_PIXELS]{@link WorldWind#OFFSET_PIXELS}</li>
         * </ul>
         * @param {Number} y The offset in the Y dimension.
         */
        var Offset = function (xUnits, x, yUnits, y) {

            /**
             * The offset in the X dimension, interpreted according to this instance's xUnits argument.
             * @type {Number}
             */
            this.x = x;

            /**
             * The offset in the Y dimension, interpreted according to this instance's yUnits argument.
             * @type {Number}
             */
            this.y = y;

            /**
             * The units of this instance's X offset. See this class' constructor description for a list of the
             * possible values.
             * @type {String}
             */
            this.xUnits = xUnits;

            /**
             * The units of this instance's Y offset. See this class' constructor description for a list of the
             * possible values.
             * @type {String}
             */
            this.yUnits = yUnits;
        };

        /**
         * Creates a new copy of this offset with identical property values.
         * @returns {Offset} A new offset instance with its property values the same as this one's.
         */
        Offset.prototype.clone = function () {
            return new Offset(this.xUnits, this.x, this.yUnits, this.y);
        };

        /**
         * Returns this offset's absolute X and Y coordinates in pixels for a rectangle of a specified size in pixels.
         * The returned offset is in pixels relative to the rectangle's origin, and is defined in the coordinate
         * system used by the caller.
         * @param {Number} width The rectangle's width in pixels.
         * @param {Number} height The rectangles height in pixels.
         * @returns {Vec2} The computed offset relative to the rectangle's origin.
         */
        Offset.prototype.offsetForSize = function (width, height) {
            var x, y;

            if (this.xUnits == WorldWind.OFFSET_FRACTION) {
                x = width * this.x;
            } else if (this.xUnits == WorldWind.OFFSET_INSET_PIXELS) {
                x = width - this.x;
            } else { // default to OFFSET_PIXELS
                x = this.x;
            }

            if (this.yUnits == WorldWind.OFFSET_FRACTION) {
                y = height * this.y;
            } else if (this.yUnits == WorldWind.OFFSET_INSET_PIXELS) {
                y = height - this.y;
            } else { // default to OFFSET_PIXELS
                y = this.y;
            }

            return new Vec2(x, y);
        };

        /**
         * Returns a string representation of this object.
         * @returns {String} A string representation of this object.
         */
        Offset.prototype.toString = function () {
            return this.xUnits + " " + this.x + " " + this.yUnits + " " + this.y;
        };

        return Offset;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TextAttributes
 */
define('shapes/TextAttributes',[
        '../util/Color',
        '../util/Font',
        '../util/Offset'
    ],
    function (Color,
              Font,
              Offset) {
        "use strict";

        /**
         * Constructs a text attributes bundle.
         * @alias TextAttributes
         * @constructor
         * @classdesc Holds attributes applied to [Text]{@link Text} shapes and [Placemark]{@link Placemark} labels.
         *
         * @param {TextAttributes} attributes Attributes to initialize this attributes instance to. May be null,
         * in which case the new instance contains default attributes.
         */
        var TextAttributes = function (attributes) {
            this._color = attributes ? attributes._color : new Color(1, 1, 1, 1);
            this._font = attributes ? attributes._font : new Font(14);
            this._offset = attributes ? attributes._offset
                : new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.0);
            this._scale = attributes ? attributes._scale : 1;
            this._depthTest = attributes ? attributes._depthTest : false;

            /**
             * Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
             * attributes change. The state key will be automatically computed the next time it's requested. This flag
             * will be set to false when that occurs.
             * @type {boolean}
             * @protected
             */
            this.stateKeyInvalid = true;
        };

        /**
         * Computes the state key for this attributes object. Subclasses that define additional attributes must
         * override this method, call it from that method, and append the state of their attributes to its
         * return value.
         * @returns {String} The state key for this object.
         * @protected
         */
        TextAttributes.prototype.computeStateKey = function () {
            return "c " + this._color.toHexString(true) +
                " f " + this._font.toString() +
                " o " + this._offset.toString() +
                " s " + this._scale +
                " dt " + this._depthTest;
        };

        Object.defineProperties(TextAttributes.prototype, {
            /**
             * A string identifying the state of this attributes object. The string encodes the current values of all
             * this object's properties. It's typically used to validate cached representations of shapes associated
             * with this attributes object.
             * @type {String}
             * @readonly
             * @memberof TextAttributes.prototype
             */
            stateKey: {
                get: function () {
                    if (this.stateKeyInvalid) {
                        this._stateKey = this.computeStateKey();
                        this.stateKeyInvalid = false;
                    }
                    return this._stateKey;
                }
            },

            /**
             * The text color.
             * @type {Color}
             * @default White (1, 1, 1, 1)
             * @memberof TextAttributes.prototype
             */
            color: {
                get: function () {
                    return this._color;
                },
                set: function (value) {
                    this._color = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * The text size, face and other characteristics, as described in [Font]{@link Font}.
             * @type {Font}
             * @default Those of [Font]{@link Font}, but with a font size of 14.
             * @memberof TextAttributes.prototype
             */
            font: {
                get: function () {
                    return this._font;
                },
                set: function (value) {
                    this._font = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the location of the text relative to its specified position.
             * May be null, in which case the text's bottom-left corner is placed at the specified position.
             * @type {Offset}
             * @default 0.5, 0.0, both fractional (Places the text's horizontal center and vertical bottom at the
             * specified position.)
             * @memberof TextAttributes.prototype
             */
            offset: {
                get: function () {
                    return this._offset;
                },
                set: function (value) {
                    this._offset = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the amount to scale the text. A value of 0 makes the text disappear.
             * @type {Number}
             * @default 1.0
             * @memberof TextAttributes.prototype
             */
            scale: {
                get: function () {
                    return this._scale;
                },
                set: function (value) {
                    this._scale = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether the text should be depth-tested against other objects in the scene. If true,
             * the text may be occluded by terrain and other objects in certain viewing situations. If false,
             * the text will not be occluded by terrain and other objects.
             * @type {Boolean}
             * @default false
             * @memberof TextAttributes.prototype
             */
            depthTest: {
                get: function () {
                    return this._depthTest;
                },
                set: function (value) {
                    this._depthTest = value;
                    this.stateKeyInvalid = true;
                }
            }
        });

        return TextAttributes;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('shapes/AnnotationAttributes',[
        '../util/Color',
        '../util/Font',
        '../util/Insets',
        '../shapes/TextAttributes'
    ],
    function (Color,
              Font,
              Insets,
              TextAttributes) {
        "use strict";

        /**
         * Constructs an annotation attributes bundle.
         * @alias AnnotationAttributes
         * @constructor
         * @classdesc Holds attributes applied to {@link Annotation} shapes.
         * @param {AnnotationAttributes} attributes Attributes to initialize this attributes instance to. May be null,
         * in which case the new instance contains default attributes.
         */
        var AnnotationAttributes = function (attributes) {

            // These are all documented with their property accessors below.
            this._cornerRadius = attributes ? attributes._cornerRadius : 0;
            this._insets = attributes ? attributes._insets : new Insets(0, 0, 0, 0);
            this._backgroundColor = attributes ? attributes._backgroundColor : Color.WHITE;
            this._leaderGapWidth = attributes ? attributes._leaderGapWidth : 40;
            this._leaderGapHeight = attributes ? attributes._leaderGapHeight : 30;
            this._opacity = attributes ? attributes._opacity : 1;
            this._scale = attributes ? attributes._scale : 1;
            this._drawLeader = attributes ? attributes._drawLeader : true;
            this._width = attributes ? attributes._width : 200;
            this._height = attributes ? attributes._height : 100;
            this._textAttributes = attributes ? attributes._textAttributes : new TextAttributes(null);

            /**
             * Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
             * attributes change. The state key will be automatically computed the next time it's requested. This flag
             * will be set to false when that occurs.
             * @type {Boolean}
             * @protected
             */
            this.stateKeyInvalid = true;
        };

        /**
         * Computes the state key for this attributes object. Subclasses that define additional attributes must
         * override this method, call it from that method, and append the state of their attributes to its
         * return value.
         * @returns {String} The state key for this object.
         * @protected
         */
        AnnotationAttributes.prototype.computeStateKey = function () {
            return "wi " + this._width
                + " he " + this._height
                + " cr " + this._cornerRadius
                + " in " + this._insets.toString()
                + " bg " + this.backgroundColor.toHexString(true)
                + " dl " + this.drawLeader
                + " lgw " + this.leaderGapWidth
                + " lgh " + this.leaderGapHeight
                + " op " + this.opacity
                + " ta " + this._textAttributes.stateKey
                + " sc " + this.scale;
        };

        Object.defineProperties(AnnotationAttributes.prototype, {

            /**
             * Indicates the width of the callout.
             * @type {Number}
             * @default 200
             * @memberof AnnotationAttributes.prototype
             */
            width: {
                get: function () {
                    return this._width;
                },
                set: function (value) {
                    this._width = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates height of the callout.
             * @type {Number}
             * @default 100
             * @memberof AnnotationAttributes.prototype
             */
            height: {
                get: function () {
                    return this._height;
                },
                set: function (value) {
                    this._height = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the radius for the corners.
             * @type {Number}
             * @default 0
             * @memberof AnnotationAttributes.prototype
             */
            cornerRadius: {
                get: function () {
                    return this._cornerRadius;
                },
                set: function (value) {
                    this._cornerRadius = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the insets instance of this object.
             * Insets adjusts top, bottom, left, right padding for the text.
             * @type {Insets}
             * @default 0, 0, 0, 0
             * @memberof AnnotationAttributes.prototype
             */
            insets: {
                get: function () {
                    return this._insets;
                },
                set: function (value) {
                    this._insets = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the background color of the callout.
             * @type {Color}
             * @default White
             * @memberof AnnotationAttributes.prototype
             */
            backgroundColor: {
                get: function () {
                    return this._backgroundColor;
                },
                set: function (value) {
                    this._backgroundColor = value;
                    this.stateKeyInvalid = true;
                }
            },


            /**
             * Indicates the attributes to apply to the annotation's text.
             * @type {TextAttributes}
             * @default The defaults of {@link TextAttributes}.
             * @memberof AnnotationAttributes.prototype
             */
            textAttributes: {
                get: function () {
                    return this._textAttributes;
                },
                set: function (value) {
                    this._textAttributes = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether to draw a leader pointing to the annotation's geographic position.
             * @type {Boolean}
             * @default true
             * @memberof AnnotationAttributes.prototype
             */
            drawLeader: {
                get: function () {
                    return this._drawLeader;
                },
                set: function (value) {
                    this._drawLeader = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the gap width of the leader in pixels.
             * @type {Number}
             * @default 40
             * @memberof AnnotationAttributes.prototype
             */
            leaderGapWidth: {
                get: function () {
                    return this._leaderGapWidth;
                },
                set: function (value) {
                    this._leaderGapWidth = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the gap height of the leader in pixels.
             * @type {Number}
             * @default 30
             * @memberof AnnotationAttributes.prototype
             */
            leaderGapHeight: {
                get: function () {
                    return this._leaderGapHeight;
                },
                set: function (value) {
                    this._leaderGapHeight = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the opacity of the annotation.
             * The value ranges from 0 to 1.
             * Opacity affects both callout and text.
             * @type {Number}
             * @default 1
             * @memberof AnnotationAttributes.prototype
             */
            opacity: {
                get: function () {
                    return this._opacity;
                },
                set: function (value) {
                    this._opacity = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the scale multiplier.
             * @type {Number}
             * @default 1
             * @memberof AnnotationAttributes.prototype
             */
            scale: {
                get: function () {
                    return this._scale;
                },
                set: function (value) {
                    this._scale = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * A string identifying the state of this attributes object. The string encodes the current values of all
             * this object's properties. It's typically used to validate cached representations of shapes associated
             * with this attributes object.
             * @type {String}
             * @readonly
             * @memberof AnnotationAttributes.prototype
             */
            stateKey: {
                get: function () {
                    if (this.stateKeyInvalid) {
                        this._stateKey = this.computeStateKey();
                        this.stateKeyInvalid = false;
                    }
                    return this._stateKey;
                }
            }
        });

        return AnnotationAttributes;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GpuShader
 */
define('shaders/GpuShader',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a GPU shader of a specified type with specified GLSL source code.
         *
         * @alias GpuShader
         * @constructor
         * @classdesc
         * Represents an OpenGL shading language (GLSL) shader and provides methods for compiling and disposing
         * of them.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} shaderType The type of shader, either WebGLRenderingContext.VERTEX_SHADER
         * or WebGLRenderingContext.FRAGMENT_SHADER.
         * @param {String} shaderSource The shader's source code.
         * @throws {ArgumentError} If the shader type is unrecognized, the shader source is null or undefined or shader
         * compilation fails. If the compilation fails the error thrown contains any compilation messages.
         */
        var GpuShader = function (gl, shaderType, shaderSource) {
            if (!(shaderType === gl.VERTEX_SHADER
                || shaderType === gl.FRAGMENT_SHADER)) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
                    "The specified shader type is unrecognized."));
            }

            if (!shaderSource) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
                    "The specified shader source is null or undefined."));
            }

            var shader = gl.createShader(shaderType);
            if (!shader) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
                    "Unable to create shader of type " +
                    (shaderType == gl.VERTEX_SHADER ? "VERTEX_SHADER." : "FRAGMENT_SHADER.")));
            }

            if (!this.compile(gl, shader, shaderType, shaderSource)) {
                var infoLog = gl.getShaderInfoLog(shader);

                gl.deleteShader(shader);

                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuShader", "constructor",
                    "Unable to compile shader: " + infoLog));
            }

            this.shaderId = shader;
        };

        /**
         * Compiles the source code for this shader. This method is not meant to be invoked by applications. It is
         * invoked internally as needed.
         * @param {WebGLRenderingContext} gl The current WebGL rendering context.
         * @param {WebGLShader} shaderId The shader ID.
         * @param {Number} shaderType The type of shader, either WebGLRenderingContext.VERTEX_SHADER
         * or WebGLRenderingContext.FRAGMENT_SHADER.
         * @param {String} shaderSource The shader's source code.
         * @returns {boolean} <code>true</code> if the shader compiled successfully, otherwise <code>false</code>.
         */
        GpuShader.prototype.compile = function (gl, shaderId, shaderType, shaderSource) {
            gl.shaderSource(shaderId, shaderSource);
            gl.compileShader(shaderId);

            return gl.getShaderParameter(shaderId, gl.COMPILE_STATUS);
        };

        /**
         * Releases this shader's WebGL shader.
         * @param {WebGLRenderingContext} gl The current WebGL rendering context.
         */
        GpuShader.prototype.dispose = function (gl) {
            if (this.shaderId) {
                gl.deleteShader(this.shaderId);
                delete this.shaderId;
            }
        };

        return GpuShader;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GpuProgram
 */
define('shaders/GpuProgram',[
        '../error/ArgumentError',
        '../util/Color',
        '../shaders/GpuShader',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              GpuShader,
              Logger) {
        "use strict";

        /**
         * Constructs a GPU program with specified source code for vertex and fragment shaders.
         * This constructor is intended to be called only by subclasses.
         * <p>
         * This constructor creates WebGL shaders for the specified shader sources and attaches them to a new GLSL
         * program. The method compiles the shaders and then links the program if compilation is successful. Use the
         * [DrawContext.bindProgram]{@link DrawContext#bindProgram} function to make the program current during rendering.
         *
         * @alias GpuProgram
         * @constructor
         * @classdesc
         * Represents an OpenGL shading language (GLSL) shader program and provides methods for identifying and
         * accessing shader variables. Shader programs are created by instances of this class and made current when the
         * DrawContext.bindProgram function is invoked.
         * <p>
         * This is an abstract class and not intended to be created directly.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {String} vertexShaderSource The source code for the vertex shader.
         * @param {String} fragmentShaderSource The source code for the fragment shader.
         * @param {String[]} attributeBindings An array of attribute variable names whose bindings are to be explicitly
         * specified. Each name is bound to its corresponding index in the array. May be null, in which case the
         * linker determines all the bindings.
         * @throws {ArgumentError} If either source is null or undefined, the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var GpuProgram = function (gl, vertexShaderSource, fragmentShaderSource, attributeBindings) {
            if (!vertexShaderSource || !fragmentShaderSource) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
                    "The specified shader source is null or undefined."));
            }

            var program, vShader, fShader;

            try {
                vShader = new GpuShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
                fShader = new GpuShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
            } catch (e) {
                if (vShader)
                    vShader.dispose(gl);
                if (fShader)
                    fShader.dispose(gl);

                throw e;
            }

            program = gl.createProgram();
            if (!program) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
                    "Unable to create shader program."));
            }

            gl.attachShader(program, vShader.shaderId);
            gl.attachShader(program, fShader.shaderId);

            if (attributeBindings) {
                for (var i = 0, len = attributeBindings.length; i < len; i++) {
                    gl.bindAttribLocation(program, i, attributeBindings[i]);
                }
            }

            if (!this.link(gl, program)) {
                // Get the info log before deleting the program.
                var infoLog = gl.getProgramInfoLog(program);

                gl.detachShader(program, vShader.shaderId);
                gl.detachShader(program, fShader.shaderId);
                gl.deleteProgram(program);
                vShader.dispose(gl);
                fShader.dispose(gl);

                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "constructor",
                    "Unable to link shader program: " + infoLog));
            }

            /**
             * Indicates the WebGL program object associated with this GPU program.
             * @type {WebGLProgram}
             * @readonly
             */
            this.programId = program;

            // Internal. Intentionally not documented. These will be filled in as attribute locations are requested.
            this.attributeLocations = {};
            this.uniformLocations = {};

            // Internal. Intentionally not documented.
            this.vertexShader = vShader;

            // Internal. Intentionally not documented.
            this.fragmentShader = fShader;

            // Internal. Intentionally not documented.
            this.size = vertexShaderSource.length + fragmentShaderSource.length;

            // Internal. Intentionally not documented.
            this.scratchArray = new Float32Array(16);
        };

        /**
         * Releases this GPU program's WebGL program and associated shaders. Upon return this GPU program's WebGL
         * program ID is 0 as is that of the associated shaders.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         */
        GpuProgram.prototype.dispose = function (gl) {
            if (this.programId) {
                if (this.vertexShader) {
                    gl.detachShader(this.programId, this.vertexShader.shaderId);
                }
                if (this.fragmentShader) {
                    gl.detachShader(this.programId, this.fragmentShader.shaderId);
                }

                gl.deleteProgram(this.programId);
                delete this.programId;
            }

            if (this.vertexShader) {
                this.vertexShader.dispose(gl);
                delete this.vertexShader;
            }

            if (this.fragmentShader) {
                this.fragmentShader.dispose(gl);
                delete this.fragmentShader;
            }

            this.attributeLocations = {};
            this.uniformLocations = {};
        };

        /**
         * Returns the GLSL attribute location of a specified attribute name.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {String} attributeName The name of the attribute whose location is determined.
         * @returns {Number} The WebGL attribute location of the specified attribute, or -1 if the attribute is not
         * found.
         * @throws {ArgumentError} If the specified attribute name is null, empty or undefined.
         */
        GpuProgram.prototype.attributeLocation = function (gl, attributeName) {
            if (!attributeName || attributeName.length == 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "attributeLocation",
                    "The specified attribute name is null, undefined or empty."));
            }

            var location = this.attributeLocations[attributeName];
            if (!location) {
                location = gl.getAttribLocation(this.programId, attributeName);
                this.attributeLocations[attributeName] = location;
            }

            return location;
        };

        /**
         * Returns the GLSL uniform location of a specified uniform name.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {String} uniformName The name of the uniform variable whose location is determined.
         * @returns {WebGLUniformLocation} The WebGL uniform location of the specified uniform variable,
         * or -1 if the uniform is not found.
         * @throws {ArgumentError} If the specified uniform name is null, empty or undefined.
         */
        GpuProgram.prototype.uniformLocation = function (gl, uniformName) {
            if (!uniformName || uniformName.length == 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "uniformLocation",
                    "The specified uniform name is null, undefined or empty."));
            }

            var location = this.uniformLocations[uniformName];
            if (!location) {
                location = gl.getUniformLocation(this.programId, uniformName);
                this.uniformLocations[uniformName] = location;
            }

            return location;
        };

        /**
         * Links a specified GLSL program. This method is not meant to be called by applications. It is called
         * internally as needed.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {WebGLProgram} program The WebGL program.
         * @returns {Boolean} true if linking was successful, otherwise false.
         * @protected
         */
        GpuProgram.prototype.link = function (gl, program) {
            gl.linkProgram(program);

            return gl.getProgramParameter(program, gl.LINK_STATUS);
        };

        /**
         * Loads a specified matrix as the value of a GLSL 4x4 matrix uniform variable with the specified location.
         * <p>
         * This functions converts the matrix into column-major order prior to loading its components into the GLSL
         * uniform variable, but does not modify the specified matrix.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        GpuProgram.prototype.loadUniformMatrix = function (gl, matrix, location) {
            if (!matrix) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "loadUniformMatrix",
                    "missingMatrix"));
            }

            var columnMajorArray = matrix.columnMajorComponents(this.scratchArray);
            gl.uniformMatrix4fv(location, false, columnMajorArray);
        };

        /**
         * Loads a specified color as the value of a GLSL vec4 uniform variable with the specified location.
         * <p>
         * This function multiplies the red, green and blue components by the alpha component prior to loading the color
         * in the GLSL uniform variable, but does not modify the specified color.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Color} color The color to load.
         * @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
         * @throws {ArgumentError} If the specified color is null or undefined.
         */
        GpuProgram.prototype.loadUniformColor = function (gl, color, location) {
            if (!color) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GpuProgram", "loadUniformColor",
                    "missingColor"));
            }

            var premul = color.premultipliedComponents(this.scratchArray);
            gl.uniform4f(location, premul[0], premul[1], premul[2], premul[3]);
        };

        /**
         * Loads the specified RGBA color components as the value of a GLSL vec4 uniform variable with the specified
         * location.
         * <p>
         * This function multiplies the red, green and blue components by the alpha component prior to loading the color
         * in the GLSL uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} red The red component, a number between 0 and 1.
         * @param {Number} green The green component, a number between 0 and 1.
         * @param {Number} blue The blue component, a number between 0 and 1.
         * @param {Number} alpha The alpha component, a number between 0 and 1.
         * @param {WebGLUniformLocation} location The location of the uniform variable in the currently bound GLSL program.
         */
        GpuProgram.prototype.loadUniformColorComponents = function (gl, red, green, blue, alpha, location) {
            gl.uniform4f(location, red * alpha, green * alpha, blue * alpha, alpha);
        };

        return GpuProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BasicTextureProgram
 */
define('shaders/BasicTextureProgram',[
        '../error/ArgumentError',
        '../util/Color',
        '../shaders/GpuProgram',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              GpuProgram,
              Logger) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
         * method then compiles the shaders and then links the program if compilation is successful. Use the bind method to make the
         * program current during rendering.
         *
         * @alias BasicTextureProgram
         * @constructor
         * @augments GpuProgram
         * @classdesc BasicTextureProgram is a GLSL program that draws textured or untextured geometry.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or if linking of
         * the compiled shaders into a program fails.
         */
        var BasicTextureProgram = function (gl) {
            var vertexShaderSource =
                    'attribute vec4 vertexPoint;\n' +
                    'attribute vec4 vertexTexCoord;\n' +
                    'attribute vec4 normalVector;\n' +
                    'uniform mat4 mvpMatrix;\n' +
                    'uniform mat4 mvInverseMatrix;\n' +
                    'uniform mat4 texCoordMatrix;\n' +
                    'uniform bool applyLighting;\n' +
                    'varying vec2 texCoord;\n' +
                    'varying vec4 normal;\n' +
                    'void main() {gl_Position = mvpMatrix * vertexPoint;\n' +
                    'texCoord = (texCoordMatrix * vertexTexCoord).st;\n' +
                    'if (applyLighting) {normal = mvInverseMatrix * normalVector;}\n' +
                    '}',
                fragmentShaderSource =
                    'precision mediump float;\n' +
                    'uniform float opacity;\n' +
                    'uniform vec4 color;\n' +
                    'uniform bool enableTexture;\n' +
                    'uniform bool modulateColor;\n' +
                    'uniform sampler2D textureSampler;\n' +
                    'uniform bool applyLighting;\n' +
                    'varying vec2 texCoord;\n' +
                    'varying vec4 normal;\n' +
                    'void main() {\n' +
                    'vec4 textureColor = texture2D(textureSampler, texCoord);\n' +
                    'float ambient = 0.15; vec4 lightDirection = vec4(0, 0, 1, 0);\n' +
                    'if (enableTexture && !modulateColor)\n' +
                    '    gl_FragColor = textureColor * color * opacity;\n' +
                    'else if (enableTexture && modulateColor)\n' +
                    '    gl_FragColor = color * floor(textureColor.a + 0.5);\n' +
                    'else\n' +
                    '    gl_FragColor = color * opacity;\n' +
                    'if (gl_FragColor.a == 0.0) {discard;}\n' +
                    'if (applyLighting) {\n' +
                    '    vec4 n = normal * (gl_FrontFacing ? 1.0 : -1.0);\n' +
                    '    gl_FragColor.rgb *= clamp(ambient + dot(lightDirection, n), 0.0, 1.0);\n' +
                    '}\n' +
                    '}';

            // Specify bindings to avoid the WebGL performance warning that's generated when normalVector gets
            // bound to location 0.
            var bindings = ["vertexPoint", "normalVector", "vertexTexCoord"];

            // Call to the superclass, which performs shader program compiling and linking.
            GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, bindings);

            /**
             * The WebGL location for this program's 'vertexPoint' attribute.
             * @type {Number}
             * @readonly
             */
            this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");

            /**
             * The WebGL location for this program's 'normalVector' attribute.
             * @type {Number}
             * @readonly
             */
            this.normalVectorLocation = this.attributeLocation(gl, "normalVector");

            /**
             * The WebGL location for this program's 'vertexTexCoord' attribute.
             * @type {Number}
             * @readonly
             */
            this.vertexTexCoordLocation = this.attributeLocation(gl, "vertexTexCoord");

            /**
             * The WebGL location for this program's 'mvpMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");

            /**
             * The WebGL location for this program's 'mvInverseMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvInverseMatrixLocation = this.uniformLocation(gl, "mvInverseMatrix");

            /**
             * The WebGL location for this program's 'color' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.colorLocation = this.uniformLocation(gl, "color");

            /**
             * The WebGL location for this program's 'enableTexture' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.textureEnabledLocation = this.uniformLocation(gl, "enableTexture");

            /**
             * The WebGL location for this program's 'modulateColor' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.modulateColorLocation = this.uniformLocation(gl, "modulateColor");

            /**
             * The WebGL location for this program's 'textureSampler' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.textureUnitLocation = this.uniformLocation(gl, "textureSampler");

            /**
             * The WebGL location for this program's 'texCoordMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.textureMatrixLocation = this.uniformLocation(gl, "texCoordMatrix");

            /**
             * The WebGL location for this program's 'opacity' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.opacityLocation = this.uniformLocation(gl, "opacity");

            /**
             * The WegGL location for this program's 'enableLighting' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.applyLightingLocation = this.uniformLocation(gl, "applyLighting");
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        BasicTextureProgram.key = "WorldWindGpuBasicTextureProgram";

        // Inherit from GpuProgram.
        BasicTextureProgram.prototype = Object.create(GpuProgram.prototype);

        /**
         * Loads the specified matrix as the value of this program's 'mvInverseMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        BasicTextureProgram.prototype.loadModelviewInverse = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadModelviewInverse", "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvInverseMatrixLocation);
        };

        /**
         * Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        BasicTextureProgram.prototype.loadModelviewProjection = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadModelviewProjection", "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
        };

        /**
         * Loads the specified color as the value of this program's 'color' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Color} color The color to load.
         * @throws {ArgumentError} If the specified color is null or undefined.
         */
        BasicTextureProgram.prototype.loadColor = function (gl, color) {
            if (!color) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTextureProgram", "loadColor", "missingColor"));
            }

            this.loadUniformColor(gl, color, this.colorLocation);
        };

        /**
         * Loads the specified boolean as the value of this program's 'enableTexture' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Boolean} enable true to enable texturing, false to disable texturing.
         */
        BasicTextureProgram.prototype.loadTextureEnabled = function (gl, enable) {
            gl.uniform1i(this.textureEnabledLocation, enable ? 1 : 0);
        };

        /**
         * Loads the specified boolean as the value of this program's 'modulateColor' uniform variable. When this
         * value is true and the value of the textureEnabled variable is true, the color uniform of this shader is
         * multiplied by the rounded alpha component of the texture color at each fragment. This causes the color
         * to be either fully opaque or fully transparent depending on the value of the texture color's alpha value.
         * This is used during picking to replace opaque or mostly opaque texture colors with the pick color, and
         * to make all other texture colors transparent.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Boolean} enable true to enable modulation, false to disable modulation.
         */
        BasicTextureProgram.prototype.loadModulateColor = function (gl, enable) {
            gl.uniform1i(this.modulateColorLocation, enable ? 1 : 0);
        };

        /**
         * Loads the specified number as the value of this program's 'textureSampler' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} unit The texture unit.
         */
        BasicTextureProgram.prototype.loadTextureUnit = function (gl, unit) {
            gl.uniform1i(this.textureUnitLocation, unit - gl.TEXTURE0);
        };

        /**
         * Loads the specified matrix as the value of this program's 'texCoordMatrix' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The texture coordinate matrix.
         */
        BasicTextureProgram.prototype.loadTextureMatrix = function (gl, matrix) {
            this.loadUniformMatrix(gl, matrix, this.textureMatrixLocation);
        };

        /**
         * Loads the specified number as the value of this program's 'opacity' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} opacity The opacity in the range [0, 1].
         */
        BasicTextureProgram.prototype.loadOpacity = function (gl, opacity) {
            gl.uniform1f(this.opacityLocation, opacity);
        };

        /**
         * Loads the specified boolean as the value of this program's 'applyLighting' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} applyLighting true to apply lighting, otherwise false.
         */
        BasicTextureProgram.prototype.loadApplyLighting = function (gl, applyLighting) {
            gl.uniform1i(this.applyLightingLocation, applyLighting);
        };

        return BasicTextureProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Line
 */
define('geom/Line',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Vec3'],
    function (ArgumentError,
              Logger,
              Vec3) {
        "use strict";

        /**
         * Constructs a line from a specified origin and direction.
         * @alias Line
         * @constructor
         * @classdesc Represents a line in Cartesian coordinates.
         * @param {Vec3} origin The line's origin.
         * @param {Vec3} direction The line's direction.
         * @throws {ArgumentError} If either the origin or the direction are null or undefined.
         */
        var Line = function (origin, direction) {
            if (!origin) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "constructor",
                    "Origin is null or undefined."));
            }

            if (!direction) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "constructor",
                    "Direction is null or undefined."));
            }

            /**
             * This line's origin.
             * @type {Vec3}
             */
            this.origin = origin;

            /**
             * This line's direction.
             * @type {Vec3}
             */
            this.direction = direction;
        };

        /**
         * Creates a line given two specified endpoints.
         * @param {Vec3} pointA The first endpoint.
         * @param {Vec3} pointB The second endpoint.
         * @return {Line} The new line.
         * @throws {ArgumentError} If either endpoint is null or undefined.
         */
        Line.fromSegment = function (pointA, pointB) {
            if (!pointA || !pointB) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "fromSegment", "missingPoint"));
            }

            var origin = new Vec3(pointA[0], pointA[1], pointA[2]),
                direction = new Vec3(pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2]);

            return new Line(origin, direction);
        };

        /**
         * Computes a Cartesian point a specified distance along this line.
         * @param {Number} distance The distance from this line's origin at which to compute the point.
         * @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed point.
         * @return {Vec3} The specified result argument containing the computed point.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Line.prototype.pointAt = function (distance, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Line", "pointAt", "missingResult."));
            }

            result[0] = this.origin[0] + this.direction[0] * distance;
            result[1] = this.origin[1] + this.direction[1] * distance;
            result[2] = this.origin[2] + this.direction[2] * distance;

            return result;
        };

        return Line;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Plane
 */
define('geom/Plane',[
        '../error/ArgumentError',
        '../geom/Line',
        '../util/Logger',
        '../geom/Vec3'
    ],
    function (ArgumentError,
              Line,
              Logger,
              Vec3) {
        "use strict";

        /**
         * Constructs a plane.
         * This constructor does not normalize the components. It assumes that a unit normal vector is provided.
         * @alias Plane
         * @constructor
         * @classdesc Represents a plane in Cartesian coordinates.
         * The plane's X, Y and Z components indicate the plane's normal vector. The distance component
         * indicates the plane's distance from the origin relative to its unit normal.
         * The components are expected to be normalized.
         * @param {Number} x The X coordinate of the plane's unit normal vector.
         * @param {Number} y The Y coordinate of the plane's unit normal vector.
         * @param {Number} z The Z coordinate of the plane's unit normal vector.
         * @param {Number} distance The plane's distance from the origin.
         */
        var Plane = function (x, y, z, distance) {
            /**
             * The normal vector to the plane.
             * @type {Vec3}
             */
            this.normal = new Vec3(x, y, z);

            /**
             * The plane's distance from the origin.
             * @type {Number}
             */
            this.distance = distance;
        };

        /**
         * Computes a plane that passes through the specified three points.
         * The plane's normal is the cross product of the
         * two vectors from pb to pa and pc to pa, respectively. The
         * returned plane is undefined if any of the specified points are colinear.
         *
         * @param {Vec3} pa The first point.
         * @param {Vec3} pb The second point.
         * @param {Vec3} pc The third point.
         *
         * @return {Plane} A plane passing through the specified points.
         *
         * @throws {ArgumentError} if pa, pb, or pc is null or undefined.
         */
        Plane.fromPoints = function(pa, pb, pc) {
            if (!pa || !pb || !pc) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "fromPoints", "missingVector"));
            }

            var vab = new Vec3(pb[0], pb[1], pb[2]);
            vab.subtract(pa);
            var vac = new Vec3(pc[0], pc[1], pc[2]);
            vac.subtract(pa);
            vab.cross(vac);
            vab.normalize();
            var d = -vab.dot(pa);

            return new Plane(vab[0], vab[1], vab[2], d);
        };

        /**
         * Computes the dot product of this plane's normal vector with a specified vector.
         * Since the plane was defined with a unit normal vector, this function returns the distance of the vector from
         * the plane.
         * @param {Vec3} vector The vector to dot with this plane's normal vector.
         * @returns {Number} The computed dot product.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Plane.prototype.dot = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "dot", "missingVector"));
            }

            return this.normal.dot(vector) + this.distance;
        };

        /**
         * Computes the distance between this plane and a point.
         * @param {Vec3} point The point whose distance to compute.
         * @returns {Number} The computed distance.
         * @throws {ArgumentError} If the specified point is null or undefined.
         */
        Plane.prototype.distanceToPoint = function (point) {
            return this.dot(point);
        };

        /**
         * Transforms this plane by a specified matrix.
         * @param {Matrix} matrix The matrix to apply to this plane.
         * @returns {Plane} This plane transformed by the specified matrix.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Plane.prototype.transformByMatrix = function (matrix){
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "transformByMatrix", "missingMatrix"));
            }

            var x = matrix[0] * this.normal[0] + matrix[1] * this.normal[1] + matrix[2] * this.normal[2] + matrix[3] * this.distance,
                y = matrix[4] * this.normal[0] + matrix[5] * this.normal[1] + matrix[6] * this.normal[2] + matrix[7] * this.distance,
                z = matrix[8] * this.normal[0] + matrix[9] * this.normal[1] + matrix[10] * this.normal[2] + matrix[11] * this.distance,
                distance = matrix[12] * this.normal[0] + matrix[13] * this.normal[1] + matrix[14] * this.normal[2] + matrix[15] * this.distance;

            this.normal[0] = x;
            this.normal[1] = y;
            this.normal[2] = z;
            this.distance = distance;
            
            return this;
        };

        /**
         * Normalizes the components of this plane.
         * @returns {Plane} This plane with its components normalized.
         */
        Plane.prototype.normalize = function () {
            var magnitude = this.normal.magnitude();

            if (magnitude === 0)
                return this;

            this.normal.divide(magnitude);
            this.distance /= magnitude;

            return this;
        };

        /**
         * Determines whether a specified line segment intersects this plane.
         *
         * @param {Vec3} endPoint1 The first end point of the line segment.
         * @param {Vec3} endPoint2 The second end point of the line segment.
         * @returns {Boolean} true if the line segment intersects this plane, otherwise false.
         */
        Plane.prototype.intersectsSegment = function(endPoint1, endPoint2) {
            var distance1 = this.dot(endPoint1),
                distance2 = this.dot(endPoint2);

            return distance1 * distance2 <= 0;
        };

        /**
         * Computes the intersection point of this plane with a specified line segment.
         *
         * @param {Vec3} endPoint1 The first end point of the line segment.
         * @param {Vec3} endPoint2 The second end point of the line segment.
         * @param {Vec3} result A variable in which to return the intersection point of the line segment with this plane.
         * @returns {Boolean} true If the line segment intersects this plane, otherwise false.
         */
        Plane.prototype.intersectsSegmentAt = function (endPoint1, endPoint2, result) {
            // Compute the distance from the end-points.
            var distance1 = this.dot(endPoint1),
                distance2 = this.dot(endPoint2);

            // If both points points lie on the plane, ...
            if (distance1 === 0 && distance2 === 0) {
                // Choose an arbitrary endpoint as the intersection.
                result[0] = endPoint1[0];
                result[1] = endPoint1[1];
                result[2] = endPoint1[2];

                return true;
            }
            else if (distance1 === distance2) {
                // The intersection is undefined.
                return false;
            }

            var weight1 = -distance1 / (distance2 - distance1),
                weight2 = 1 - weight1;

            result[0] = weight1 * endPoint1[0] + weight2 * endPoint2[0];
            result[1] = weight1 * endPoint1[1] + weight2 * endPoint2[1];
            result[2] = weight1 * endPoint1[2] + weight2 * endPoint2[2];

            return distance1 * distance2 <= 0;
        };

        /**
         * Determines whether two points are on the same side of this plane.
         *
         * @param {Vec3} pointA the first point.
         * @param {Vec3} pointB the second point.
         *
         * @return {Number} -1 If both points are on the negative side of this plane, +1 if both points are on the
         * positive side of this plane, 0 if the points are on opposite sides of this plane.
         *
         * @throws {ArgumentError} If either point is null or undefined.
         */
        Plane.prototype.onSameSide = function (pointA, pointB) {
            if (!pointA || !pointB) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "onSameSide", "missingPoint"));
            }

            var da = this.distanceToPoint(pointA),
                db = this.distanceToPoint(pointB);

            if (da < 0 && db < 0)
                return -1;

            if (da > 0 && db > 0)
                return 1;

            return 0;
        };

        /**
         * Clips a line segment to this plane.
         * @param {Vec3} pointA The first line segment endpoint.
         * @param {Vec3} pointB The second line segment endpoint.
         *
         * @returns {Vec3[]}  An array of two points both on the positive side of the plane. If the direction of the line formed by the
         *         two points is positive with respect to this plane's normal vector, the first point in the array will be
         *         the intersection point on the plane, and the second point will be the original segment end point. If the
         *         direction of the line is negative with respect to this plane's normal vector, the first point in the
         *         array will be the original segment's begin point, and the second point will be the intersection point on
         *         the plane. If the segment does not intersect the plane, null is returned. If the segment is coincident
         *         with the plane, the input points are returned, in their input order.
         *
         * @throws {ArgumentError} If either point is null or undefined.
         */
        Plane.prototype.clip = function (pointA, pointB) {
            if (!pointA || !pointB) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Plane", "clip", "missingPoint"));
            }

            if (pointA.equals(pointB)) {
                return null;
            }

            // Get the projection of the segment onto the plane.
            var line = Line.fromSegment(pointA, pointB),
                lDotV = this.normal.dot(line.direction),
                lDotS, t, p;

            // Are the line and plane parallel?
            if (lDotV === 0) { // line and plane are parallel and may be coincident.
                lDotS = this.dot(line.origin);
                if (lDotS === 0) {
                    return [pointA, pointB]; // line is coincident with the plane
                } else {
                    return null; // line is not coincident with the plane.
                }
            }

            // Not parallel so the line intersects. But does the segment intersect?
            t = -this.dot(line.origin) / lDotV; // lDotS / lDotV
            if (t < 0 || t > 1) { // segment does not intersect
                return null;
            }

            p = line.pointAt(t, new Vec3(0, 0, 0));
            if (lDotV > 0) {
                return [p, pointB];
            } else {
                return [pointA, p];
            }
        };

        return Plane;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Rectangle
 */
define('geom/Rectangle',[
        '../util/Logger'
    ],
    function (Logger) {
        "use strict";

        /**
         * Constructs a rectangle with a specified origin and size.
         * @alias Rectangle
         * @constructor
         * @classdesc Represents a rectangle in 2D Cartesian coordinates.
         * @param {Number} x The X coordinate of the rectangle's origin.
         * @param {Number} y The Y coordinate of the rectangle's origin.
         * @param {Number} width The rectangle's width.
         * @param {Number} height The rectangle's height.
         */
        var Rectangle = function (x, y, width, height) {

            /**
             * The X coordinate of this rectangle's origin.
             * @type {Number}
             */
            this.x = x;

            /**
             * The Y coordinate of this rectangle's origin.
             * @type {Number}
             */
            this.y = y;

            /**
             * This rectangle's width.
             * @type {Number}
             */
            this.width = width;

            /**
             * This rectangle's height.
             * @type {Number}
             */
            this.height = height;
        };

        /**
         * Sets all this rectangle's properties.
         * @param {Number} x The X coordinate of the rectangle's origin.
         * @param {Number} y The Y coordinate of the rectangle's origin.
         * @param {Number} width The rectangle's width.
         * @param {Number} height The rectangle's height.
         */
        Rectangle.prototype.set = function(x, y, width, height) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
        };

        /**
         * Returns the minimum X value of this rectangle.
         * @returns {Number} The rectangle's minimum X value.
         */
        Rectangle.prototype.getMinX = function () {
            return this.x;
        };

        /**
         * Returns the minimum Y value of this rectangle.
         * @returns {Number} The rectangle's minimum Y value.
         */
        Rectangle.prototype.getMinY = function () {
            return this.y;
        };

        /**
         * Returns the maximum X value of this rectangle.
         * @returns {Number} The rectangle's maximum X value.
         */
        Rectangle.prototype.getMaxX = function () {
            return this.x + this.width;
        };

        /**
         * Returns the maximum Y value of this rectangle.
         * @returns {Number} The rectangle's maximum Y value.
         */
        Rectangle.prototype.getMaxY = function () {
            return this.y + this.height;
        };

        /**
         * Indicates whether this rectangle contains a specified point.
         * @param {Vec2} point The point to test.
         * @returns {Boolean} true if this rectangle contains the specified point, otherwise false.
         */
        Rectangle.prototype.containsPoint = function (point) {
            return point[0] >= this.x && point[0] <= (this.x + this.width)
                && point[1] >= this.y && point[1] <= (this.y + this.height);
        };
        /**
         *
         * Indicates whether this rectangle intersects a specified one.
         * @param {Rectangle} that The rectangle to test.
         * @returns {Boolean} true if this triangle and the specified one intersect, otherwise false.
         */
        Rectangle.prototype.intersects = function (that) {
            if ((that.x + that.width) < this.x)
                return false;

            if (that.x > (this.x + this.width))
                return false;

            if ((that.y + that.height) < this.y)
                return false;

            //noinspection RedundantIfStatementJS
            if (that.y > (this.y + this.height))
                return false;

            return true;
        };

        /**
         * Indicates whether this rectangle intersects any rectangle in a specified array of rectangles.
         * @param {Rectangle[]} rectangles The rectangles to test intersection with.
         * @returns {Boolean} true if this rectangle intersects any rectangle in the array, otherwise false.
         */
        Rectangle.prototype.intersectsRectangles = function (rectangles) {
            if (rectangles) {
                for (var i = 0; i < rectangles.length; i++){
                    if (this.intersects(rectangles[i])) {
                        return true;
                    }
                }
            }

            return false;
        };

        /**
         * Returns a string representation of this object.
         * @returns {String} A string representation of this object.
         */
        Rectangle.prototype.toString = function () {
            return this.x + ", " + this.y + ", " + this.width + ", " + this.height;
        };

        return Rectangle;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/WWMath',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Line',
        '../util/Logger',
        '../geom/Rectangle',
        '../geom/Vec3'
    ],
    function (Angle,
              ArgumentError,
              Line,
              Logger,
              Rectangle,
              Vec3) {
        "use strict";
        /**
         * Provides math constants and functions.
         * @exports WWMath
         */
        var WWMath = {

            /**
             * Returns a number within the range of a specified minimum and maximum.
             * @param {Number} value The value to clamp.
             * @param {Number} minimum The minimum value to return.
             * @param {Number} maximum The maximum value to return.
             * @returns {Number} The minimum value if the specified value is less than the minimum, the maximum value if
             * the specified value is greater than the maximum, otherwise the value specified is returned.
             */
            clamp: function (value, minimum, maximum) {
                return value < minimum ? minimum : value > maximum ? maximum : value;
            },

            /**
             * Computes a number between two numbers.
             * @param amount {Number} The relative distance between the numbers at which to compute the new number. This
             * should normally be a number between 0 and 1 but whatever number is specified is applied.
             * @param {Number} value1 The first number.
             * @param {Number} value2 The second number.
             * @returns {Number} the computed value.
             */
            interpolate: function (amount, value1, value2) {
                return (1 - amount) * value1 + amount * value2;
            },

            /**
             * Returns the cube root of a specified value.
             * @param {Number} x The value whose cube root is computed.
             * @returns {Number} The cube root of the specified number.
             */
            cbrt: function (x) {
                // Use the built-in version if it exists. cbrt() is defined in ECMA6.
                if (typeof Math.cbrt == 'function') {
                    return Math.cbrt(x);
                } else {
                    return Math.pow(x, 1 / 3);
                }
            },

            /**
             * Computes the Cartesian intersection point of a specified line with an ellipsoid.
             * @param {Line} line The line for which to compute the intersection.
             * @param {Number} equatorialRadius The ellipsoid's major radius.
             * @param {Number} polarRadius The ellipsoid's minor radius.
             * @param {Vec3} result A pre-allocated Vec3 instance in which to return the computed point.
             * @returns {boolean} true if the line intersects the ellipsoid, otherwise false
             * @throws {ArgumentError} If the specified line or result is null or undefined.
             * @deprecated utilize the Globe.intersectsLine method attached implementation
             */
            computeEllipsoidalGlobeIntersection: function (line, equatorialRadius, polarRadius, result) {
                if (!line) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeEllipsoidalGlobeIntersection", "missingLine"));
                }

                if (!result) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeEllipsoidalGlobeIntersection", "missingResult"));
                }

                // Taken from "Mathematics for 3D Game Programming and Computer Graphics, Second Edition", Section 5.2.3.
                //
                // Note that the parameter n from in equations 5.70 and 5.71 is omitted here. For an ellipsoidal globe this
                // parameter is always 1, so its square and its product with any other value simplifies to the identity.

                var vx = line.direction[0],
                    vy = line.direction[1],
                    vz = line.direction[2],
                    sx = line.origin[0],
                    sy = line.origin[1],
                    sz = line.origin[2],
                    m = equatorialRadius / polarRadius, // ratio of the x semi-axis length to the y semi-axis length
                    m2 = m * m,
                    r2 = equatorialRadius * equatorialRadius, // nominal radius squared
                    a = vx * vx + m2 * vy * vy + vz * vz,
                    b = 2 * (sx * vx + m2 * sy * vy + sz * vz),
                    c = sx * sx + m2 * sy * sy + sz * sz - r2,
                    d = b * b - 4 * a * c, // discriminant
                    t;

                if (d < 0) {
                    return false;
                }
                else {
                    t = (-b - Math.sqrt(d)) / (2 * a);
                    result[0] = sx + vx * t;
                    result[1] = sy + vy * t;
                    result[2] = sz + vz * t;
                    return true;
                }
            },

            /**
             * Computes the Cartesian intersection point of a specified line with a triangle.
             * @param {Line} line The line for which to compute the intersection.
             * @param {Vec3} vertex0 The triangle's first vertex.
             * @param {Vec3} vertex1 The triangle's second vertex.
             * @param {Vec3} vertex2 The triangle's third vertex.
             * @param {Vec3} result A pre-allocated Vec3 instance in which to return the computed point.
             * @returns {boolean} true if the line intersects the triangle, otherwise false
             * @throws {ArgumentError} If the specified line, vertex or result is null or undefined.
             */
            computeTriangleIntersection: function (line, vertex0, vertex1, vertex2, result) {
                if (!line) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriangleIntersection", "missingLine"));
                }

                if (!vertex0 || !vertex1 || !vertex2) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriangleIntersection", "missingVertex"));
                }

                if (!result) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriangleIntersection", "missingResult"));
                }

                // Taken from Moller and Trumbore
                // https://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf

                var vx = line.direction[0],
                    vy = line.direction[1],
                    vz = line.direction[2],
                    sx = line.origin[0],
                    sy = line.origin[1],
                    sz = line.origin[2],
                    EPSILON = 0.00001;

                // find vectors for two edges sharing point a: vertex1 - vertex0 and vertex2 - vertex0
                var edge1x = vertex1[0] - vertex0[0],
                    edge1y = vertex1[1] - vertex0[1],
                    edge1z = vertex1[2] - vertex0[2],
                    edge2x = vertex2[0] - vertex0[0],
                    edge2y = vertex2[1] - vertex0[1],
                    edge2z = vertex2[2] - vertex0[2];

                // Compute cross product of line direction and edge2
                var px = (vy * edge2z) - (vz * edge2y),
                    py = (vz * edge2x) - (vx * edge2z),
                    pz = (vx * edge2y) - (vy * edge2x);

                // Get determinant
                var det = edge1x * px + edge1y * py + edge1z * pz; // edge1 dot p
                if (det > -EPSILON && det < EPSILON) { // if det is near zero then ray lies in plane of triangle
                    return false;
                }

                var inv_det = 1.0 / det;

                // Compute distance for vertex A to ray origin: origin - vertex0
                var tx = sx - vertex0[0],
                    ty = sy - vertex0[1],
                    tz = sz - vertex0[2];

                // Calculate u parameter and test bounds: 1/det * t dot p
                var u = inv_det * (tx * px + ty * py + tz * pz);
                if (u < -EPSILON || u > 1 + EPSILON) {
                    return false;
                }

                // Prepare to test v parameter: t cross edge1
                var qx = (ty * edge1z) - (tz * edge1y),
                    qy = (tz * edge1x) - (tx * edge1z),
                    qz = (tx * edge1y) - (ty * edge1x);

                // Calculate v parameter and test bounds: 1/det * dir dot q
                var v = inv_det * (vx * qx + vy * qy + vz * qz);
                if (v < -EPSILON || u + v > 1 + EPSILON) {
                    return false;
                }

                // Calculate the point of intersection on the line: t = 1/det * edge2 dot q
                var t = inv_det * (edge2x * qx + edge2y * qy + edge2z * qz);
                if (t < 0) {
                    return false;
                } else {
                    result[0] = sx + vx * t;
                    result[1] = sy + vy * t;
                    result[2] = sz + vz * t;
                    return true;
                }
            },

            computeIndexedTrianglesIntersection: function (line, points, indices, results) {
                if (!line) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeIndexedTrianglesIntersection", "missingLine"));
                }

                if (!points) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeIndexedTrianglesIntersection", "missingPoints"));
                }

                if (!indices) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeIndexedTrianglesIntersection", "missingIndices"));
                }

                if (!results) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeIndexedTrianglesIntersection", "missingResults"));
                }

                var v0 = new Vec3(0, 0, 0),
                    v1 = new Vec3(0, 0, 0),
                    v2 = new Vec3(0, 0, 0),
                    iPoint = new Vec3(0, 0, 0);

                for (var i = 0, len = indices.length; i < len; i += 3) {
                    var i0 = 3 * indices[i],
                        i1 = 3 * indices[i + 1],
                        i2 = 3 * indices[i + 2];

                    v0[0] = points[i0];
                    v0[1] = points[i0 + 1];
                    v0[2] = points[i0 + 2];

                    v1[0] = points[i1];
                    v1[1] = points[i1 + 1];
                    v1[2] = points[i1 + 2];

                    v2[0] = points[i2];
                    v2[1] = points[i2 + 1];
                    v2[2] = points[i2 + 2];

                    if (WWMath.computeTriangleIntersection(line, v0, v1, v2, iPoint)) {
                        results.push(iPoint);
                        iPoint = new Vec3(0, 0, 0);
                    }
                }

                return results.length > 0;
            },

            /**
             * Computes the Cartesian intersection points of a specified line with a triangle strip. The triangle strip
             * is specified by a list of vertex points and a list of indices indicating the triangle strip tessellation
             * of those vertices. The triangle strip indices are interpreted in the same manner as WebGL, where each
             * index indicates a vertex position rather than an actual index into the points array (e.g. a triangle
             * strip index of 1 indicates the XYZ tuple starting at array index 3). This is equivalent to calling
             * computeTriangleIntersection for each individual triangle in the triangle strip, but is potentially much
             * more efficient.
             * @param {Line} line The line for which to compute the intersection.
             * @param {Array} points The list of vertex points, organized as a list of tightly-packed XYZ tuples.
             * @param {Array} indices The list of triangle strip indices, organized as a list of vertex positions.
             * @param {Array} results A pre-allocated array instance in which to return the intersection points as
             * {@link Vec3} instances.
             * @throws {ArgumentError} If the specified line, points, indices or results is null or undefined.
             */
            computeTriStripIntersections: function (line, points, indices, results) {
                if (!line) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriStripIntersections", "missingLine"));
                }

                if (!points) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriStripIntersections", "missingPoints"));
                }

                if (!indices) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriStripIntersections", "missingIndices"));
                }

                if (!results) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "computeTriStripIntersections", "missingResults"));
                }

                // Taken from Moller and Trumbore
                // https://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf

                // Adapted from the original ray-triangle intersection algorithm to optimize for ray-triangle strip
                // intersection. We optimize by reusing constant terms, replacing use of Vec3 with inline primitives,
                // and exploiting the triangle strip organization to reuse computations common to adjacent triangles.
                // These optimizations reduce worst-case terrain picking performance by approximately 50% in Chrome on a
                // 2010 iMac and a Nexus 9.

                var vx = line.direction[0],
                    vy = line.direction[1],
                    vz = line.direction[2],
                    sx = line.origin[0],
                    sy = line.origin[1],
                    sz = line.origin[2],
                    vert0x, vert0y, vert0z,
                    vert1x, vert1y, vert1z,
                    vert2x, vert2y, vert2z,
                    edge1x, edge1y, edge1z,
                    edge2x, edge2y, edge2z,
                    px, py, pz,
                    tx, ty, tz,
                    qx, qy, qz,
                    u, v, t,
                    det, inv_det,
                    index,
                    EPSILON = 0.00001;

                // Get the triangle strip's first vertex.
                index = 3 * indices[0];
                vert1x = points[index++];
                vert1y = points[index++];
                vert1z = points[index];

                // Get the triangle strip's second vertex.
                index = 3 * indices[1];
                vert2x = points[index++];
                vert2y = points[index++];
                vert2z = points[index];

                // Compute the intersection of each triangle with the specified ray.
                for (var i = 2, len = indices.length; i < len; i++) {
                    // Move the last two vertices into the first two vertices. This takes advantage of the triangle
                    // strip's structure and avoids redundant reads from points and indices. During the first
                    // iteration this places the triangle strip's first three vertices in vert0, vert1 and vert2,
                    // respectively.
                    vert0x = vert1x;
                    vert0y = vert1y;
                    vert0z = vert1z;
                    vert1x = vert2x;
                    vert1y = vert2y;
                    vert1z = vert2z;

                    // Get the triangle strip's next vertex.
                    index = 3 * indices[i];
                    vert2x = points[index++];
                    vert2y = points[index++];
                    vert2z = points[index];

                    // find vectors for two edges sharing point a: vert1 - vert0 and vert2 - vert0
                    edge1x = vert1x - vert0x;
                    edge1y = vert1y - vert0y;
                    edge1z = vert1z - vert0z;
                    edge2x = vert2x - vert0x;
                    edge2y = vert2y - vert0y;
                    edge2z = vert2z - vert0z;

                    // Compute cross product of line direction and edge2
                    px = (vy * edge2z) - (vz * edge2y);
                    py = (vz * edge2x) - (vx * edge2z);
                    pz = (vx * edge2y) - (vy * edge2x);

                    // Get determinant
                    det = edge1x * px + edge1y * py + edge1z * pz; // edge1 dot p
                    if (det > -EPSILON && det < EPSILON) { // if det is near zero then ray lies in plane of triangle
                        continue;
                    }

                    inv_det = 1.0 / det;

                    // Compute distance for vertex A to ray origin: origin - vert0
                    tx = sx - vert0x;
                    ty = sy - vert0y;
                    tz = sz - vert0z;

                    // Calculate u parameter and test bounds: 1/det * t dot p
                    u = inv_det * (tx * px + ty * py + tz * pz);
                    if (u < -EPSILON || u > 1 + EPSILON) {
                        continue;
                    }

                    // Prepare to test v parameter: tvec cross edge1
                    qx = (ty * edge1z) - (tz * edge1y);
                    qy = (tz * edge1x) - (tx * edge1z);
                    qz = (tx * edge1y) - (ty * edge1x);

                    // Calculate v parameter and test bounds: 1/det * dir dot q
                    v = inv_det * (vx * qx + vy * qy + vz * qz);
                    if (v < -EPSILON || u + v > 1 + EPSILON) {
                        continue;
                    }

                    // Calculate the point of intersection on the line: t = 1/det * edge2 dot q
                    t = inv_det * (edge2x * qx + edge2y * qy + edge2z * qz);
                    if (t >= 0) {
                        results.push(new Vec3(sx + vx * t, sy + vy * t, sz + vz * t));
                    }
                }
            },

            /**
             * Computes the absolute value of a specified value.
             * @param {Number} a The value whose absolute value to compute.
             * @returns {Number} The absolute value of the specified number.
             */
            fabs: function (a) {
                return a >= 0 ? a : -a;
            },

            /**
             * Computes the floating-point modulus of a specified number.
             * @param {Number} number The number whose modulus to compute.
             * @param {Number} modulus The modulus.
             * @returns {Number} The remainder after dividing the number by the modulus: number % modulus.
             */
            fmod: function (number, modulus) {
                return modulus === 0 ? 0 : number - Math.floor(number / modulus) * modulus;
            },

            /**
             * Returns the fractional part of a specified number
             * @param {Number} number The number whose fractional part to compute.
             * @returns {Number} The fractional part of the specified number: number - floor(number).
             */
            fract: function (number) {
                return number - Math.floor(number);
            },

            /**
             * Returns the integer modulus of a specified number. This differs from the % operator in that
             * the result is always positive when the modulus is positive. For example -1 % 10 = -1,
             * whereas mod(-1, 10) = 1.
             * @param {Number} number The number whose modulus to compute.
             * @param {Number} modulus The modulus.
             * @returns {Number} The remainder after dividing the number by the modulus.
             */
            mod: function (number, modulus) {
                return ((number % modulus) + modulus) % modulus;
            },

            /**
             * Returns the maximum of two specified numbers.
             * @param {Number} value1 The first value to compare.
             * @param {Number} value2 The second value to compare.
             * @returns {Number} The maximum of the two specified values.
             */
            max: function (value1, value2) {
                return value1 > value2 ? value1 : value2;
            },

            /**
             * Computes the axes of a local coordinate system on the specified globe, placing the resultant axes in the specified
             * axis arguments.
             *
             * Upon return the specified axis arguments contain three orthogonal axes identifying the X, Y, and Z axes. Each
             * axis has unit length.
             *
             * The local coordinate system is defined such that the Z axis maps to the globe's surface normal at the point, the
             * Y axis maps to the north pointing tangent, and the X axis maps to the east pointing tangent.
             *
             * @param {Vec3} origin The local coordinate system origin, in model coordinates.
             * @param {Globe} globe The globe the coordinate system is relative to.
             * @param {Vec3} xAxisResult A pre-allocated Vec3 in which to return the computed X axis.
             * @param {Vec3} yAxisResult A pre-allocated Vec3 in which to return the computed Y axis.
             * @param {Vec3} zAxisResult A pre-allocated Vec3 in which to return the computed Z axis.
             * @throws {ArgumentError} If any argument is null or undefined.
             */
            localCoordinateAxesAtPoint: function (origin, globe, xAxisResult, yAxisResult, zAxisResult) {
                if (!origin) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "localCoordinateAxesAtPoint", "missingVector"));
                }

                if (!globe) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "localCoordinateAxesAtPoint", "missingGlobe"));
                }

                if (!xAxisResult || !yAxisResult || !zAxisResult) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "localCoordinateAxesAtPoint", "missingResult"));
                }

                var x = origin[0],
                    y = origin[1],
                    z = origin[2];

                // Compute the z axis from the surface normal in model coordinates. This axis is used to determine the other two
                // axes, and is the only constant in the computations below.
                globe.surfaceNormalAtPoint(x, y, z, zAxisResult);

                // Compute the y axis from the north pointing tangent in model coordinates. This axis is known to be orthogonal to
                // the z axis, and is therefore used to compute the x axis.
                globe.northTangentAtPoint(x, y, z, yAxisResult);

                // Compute the x axis as the cross product of the y and z axes. This ensures that the x and z axes are orthogonal.
                xAxisResult.set(yAxisResult[0], yAxisResult[1], yAxisResult[2]);
                xAxisResult.cross(zAxisResult);
                xAxisResult.normalize();

                // Re-compute the y axis as the cross product of the z and x axes. This ensures that all three axes are orthogonal.
                // Though the initial y axis computed above is likely to be very nearly orthogonal, we re-compute it using cross
                // products to reduce the effect of floating point rounding errors caused by working with Earth sized coordinates.
                yAxisResult.set(zAxisResult[0], zAxisResult[1], zAxisResult[2]);
                yAxisResult.cross(xAxisResult);
                yAxisResult.normalize();
            },

            /**
             * Computes the distance to a globe's horizon from a viewer at a given altitude.
             *
             * Only the globe's ellipsoid is considered; terrain height is not incorporated. This returns zero if the radius is zero
             * or if the altitude is less than or equal to zero.
             *
             * @param {Number} radius The globe's radius, in meters.
             * @param {Number} altitude The viewer's altitude above the globe, in meters.
             * @returns {Number} The distance to the horizon, in model coordinates.
             * @throws {ArgumentError} If the specified globe radius is negative.
             */
            horizonDistanceForGlobeRadius: function (radius, altitude) {
                if (radius < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
                        "horizontalDistanceForGlobeRadius", "The specified globe radius is negative."));
                }

                return (radius > 0 && altitude > 0) ? Math.sqrt(altitude * (2 * radius + altitude)) : 0;
            },

            /**
             * Computes the near clip distance that corresponds to a specified far clip distance and resolution at the far clip
             * plane.
             *
             * This computes a near clip distance appropriate for use in [perspectiveFrustumRect]{@link WWMath#perspectiveFrustumRectangle}
             * and [setToPerspectiveProjection]{@link Matrix#setToPerspectiveProjection}. This returns zero if either the distance or the
             * resolution are zero.
             *
             * @param {Number} farDistance The far clip distance, in meters.
             * @param {Number} farResolution The depth resolution at the far clip plane, in meters.
             * @param {Number} depthBits The number of bit-planes in the depth buffer.
             * @returns {Number} The near clip distance, in meters.
             * @throws {ArgumentError} If either the distance or resolution is negative, or if the depth bits is less
             * than one.
             */
            perspectiveNearDistanceForFarDistance: function (farDistance, farResolution, depthBits) {
                if (farDistance < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
                        "The specified distance is negative."));
                }

                if (farResolution < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
                        "The specified resolution is negative."));
                }

                if (depthBits < 1) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
                        "The specified depth bits is negative."));
                }

                var maxDepthValue = (1 << depthBits) - 1;

                return farDistance / (maxDepthValue / (1 - farResolution / farDistance) - maxDepthValue + 1);
            },

            /**
             * Computes the maximum near clip distance for a perspective projection that avoids clipping an object at a
             * given distance from the eye point.
             * <p/>
             * This computes a near clip distance appropriate for use in perspectiveFrustumRect and
             * Matrix.setToPerspectiveProjection. The given distance should specify the smallest distance between the
             * eye and the object being viewed, but may be an approximation if an exact distance is not required.
             *
             * @param {Number} viewportWidth The viewport width, in screen coordinates.
             * @param {Number} viewportHeight The viewport height, in screen coordinates.
             * @param {Number} distanceToSurface The distance from the perspective eye point to the nearest object, in
             * meters.
             * @returns {Number} The maximum near clip distance, in meters.
             * @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
             * specified distance is negative.
             */
            perspectiveNearDistance: function (viewportWidth, viewportHeight, distanceToSurface) {
                if (viewportWidth <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
                        "invalidWidth"));
                }

                if (viewportHeight <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
                        "invalidHeight"));
                }

                if (distanceToSurface < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
                        "The specified distance is negative."));
                }

                // Compute the maximum near clip distance that avoids clipping an object at the specified distance from
                // the eye. Since the furthest points on the near clip rectangle are the four corners, we compute a near
                // distance that puts any one of these corners exactly at the given distance. The distance to one of the
                // four corners can be expressed in terms of the near clip distance, given distance to a corner 'd',
                // near distance 'n', and aspect ratio 'a':
                //
                // d*d = x*x + y*y + z*z
                // d*d = (n*n/4 * a*a) + (n*n/4) + (n*n)
                //
                // Extracting 'n*n/4' from the right hand side gives:
                //
                // d*d = (n*n/4) * (a*a + 1 + 4)
                // d*d = (n*n/4) * (a*a + 5)
                //
                // Finally, solving for 'n' gives:
                //
                // n*n = 4 * d*d / (a*a + 5)
                // n = 2 * d / sqrt(a*a + 5)

                // Assumes a 45 degree horizontal field of view.
                var aspectRatio = viewportHeight / viewportWidth;

                return 2 * distanceToSurface / Math.sqrt(aspectRatio * aspectRatio + 5);
            },

            /**
             * Computes the coordinates of a rectangle carved out of a perspective projection's frustum at a given
             * distance in model coordinates. This returns an empty rectangle if the specified distance is zero.
             *
             * @param {Number} viewportWidth The viewport width, in screen coordinates.
             * @param {Number} viewportHeight The viewport height, in screen coordinates.
             * @param {Number} distance The distance along the negative Z axis, in model coordinates.
             * @returns {Rectangle} The frustum rectangle, in model coordinates.
             * @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
             * specified distance is negative.
             */
            perspectiveFrustumRectangle: function (viewportWidth, viewportHeight, distance) {
                if (viewportWidth <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
                        "invalidWidth"));
                }

                if (viewportHeight <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
                        "invalidHeight"));
                }

                if (distance < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
                        "The specified distance is negative."));
                }

                // Assumes a 45 degree horizontal field of view.
                var width = distance,
                    height = distance * viewportHeight / viewportWidth;

                return new Rectangle(-width / 2, -height / 2, width, height);
            },

            /**
             * Computes the vertical size of a pixel in model coordinates at a given distance from the eye point in a
             * perspective projection. This returns zero if the specified distance is zero. The returned size is
             * undefined if the distance is less than zero.
             * <p/>
             * This method assumes the model of a screen composed of rectangular pixels, where pixel coordinates denote
             * infinitely thin space between pixels. The units of the returned size are in model coordinates per pixel
             * (usually meters per pixel).
             *
             * @param {Number} viewportWidth The viewport width, in screen coordinates.
             * @param {Number} viewportHeight The viewport height, in screen coordinates.
             * @param {Number} distance The distance from the perspective eye point at which to determine pixel size, in
             * model coordinates.
             * @returns {Number} The pixel size at the specified distance from the eye point, in model coordinates per
             * pixel.
             * @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
             * specified distance is negative.
             */
            perspectivePixelSize: function (viewportWidth, viewportHeight, distance) {
                if (viewportWidth <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
                        "invalidWidth"));
                }

                if (viewportHeight <= 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
                        "invalidHeight"));
                }

                if (distance < 0) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
                        "The specified distance is negative."));
                }

                var frustumHeight = WWMath.perspectiveFrustumRectangle(viewportWidth, viewportHeight, distance).height;
                return frustumHeight / viewportHeight;
            },

            /**
             * Computes the bounding rectangle for a unit quadrilateral after applying a transformation matrix to that
             * quadrilateral.
             * @param {Matrix} transformMatrix The matrix to apply to the unit quadrilateral.
             * @returns {Rectangle} The computed bounding rectangle.
             */
            boundingRectForUnitQuad: function (transformMatrix) {
                if (!transformMatrix) {
                    throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "boundingRectForUnitQuad",
                        "missingMatrix"));
                }

                var m = transformMatrix,
                // transform of (0, 0)
                    x1 = m[3],
                    y1 = m[7],
                // transform of (1, 0)
                    x2 = m[0] + m[3],
                    y2 = m[4] + m[7],
                // transform of (0, 1)
                    x3 = m[1] + m[3],
                    y3 = m[5] + m[7],
                // transform of (1, 1)
                    x4 = m[0] + m[1] + m[3],
                    y4 = m[4] + m[5] + m[7],
                    minX = Math.min(Math.min(x1, x2), Math.min(x3, x4)),
                    maxX = Math.max(Math.max(x1, x2), Math.max(x3, x4)),
                    minY = Math.min(Math.min(y1, y2), Math.min(y3, y4)),
                    maxY = Math.max(Math.max(y1, y2), Math.max(y3, y4));

                return new Rectangle(minX, minY, maxX - minX, maxY - minY);
            },

            /**
             * Indicates whether a specified value is a power of two.
             * @param {Number} value The value to test.
             * @returns {boolean} <code>true</code> if the specified value is a power of two,
             * otherwise <code>false</code>.
             */
            isPowerOfTwo: function (value) {
                return value != 0 && (value & (value - 1)) === 0;
            },

            /**
             * Determine the sign of a number.
             * @param {Number} value The value to determine the sign of.
             * @returns {Number} 1, -1, or 0, depending on the sign of the value.
             */
            signum: function (value) {
                return value > 0 ? 1 : value < 0 ? -1 : 0;
            },

            /**
             * Calculates the Gudermannian inverse used to unproject Mercator projections.
             * @param {Number} latitude The latitude in degrees.
             * @returns {Number} The Gudermannian inverse for the specified latitude.
             */
            gudermannianInverse: function (latitude) {
                return Math.log(Math.tan(Math.PI / 4 + (latitude * Angle.DEGREES_TO_RADIANS) / 2)) / Math.PI;
            },

            epsg3857ToEpsg4326: function (easting, northing) {
                var r = 6.3781e6,
                    latRadians = (Math.PI / 2) - 2 * Math.atan(Math.exp(-northing / r)),
                    lonRadians = easting / r;

                return [
                    WWMath.clamp(latRadians * Angle.RADIANS_TO_DEGREES, -90, 90),
                    WWMath.clamp(lonRadians * Angle.RADIANS_TO_DEGREES, -180, 180)
                ];
            },

            /**
             * Returns the value that is the nearest power of 2 less than or equal to the given value.
             * @param {Number} value the reference value. The power of 2 returned is less than or equal to this value.
             * @returns {Number} the value that is the nearest power of 2 less than or equal to the reference value
             */
            powerOfTwoFloor: function (value) {
                var power = Math.floor(Math.log(value) / Math.log(2));
                return Math.pow(2, power);
            },

            /**
             * Restricts an angle to the range [0, 360] degrees, wrapping angles outside the range.
             * Wrapping takes place as though traversing the edge of a unit circle;
             * angles less than 0 wrap back to 360, while angles greater than 360 wrap back to 0.
             *
             * @param {Number} degrees the angle to wrap in degrees
             *
             * @return {Number} the specified angle wrapped to [0, 360] degrees
             */
            normalizeAngle360: function(degrees) {
                var angle = degrees % 360;
                return angle >= 0 ? angle : (angle < 0 ? 360 + angle : 360 - angle);
            }
        };

        return WWMath;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Location
 */
define('geom/Location',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Plane',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Logger,
              Plane,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a location from a specified latitude and longitude in degrees.
         * @alias Location
         * @constructor
         * @classdesc Represents a latitude, longitude pair in degrees.
         * @param {Number} latitude The latitude in degrees.
         * @param {Number} longitude The longitude in degrees.
         */
        var Location = function (latitude, longitude) {
            /**
             * The latitude in degrees.
             * @type {Number}
             */
            this.latitude = latitude;
            /**
             * The longitude in degrees.
             * @type {Number}
             */
            this.longitude = longitude;
        };

        /**
         * A Location with latitude and longitude both 0.
         * @constant
         * @type {Location}
         */
        Location.ZERO = new Location(0, 0);

        /**
         * Creates a location from angles specified in radians.
         * @param {Number} latitudeRadians The latitude in radians.
         * @param {Number} longitudeRadians The longitude in radians
         * @returns {Location} The new location with latitude and longitude in degrees.
         */
        Location.fromRadians = function (latitudeRadians, longitudeRadians) {
            return new Location(latitudeRadians * Angle.RADIANS_TO_DEGREES, longitudeRadians * Angle.RADIANS_TO_DEGREES);
        };

        /**
         * Copies this location to the latitude and longitude of a specified location.
         * @param {Location} location The location to copy.
         * @returns {Location} This location, set to the values of the specified location.
         * @throws {ArgumentError} If the specified location is null or undefined.
         */
        Location.prototype.copy = function (location) {
            if (!location) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "copy", "missingLocation"));
            }

            this.latitude = location.latitude;
            this.longitude = location.longitude;

            return this;
        };

        /**
         * Sets this location to the latitude and longitude.
         * @param {number} latitude The latitude to set.
         * @param {number} longitude The longitude to set.
         * @returns {Location} This location, set to the values of the specified location.
         * @throws {ArgumentError} If the specified location is null or undefined.
         */
        Location.prototype.set = function (latitude, longitude) {
            this.latitude = latitude;
            this.longitude = longitude;

            return this;
        };

        /**
         * Indicates whether this location is equal to a specified location.
         * @param {Location} location The location to compare this one to.
         * @returns {boolean} <code>true</code> if this location is equal to the specified location, otherwise
         * <code>false</code>.
         */
        Location.prototype.equals = function (location) {
            return location
                && location.latitude === this.latitude && location.longitude === this.longitude;
        };

        /**
         * Compute a location along a path at a specified distance between two specified locations.
         * @param {String} pathType The type of path to assume. Recognized values are
         * [WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE},
         * [WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE} and
         * [WorldWind.LINEAR]{@link WorldWind#LINEAR}.
         * If the path type is not recognized then WorldWind.LINEAR is used.
         * @param {Number} amount The fraction of the path between the two locations at which to compute the new
         * location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If either specified location or the result argument is null or undefined.
         */
        Location.interpolateAlongPath = function (pathType, amount, location1, location2, result) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateAlongPath", "missingLocation"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateAlongPath", "missingResult"));
            }

            if (pathType === WorldWind.GREAT_CIRCLE) {
                return this.interpolateGreatCircle(amount, location1, location2, result);
            } else if (pathType && pathType === WorldWind.RHUMB_LINE) {
                return this.interpolateRhumb(amount, location1, location2, result);
            } else {
                return this.interpolateLinear(amount, location1, location2, result);
            }
        };

        /**
         * Compute a location along a great circle path at a specified distance between two specified locations.
         * @param {Number} amount The fraction of the path between the two locations at which to compute the new
         * location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * This function uses a spherical model, not elliptical.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If either specified location or the result argument is null or undefined.
         */
        Location.interpolateGreatCircle = function (amount, location1, location2, result) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateGreatCircle", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateGreatCircle", "missingResult"));
            }

            if (location1.equals(location2)) {
                result.latitude = location1.latitude;
                result.longitude = location1.longitude;
                return result;
            }

            var t = WWMath.clamp(amount, 0, 1),
                azimuthDegrees = this.greatCircleAzimuth(location1, location2),
                distanceRadians = this.greatCircleDistance(location1, location2);

            return this.greatCircleLocation(location1, azimuthDegrees, t * distanceRadians, result);
        };

        /**
         * Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
         * This angle can be used as the starting azimuth for a great circle arc that begins at the first location, and
         * passes through the second location.
         * This function uses a spherical model, not elliptical.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed azimuth, in degrees.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.greatCircleAzimuth = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleAzimuth", "missingLocation"));
            }

            var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
                x,
                y,
                azimuthRadians;

            if (lat1 == lat2 && lon1 == lon2) {
                return 0;
            }

            if (lon1 == lon2) {
                return lat1 > lat2 ? 180 : 0;
            }

            // Taken from "Map Projections - A Working Manual", page 30, equation 5-4b.
            // The atan2() function is used in place of the traditional atan(y/x) to simplify the case when x == 0.
            y = Math.cos(lat2) * Math.sin(lon2 - lon1);
            x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
            azimuthRadians = Math.atan2(y, x);

            return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
        };

        /**
         * Computes the great circle angular distance between two locations. The return value gives the distance as the
         * angle between the two positions. In radians, this angle is the arc length of the segment between the two
         * positions. To compute a distance in meters from this value, multiply the return value by the radius of the
         * globe.
         * This function uses a spherical model, not elliptical.
         *
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed distance, in radians.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.greatCircleDistance = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleDistance", "missingLocation"));
            }

            var lat1Radians = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2Radians = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1Radians = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2Radians = location2.longitude * Angle.DEGREES_TO_RADIANS,
                a,
                b,
                c,
                distanceRadians;

            if (lat1Radians == lat2Radians && lon1Radians == lon2Radians) {
                return 0;
            }

            // "Haversine formula," taken from https://en.wikipedia.org/wiki/Great-circle_distance#Formul.C3.A6
            a = Math.sin((lat2Radians - lat1Radians) / 2.0);
            b = Math.sin((lon2Radians - lon1Radians) / 2.0);
            c = a * a + Math.cos(lat1Radians) * Math.cos(lat2Radians) * b * b;
            distanceRadians = 2.0 * Math.asin(Math.sqrt(c));

            return isNaN(distanceRadians) ? 0 : distanceRadians;
        };

        /**
         * Computes the location on a great circle path corresponding to a given starting location, azimuth, and
         * arc distance.
         * This function uses a spherical model, not elliptical.
         *
         * @param {Location} location The starting location.
         * @param {Number} greatCircleAzimuthDegrees The azimuth in degrees.
         * @param {Number} pathLengthRadians The radian distance along the path at which to compute the end location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If the specified location or the result argument is null or undefined.
         */
        Location.greatCircleLocation = function (location, greatCircleAzimuthDegrees, pathLengthRadians, result) {
            if (!location) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleLocation", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleLocation", "missingResult"));
            }

            if (pathLengthRadians == 0) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
                return result;
            }

            var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
                lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
                azimuthRadians = greatCircleAzimuthDegrees * Angle.DEGREES_TO_RADIANS,
                endLatRadians,
                endLonRadians;

            // Taken from "Map Projections - A Working Manual", page 31, equation 5-5 and 5-6.
            endLatRadians = Math.asin(Math.sin(latRadians) * Math.cos(pathLengthRadians) +
                Math.cos(latRadians) * Math.sin(pathLengthRadians) * Math.cos(azimuthRadians));
            endLonRadians = lonRadians + Math.atan2(
                    Math.sin(pathLengthRadians) * Math.sin(azimuthRadians),
                    Math.cos(latRadians) * Math.cos(pathLengthRadians) -
                    Math.sin(latRadians) * Math.sin(pathLengthRadians) * Math.cos(azimuthRadians));

            if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
            } else {
                result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
                result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
            }

            return result;
        };

        /**
         * Compute a location along a rhumb path at a specified distance between two specified locations.
         * This function uses a spherical model, not elliptical.
         * @param {Number} amount The fraction of the path between the two locations at which to compute the new
         * location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If either specified location or the result argument is null or undefined.
         */
        Location.interpolateRhumb = function (amount, location1, location2, result) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateRhumb", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateRhumb", "missingResult"));
            }

            if (location1.equals(location2)) {
                result.latitude = location1.latitude;
                result.longitude = location1.longitude;
                return result;
            }

            var t = WWMath.clamp(amount, 0, 1),
                azimuthDegrees = this.rhumbAzimuth(location1, location2),
                distanceRadians = this.rhumbDistance(location1, location2);

            return this.rhumbLocation(location1, azimuthDegrees, t * distanceRadians, result);
        };

        /**
         * Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
         * This angle can be used as the azimuth for a rhumb arc that begins at the first location, and
         * passes through the second location.
         * This function uses a spherical model, not elliptical.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed azimuth, in degrees.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.rhumbAzimuth = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbAzimuth", "missingLocation"));
            }

            var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
                dLon,
                dPhi,
                azimuthRadians;

            if (lat1 == lat2 && lon1 == lon2) {
                return 0;
            }

            dLon = lon2 - lon1;
            dPhi = Math.log(Math.tan(lat2 / 2.0 + Math.PI / 4) / Math.tan(lat1 / 2.0 + Math.PI / 4));

            // If lonChange over 180 take shorter rhumb across 180 meridian.
            if (WWMath.fabs(dLon) > Math.PI) {
                dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon);
            }

            azimuthRadians = Math.atan2(dLon, dPhi);

            return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
        };

        /**
         * Computes the rhumb angular distance between two locations. The return value gives the distance as the
         * angle between the two positions in radians. This angle is the arc length of the segment between the two
         * positions. To compute a distance in meters from this value, multiply the return value by the radius of the
         * globe.
         * This function uses a spherical model, not elliptical.
         *
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed distance, in radians.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.rhumbDistance = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbDistance", "missingLocation"));
            }

            var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
                dLat,
                dLon,
                dPhi,
                q,
                distanceRadians;

            if (lat1 == lat2 && lon1 == lon2) {
                return 0;
            }

            dLat = lat2 - lat1;
            dLon = lon2 - lon1;
            dPhi = Math.log(Math.tan(lat2 / 2.0 + Math.PI / 4) / Math.tan(lat1 / 2.0 + Math.PI / 4));
            q = dLat / dPhi;

            if (isNaN(dPhi) || isNaN(q)) {
                q = Math.cos(lat1);
            }

            // If lonChange over 180 take shorter rhumb across 180 meridian.
            if (WWMath.fabs(dLon) > Math.PI) {
                dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon);
            }

            distanceRadians = Math.sqrt(dLat * dLat + q * q * dLon * dLon);

            return isNaN(distanceRadians) ? 0 : distanceRadians;
        };

        /**
         * Computes the location on a rhumb arc with the given starting location, azimuth, and arc distance.
         * This function uses a spherical model, not elliptical.
         *
         * @param {Location} location The starting location.
         * @param {Number} azimuthDegrees The azimuth in degrees.
         * @param {Number} pathLengthRadians The radian distance along the path at which to compute the location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If the specified location or the result argument is null or undefined.
         */
        Location.rhumbLocation = function (location, azimuthDegrees, pathLengthRadians, result) {
            if (!location) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbLocation", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "rhumbLocation", "missingResult"));
            }

            if (pathLengthRadians == 0) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
                return result;
            }

            var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
                lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
                azimuthRadians = azimuthDegrees * Angle.DEGREES_TO_RADIANS,
                endLatRadians = latRadians + pathLengthRadians * Math.cos(azimuthRadians),
                dPhi = Math.log(Math.tan(endLatRadians / 2 + Math.PI / 4) / Math.tan(latRadians / 2 + Math.PI / 4)),
                q = (endLatRadians - latRadians) / dPhi,
                dLon,
                endLonRadians;

            if (isNaN(dPhi) || isNaN(q) || !isFinite(q)) {
                q = Math.cos(latRadians);
            }

            dLon = pathLengthRadians * Math.sin(azimuthRadians) / q;

            // Handle latitude passing over either pole.
            if (WWMath.fabs(endLatRadians) > Math.PI / 2) {
                endLatRadians = endLatRadians > 0 ? Math.PI - endLatRadians : -Math.PI - endLatRadians;
            }

            endLonRadians = WWMath.fmod(lonRadians + dLon + Math.PI, 2 * Math.PI) - Math.PI;

            if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
            } else {
                result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
                result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
            }

            return result;
        };

        /**
         * Compute a location along a linear path at a specified distance between two specified locations.
         * @param {Number} amount The fraction of the path between the two locations at which to compute the new
         * location. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If either specified location or the result argument is null or undefined.
         */
        Location.interpolateLinear = function (amount, location1, location2, result) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateLinear", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "interpolateLinear", "missingResult"));
            }

            if (location1.equals(location2)) {
                result.latitude = location1.latitude;
                result.longitude = location1.longitude;
                return result;
            }

            var t = WWMath.clamp(amount, 0, 1),
                azimuthDegrees = this.linearAzimuth(location1, location2),
                distanceRadians = this.linearDistance(location1, location2);

            return this.linearLocation(location1, azimuthDegrees, t * distanceRadians, result);
        };

        /**
         * Computes the azimuth angle (clockwise from North) that points from the first location to the second location.
         * This angle can be used as the azimuth for a linear arc that begins at the first location, and
         * passes through the second location.
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed azimuth, in degrees.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.linearAzimuth = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearAzimuth", "missingLocation"));
            }

            var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
                dLon,
                dPhi,
                azimuthRadians;

            if (lat1 == lat2 && lon1 == lon2) {
                return 0;
            }

            dLon = lon2 - lon1;
            dPhi = lat2 - lat1;

            // If longitude change is over 180 take shorter path across 180 meridian.
            if (WWMath.fabs(dLon) > Math.PI) {
                dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon);
            }

            azimuthRadians = Math.atan2(dLon, dPhi);

            return isNaN(azimuthRadians) ? 0 : azimuthRadians * Angle.RADIANS_TO_DEGREES;
        };

        /**
         * Computes the linear angular distance between two locations. The return value gives the distance as the
         * angle between the two positions in radians. This angle is the arc length of the segment between the two
         * positions. To compute a distance in meters from this value, multiply the return value by the radius of the
         * globe.
         *
         * @param {Location} location1 The starting location.
         * @param {Location} location2 The ending location.
         * @returns {Number} The computed distance, in radians.
         * @throws {ArgumentError} If either specified location is null or undefined.
         */
        Location.linearDistance = function (location1, location2) {
            if (!location1 || !location2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearDistance", "missingLocation"));
            }

            var lat1 = location1.latitude * Angle.DEGREES_TO_RADIANS,
                lat2 = location2.latitude * Angle.DEGREES_TO_RADIANS,
                lon1 = location1.longitude * Angle.DEGREES_TO_RADIANS,
                lon2 = location2.longitude * Angle.DEGREES_TO_RADIANS,
                dLat,
                dLon,
                distanceRadians;

            if (lat1 == lat2 && lon1 == lon2) {
                return 0;
            }

            dLat = lat2 - lat1;
            dLon = lon2 - lon1;

            // If lonChange over 180 take shorter path across 180 meridian.
            if (WWMath.fabs(dLon) > Math.PI) {
                dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon);
            }

            distanceRadians = Math.sqrt(dLat * dLat + dLon * dLon);

            return isNaN(distanceRadians) ? 0 : distanceRadians;
        };

        /**
         * Computes the location on a linear path with the given starting location, azimuth, and arc distance.
         *
         * @param {Location} location The starting location.
         * @param {Number} azimuthDegrees The azimuth in degrees.
         * @param {Number} pathLengthRadians The radian distance along the path at which to compute the location.
         * @param {Location} result A Location in which to return the result.
         * @returns {Location} The specified result location.
         * @throws {ArgumentError} If the specified location or the result argument is null or undefined.
         */
        Location.linearLocation = function (location, azimuthDegrees, pathLengthRadians, result) {
            if (!location) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearLocation", "missingLocation"));
            }
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "linearLocation", "missingResult"));
            }

            if (pathLengthRadians == 0) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
                return result;
            }

            var latRadians = location.latitude * Angle.DEGREES_TO_RADIANS,
                lonRadians = location.longitude * Angle.DEGREES_TO_RADIANS,
                azimuthRadians = azimuthDegrees * Angle.DEGREES_TO_RADIANS,
                endLatRadians = latRadians + pathLengthRadians * Math.cos(azimuthRadians),
                endLonRadians;

            // Handle latitude passing over either pole.
            if (WWMath.fabs(endLatRadians) > Math.PI / 2) {
                endLatRadians = endLatRadians > 0 ? Math.PI - endLatRadians : -Math.PI - endLatRadians;
            }

            endLonRadians =
                WWMath.fmod(lonRadians + pathLengthRadians * Math.sin(azimuthRadians) + Math.PI, 2 * Math.PI) - Math.PI;

            if (isNaN(endLatRadians) || isNaN(endLonRadians)) {
                result.latitude = location.latitude;
                result.longitude = location.longitude;
            } else {
                result.latitude = Angle.normalizedDegreesLatitude(endLatRadians * Angle.RADIANS_TO_DEGREES);
                result.longitude = Angle.normalizedDegreesLongitude(endLonRadians * Angle.RADIANS_TO_DEGREES);
            }

            return result;
        };

        /**
         * Determine whether a list of locations crosses the dateline.
         * @param {Location[]} locations The locations to test.
         * @returns {boolean} True if the dateline is crossed, else false.
         * @throws {ArgumentError} If the locations list is null.
         */
        Location.locationsCrossDateLine = function (locations) {
            if (!locations) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "locationsCrossDateline", "missingLocation"));
            }

            var pos = null;
            for (var idx = 0, len = locations.length; idx < len; idx += 1) {
                var posNext = locations[idx];

                if (pos != null) {
                    // A segment cross the line if end pos have different longitude signs
                    // and are more than 180 degrees longitude apart
                    if (WWMath.signum(pos.longitude) != WWMath.signum(posNext.longitude)) {
                        var delta = Math.abs(pos.longitude - posNext.longitude);
                        if (delta > 180 && delta < 360)
                            return true;
                    }
                }
                pos = posNext;
            }

            return false;
        };

        /**
         * Returns two locations with the most extreme latitudes on the sequence of great circle arcs defined by each pair
         * of locations in the specified iterable.
         *
         * @param {Location[]} locations The pairs of locations defining a sequence of great circle arcs.
         *
         * @return {Location[]} Two locations with the most extreme latitudes on the great circle arcs.
         *
         * @throws IllegalArgumentException if locations is null.
         */
        Location.greatCircleArcExtremeLocations = function (locations) {
            if (!locations) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeLocations", "missingLocation"));
            }

            var minLatLocation = null;
            var maxLatLocation = null;

            var lastLocation = null;

            for (var idx = 0, len = locations.length; idx < len; idx += 1) {
                var location = locations[idx];

                if (lastLocation != null) {
                    var extremes = Location.greatCircleArcExtremeForTwoLocations(lastLocation, location);
                    if (extremes == null) {
                        continue;
                    }

                    if (minLatLocation == null || minLatLocation.latitude > extremes[0].latitude) {
                        minLatLocation = extremes[0];
                    }
                    if (maxLatLocation == null || maxLatLocation.latitude < extremes[1].latitude) {
                        maxLatLocation = extremes[1];
                    }
                }

                lastLocation = location;
            }

            return [minLatLocation, maxLatLocation];
        };

        /**
         * Returns two locations with the most extreme latitudes on the great circle arc defined by, and limited to, the two
         * locations.
         *
         * @param {Location} begin Beginning location on the great circle arc.
         * @param {Location} end   Ending location on the great circle arc.
         *
         * @return {Location[]} Two locations with the most extreme latitudes on the great circle arc.
         *
         * @throws {ArgumentError} If either begin or end are null.
         */
        Location.greatCircleArcExtremeForTwoLocations = function (begin, end) {
            if (!begin || !end) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeForTwoLocations", "missingLocation"));
            }

            var idx, len, location; // Iteration variables.
            var minLatLocation = null;
            var maxLatLocation = null;
            var minLat = 90;
            var maxLat = -90;

            // Compute the min and max latitude and associated locations from the arc endpoints.
            var locations = [begin, end];
            for (idx = 0, len = locations.length; idx < len; idx += 1) {
                location = locations[idx];

                if (minLat >= location.latitude) {
                    minLat = location.latitude;
                    minLatLocation = location;
                }
                if (maxLat <= location.latitude) {
                    maxLat = location.latitude;
                    maxLatLocation = location;
                }
            }
            // The above could be written for greater clarity, simplicity, and speed:
            // minLat = Math.min(begin.latitude, end.latitude);
            // maxLat = Math.max(begin.latitude, end.latitude);
            // minLatLocation = minLat == begin.latitude ? begin : end;
            // maxLatLocation = maxLat == begin.latitude ? begin : end;

            // Compute parameters for the great circle arc defined by begin and end. Then compute the locations of extreme
            // latitude on entire the great circle which that arc is part of.
            var greatArcAzimuth = Location.greatCircleAzimuth(begin, end);
            var greatArcDistance = Location.greatCircleDistance(begin, end);
            var greatCircleExtremes = Location.greatCircleExtremeLocationsUsingAzimuth(begin, greatArcAzimuth);

            // Determine whether either of the extreme locations are inside the arc defined by begin and end. If so,
            // adjust the min and max latitude accordingly.
            for (idx = 0, len = greatCircleExtremes.length; idx < len; idx += 1) {
                location = greatCircleExtremes[idx];

                var az = Location.greatCircleAzimuth(begin, location);
                var d = Location.greatCircleDistance(begin, location);

                // The extreme location must be between the begin and end locations. Therefore its azimuth relative to
                // the begin location should have the same signum, and its distance relative to the begin location should
                // be between 0 and greatArcDistance, inclusive.
                if (WWMath.signum(az) == WWMath.signum(greatArcAzimuth)) {
                    if (d >= 0 && d <= greatArcDistance) {
                        if (minLat >= location.latitude) {
                            minLat = location.latitude;
                            minLatLocation = location;
                        }
                        if (maxLat <= location.latitude) {
                            maxLat = location.latitude;
                            maxLatLocation = location;
                        }
                    }
                }
            }

            return [minLatLocation, maxLatLocation];
        };

        /**
         * Returns two locations with the most extreme latitudes on the great circle with the given starting location and
         * azimuth.
         *
         * @param {Location} location Location on the great circle.
         * @param {number} azimuth  Great circle azimuth angle (clockwise from North).
         *
         * @return {Location[]} Two locations where the great circle has its extreme latitudes.
         *
         * @throws {ArgumentError} If location is null.
         */
        Location.greatCircleExtremeLocationsUsingAzimuth = function (location, azimuth) {
            if (!location) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Location", "greatCircleArcExtremeLocationsUsingAzimuth", "missingLocation"));
            }

            var lat0 = location.latitude;
            var az = azimuth * Angle.DEGREES_TO_RADIANS;

            // Derived by solving the function for longitude on a great circle against the desired longitude. We start
            // with the equation in "Map Projections - A Working Manual", page 31, equation 5-5:
            //
            //     lat = asin( sin(lat0) * cos(C) + cos(lat0) * sin(C) * cos(Az) )
            //
            // Where (lat0, lon) are the starting coordinates, c is the angular distance along the great circle from the
            // starting coordinate, and Az is the azimuth. All values are in radians. Solving for angular distance gives
            // distance to the equator:
            //
            //     tan(C) = -tan(lat0) / cos(Az)
            //
            // The great circle is by definition centered about the Globe's origin. Therefore intersections with the
            // equator will be antipodal (exactly 180 degrees opposite each other), as will be the extreme latitudes.
            // By observing the symmetry of a great circle, it is also apparent that the extreme latitudes will be 90
            // degrees from either intersection with the equator.
            //
            // d1 = c + 90
            // d2 = c - 90

            var tanDistance = -Math.tan(lat0) / Math.cos(az);
            var distance = Math.atan(tanDistance);

            var extremeDistance1 = distance + (Math.PI / 2.0);
            var extremeDistance2 = distance - (Math.PI / 2.0);

            return [
                Location.greatCircleLocation(location, azimuth, extremeDistance1, new Location(0, 0)),
                Location.greatCircleLocation(location, azimuth, extremeDistance2, new Location(0, 0))
            ];
        };

        /**
         * Determine where a line between two positions crosses a given meridian. The intersection test is performed by
         * intersecting a line in Cartesian space between the two positions with a plane through the meridian. Thus, it is
         * most suitable for working with positions that are fairly close together as the calculation does not take into
         * account great circle or rhumb paths.
         *
         * @param {Location} p1         First position.
         * @param {Location} p2         Second position.
         * @param {number} meridian     Longitude line to intersect with.
         * @param {Globe} globe         Globe used to compute intersection.
         *
         * @return {number} latitude The intersection latitude along the meridian
         *
         * TODO: this code allocates 4 new Vec3 and 1 new Position; use scratch variables???
         * TODO: Why not? Every location created would then allocated those variables as well, even if they aren't needed :(.
         */
        Location.intersectionWithMeridian = function (p1, p2, meridian, globe) {
            // TODO: add support for 2D
            //if (globe instanceof Globe2D)
            //{
            //    // y = mx + b case after normalizing negative angles.
            //    double lon1 = p1.getLongitude().degrees < 0 ? p1.getLongitude().degrees + 360 : p1.getLongitude().degrees;
            //    double lon2 = p2.getLongitude().degrees < 0 ? p2.getLongitude().degrees + 360 : p2.getLongitude().degrees;
            //    if (lon1 == lon2)
            //        return null;
            //
            //    double med = meridian.degrees < 0 ? meridian.degrees + 360 : meridian.degrees;
            //    double slope = (p2.latitude.degrees - p1.latitude.degrees) / (lon2 - lon1);
            //    double lat = p1.latitude.degrees + slope * (med - lon1);
            //
            //    return LatLon.fromDegrees(lat, meridian.degrees);
            //}

            var pt1 = globe.computePointFromLocation(p1.latitude, p1.longitude, new Vec3(0, 0, 0));
            var pt2 = globe.computePointFromLocation(p2.latitude, p2.longitude, new Vec3(0, 0, 0));

            // Compute a plane through the origin, North Pole, and the desired meridian.
            var northPole = globe.computePointFromLocation(90, meridian, new Vec3(0, 0, 0));
            var pointOnEquator = globe.computePointFromLocation(0, meridian, new Vec3(0, 0, 0));

            var plane = Plane.fromPoints(northPole, pointOnEquator, Vec3.ZERO);

            var intersectionPoint = new Vec3(0, 0, 0);
            if (!plane.intersectsSegmentAt(pt1, pt2, intersectionPoint)) {
                return null;
            }

            // TODO: unable to simply create a new Position(0, 0, 0)
            var pos = new WorldWind.Position(0, 0, 0);
            globe.computePositionFromPoint(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2], pos);

            return pos.latitude;
        };

        /**
         * Determine where a line between two positions crosses a given meridian. The intersection test is performed by
         * intersecting a line in Cartesian space. Thus, it is most suitable for working with positions that are fairly
         * close together as the calculation does not take into account great circle or rhumb paths.
         *
         * @param {Location | Position} p1 First position.
         * @param {Location | Position} p2 Second position.
         * @param {number} meridian Longitude line to intersect with.
         *
         * @return {number | null} latitude The intersection latitude along the meridian
         * or null if the line is collinear with the meridian
         */
        Location.meridianIntersection = function(p1, p2, meridian){
                // y = mx + b case after normalizing negative angles.
                var lon1 = p1.longitude < 0 ? p1.longitude + 360 : p1.longitude;
                var lon2 = p2.longitude < 0 ? p2.longitude + 360 : p2.longitude;
                if (lon1 === lon2) {
                    //infinite solutions, the line is collinear with the anti-meridian
                    return null;
                }

                var med = meridian < 0 ? meridian + 360 : meridian;
                var slope = (p2.latitude - p1.latitude) / (lon2 - lon1);
                var lat = p1.latitude + slope * (med - lon1);

                return lat;
        };

        /**
         * A bit mask indicating which if any pole is being referenced.
         * This corresponds to Java WW's AVKey.NORTH and AVKey.SOUTH,
         * although this encoding can capture both poles simultaneously, which was
         * a 'to do' item in the Java implementation.
         * @type {{NONE: number, NORTH: number, SOUTH: number}}
         */
        Location.poles = {
            'NONE': 0,
            'NORTH': 1,
            'SOUTH': 2
        };

        return Location;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Position
 */
define('geom/Position',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger,
              WWMath) {
        "use strict";

        /**
         * Constructs a position from a specified latitude and longitude in degrees and altitude in meters.
         * @alias Position
         * @constructor
         * @classdesc Represents a latitude, longitude, altitude triple, with latitude and longitude in degrees and
         * altitude in meters.
         * @param {Number} latitude The latitude in degrees.
         * @param {Number} longitude The longitude in degrees.
         * @param {Number} altitude The altitude in meters.
         */
        var Position = function (latitude, longitude, altitude) {
            /**
             * The latitude in degrees.
             * @type {Number}
             */
            this.latitude = latitude;
            /**
             * The longitude in degrees.
             * @type {Number}
             */
            this.longitude = longitude;
            /**
             * The altitude in meters.
             * @type {Number}
             */
            this.altitude = altitude;
        };

        /**
         * A Position with latitude, longitude and altitude all 0.
         * @constant
         * @type {Position}
         */
        Position.ZERO = new Position(0, 0, 0);

        /**
         * Creates a position from angles specified in radians.
         * @param {Number} latitudeRadians The latitude in radians.
         * @param {Number} longitudeRadians The longitude in radians.
         * @param {Number} altitude The altitude in meters.
         * @returns {Position} The new position with latitude and longitude in degrees.
         */
        Position.fromRadians = function (latitudeRadians, longitudeRadians, altitude) {
            return new Position(
                latitudeRadians * Angle.RADIANS_TO_DEGREES,
                longitudeRadians * Angle.RADIANS_TO_DEGREES,
                altitude);
        };

        /**
         * Sets this position to the latitude, longitude and altitude of a specified position.
         * @param {Position} position The position to copy.
         * @returns {Position} This position, set to the values of the specified position.
         * @throws {ArgumentError} If the specified position is null or undefined.
         */
        Position.prototype.copy = function (position) {
            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "copy", "missingPosition"));
            }

            this.latitude = position.latitude;
            this.longitude = position.longitude;
            this.altitude = position.altitude;

            return this;
        };

        /**
         * Indicates whether this position has the same latitude, longitude and altitude as a specified position.
         * @param {Position} position The position to compare with this one.
         * @returns {Boolean} true if this position is equal to the specified one, otherwise false.
         */
        Position.prototype.equals = function (position) {
            return position
                && position.latitude === this.latitude
                && position.longitude === this.longitude
                && position.altitude === this.altitude;
        };

        /**
         * Computes a position along a great circle path at a specified distance between two specified positions.
         * @param {Number} amount The fraction of the path between the two positions at which to compute the new
         * position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Position} position1 The starting position.
         * @param {Position} position2 The ending position.
         * @param {Position} result A Position in which to return the result.
         * @returns {Position} The specified result position.
         * @throws {ArgumentError} If either specified position or the result argument is null or undefined.
         */
        Position.interpolateGreatCircle = function (amount, position1, position2, result) {
            if (!position1 || !position2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateGreatCircle", "missingPosition"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateGreatCircle", "missingResult"));
            }

            var t = WWMath.clamp(amount, 0, 1);
            result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);

            //noinspection JSCheckFunctionSignatures
            Location.interpolateGreatCircle(t, position1, position2, result);

            return result;
        };

        /**
         * Computes a position along a rhumb path at a specified distance between two specified positions.
         * @param {Number} amount The fraction of the path between the two positions at which to compute the new
         * position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Position} position1 The starting position.
         * @param {Position} position2 The ending position.
         * @param {Position} result A Position in which to return the result.
         * @returns {Position} The specified result position.
         * @throws {ArgumentError} If either specified position or the result argument is null or undefined.
         */
        Position.interpolateRhumb = function (amount, position1, position2, result) {
            if (!position1 || !position2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateRhumb", "missingPosition"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateRhumb", "missingResult"));
            }

            var t = WWMath.clamp(amount, 0, 1);
            result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);

            //noinspection JSCheckFunctionSignatures
            Location.interpolateRhumb(t, position1, position2, result);

            return result;
        };

        /**
         * Computes a position along a linear path at a specified distance between two specified positions.
         * @param {Number} amount The fraction of the path between the two positions at which to compute the new
         * position. This number should be between 0 and 1. If not, it is clamped to the nearest of those values.
         * @param {Position} position1 The starting position.
         * @param {Position} position2 The ending position.
         * @param {Position} result A Position in which to return the result.
         * @returns {Position} The specified result position.
         * @throws {ArgumentError} If either specified position or the result argument is null or undefined.
         */
        Position.interpolateLinear = function (amount, position1, position2, result) {
            if (!position1 || !position2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateLinear", "missingPosition"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Position", "interpolateLinear", "missingResult"));
            }

            var t = WWMath.clamp(amount, 0, 1);
            result.altitude = WWMath.interpolate(t, position1.altitude, position2.altitude);

            //noinspection JSCheckFunctionSignatures
            Location.interpolateLinear(t, position1, position2, result);

            return result;
        };

        /**
         * Returns a string representation of this position.
         * @returns {String}
         */
        Position.prototype.toString = function () {
            return "(" + this.latitude.toString() + "\u00b0, " + this.longitude.toString() + "\u00b0, "
                + this.altitude.toString() + ")";
        };

        return Position;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Texture
 */
define('render/Texture',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Logger,
              WWMath) {
        "use strict";

        /**
         * Constructs a texture for a specified image.
         * @alias Texture
         * @constructor
         * @classdesc Represents a WebGL texture. Applications typically do not interact with this class.
         * @param {WebGLRenderingContext} gl The current WebGL rendering context.
         * @param {Image} image The texture's image.
         * @param {GL.enum} wrapMode Optional. Specifies the wrap mode of the texture. Defaults to gl.CLAMP_TO_EDGE
         * @throws {ArgumentError} If the specified WebGL context or image is null or undefined.
         */
        var Texture = function (gl, image, wrapMode) {

            if (!gl) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
                    "missingGlContext"));
            }

            if (!image) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
                    "missingImage"));
            }

            if (!wrapMode) {
                wrapMode = gl.CLAMP_TO_EDGE;
            }

            var textureId = gl.createTexture(),
                isPowerOfTwo = (WWMath.isPowerOfTwo(image.width) && WWMath.isPowerOfTwo(image.height));

            this.originalImageWidth = image.width;
            this.originalImageHeight = image.height;

            if (wrapMode === gl.REPEAT && !isPowerOfTwo) {
                image = this.resizeImage(image);
                isPowerOfTwo = true;
            }

            this.imageWidth = image.width;
            this.imageHeight = image.height;
            this.size = image.width * image.height * 4;

            gl.bindTexture(gl.TEXTURE_2D, textureId);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
                isPowerOfTwo ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,
                gl.LINEAR);

            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);

            // Setup 4x anisotropic texture filtering when this feature is available.
            // https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic
            var ext = (
            gl.getExtension("EXT_texture_filter_anisotropic") ||
            gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"));
            if (ext) {
                gl.texParameteri(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, 4);
            }

            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
            gl.texImage2D(gl.TEXTURE_2D, 0,
                gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);

            if (isPowerOfTwo) {
                gl.generateMipmap(gl.TEXTURE_2D);
            }

            this.textureId = textureId;

            /**
             * The time at which this texture was created.
             * @type {Date}
             */
            this.creationTime = new Date();
        };

        /**
         * Disposes of the WebGL texture object associated with this texture.
         * @param gl
         */
        Texture.prototype.dispose = function (gl) {
            gl.deleteTexture(this.textureId);
            delete this.textureId;
        };

        /**
         * Binds this texture in the current WebGL graphics context.
         * @param {DrawContext} dc The current draw context.
         */
        Texture.prototype.bind = function (dc) {
            dc.currentGlContext.bindTexture(dc.currentGlContext.TEXTURE_2D, this.textureId);
            dc.frameStatistics.incrementTextureLoadCount(1);
            return true;
        };

        /**
         * Resizes an image to a power of two.
         * @param {Image} image The image to resize.
         */
        Texture.prototype.resizeImage = function (image) {
            var canvas = document.createElement("canvas");
            canvas.width = WWMath.powerOfTwoFloor(image.width);
            canvas.height = WWMath.powerOfTwoFloor(image.height);
            var ctx = canvas.getContext("2d");
            ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
            return canvas;
        };


        return Texture;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Matrix
 */
define('geom/Matrix',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Plane',
        '../geom/Position',
        '../geom/Rectangle',
        '../render/Texture',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Logger,
              Plane,
              Position,
              Rectangle,
              Texture,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a matrix.
         * @alias Matrix
         * @constructor
         * @classdesc Represents a 4 x 4 double precision matrix stored in a Float64Array in row-major order.
         * @param {Number} m11 matrix element at row 1, column 1.
         * @param {Number} m12 matrix element at row 1, column 2.
         * @param {Number} m13 matrix element at row 1, column 3.
         * @param {Number} m14 matrix element at row 1, column 4.
         * @param {Number} m21 matrix element at row 2, column 1.
         * @param {Number} m22 matrix element at row 2, column 2.
         * @param {Number} m23 matrix element at row 2, column 3.
         * @param {Number} m24 matrix element at row 2, column 4.
         * @param {Number} m31 matrix element at row 3, column 1.
         * @param {Number} m32 matrix element at row 3, column 2.
         * @param {Number} m33 matrix element at row 3, column 3.
         * @param {Number} m34 matrix element at row 3, column 4.
         * @param {Number} m41 matrix element at row 4, column 1.
         * @param {Number} m42 matrix element at row 4, column 2.
         * @param {Number} m43 matrix element at row 4, column 3.
         * @param {Number} m44 matrix element at row 4, column 4.
         */
        var Matrix = function (m11, m12, m13, m14,
                               m21, m22, m23, m24,
                               m31, m32, m33, m34,
                               m41, m42, m43, m44) {
            this[0] = m11;
            this[1] = m12;
            this[2] = m13;
            this[3] = m14;
            this[4] = m21;
            this[5] = m22;
            this[6] = m23;
            this[7] = m24;
            this[8] = m31;
            this[9] = m32;
            this[10] = m33;
            this[11] = m34;
            this[12] = m41;
            this[13] = m42;
            this[14] = m43;
            this[15] = m44;
        };

        // Derives from Float64Array.
        Matrix.prototype = new Float64Array(16);

        /**
         * Creates an identity matrix.
         * @returns {Matrix} A new identity matrix.
         */
        Matrix.fromIdentity = function () {
            return new Matrix(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
            );
        };

        /**
         * Computes the principal axes of a point collection expressed in a typed array.
         * @param {Float32Array} points The points for which to compute the axes,
         * expressed as X0, Y0, Z0, X1, Y1, Z1, ...
         * @param {Vec3} axis1 A vector in which to return the first (longest) principal axis.
         * @param {Vec3} axis2 A vector in which to return the second (mid-length) principal axis.
         * @param {Vec3} axis3 A vector in which to return the third (shortest) principal axis.
         * @throws {ArgumentError} If the specified points array is null, undefined or empty, or one of the
         * specified axes arguments is null or undefined.
         */
        Matrix.principalAxesFromPoints = function (points, axis1, axis2, axis3) {
            if (!points || points.length < 1) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "principalAxesFromPoints",
                    "missingPoints"));
            }

            if (!axis1 || !axis2 || !axis3) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "principalAxesFromPoints",
                    "An axis argument is null or undefined."));
            }

            // Compute the covariance matrix.
            var covariance = Matrix.fromIdentity();
            covariance.setToCovarianceOfPoints(points);

            // Compute the eigenvectors from the covariance matrix. Since the covariance matrix is symmetric by
            // definition, we can safely use the "symmetric" method below.
            covariance.eigensystemFromSymmetricMatrix(axis1, axis2, axis3);

            // Normalize the eigenvectors, which are already sorted in order from most prominent to least prominent.
            axis1.normalize();
            axis2.normalize();
            axis3.normalize();
        };

        /**
         * Sets the components of this matrix to specified values.
         * @param {Number} m11 matrix element at row 1, column 1.
         * @param {Number} m12 matrix element at row 1, column 2.
         * @param {Number} m13 matrix element at row 1, column 3.
         * @param {Number} m14 matrix element at row 1, column 4.
         * @param {Number} m21 matrix element at row 2, column 1.
         * @param {Number} m22 matrix element at row 2, column 2.
         * @param {Number} m23 matrix element at row 2, column 3.
         * @param {Number} m24 matrix element at row 2, column 4.
         * @param {Number} m31 matrix element at row 3, column 1.
         * @param {Number} m32 matrix element at row 3, column 2.
         * @param {Number} m33 matrix element at row 3, column 3.
         * @param {Number} m34 matrix element at row 3, column 4.
         * @param {Number} m41 matrix element at row 4, column 1.
         * @param {Number} m42 matrix element at row 4, column 2.
         * @param {Number} m43 matrix element at row 4, column 3.
         * @param {Number} m44 matrix element at row 4, column 4.
         * @returns {Matrix} This matrix with its components set to the specified values.
         */
        Matrix.prototype.set = function (m11, m12, m13, m14,
                                         m21, m22, m23, m24,
                                         m31, m32, m33, m34,
                                         m41, m42, m43, m44) {
            this[0] = m11;
            this[1] = m12;
            this[2] = m13;
            this[3] = m14;
            this[4] = m21;
            this[5] = m22;
            this[6] = m23;
            this[7] = m24;
            this[8] = m31;
            this[9] = m32;
            this[10] = m33;
            this[11] = m34;
            this[12] = m41;
            this[13] = m42;
            this[14] = m43;
            this[15] = m44;

            return this;
        };

        /**
         * Sets this matrix to the identity matrix.
         * @returns {Matrix} This matrix set to the identity matrix.
         */
        Matrix.prototype.setToIdentity = function () {
            this[0] = 1;
            this[1] = 0;
            this[2] = 0;
            this[3] = 0;
            this[4] = 0;
            this[5] = 1;
            this[6] = 0;
            this[7] = 0;
            this[8] = 0;
            this[9] = 0;
            this[10] = 1;
            this[11] = 0;
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;
        };

        /**
         * Copies the components of a specified matrix to this matrix.
         * @param {Matrix} matrix The matrix to copy.
         * @returns {Matrix} This matrix set to the values of the specified matrix.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Matrix.prototype.copy = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "copy", "missingMatrix"));
            }

            this[0] = matrix[0];
            this[1] = matrix[1];
            this[2] = matrix[2];
            this[3] = matrix[3];
            this[4] = matrix[4];
            this[5] = matrix[5];
            this[6] = matrix[6];
            this[7] = matrix[7];
            this[8] = matrix[8];
            this[9] = matrix[9];
            this[10] = matrix[10];
            this[11] = matrix[11];
            this[12] = matrix[12];
            this[13] = matrix[13];
            this[14] = matrix[14];
            this[15] = matrix[15];
        };

        /**
         * Indicates whether the components of this matrix are equal to those of a specified matrix.
         * @param {Matrix} matrix The matrix to test equality with. May be null or undefined, in which case this
         * function returns false.
         * @returns {boolean} true if all components of this matrix are equal to the corresponding
         * components of the specified matrix, otherwise false.
         */
        Matrix.prototype.equals = function (matrix) {
            return matrix
                && this[0] == matrix[0]
                && this[1] == matrix[1]
                && this[2] == matrix[2]
                && this[3] == matrix[3]
                && this[4] == matrix[4]
                && this[5] == matrix[5]
                && this[6] == matrix[6]
                && this[7] == matrix[7]
                && this[8] == matrix[8]
                && this[9] == matrix[9]
                && this[10] == matrix[10]
                && this[11] == matrix[11]
                && this[12] == matrix[12]
                && this[13] == matrix[13]
                && this[14] == matrix[14]
                && this[15] == matrix[15];
        };

        /**
         * Stores this matrix's components in column-major order in a specified array.
         * <p>
         * The array must have space for at least 16 elements. This matrix's components are stored in the array
         * starting with row 0 column 0 in index 0, row 1 column 0 in index 1, row 2 column 0 in index 2, and so on.
         *
         * @param {Float32Array | Float64Array | Number[]} result An array of at least 16 elements. Upon return,
         * contains this matrix's components in column-major.
         * @returns {Float32Array} The specified result array.
         * @throws {ArgumentError} If the specified result array in null or undefined.
         */
        Matrix.prototype.columnMajorComponents = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "columnMajorComponents", "missingResult"));
            }

            // Column 1
            result[0] = this[0];
            result[1] = this[4];
            result[2] = this[8];
            result[3] = this[12];
            // Column 2
            result[4] = this[1];
            result[5] = this[5];
            result[6] = this[9];
            result[7] = this[13];
            // Column 3
            result[8] = this[2];
            result[9] = this[6];
            result[10] = this[10];
            result[11] = this[14];
            // Column 4
            result[12] = this[3];
            result[13] = this[7];
            result[14] = this[11];
            result[15] = this[15];

            return result;
        };

        /**
         * Sets this matrix to a translation matrix with specified translation components.
         * @param {Number} x The X translation component.
         * @param {Number} y The Y translation component.
         * @param {Number} z The Z translation component.
         * @returns {Matrix} This matrix with its translation components set to those specified and all other
         * components set to that of an identity matrix.
         */
        Matrix.prototype.setToTranslation = function (x, y, z) {
            this[0] = 1;
            this[1] = 0;
            this[2] = 0;
            this[3] = x;
            this[4] = 0;
            this[5] = 1;
            this[6] = 0;
            this[7] = y;
            this[8] = 0;
            this[9] = 0;
            this[10] = 1;
            this[11] = z;
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;

            return this;
        };

        /**
         * Sets the translation components of this matrix to specified values.
         * @param {Number} x The X translation component.
         * @param {Number} y The Y translation component.
         * @param {Number} z The Z translation component.
         * @returns {Matrix} This matrix with its translation components set to the specified values and all other
         * components unmodified.
         */
        Matrix.prototype.setTranslation = function (x, y, z) {
            this[3] = x;
            this[7] = y;
            this[11] = z;

            return this;
        };

        /**
         * Sets this matrix to a scale matrix with specified scale components.
         * @param {Number} xScale The X scale component.
         * @param {Number} yScale The Y scale component.
         * @param {Number} zScale The Z scale component.
         * @returns {Matrix} This matrix with its scale components set to those specified and all other
         * components set to that of an identity matrix.
         */
        Matrix.prototype.setToScale = function (xScale, yScale, zScale) {
            this[0] = xScale;
            this[1] = 0;
            this[2] = 0;
            this[3] = 0;
            this[4] = 0;
            this[5] = yScale;
            this[6] = 0;
            this[7] = 0;
            this[8] = 0;
            this[9] = 0;
            this[10] = zScale;
            this[11] = 0;
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;

            return this;
        };

        /**
         * Sets the scale components of this matrix to specified values.
         * @param {Number} xScale The X scale component.
         * @param {Number} yScale The Y scale component.
         * @param {Number} zScale The Z scale component.
         * @returns {Matrix} This matrix with its scale components set to the specified values and all other
         * components unmodified.
         */
        Matrix.prototype.setScale = function (xScale, yScale, zScale) {
            this[0] = xScale;
            this[5] = yScale;
            this[10] = zScale;

            return this;
        };

        /**
         * Sets this matrix to the transpose of a specified matrix.
         * @param {Matrix} matrix The matrix whose transpose is to be copied.
         * @returns {Matrix} This matrix, with its values set to the transpose of the specified matrix.
         * @throws {ArgumentError} If the specified matrix in null or undefined.
         */
        Matrix.prototype.setToTransposeOfMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToTransposeOfMatrix", "missingMatrix"));
            }

            this[0] = matrix[0];
            this[1] = matrix[4];
            this[2] = matrix[8];
            this[3] = matrix[12];
            this[4] = matrix[1];
            this[5] = matrix[5];
            this[6] = matrix[9];
            this[7] = matrix[13];
            this[8] = matrix[2];
            this[9] = matrix[6];
            this[10] = matrix[10];
            this[11] = matrix[14];
            this[12] = matrix[3];
            this[13] = matrix[7];
            this[14] = matrix[11];
            this[15] = matrix[15];

            return this;
        };

        /**
         * Sets this matrix to the matrix product of two specified matrices.
         * @param {Matrix} matrixA The first matrix multiplicand.
         * @param {Matrix} matrixB The second matrix multiplicand.
         * @returns {Matrix} This matrix set to the product of matrixA x matrixB.
         * @throws {ArgumentError} If either specified matrix is null or undefined.
         */
        Matrix.prototype.setToMultiply = function (matrixA, matrixB) {
            if (!matrixA || !matrixB) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToMultiply", "missingMatrix"));
            }

            var ma = matrixA,
                mb = matrixB;

            this[0] = ma[0] * mb[0] + ma[1] * mb[4] + ma[2] * mb[8] + ma[3] * mb[12];
            this[1] = ma[0] * mb[1] + ma[1] * mb[5] + ma[2] * mb[9] + ma[3] * mb[13];
            this[2] = ma[0] * mb[2] + ma[1] * mb[6] + ma[2] * mb[10] + ma[3] * mb[14];
            this[3] = ma[0] * mb[3] + ma[1] * mb[7] + ma[2] * mb[11] + ma[3] * mb[15];

            this[4] = ma[4] * mb[0] + ma[5] * mb[4] + ma[6] * mb[8] + ma[7] * mb[12];
            this[5] = ma[4] * mb[1] + ma[5] * mb[5] + ma[6] * mb[9] + ma[7] * mb[13];
            this[6] = ma[4] * mb[2] + ma[5] * mb[6] + ma[6] * mb[10] + ma[7] * mb[14];
            this[7] = ma[4] * mb[3] + ma[5] * mb[7] + ma[6] * mb[11] + ma[7] * mb[15];

            this[8] = ma[8] * mb[0] + ma[9] * mb[4] + ma[10] * mb[8] + ma[11] * mb[12];
            this[9] = ma[8] * mb[1] + ma[9] * mb[5] + ma[10] * mb[9] + ma[11] * mb[13];
            this[10] = ma[8] * mb[2] + ma[9] * mb[6] + ma[10] * mb[10] + ma[11] * mb[14];
            this[11] = ma[8] * mb[3] + ma[9] * mb[7] + ma[10] * mb[11] + ma[11] * mb[15];

            this[12] = ma[12] * mb[0] + ma[13] * mb[4] + ma[14] * mb[8] + ma[15] * mb[12];
            this[13] = ma[12] * mb[1] + ma[13] * mb[5] + ma[14] * mb[9] + ma[15] * mb[13];
            this[14] = ma[12] * mb[2] + ma[13] * mb[6] + ma[14] * mb[10] + ma[15] * mb[14];
            this[15] = ma[12] * mb[3] + ma[13] * mb[7] + ma[14] * mb[11] + ma[15] * mb[15];

            return this;
        };

        /**
         * Sets this matrix to the symmetric covariance Matrix computed from the x, y, z coordinates of a specified
         * points array.
         * <p/>
         * The computed covariance matrix represents the correlation between each pair of x-, y-, and z-coordinates as
         * they're distributed about the point array's arithmetic mean. Its layout is as follows:
         * <p/>
         * <code> C(x, x)  C(x, y)  C(x, z) <br/> C(x, y)  C(y, y)  C(y, z) <br/> C(x, z)  C(y, z)  C(z, z) </code>
         * <p/>
         * C(i, j) is the covariance of coordinates i and j, where i or j are a coordinate's dispersion about its mean
         * value. If any entry is zero, then there's no correlation between the two coordinates defining that entry. If the
         * returned matrix is diagonal, then all three coordinates are uncorrelated, and the specified point is
         * distributed evenly about its mean point.
         * @param {Float32Array | Float64Array | Number[]} points The points to consider.
         * @returns {Matrix} This matrix set to the covariance matrix for the specified list of points.
         * @throws {ArgumentError} If the specified array of points is null, undefined or empty.
         */
        Matrix.prototype.setToCovarianceOfPoints = function (points) {
            if (!points || points.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToCovarianceOfPoints", "missingArray"));
            }

            var mean,
                dx,
                dy,
                dz,
                count = 0,
                c11 = 0,
                c22 = 0,
                c33 = 0,
                c12 = 0,
                c13 = 0,
                c23 = 0,
                vec = new Vec3(0, 0, 0);

            mean = Vec3.averageOfBuffer(points, new Vec3(0, 0, 0));

            for (var i = 0, len = points.length / 3; i < len; i++) {
                vec[0] = points[i * 3];
                vec[1] = points[i * 3 + 1];
                vec[2] = points[i * 3 + 2];

                dx = vec[0] - mean[0];
                dy = vec[1] - mean[1];
                dz = vec[2] - mean[2];

                ++count;
                c11 += dx * dx;
                c22 += dy * dy;
                c33 += dz * dz;
                c12 += dx * dy; // c12 = c21
                c13 += dx * dz; // c13 = c31
                c23 += dy * dz; // c23 = c32
            }

            // Row 1
            this[0] = c11 / count;
            this[1] = c12 / count;
            this[2] = c13 / count;
            this[3] = 0;

            // Row 2
            this[4] = c12 / count;
            this[5] = c22 / count;
            this[6] = c23 / count;
            this[7] = 0;

            // Row 3
            this[8] = c13 / count;
            this[9] = c23 / count;
            this[10] = c33 / count;
            this[11] = 0;

            // Row 4
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 0;

            return this;
        };

        /**
         * Multiplies this matrix by a translation matrix with specified translation values.
         * @param {Number} x The X translation component.
         * @param {Number} y The Y translation component.
         * @param {Number} z The Z translation component.
         * @returns {Matrix} This matrix multiplied by the translation matrix implied by the specified values.
         */
        Matrix.prototype.multiplyByTranslation = function (x, y, z) {

            this.multiply(
                1, 0, 0, x,
                0, 1, 0, y,
                0, 0, 1, z,
                0, 0, 0, 1);

            return this;
        };

        /**
         * Multiplies this matrix by a rotation matrix about a specified axis and angle.
         * @param {Number} x The X component of the rotation axis.
         * @param {Number} y The Y component of the rotation axis.
         * @param {Number} z The Z component of the rotation axis.
         * @param {Number} angleDegrees The angle to rotate, in degrees.
         * @returns {Matrix} This matrix multiplied by the rotation matrix implied by the specified values.
         */
        Matrix.prototype.multiplyByRotation = function (x, y, z, angleDegrees) {

            var c = Math.cos(angleDegrees * Angle.DEGREES_TO_RADIANS),
                s = Math.sin(angleDegrees * Angle.DEGREES_TO_RADIANS);

            this.multiply(
                c + (1 - c) * x * x, (1 - c) * x * y - s * z, (1 - c) * x * z + s * y, 0,
                (1 - c) * x * y + s * z, c + (1 - c) * y * y, (1 - c) * y * z - s * x, 0,
                (1 - c) * x * z - s * y, (1 - c) * y * z + s * x, c + (1 - c) * z * z, 0,
                0, 0, 0, 1);

            return this;
        };

        /**
         * Multiplies this matrix by a scale matrix with specified values.
         * @param {Number} xScale The X scale component.
         * @param {Number} yScale The Y scale component.
         * @param {Number} zScale The Z scale component.
         * @returns {Matrix} This matrix multiplied by the scale matrix implied by the specified values.
         */
        Matrix.prototype.multiplyByScale = function (xScale, yScale, zScale) {

            this.multiply(
                xScale, 0, 0, 0,
                0, yScale, 0, 0,
                0, 0, zScale, 0,
                0, 0, 0, 1);

            return this;
        };

        /**
         * Sets this matrix to one that flips and shifts the y-axis.
         * <p>
         * The resultant matrix maps Y=0 to Y=1 and Y=1 to Y=0. All existing values are overwritten. This matrix is
         * usually used to change the coordinate origin from an upper left coordinate origin to a lower left coordinate
         * origin. This is typically necessary to align the coordinate system of images (top-left origin) with that of
         * OpenGL (bottom-left origin).
         * @returns {Matrix} This matrix set to values described above.
         */
        Matrix.prototype.setToUnitYFlip = function () {

            this[0] = 1;
            this[1] = 0;
            this[2] = 0;
            this[3] = 0;
            this[4] = 0;
            this[5] = -1;
            this[6] = 0;
            this[7] = 1;
            this[8] = 0;
            this[9] = 0;
            this[10] = 1;
            this[11] = 0;
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;

            return this;
        };

        /**
         * Multiplies this matrix by a local coordinate system transform for the specified globe.
         * <p>
         * The local coordinate system is defined such that the local origin (0, 0, 0) maps to the specified origin
         * point, the z axis maps to the globe's surface normal at the point, the y-axis maps to the north pointing
         * tangent, and the x-axis maps to the east pointing tangent.
         *
         * @param {Vec3} origin The local coordinate system origin, in model coordinates.
         * @param {Globe} globe The globe the coordinate system is relative to.
         *
         * @throws {ArgumentError} If either argument is null or undefined.
         */
        Matrix.prototype.multiplyByLocalCoordinateTransform = function (origin, globe) {
            if (!origin) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByLocalCoordinateTransform",
                        "Origin vector is null or undefined"));
            }

            if (!globe) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByLocalCoordinateTransform",
                        "missingGlobe"));
            }

            var xAxis = new Vec3(0, 0, 0),
                yAxis = new Vec3(0, 0, 0),
                zAxis = new Vec3(0, 0, 0);

            WWMath.localCoordinateAxesAtPoint(origin, globe, xAxis, yAxis, zAxis);

            this.multiply(
                xAxis[0], yAxis[0], zAxis[0], origin[0],
                xAxis[1], yAxis[1], zAxis[1], origin[1],
                xAxis[2], yAxis[2], zAxis[2], origin[2],
                0, 0, 0, 1);

            return this;
        };

        /**
         * Multiplies this matrix by a texture transform for the specified texture.
         * <p>
         * A texture image transform maps the bottom-left corner of the texture's image data to coordinate [0,0] and maps the
         * top-right of the texture's image data to coordinate [1,1]. This correctly handles textures whose image data has
         * non-power-of-two dimensions, and correctly orients textures whose image data has its origin in the upper-left corner.
         *
         * @param {Texture} texture The texture to multiply a transform for.
         *
         * @throws {ArgumentError} If the texture is null or undefined.
         */
        Matrix.prototype.multiplyByTextureTransform = function (texture) {
            if (!texture) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByTextureTransform",
                        "missingTexture"));
            }

            // Compute the scale necessary to map the edge of the image data to the range [0,1]. When the texture contains
            // power-of-two image data the scale is 1 and has no effect. Otherwise, the scale is computed such that the portion
            // of the texture containing image data maps to the range [0,1].
            var sx = texture.originalImageWidth / texture.imageWidth,
                sy = texture.originalImageHeight / texture.imageHeight;

            // Multiply this by a scaling matrix that maps the texture's image data to the range [0,1] and inverts the y axis.
            // We have precomputed the result here in order to avoid an unnecessary matrix multiplication.
            this.multiply(
                sx, 0, 0, 0,
                0, -sy, 0, sy,
                0, 0, 1, 0,
                0, 0, 0, 1);

            return this;
        };

        /**
         * Returns the translation components of this matrix.
         * @param {Vec3} result A pre-allocated {@link Vec3} in which to return the translation components.
         * @returns {Vec3} The specified result argument set to the translation components of this matrix.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Matrix.prototype.extractTranslation = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractTranslation", "missingResult"));
            }

            result[0] = this[3];
            result[1] = this[7];
            result[2] = this[11];

            return result;
        };

        /**
         * Returns the rotation angles of this matrix.
         * @param {Vec3} result A pre-allocated {@link Vec3} in which to return the rotation angles.
         * @returns {Vec3} The specified result argument set to the rotation angles of this matrix. The angles are in
         * degrees.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Matrix.prototype.extractRotationAngles = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractRotationAngles", "missingResult"));
            }

            // Taken from Extracting Euler Angles from a Rotation Matrix by Mike Day, Insomniac Games.
            // http://www.insomniacgames.com/mike-day-extracting-euler-angles-from-a-rotation-matrix/

            var x = Math.atan2(this[6], this[10]),
                y = Math.atan2(-this[2], Math.sqrt(this[0] * this[0] + this[1] * this[1])),
                cx = Math.cos(x),
                sx = Math.sin(x),
                z = Math.atan2(sx * this[8] - cx * this[4], cx * this[5] - sx * this[9]);

            result[0] = x * Angle.RADIANS_TO_DEGREES;
            result[1] = y * Angle.RADIANS_TO_DEGREES;
            result[2] = z * Angle.RADIANS_TO_DEGREES;

            return result;
        };

        /**
         * Multiplies this matrix by a first person viewing matrix for the specified globe.
         * <p>
         * A first person viewing matrix places the viewer's eye at the specified eyePosition. By default the viewer is looking
         * straight down at the globe's surface from the eye position, with the globe's normal vector coming out of the screen
         * and north pointing toward the top of the screen.
         * <p>
         * Heading specifies the viewer's azimuth, or its angle relative to North. Heading values range from -180 degrees to 180
         * degrees. A heading of 0 degrees looks North, 90 degrees looks East, +-180 degrees looks South, and -90 degrees looks
         * West.
         * <p>
         * Tilt specifies the viewer's angle relative to the surface. Tilt values range from -180 degrees to 180 degrees. A tilt
         * of 0 degrees looks straight down at the globe's surface, 90 degrees looks at the horizon, and 180 degrees looks
         * straight up. Tilt values greater than 180 degrees cause the viewer to turn upside down, and are therefore rarely used.
         * <p>
         * Roll specifies the viewer's angle relative to the horizon. Roll values range from -180 degrees to 180 degrees. A roll
         * of 0 degrees orients the viewer so that up is pointing to the top of the screen, at 90 degrees up is pointing to the
         * right, at +-180 degrees up is pointing to the bottom, and at -90 up is pointing to the left.
         *
         * @param {Position} eyePosition The viewer's geographic eye position relative to the specified globe.
         * @param {Number} heading The viewer's angle relative to north, in degrees.
         * @param {Number} tilt The viewer's angle relative to the surface, in degrees.
         * @param {Number} roll The viewer's angle relative to the horizon, in degrees.
         * @param {Globe} globe The globe the viewer is looking at.
         *
         * @throws {ArgumentError} If the specified position or globe is null or undefined.
         */
        Matrix.prototype.multiplyByFirstPersonModelview = function (eyePosition, heading, tilt, roll, globe) {
            if (!eyePosition) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByFirstPersonModelview", "missingPosition"));
            }

            if (!globe) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByFirstPersonModelview", "missingGlobe"));
            }

            var c,
                s,
                ex, ey, ez,
                xx, xy, xz,
                yx, yy, yz,
                zx, zy, zz,
                eyePoint = new Vec3(0, 0, 0),
                xAxis = new Vec3(0, 0, 0),
                yAxis = new Vec3(0, 0, 0),
                zAxis = new Vec3(0, 0, 0);

            // Roll. Rotate the eye point in a counter-clockwise direction about the z axis. Note that we invert the sines used
            // in the rotation matrix in order to produce the counter-clockwise rotation. We invert only the cosines since
            // sin(-a) = -sin(a) and cos(-a) = cos(a).
            c = Math.cos(roll * Angle.DEGREES_TO_RADIANS);
            s = Math.sin(roll * Angle.DEGREES_TO_RADIANS);
            this.multiply(
                c, s, 0, 0,
                -s, c, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);

            // Tilt. Rotate the eye point in a counter-clockwise direction about the x axis. Note that we invert the sines used
            // in the rotation matrix in order to produce the counter-clockwise rotation. We invert only the cosines since
            // sin(-a) = -sin(a) and cos(-a) = cos(a).
            c = Math.cos(tilt * Angle.DEGREES_TO_RADIANS);
            s = Math.sin(tilt * Angle.DEGREES_TO_RADIANS);
            this.multiply(1, 0, 0, 0,
                0, c, s, 0,
                0, -s, c, 0,
                0, 0, 0, 1);

            // Heading. Rotate the eye point in a clockwise direction about the z axis again. This has a different effect than
            // roll when tilt is non-zero because the viewer is no longer looking down the z axis.
            c = Math.cos(heading * Angle.DEGREES_TO_RADIANS);
            s = Math.sin(heading * Angle.DEGREES_TO_RADIANS);
            this.multiply(c, -s, 0, 0,
                s, c, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);

            // Compute the eye point in model coordinates. This point is mapped to the origin in the look at transform below.
            globe.computePointFromPosition(eyePosition.latitude, eyePosition.longitude, eyePosition.altitude, eyePoint);
            ex = eyePoint[0];
            ey = eyePoint[1];
            ez = eyePoint[2];

            // Transform the origin to the local coordinate system at the eye point.
            WWMath.localCoordinateAxesAtPoint(eyePoint, globe, xAxis, yAxis, zAxis);
            xx = xAxis[0];
            xy = xAxis[1];
            xz = xAxis[2];
            yx = yAxis[0];
            yy = yAxis[1];
            yz = yAxis[2];
            zx = zAxis[0];
            zy = zAxis[1];
            zz = zAxis[2];

            this.multiply(xx, xy, xz, -xx * ex - xy * ey - xz * ez,
                yx, yy, yz, -yx * ex - yy * ey - yz * ez,
                zx, zy, zz, -zx * ex - zy * ey - zz * ez,
                0, 0, 0, 1);

            return this;
        };

        /**
         * Multiplies this matrix by a look at viewing matrix for the specified globe.
         * <p>
         * A look at viewing matrix places the center of the screen at the specified lookAtPosition. By default the viewer is
         * looking straight down at the look at position from the specified range, with the globe's normal vector coming out of
         * the screen and north pointing toward the top of the screen.
         * <p>
         * Range specifies the distance between the look at position and the viewer's eye point. Range values may be any positive
         * real number. A range of 0 places the eye point at the look at point, while a positive range moves the eye point away
         * from but still looking at the look at point.
         * <p>
         * Heading specifies the viewer's azimuth, or its angle relative to North. Heading values range from -180 degrees to 180
         * degrees. A heading of 0 degrees looks North, 90 degrees looks East, +-180 degrees looks South, and -90 degrees looks
         * West.
         * <p>
         * Tilt specifies the viewer's angle relative to the surface. Tilt values range from -180 degrees to 180 degrees. A tilt
         * of 0 degrees looks straight down at the globe's surface, 90 degrees looks at the horizon, and 180 degrees looks
         * straight up. Tilt values greater than 180 degrees cause the viewer to turn upside down, and are therefore rarely used.
         * <p>
         * Roll specifies the viewer's angle relative to the horizon. Roll values range from -180 degrees to 180 degrees. A roll
         * of 0 degrees orients the viewer so that up is pointing to the top of the screen, at 90 degrees up is pointing to the
         * right, at +-180 degrees up is pointing to the bottom, and at -90 up is pointing to the left.
         *
         * @param {Position} lookAtPosition The viewer's geographic look at position relative to the specified globe.
         * @param {Number} range The distance between the eye point and the look at point, in model coordinates.
         * @param {Number} heading The viewer's angle relative to north, in degrees.
         * @param {Number} tilt The viewer's angle relative to the surface, in degrees.
         * @param {Number} roll The viewer's angle relative to the horizon, in degrees.
         * @param {Globe} globe The globe the viewer is looking at.
         *
         * @throws {ArgumentError} If either the specified look-at position or globe is null or undefined, or the
         * specified range is less than zero.
         */
        Matrix.prototype.multiplyByLookAtModelview = function (lookAtPosition, range, heading, tilt, roll, globe) {
            if (!lookAtPosition) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByLookAtModelview", "missingPosition"));
            }

            if (range < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByLookAtModelview",
                        "Range is less than zero"));
            }

            if (!globe) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyByLookAtModelview", "missingGlobe"));
            }

            // Translate the eye point along the positive z axis while keeping the look at point in the center of the viewport.
            this.multiplyByTranslation(0, 0, -range);

            // Transform the origin to the local coordinate system at the look at position, and rotate the viewer by the
            // specified heading, tilt and roll.
            this.multiplyByFirstPersonModelview(lookAtPosition, heading, tilt, roll, globe);

            return this;
        };

        /**
         * Sets this matrix to a perspective projection matrix for the specified viewport dimensions and clip distances.
         * <p>
         * A perspective projection matrix maps points in eye coordinates into clip coordinates in a way that causes
         * distant objects to appear smaller, and preserves the appropriate depth information for each point. In model
         * coordinates, a perspective projection is defined by frustum originating at the eye position and extending
         * outward in the viewer's direction. The near distance and the far distance identify the minimum and maximum
         * distance, respectively, at which an object in the scene is visible. Near and far distances must be positive
         * and may not be equal.
         *
         * @param {Number} viewportWidth The viewport width, in screen coordinates.
         * @param {Number} viewportHeight The viewport height, in screen coordinates.
         * @param {Number} nearDistance The near clip plane distance, in model coordinates.
         * @param {Number} farDistance The far clip plane distance, in model coordinates.
         * @throws {ArgumentError} If the specified width or height is less than or equal to zero, if the near and far
         * distances are equal, or if either the near or far distance are less than or equal to zero.
         */
        Matrix.prototype.setToPerspectiveProjection = function (viewportWidth, viewportHeight, nearDistance, farDistance) {
            if (viewportWidth <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToPerspectiveProjection",
                    "invalidWidth"));
            }

            if (viewportHeight <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToPerspectiveProjection",
                    "invalidHeight"));
            }

            if (nearDistance === farDistance) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToPerspectiveProjection",
                        "Near and far distance are the same."));
            }

            if (nearDistance <= 0 || farDistance <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToPerspectiveProjection",
                        "Near or far distance is less than or equal to zero."));
            }

            // Compute the dimensions of the viewport rectangle at the near distance.
            var nearRect = WWMath.perspectiveFrustumRectangle(viewportWidth, viewportHeight, nearDistance),
                left = nearRect.getMinX(),
                right = nearRect.getMaxX(),
                bottom = nearRect.getMinY(),
                top = nearRect.getMaxY();

            // Taken from Mathematics for 3D Game Programming and Computer Graphics, Second Edition, equation 4.52.

            // Row 1
            this[0] = 2 * nearDistance / (right - left);
            this[1] = 0;
            this[2] = (right + left) / (right - left);
            this[3] = 0;
            // Row 2
            this[4] = 0;
            this[5] = 2 * nearDistance / (top - bottom);
            this[6] = (top + bottom) / (top - bottom);
            this[7] = 0;
            // Row 3
            this[8] = 0;
            this[9] = 0;
            this[10] = -(farDistance + nearDistance) / (farDistance - nearDistance);
            this[11] = -2 * nearDistance * farDistance / (farDistance - nearDistance);
            // Row 4
            this[12] = 0;
            this[13] = 0;
            this[14] = -1;
            this[15] = 0;

            return this;
        };

        /**
         * Sets this matrix to a screen projection matrix for the specified viewport dimensions.
         * <p>
         * A screen projection matrix is an orthographic projection that assumes that points in model coordinates
         * represent a screen point and a depth. Screen projection matrices therefore map model coordinates directly
         * into screen coordinates without modification. A point's xy coordinates are interpreted as literal screen
         * coordinates and must be in the viewport to be visible. A point's z coordinate is interpreted as a depth value
         * that ranges from 0 to 1. Additionally, the screen projection matrix preserves the depth value returned by
         * [NavigatorState.project]{@link NavigatorState#project}.
         *
         * @param {Number} viewportWidth The viewport width, in screen coordinates.
         * @param {Number} viewportHeight The viewport height, in screen coordinates.
         * @throws {ArgumentError} If the specified width or height is less than or equal to zero.
         */
        Matrix.prototype.setToScreenProjection = function (viewportWidth, viewportHeight) {
            if (viewportWidth <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToScreenProjection",
                    "invalidWidth"));
            }

            if (viewportHeight <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "setToScreenProjection",
                    "invalidHeight"));
            }

            // Taken from Mathematics for 3D Game Programming and Computer Graphics, Second Edition, equation 4.57.
            // Simplified to assume that the viewport origin is (0, 0).
            //
            // The third row of this projection matrix is configured so that points with z coordinates representing
            // depth values ranging from 0 to 1 are not modified after transformation into window coordinates. This
            // projection matrix maps z values in the range [0, 1] to the range [-1, 1] by applying the following
            // function to incoming z coordinates:
            //
            // zp = z0 * 2 - 1
            //
            // Where 'z0' is the point's z coordinate and 'zp' is the projected z coordinate. The GPU then maps the
            // projected z coordinate into window coordinates in the range [0, 1] by applying the following function:
            //
            // zw = zp * 0.5 + 0.5
            //
            // The result is that a point's z coordinate is effectively passed to the GPU without modification.

            // Row 1
            this[0] = 2 / viewportWidth;
            this[1] = 0;
            this[2] = 0;
            this[3] = -1;
            // Row 2
            this[4] = 0;
            this[5] = 2 / viewportHeight;
            this[6] = 0;
            this[7] = -1;
            // Row 3
            this[8] = 0;
            this[9] = 0;
            this[10] = 2;
            this[11] = -1;
            // Row 4
            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;

            return this;
        };

        /**
         * Returns this viewing matrix's eye point.
         * <p>
         * This method assumes that this matrix represents a viewing matrix. If this does not represent a viewing matrix the
         * results are undefined.
         * <p>
         * In model coordinates, a viewing matrix's eye point is the point the viewer is looking from and maps to the center of
         * the screen.
         *
         * @param {Vec3} result A pre-allocated {@link Vec3} in which to return the extracted values.
         * @return {Vec3} The specified result argument containing the viewing matrix's eye point, in model coordinates.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Matrix.prototype.extractEyePoint = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractEyePoint", "missingResult"));
            }

            // The eye point of a modelview matrix is computed by transforming the origin (0, 0, 0, 1) by the matrix's inverse.
            // This is equivalent to transforming the inverse of this matrix's translation components in the rightmost column by
            // the transpose of its upper 3x3 components.
            result[0] = -(this[0] * this[3]) - (this[4] * this[7]) - (this[8] * this[11]);
            result[1] = -(this[1] * this[3]) - (this[5] * this[7]) - (this[9] * this[11]);
            result[2] = -(this[2] * this[3]) - (this[6] * this[7]) - (this[10] * this[11]);

            return result;
        };

        /**
         * Returns this viewing matrix's forward vector.
         * <p>
         * This method assumes that this matrix represents a viewing matrix. If this does not represent a viewing matrix the
         * results are undefined.
         *
         * @param {Vec3} result A pre-allocated {@link Vec3} in which to return the extracted values.
         * @return {Vec3} The specified result argument containing the viewing matrix's forward vector, in model coordinates.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Matrix.prototype.extractForwardVector = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractForwardVector", "missingResult"));
            }

            // The forward vector of a modelview matrix is computed by transforming the negative Z axis (0, 0, -1, 0) by the
            // matrix's inverse. We have pre-computed the result inline here to simplify this computation.
            result[0] = -this[8];
            result[1] = -this[9];
            result[2] = -this[10];

            return result;
        };

        /**
         * Extracts this viewing matrix's parameters given a viewing origin and a globe.
         * <p>
         * This method assumes that this matrix represents a viewing matrix. If this does not represent a viewing matrix the
         * results are undefined.
         * <p>
         * This returns a parameterization of this viewing matrix based on the specified origin and globe. The origin indicates
         * the model coordinate point that the view's orientation is relative to, while the globe provides the necessary model
         * coordinate context for the origin and the orientation. The origin should be either the view's eye point or a point on
         * the view's forward vector. The view's roll must be specified in order to disambiguate heading and roll when the view's
         * tilt is zero.
         * <p>
         * The following list outlines the returned key-value pairs and their meanings:
         * <ul>
         * <li> 'origin' - The geographic position corresponding to the origin point.</li>
         * <li> 'range' - The distance between the specified origin point and the view's eye point, in model coordinates.</li>
         * <li> 'heading' - The view's heading angle relative to the globe's north pointing tangent at the origin point, in degrees.</li>
         * <li> 'tilt' - The view's tilt angle relative to the globe's normal vector at the origin point, in degrees.</li>
         * <li> 'roll' - The view's roll relative to the globe's normal vector at the origin point, in degrees.</li>
         * </ul>
         * @param {Vec3} origin The origin of the viewing parameters, in model coordinates.
         * @param {Number} roll The view's roll, in degrees.
         * @param {Globe} globe The globe the viewer is looking at.
         * @param {Object} result A pre-allocated object in which to return the viewing parameters.
         *
         * @return {Object} The specified result argument containing a parameterization of this viewing matrix.
         *
         * @throws {ArgumentError} If either the specified origin or globe are null or undefined or the specified
         * result argument is null or undefined.
         */
        Matrix.prototype.extractViewingParameters = function (origin, roll, globe, result) {
            if (!origin) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractViewingParameters",
                        "The specified origin is null or undefined."));
            }

            if (!globe) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractViewingParameters", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "extractViewingParameters", "missingResult"));
            }

            var originPos = new Position(0, 0, 0),
                modelviewLocal = Matrix.fromIdentity(),
                range,
                ct,
                st,
                tilt,
                cr, sr,
                ch, sh,
                heading;

            globe.computePositionFromPoint(origin[0], origin[1], origin[2], originPos);

            // Transform the modelview matrix to a local coordinate system at the origin. This eliminates the geographic
            // transform contained in the modelview matrix while maintaining rotation and translation relative to the origin.
            modelviewLocal.copy(this);
            modelviewLocal.multiplyByLocalCoordinateTransform(origin, globe);

            range = -modelviewLocal[11];
            ct = modelviewLocal[10];
            st = Math.sqrt(modelviewLocal[2] * modelviewLocal[2] + modelviewLocal[6] * modelviewLocal[6]);
            tilt = Math.atan2(st, ct) * Angle.RADIANS_TO_DEGREES;

            cr = Math.cos(roll * Angle.DEGREES_TO_RADIANS);
            sr = Math.sin(roll * Angle.DEGREES_TO_RADIANS);
            ch = cr * modelviewLocal[0] - sr * modelviewLocal[4];
            sh = sr * modelviewLocal[5] - cr * modelviewLocal[1];
            heading = Math.atan2(sh, ch) * Angle.RADIANS_TO_DEGREES;

            result['origin'] = originPos;
            result['range'] = range;
            result['heading'] = heading;
            result['tilt'] = tilt;
            result['roll'] = roll;

            return result;
        };

        /**
         * Applies a specified depth offset to this projection matrix.
         * <p>
         * This method assumes that this matrix represents a projection matrix. If this does not represent a projection
         * matrix the results are undefined. Projection matrices can be created by calling
         * [setToPerspectiveProjection]{@link Matrix#setToPerspectiveProjection} or [setToScreenProjection]{@link Matrix#setToScreenProjection}.
         * <p>
         * The depth offset may be any real number and is typically used to draw geometry slightly closer to the user's
         * eye in order to give those shapes visual priority over nearby or geometry. An offset of zero has no effect.
         * An offset less than zero brings depth values closer to the eye, while an offset greater than zero pushes
         * depth values away from the eye.
         * <p>
         * Depth offset may be applied to both perspective and orthographic projection matrices. The effect on each
         * projection type is outlined here:
         * <p>
         * <strong>Perspective Projection</strong>
         * <p>
         * The effect of depth offset on a perspective projection increases exponentially with distance from the eye.
         * This has the effect of adjusting the offset for the loss in depth precision with geometry drawn further from
         * the eye. Distant geometry requires a greater offset to differentiate itself from nearby geometry, while close
         * geometry does not.
         * <p>
         * <strong>Orthographic Projection</strong>
         * <p>
         * The effect of depth offset on an orthographic projection increases linearly with distance from the eye. While
         * it is reasonable to apply a depth offset to an orthographic projection, the effect is most appropriate when
         * applied to the projection used to draw the scene. For example, when an object's coordinates are projected by
         * a perspective projection into screen coordinates then drawn using an orthographic projection, it is best to
         * apply the offset to the original perspective projection. The method [NavigatorState.project]{@link NavigatorState#project} performs the
         * correct behavior for the projection type used to draw the scene.
         *
         * @param {Number} depthOffset The amount of offset to apply.
         * @returns {Matrix} This matrix with it's depth offset set to the specified offset.
         */
        Matrix.prototype.offsetProjectionDepth = function (depthOffset) {

            this[10] *= 1 + depthOffset;

            return this;
        };

        /**
         * Multiplies this matrix by a specified matrix.
         *
         * @param {Matrix} matrix The matrix to multiply with this matrix.
         * @returns {Matrix} This matrix after multiplying it by the specified matrix.
         * @throws {ArgumentError} if the specified matrix is null or undefined.
         */
        Matrix.prototype.multiplyMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "multiplyMatrix", "missingMatrix"));
            }

            var ma = this,
                mb = matrix,
                ma0, ma1, ma2, ma3;

            // Row 1
            ma0 = ma[0];
            ma1 = ma[1];
            ma2 = ma[2];
            ma3 = ma[3];
            ma[0] = (ma0 * mb[0]) + (ma1 * mb[4]) + (ma2 * mb[8]) + (ma3 * mb[12]);
            ma[1] = (ma0 * mb[1]) + (ma1 * mb[5]) + (ma2 * mb[9]) + (ma3 * mb[13]);
            ma[2] = (ma0 * mb[2]) + (ma1 * mb[6]) + (ma2 * mb[10]) + (ma3 * mb[14]);
            ma[3] = (ma0 * mb[3]) + (ma1 * mb[7]) + (ma2 * mb[11]) + (ma3 * mb[15]);

            // Row 2
            ma0 = ma[4];
            ma1 = ma[5];
            ma2 = ma[6];
            ma3 = ma[7];
            ma[4] = (ma0 * mb[0]) + (ma1 * mb[4]) + (ma2 * mb[8]) + (ma3 * mb[12]);
            ma[5] = (ma0 * mb[1]) + (ma1 * mb[5]) + (ma2 * mb[9]) + (ma3 * mb[13]);
            ma[6] = (ma0 * mb[2]) + (ma1 * mb[6]) + (ma2 * mb[10]) + (ma3 * mb[14]);
            ma[7] = (ma0 * mb[3]) + (ma1 * mb[7]) + (ma2 * mb[11]) + (ma3 * mb[15]);

            // Row 3
            ma0 = ma[8];
            ma1 = ma[9];
            ma2 = ma[10];
            ma3 = ma[11];
            ma[8] = (ma0 * mb[0]) + (ma1 * mb[4]) + (ma2 * mb[8]) + (ma3 * mb[12]);
            ma[9] = (ma0 * mb[1]) + (ma1 * mb[5]) + (ma2 * mb[9]) + (ma3 * mb[13]);
            ma[10] = (ma0 * mb[2]) + (ma1 * mb[6]) + (ma2 * mb[10]) + (ma3 * mb[14]);
            ma[11] = (ma0 * mb[3]) + (ma1 * mb[7]) + (ma2 * mb[11]) + (ma3 * mb[15]);

            // Row 4
            ma0 = ma[12];
            ma1 = ma[13];
            ma2 = ma[14];
            ma3 = ma[15];
            ma[12] = (ma0 * mb[0]) + (ma1 * mb[4]) + (ma2 * mb[8]) + (ma3 * mb[12]);
            ma[13] = (ma0 * mb[1]) + (ma1 * mb[5]) + (ma2 * mb[9]) + (ma3 * mb[13]);
            ma[14] = (ma0 * mb[2]) + (ma1 * mb[6]) + (ma2 * mb[10]) + (ma3 * mb[14]);
            ma[15] = (ma0 * mb[3]) + (ma1 * mb[7]) + (ma2 * mb[11]) + (ma3 * mb[15]);

            return this;
        };

        /**
         * Multiplies this matrix by a matrix specified by individual components.
         *
         * @param {Number} m00 matrix element at row 1, column 1.
         * @param {Number} m01 matrix element at row 1, column 2.
         * @param {Number} m02 matrix element at row 1, column 3.
         * @param {Number} m03 matrix element at row 1, column 4.
         * @param {Number} m10 matrix element at row 2, column 1.
         * @param {Number} m11 matrix element at row 2, column 2.
         * @param {Number} m12 matrix element at row 2, column 3.
         * @param {Number} m13 matrix element at row 2, column 4.
         * @param {Number} m20 matrix element at row 3, column 1.
         * @param {Number} m21 matrix element at row 3, column 2.
         * @param {Number} m22 matrix element at row 3, column 3.
         * @param {Number} m23 matrix element at row 3, column 4.
         * @param {Number} m30 matrix element at row 4, column 1.
         * @param {Number} m31 matrix element at row 4, column 2.
         * @param {Number} m32 matrix element at row 4, column 3.
         * @param {Number} m33 matrix element at row 4, column 4.
         * @returns {Matrix} This matrix with its components multiplied by the specified values.
         */
        Matrix.prototype.multiply = function (m00, m01, m02, m03,
                                              m10, m11, m12, m13,
                                              m20, m21, m22, m23,
                                              m30, m31, m32, m33) {

            var ma = this,
                ma0, ma1, ma2, ma3;

            // Row 1
            ma0 = ma[0];
            ma1 = ma[1];
            ma2 = ma[2];
            ma3 = ma[3];
            ma[0] = (ma0 * m00) + (ma1 * m10) + (ma2 * m20) + (ma3 * m30);
            ma[1] = (ma0 * m01) + (ma1 * m11) + (ma2 * m21) + (ma3 * m31);
            ma[2] = (ma0 * m02) + (ma1 * m12) + (ma2 * m22) + (ma3 * m32);
            ma[3] = (ma0 * m03) + (ma1 * m13) + (ma2 * m23) + (ma3 * m33);

            // Row 2
            ma0 = ma[4];
            ma1 = ma[5];
            ma2 = ma[6];
            ma3 = ma[7];
            ma[4] = (ma0 * m00) + (ma1 * m10) + (ma2 * m20) + (ma3 * m30);
            ma[5] = (ma0 * m01) + (ma1 * m11) + (ma2 * m21) + (ma3 * m31);
            ma[6] = (ma0 * m02) + (ma1 * m12) + (ma2 * m22) + (ma3 * m32);
            ma[7] = (ma0 * m03) + (ma1 * m13) + (ma2 * m23) + (ma3 * m33);

            // Row 3
            ma0 = ma[8];
            ma1 = ma[9];
            ma2 = ma[10];
            ma3 = ma[11];
            ma[8] = (ma0 * m00) + (ma1 * m10) + (ma2 * m20) + (ma3 * m30);
            ma[9] = (ma0 * m01) + (ma1 * m11) + (ma2 * m21) + (ma3 * m31);
            ma[10] = (ma0 * m02) + (ma1 * m12) + (ma2 * m22) + (ma3 * m32);
            ma[11] = (ma0 * m03) + (ma1 * m13) + (ma2 * m23) + (ma3 * m33);

            // Row 4
            ma0 = ma[12];
            ma1 = ma[13];
            ma2 = ma[14];
            ma3 = ma[15];
            ma[12] = (ma0 * m00) + (ma1 * m10) + (ma2 * m20) + (ma3 * m30);
            ma[13] = (ma0 * m01) + (ma1 * m11) + (ma2 * m21) + (ma3 * m31);
            ma[14] = (ma0 * m02) + (ma1 * m12) + (ma2 * m22) + (ma3 * m32);
            ma[15] = (ma0 * m03) + (ma1 * m13) + (ma2 * m23) + (ma3 * m33);

            return this;
        };

        /**
         * Inverts the specified matrix and stores the result in this matrix.
         * <p>
         * This throws an exception if the specified matrix is singular.
         * <p>
         * The result of this method is undefined if this matrix is passed in as the matrix to invert.
         *
         * @param {Matrix} matrix The matrix whose inverse is computed.
         * @returns {Matrix} This matrix set to the inverse of the specified matrix.
         *
         * @throws {ArgumentError} If the specified matrix is null, undefined or cannot be inverted.
         */
        Matrix.prototype.invertMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "invertMatrix", "missingMatrix"));
            }

            // Copy the specified matrix into a mutable two-dimensional array.
            var A = [[], [], [], []];
            A[0][0] = matrix[0];
            A[0][1] = matrix[1];
            A[0][2] = matrix[2];
            A[0][3] = matrix[3];
            A[1][0] = matrix[4];
            A[1][1] = matrix[5];
            A[1][2] = matrix[6];
            A[1][3] = matrix[7];
            A[2][0] = matrix[8];
            A[2][1] = matrix[9];
            A[2][2] = matrix[10];
            A[2][3] = matrix[11];
            A[3][0] = matrix[12];
            A[3][1] = matrix[13];
            A[3][2] = matrix[14];
            A[3][3] = matrix[15];

            var index = [],
                d = Matrix.ludcmp(A, index),
                i,
                j;

            // Compute the matrix's determinant.
            for (i = 0; i < 4; i += 1) {
                d *= A[i][i];
            }

            // The matrix is singular if its determinant is zero or very close to zero.
            if (Math.abs(d) < 1.0e-8)
                return null;

            var Y = [[], [], [], []],
                col = [];
            for (j = 0; j < 4; j += 1) {
                for (i = 0; i < 4; i += 1) {
                    col[i] = 0.0;
                }

                col[j] = 1.0;
                Matrix.lubksb(A, index, col);

                for (i = 0; i < 4; i += 1) {
                    Y[i][j] = col[i];
                }
            }

            this[0] = Y[0][0];
            this[1] = Y[0][1];
            this[2] = Y[0][2];
            this[3] = Y[0][3];
            this[4] = Y[1][0];
            this[5] = Y[1][1];
            this[6] = Y[1][2];
            this[7] = Y[1][3];
            this[8] = Y[2][0];
            this[9] = Y[2][1];
            this[10] = Y[2][2];
            this[11] = Y[2][3];
            this[12] = Y[3][0];
            this[13] = Y[3][1];
            this[14] = Y[3][2];
            this[15] = Y[3][3];

            return this;
        };

        /* Internal. Intentionally not documented.
         * Utility method to solve a linear system with an LU factorization of a matrix.
         * Solves Ax=b, where A is in LU factorized form.
         * Algorithm derived from "Numerical Recipes in C", Press et al., 1988.
         *
         * @param {Number[]} A An LU factorization of a matrix.
         * @param {Number[]} index Permutation vector of that LU factorization.
         * @param {Number[]} b Vector to be solved.
         */
            // Method "lubksb" derived from "Numerical Recipes in C", Press et al., 1988
        Matrix.lubksb = function (A, index, b) {
            var ii = -1,
                i,
                j,
                sum;
            for (i = 0; i < 4; i += 1) {
                var ip = index[i];
                sum = b[ip];
                b[ip] = b[i];

                if (ii != -1) {
                    for (j = ii; j <= i - 1; j += 1) {
                        sum -= A[i][j] * b[j];
                    }
                }
                else if (sum != 0.0) {
                    ii = i;
                }

                b[i] = sum;
            }

            for (i = 3; i >= 0; i -= 1) {
                sum = b[i];
                for (j = i + 1; j < 4; j += 1) {
                    sum -= A[i][j] * b[j];
                }

                b[i] = sum / A[i][i];
            }
        };

        /* Internal. Intentionally not documented.
         * Utility method to perform an LU factorization of a matrix.
         * "ludcmp" is derived from "Numerical Recipes in C", Press et al., 1988.
         *
         * @param {Number[]} A matrix to be factored
         * @param {Number[]} index permutation vector
         * @returns {Number} Condition number of matrix.
         */
        Matrix.ludcmp = function (A, index) {
            var TINY = 1.0e-20,
                vv = [], /* new double[4]; */
                d = 1.0,
                temp,
                i,
                j,
                k,
                big,
                sum,
                imax,
                dum;
            for (i = 0; i < 4; i += 1) {
                big = 0.0;
                for (j = 0; j < 4; j += 1) {
                    if ((temp = Math.abs(A[i][j])) > big) {
                        big = temp;
                    }
                }

                if (big == 0.0) {
                    return 0.0; // Matrix is singular if the entire row contains zero.
                }
                else {
                    vv[i] = 1.0 / big;
                }
            }

            for (j = 0; j < 4; j += 1) {
                for (i = 0; i < j; i += 1) {
                    sum = A[i][j];
                    for (k = 0; k < i; k += 1) {
                        sum -= A[i][k] * A[k][j];
                    }

                    A[i][j] = sum;
                }

                big = 0.0;
                imax = -1;
                for (i = j; i < 4; i += 1) {
                    sum = A[i][j];
                    for (k = 0; k < j; k++) {
                        sum -= A[i][k] * A[k][j];
                    }

                    A[i][j] = sum;

                    if ((dum = vv[i] * Math.abs(sum)) >= big) {
                        big = dum;
                        imax = i;
                    }
                }

                if (j != imax) {
                    for (k = 0; k < 4; k += 1) {
                        dum = A[imax][k];
                        A[imax][k] = A[j][k];
                        A[j][k] = dum;
                    }

                    d = -d;
                    vv[imax] = vv[j];
                }

                index[j] = imax;
                if (A[j][j] == 0.0)
                    A[j][j] = TINY;

                if (j != 3) {
                    dum = 1.0 / A[j][j];
                    for (i = j + 1; i < 4; i += 1) {
                        A[i][j] *= dum;
                    }
                }
            }

            return d;
        };

        /**
         * Inverts the specified matrix and stores the result in this matrix.
         * <p>
         * The specified matrix is assumed to represent an orthonormal transform matrix. This matrix's upper 3x3 is
         * transposed, then its fourth column is transformed by the transposed upper 3x3 and negated.
         * <p>
         * The result of this method is undefined if this matrix is passed in as the matrix to invert.
         *
         * @param {Matrix} matrix The matrix whose inverse is computed. This matrix is assumed to represent an
         * orthonormal transform matrix.
         * @returns {Matrix} This matrix set to the inverse of the specified matrix.
         *
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Matrix.prototype.invertOrthonormalMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "invertOrthonormalMatrix", "missingMatrix"));
            }

            // 'a' is assumed to contain a 3D transformation matrix.
            // Upper-3x3 is inverted, translation is transformed by inverted-upper-3x3 and negated.

            var a = matrix;

            this[0] = a[0];
            this[1] = a[4];
            this[2] = a[8];
            this[3] = 0.0 - (a[0] * a[3]) - (a[4] * a[7]) - (a[8] * a[11]);

            this[4] = a[1];
            this[5] = a[5];
            this[6] = a[9];
            this[7] = 0.0 - (a[1] * a[3]) - (a[5] * a[7]) - (a[9] * a[11]);

            this[8] = a[2];
            this[9] = a[6];
            this[10] = a[10];
            this[11] = 0.0 - (a[2] * a[3]) - (a[6] * a[7]) - (a[10] * a[11]);

            this[12] = 0;
            this[13] = 0;
            this[14] = 0;
            this[15] = 1;

            return this;
        };

        /**
         * Computes the eigenvectors of this matrix.
         * <p>
         * The eigenvectors are returned sorted from the most prominent vector to the least prominent vector.
         * Each eigenvector has length equal to its corresponding eigenvalue.
         *
         * @param {Vec3} result1 A pre-allocated vector in which to return the most prominent eigenvector.
         * @param {Vec3} result2 A pre-allocated vector in which to return the second most prominent eigenvector.
         * @param {Vec3} result3 A pre-allocated vector in which to return the least prominent eigenvector.
         *
         * @throws {ArgumentError} if any argument is null or undefined or if this matrix is not symmetric.
         */
        Matrix.prototype.eigensystemFromSymmetricMatrix = function (result1, result2, result3) {
            if (!result1 || !result2 || !result3) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "eigensystemFromSymmetricMatrix", "missingResult"));
            }

            if (this[1] != this[4] || this[2] != this[8] || this[6] != this[9]) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix", "eigensystemFromSymmetricMatrix",
                        "Matrix is not symmetric"));
            }

            // Taken from Mathematics for 3D Game Programming and Computer Graphics, Second Edition, listing 14.6.

            var epsilon = 1.0e-10,
            // Since the matrix is symmetric m12=m21, m13=m31 and m23=m32, therefore we can ignore the values m21,
            // m32 and m32.
                m11 = this[0],
                m12 = this[1],
                m13 = this[2],
                m22 = this[5],
                m23 = this[6],
                m33 = this[10],
                r = [
                    [1, 0, 0],
                    [0, 1, 0],
                    [0, 0, 1]
                ],
                maxSweeps = 32,
                u, u2, u2p1, t, c, s, temp, i, i1, i2, i3;

            for (var a = 0; a < maxSweeps; a++) {
                // Exit if off-diagonal entries small enough
                if (WWMath.fabs(m12) < epsilon && WWMath.fabs(m13) < epsilon && WWMath.fabs(m23) < epsilon)
                    break;

                // Annihilate (1,2) entry.
                if (m12 != 0) {
                    u = (m22 - m11) * 0.5 / m12;
                    u2 = u * u;
                    u2p1 = u2 + 1;
                    t = (u2p1 != u2) ? ((u < 0) ? -1 : 1) * (Math.sqrt(u2p1) - WWMath.fabs(u)) : 0.5 / u;
                    c = 1 / Math.sqrt(t * t + 1);
                    s = c * t;

                    m11 -= t * m12;
                    m22 += t * m12;
                    m12 = 0;

                    temp = c * m13 - s * m23;
                    m23 = s * m13 + c * m23;
                    m13 = temp;

                    for (i = 0; i < 3; i++) {
                        temp = c * r[i][0] - s * r[i][1];
                        r[i][1] = s * r[i][0] + c * r[i][1];
                        r[i][0] = temp;
                    }
                }

                // Annihilate (1,3) entry.
                if (m13 != 0) {
                    u = (m33 - m11) * 0.5 / m13;
                    u2 = u * u;
                    u2p1 = u2 + 1;
                    t = (u2p1 != u2) ? ((u < 0) ? -1 : 1) * (Math.sqrt(u2p1) - WWMath.fabs(u)) : 0.5 / u;
                    c = 1 / Math.sqrt(t * t + 1);
                    s = c * t;

                    m11 -= t * m13;
                    m33 += t * m13;
                    m13 = 0;

                    temp = c * m12 - s * m23;
                    m23 = s * m12 + c * m23;
                    m12 = temp;

                    for (i = 0; i < 3; i++) {
                        temp = c * r[i][0] - s * r[i][2];
                        r[i][2] = s * r[i][0] + c * r[i][2];
                        r[i][0] = temp;
                    }
                }

                // Annihilate (2,3) entry.
                if (m23 != 0) {
                    u = (m33 - m22) * 0.5 / m23;
                    u2 = u * u;
                    u2p1 = u2 + 1;
                    t = (u2p1 != u2) ? ((u < 0) ? -1 : 1) * (Math.sqrt(u2p1) - WWMath.fabs(u)) : 0.5 / u;
                    c = 1 / Math.sqrt(t * t + 1);
                    s = c * t;

                    m22 -= t * m23;
                    m33 += t * m23;
                    m23 = 0;

                    temp = c * m12 - s * m13;
                    m13 = s * m12 + c * m13;
                    m12 = temp;

                    for (i = 0; i < 3; i++) {
                        temp = c * r[i][1] - s * r[i][2];
                        r[i][2] = s * r[i][1] + c * r[i][2];
                        r[i][1] = temp;
                    }
                }
            }

            i1 = 0;
            i2 = 1;
            i3 = 2;

            if (m11 < m22) {
                temp = m11;
                m11 = m22;
                m22 = temp;

                temp = i1;
                i1 = i2;
                i2 = temp;
            }

            if (m22 < m33) {
                temp = m22;
                m22 = m33;
                m33 = temp;

                temp = i2;
                i2 = i3;
                i3 = temp;
            }

            if (m11 < m22) {
                temp = m11;
                m11 = m22;
                m22 = temp;

                temp = i1;
                i1 = i2;
                i2 = temp;
            }

            result1[0] = r[0][i1];
            result1[1] = r[1][i1];
            result1[2] = r[2][i1];

            result2[0] = r[0][i2];
            result2[1] = r[1][i2];
            result2[2] = r[2][i2];

            result3[0] = r[0][i3];
            result3[1] = r[1][i3];
            result3[2] = r[2][i3];

            result1.normalize();
            result2.normalize();
            result3.normalize();

            result1.multiply(m11);
            result2.multiply(m22);
            result3.multiply(m33);
        };

        /**
         * Extracts and returns a new matrix whose upper 3x3 entries are identical to those of this matrix,
         * and whose fourth row and column are 0 except for a 1 in the diagonal position.
         * @returns {Matrix} The upper 3x3 matrix of this matrix.
         */
        Matrix.prototype.upper3By3 = function () {
            var result = Matrix.fromIdentity();

            result[0] = this[0];
            result[1] = this[1];
            result[2] = this[2];

            result[4] = this[4];
            result[5] = this[5];
            result[6] = this[6];

            result[8] = this[8];
            result[9] = this[9];
            result[10] = this[10];

            return result;
        };

        return Matrix;
    });


/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PickedObject
 */
define('pick/PickedObject',[],
    function () {
        "use strict";

        /**
         * Constructs a picked object.
         * @alias PickedObject
         * @constructor
         * @classdesc Represents a picked object.
         * @param {Color} color The pick color identifying the object.
         * @param {Object} userObject An object to associate with this picked object, usually the picked shape.
         * @param {Position} position The picked object's geographic position. May be null if unknown.
         * @param {Layer} parentLayer The layer containing the picked object.
         * @param {Boolean} isTerrain true if the picked object is terrain, otherwise false.
         */
        var PickedObject = function (color, userObject, position, parentLayer, isTerrain) {

            /**
             * This picked object's pick color.
             * @type {Color}
             * @readonly
             */
            this.color = color;

            /**
             * The picked shape.
             * @type {Object}
             * @readonly
             */
            this.userObject = userObject;

            /**
             * This picked object's geographic position.
             * @type {Position}
             * @readonly
             */
            this.position = position;

            /**
             * The layer containing this picked object.
             * @type {Layer}
             * @readonly
             */
            this.parentLayer = parentLayer;

            /**
             * Indicates whether this picked object is terrain.
             * @type {Boolean}
             * @readonly
             */
            this.isTerrain = isTerrain;

            /**
             * Indicates whether this picked object is the top object.
             * @type {boolean}
             */
            this.isOnTop = false;
        };

        return PickedObject;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports UnsupportedOperationError
 */
define('error/UnsupportedOperationError',['../error/AbstractError'],
    function (AbstractError) {
        "use strict";

        /**
         * Constructs an unsupported-operation error with a specified message.
         * @alias UnsupportedOperationError
         * @constructor
         * @classdesc Represents an error associated with an operation that is not available or should not be invoked.
         * Typically raised when an abstract function of an abstract base class is called because a subclass has not
         * implemented the function.
         * @augments AbstractError
         * @param {String} message The message.
         */
        var UnsupportedOperationError = function (message) {
            AbstractError.call(this, "UnsupportedOperationError", message);

            var stack;
            try {
                //noinspection ExceptionCaughtLocallyJS
                throw new Error();
            } catch (e) {
                stack = e.stack;
            }
            this.stack = stack;
        };

        UnsupportedOperationError.prototype = Object.create(AbstractError.prototype);

        return UnsupportedOperationError;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Renderable
 */
define('render/Renderable',[
        '../util/Logger',
        '../error/UnsupportedOperationError'
    ],
    function (Logger,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Constructs a base renderable.
         * @alias Renderable
         * @constructor
         * @classdesc Represents a shape or other object that can be rendered. This is an abstract class and is not
         * meant to be instantiated directly.
         */
        var Renderable = function () {

            /**
             * The display name of the renderable.
             * @type {String}
             * @default "Renderable"
             */
            this.displayName = "Renderable";

            /**
             * Indicates whether to display this renderable.
             * @type {Boolean}
             * @default true
             */
            this.enabled = true;

            /**
             * Indicates the object to return as the userObject of this shape when picked. If null,
             * then this shape is returned as the userObject.
             * @type {Object}
             * @default null
             * @see  [PickedObject.userObject]{@link PickedObject#userObject}
             */
            this.pickDelegate = null;

            /**
             * An application defined object associated with this renderable. A typical use case is to associate
             * application defined data with a picked renderable.
             * @type {Object}
             * @default An empty object
             */
            this.userProperties = {};
        };

        /**
         * Render this renderable. Some shapes actually draw themselves during this call, others only add themselves
         * to the draw context's ordered rendering list for subsequent drawing when their renderOrdered method is called.
         * This method is intended to be called by layers such as {@link RenderableLayer} and not by applications.
         * @param {DrawContext} dc The current draw context.
         */
        Renderable.prototype.render = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "Renderable", "render", "abstractInvocation"));
        };

        return Renderable;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('shapes/Annotation',[
        '../shapes/AnnotationAttributes',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/Font',
        '../util/Insets',
        '../util/Logger',
        '../geom/Matrix',
        '../util/Offset',
        '../pick/PickedObject',
        '../render/Renderable',
        '../shapes/TextAttributes',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (AnnotationAttributes,
              ArgumentError,
              BasicTextureProgram,
              Color,
              Font,
              Insets,
              Logger,
              Matrix,
              Offset,
              PickedObject,
              Renderable,
              TextAttributes,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs an annotation.
         * @alias Annotation
         * @constructor
         * @augments Renderable
         * @classdesc Represents an Annotation shape. An annotation displays a callout, a text and a leader pointing
         * the annotation's geographic position to the ground.
         * @param {Position} position The annotations's geographic position.
         * @param {AnnotationAttributes} attributes The attributes to associate with this annotation.
         * @throws {ArgumentError} If the specified position is null or undefined.
         */
        var Annotation = function (position, attributes) {

            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Annotation", "constructor", "missingPosition"));
            }

            Renderable.call(this);

            /**
             * This annotation's geographic position.
             * @type {Position}
             */
            this.position = position;

            /**
             * The annotation's attributes.
             * @type {AnnotationAttributes}
             * @default see [AnnotationAttributes]{@link AnnotationAttributes}
             */
            this.attributes = attributes ? attributes : new AnnotationAttributes(null);

            /**
             * This annotation's altitude mode. May be one of
             * <ul>
             *  <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
             *  <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
             *  <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
             * </ul>
             * @default WorldWind.ABSOLUTE
             */
            this.altitudeMode = WorldWind.ABSOLUTE;

            // Internal use only. Intentionally not documented.
            this.layer = null;

            // Internal use only. Intentionally not documented.
            this.lastStateKey = null;

            // Internal use only. Intentionally not documented.
            this.calloutTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.calloutOffset = new WorldWind.Offset(
                WorldWind.OFFSET_FRACTION, 0.5,
                WorldWind.OFFSET_FRACTION, 0);

            // Internal use only. Intentionally not documented.
            this.label = "";

            // Internal use only. Intentionally not documented.
            this.labelTexture = null;

            // Internal use only. Intentionally not documented.
            this.labelTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.placePoint = new Vec3(0, 0, 0);

            // Internal use only. Intentionally not documented.
            this.depthOffset = -2.05;

            // Internal use only. Intentionally not documented.
            this.calloutPoints = null;
        };

        Annotation.matrix = Matrix.fromIdentity();
        Annotation.screenPoint = new Vec3(0, 0, 0);
        Annotation.scratchPoint = new Vec3(0, 0, 0);

        Annotation.prototype = Object.create(Renderable.prototype);

        Object.defineProperties(Annotation.prototype, {

            /**
             * The text for this annotation.
             * @type {String}
             * @memberof Annotation.prototype
             */
            text: {
                get: function () {
                    return this.label;
                },
                set: function (value) {
                    this.label = value;
                    this.lastStateKey = null;
                }
            }
        });

        /**
         * Draws this shape as an ordered renderable. Applications do not call this function. It is called by
         * [WorldWindow]{@link WorldWindow} during rendering.
         * @param {DrawContext} dc The current draw context.
         */
        Annotation.prototype.renderOrdered = function (dc) {

            this.drawOrderedAnnotation(dc);

            if (dc.pickingMode) {

                var po = new PickedObject(this.pickColor.clone(), this,
                    this.position, this.layer, false);

                if (dc.pickPoint) {
                    if (this.labelBounds.containsPoint(
                            dc.navigatorState.convertPointToViewport(dc.pickPoint, Annotation.scratchPoint))) {
                        po.labelPicked = true;
                    }
                }

                dc.resolvePick(po);
            }
        };

        /**
         * Creates a new annotation that is a copy of this annotation.
         * @returns {Annotation} The new annotation.
         */
        Annotation.prototype.clone = function () {
            var clone = new Annotation(this.position);

            clone.copy(this);
            clone.pickDelegate = this.pickDelegate ? this.pickDelegate : this;

            return clone;
        };

        /**
         * Copies the contents of a specified annotation to this annotation.
         * @param {Annotation} that The Annotation to copy.
         */
        Annotation.prototype.copy = function (that) {
            this.position = that.position;
            this.enabled = that.enabled;
            this.attributes = that.attributes;
            this.label = that.label;
            this.altitudeMode = that.altitudeMode;
            this.pickDelegate = that.pickDelegate;
            this.depthOffset = that.depthOffset;

            return this;
        };

        /**
         * Renders this annotation. This method is typically not called by applications but is called by
         * {@link RenderableLayer} during rendering. For this shape this method creates and
         * enques an ordered renderable with the draw context and does not actually draw the annotation.
         * @param {DrawContext} dc The current draw context.
         */
        Annotation.prototype.render = function (dc) {

            if (!this.enabled) {
                return;
            }

            if (!dc.accumulateOrderedRenderables) {
                return;
            }

            if (dc.globe.projectionLimits
                && !dc.globe.projectionLimits.containsLocation(this.position.latitude, this.position.longitude)) {
                return;
            }

            var orderedAnnotation;
            if (this.lastFrameTime !== dc.timestamp) {
                orderedAnnotation = this.makeOrderedRenderable(dc);
            } else {
                var annotationCopy = this.clone();
                orderedAnnotation = annotationCopy.makeOrderedRenderable(dc);
            }

            if (!orderedAnnotation) {
                return;
            }

            orderedAnnotation.layer = dc.currentLayer;

            this.lastFrameTime = dc.timestamp;
            dc.addOrderedRenderable(orderedAnnotation);
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.drawOrderedAnnotation = function (dc) {
            this.beginDrawing(dc);

            try {
                this.doDrawOrderedAnnotation(dc);
            } finally {
                this.endDrawing(dc);
            }
        };

        /* Intentionally not documented
         * Creates an ordered renderable for this shape.
         * @protected
         * @param {DrawContext} dc The current draw context.
         * @returns {OrderedRenderable} The ordered renderable. May be null, in which case an ordered renderable
         * cannot be created or should not be created at the time this method is called.
         */
        Annotation.prototype.makeOrderedRenderable = function (dc) {

            var w, h, s, iLeft, iRight, iTop, iBottom,
                offset, leaderGapHeight;

            // Wraps the text based and the width and height that were set for the
            // annotation
            this.label = dc.textSupport.wrap(
                this.label,
                this.attributes.width, this.attributes.height,
                this.attributes.textAttributes.font);

            // Compute the annotation's model point.
            dc.surfacePointForMode(this.position.latitude, this.position.longitude, this.position.altitude,
                this.altitudeMode, this.placePoint);

            this.eyeDistance = dc.navigatorState.eyePoint.distanceTo(this.placePoint);

            // Compute the annotation's screen point in the OpenGL coordinate system of the WorldWindow
            // by projecting its model coordinate point onto the viewport. Apply a depth offset in order
            // to cause the annotation to appear above nearby terrain.
            if (!dc.navigatorState.projectWithDepth(this.placePoint, this.depthOffset, Annotation.screenPoint)) {
                return null;
            }

            var labelFont = this.attributes.textAttributes.font;
            var labelKey = this.label + labelFont.toString();

            this.labelTexture = dc.gpuResourceCache.resourceForKey(labelKey);

            if (!this.labelTexture) {
                this.labelTexture = dc.textSupport.createTexture(dc, this.label, labelFont, false);
                dc.gpuResourceCache.putResource(labelKey, this.labelTexture, this.labelTexture.size);
            }

            w = this.labelTexture.imageWidth;
            h = this.labelTexture.imageHeight;
            s = this.attributes.scale;
            iLeft = this.attributes.insets.left;
            iRight = this.attributes.insets.right;
            iTop = this.attributes.insets.top;
            iBottom = this.attributes.insets.bottom;
            leaderGapHeight = this.attributes.leaderGapHeight;

            offset = this.calloutOffset.offsetForSize((w + iLeft + iRight) * s, (h + iTop + iBottom) * s);

            this.calloutTransform.setTranslation(
                Annotation.screenPoint[0] - offset[0],
                Annotation.screenPoint[1] + leaderGapHeight,
                Annotation.screenPoint[2]);

            this.labelTransform.setTranslation(
                Annotation.screenPoint[0] - offset[0] + iLeft * s,
                Annotation.screenPoint[1] + leaderGapHeight + iBottom * s,
                Annotation.screenPoint[2]);

            this.labelTransform.setScale(w * s, h * s, 1);

            this.labelBounds = WWMath.boundingRectForUnitQuad(this.labelTransform);

            // Compute dimensions of the callout taking in consideration the insets
            var width = (w + iLeft + iRight) * s;
            var height = (h + iTop + iBottom) * s;

            var leaderOffsetX = (width / 2);

            var leaderOffsetY = -leaderGapHeight;

            if (!this.attributes.drawLeader) {
                leaderOffsetY = 0;
            }

            if (this.attributes.stateKey != this.lastStateKey) {
                this.calloutPoints = this.createCallout(
                    width, height,
                    leaderOffsetX, leaderOffsetY,
                    this.attributes.leaderGapWidth, this.attributes.cornerRadius);
            }

            return this;
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program;

            dc.findAndBindProgram(BasicTextureProgram);

            program = dc.currentProgram;

            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.enableVertexAttribArray(program.vertexTexCoordLocation);

            program.loadModulateColor(gl, dc.pickingMode);
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            // Clear the vertex attribute state.
            gl.disableVertexAttribArray(program.vertexPointLocation);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            // Clear GL bindings.
            dc.bindProgram(null);
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.drawCorner = function (x0, y0, cornerRadius, start, end, steps, buffer, startIdx) {
            if (cornerRadius < 1) {
                return startIdx;
            }

            var step = (end - start) / (steps - 1);
            for (var i = 1; i < steps - 1; i++) {
                var a = start + step * i;
                var x = x0 + Math.cos(a) * cornerRadius;
                var y = y0 + Math.sin(a) * cornerRadius;
                buffer[startIdx++] = x;
                buffer[startIdx++] = y;
            }

            return startIdx;
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.createCallout = function (width, height, leaderOffsetX, leaderOffsetY, leaderGapWidth,
                                                       cornerRadius) {

            var cornerSteps = 16;

            var numVertices = 2 * (12 + (cornerRadius < 1 ? 0 : 4 * (cornerSteps - 2)));

            var buffer = new Float32Array(numVertices);

            var idx = 0;

            //Bottom right
            buffer[idx++] = width / 2 + leaderGapWidth / 2;
            buffer[idx++] = 0;
            buffer[idx++] = width - cornerRadius;
            buffer[idx++] = 0;
            idx = this.drawCorner(width - cornerRadius, cornerRadius, cornerRadius, -Math.PI / 2, 0,
                cornerSteps, buffer, idx);

            //Right
            buffer[idx++] = width;
            buffer[idx++] = cornerRadius;
            buffer[idx++] = width;
            buffer[idx++] = height - cornerRadius;
            idx = this.drawCorner(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2,
                cornerSteps, buffer, idx);

            //Top
            buffer[idx++] = width - cornerRadius;
            buffer[idx++] = height;
            buffer[idx++] = cornerRadius;
            buffer[idx++] = height;
            idx = this.drawCorner(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI,
                cornerSteps, buffer, idx);

            //Left
            buffer[idx++] = 0;
            buffer[idx++] = height - cornerRadius;
            buffer[idx++] = 0;
            buffer[idx++] = cornerRadius;
            idx = this.drawCorner(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 1.5,
                cornerSteps, buffer, idx);

            //Bottom left
            buffer[idx++] = cornerRadius;
            buffer[idx++] = 0;
            buffer[idx++] = width / 2 - leaderGapWidth / 2;
            buffer[idx++] = 0;

            //Draw leader
            buffer[idx++] = leaderOffsetX;
            buffer[idx++] = leaderOffsetY;

            buffer[idx++] = width / 2 + leaderGapWidth / 2;
            buffer[idx] = 0;

            return buffer;
        };

        // Internal. Intentionally not documented.
        Annotation.prototype.doDrawOrderedAnnotation = function (dc) {

            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                textureBound;

            var refreshBuffers = false;

            if (dc.pickingMode) {
                this.pickColor = dc.uniquePickColor();
            }

            program.loadOpacity(gl, this.attributes.opacity);

            // Attributes have changed. We need to track this because the callout vbo data may
            // have changed if scaled or text wrapping changes callout dimensions
            var calloutAttributesChanged = (this.attributes.stateKey != this.lastStateKey);

            // Create new cache key if callout drawing points have changed
            if (!this.calloutCacheKey || calloutAttributesChanged) {
                this.calloutCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            var calloutVboId = dc.gpuResourceCache.resourceForKey(this.calloutCacheKey);

            if (!calloutVboId) {
                calloutVboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(this.calloutCacheKey, calloutVboId,
                    this.calloutPoints.length * 4);

                refreshBuffers = true;
            }

            // Remove the last generated vbo data if attributes changed
            if (calloutAttributesChanged && this.calloutCacheKey) {
                dc.gpuResourceCache.removeResource(this.calloutCacheKey);
            }

            // Store current statekey because we are no longer using it
            // in this iteration
            this.lastStateKey = this.attributes.stateKey;

            // Compute and specify the MVP matrix.
            Annotation.matrix.copy(dc.screenProjection);
            Annotation.matrix.multiplyMatrix(this.calloutTransform);
            program.loadModelviewProjection(gl, Annotation.matrix);

            gl.bindBuffer(gl.ARRAY_BUFFER, calloutVboId);

            if (refreshBuffers) {
                gl.bufferData(gl.ARRAY_BUFFER,
                    this.calloutPoints, gl.STATIC_DRAW);

                dc.frameStatistics.incrementVboLoadCount(1);
            }

            program.loadColor(gl, dc.pickingMode ? this.pickColor : this.attributes.backgroundColor);
            program.loadTextureEnabled(gl, false);

            gl.vertexAttribPointer(program.vertexPointLocation, 2, gl.FLOAT, false, 0, 0);
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);

            gl.drawArrays(gl.TRIANGLE_FAN, 0, this.calloutPoints.length / 2);

            // Draw text
            Annotation.matrix.copy(dc.screenProjection);
            Annotation.matrix.multiplyMatrix(this.labelTransform);
            program.loadModelviewProjection(gl, Annotation.matrix);

            Annotation.matrix.setToIdentity();
            Annotation.matrix.multiplyByTextureTransform(this.labelTexture);
            program.loadTextureMatrix(gl, Annotation.matrix);

            program.loadColor(gl, dc.pickingMode ? this.pickColor : this.attributes.textAttributes.color);
            textureBound = this.labelTexture.bind(dc);
            program.loadTextureEnabled(gl, textureBound);

            // Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
            // Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
            // that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
            // buffer for texture coords.
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);

            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        };

        return Annotation;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */

define('util/measure/MeasurerUtils',[
        '../../geom/Location',
        '../../geom/Position'
    ],
    function (Location,
              Position) {
        'use strict';

        /**
         * Provides utilities for Measurements.
         * @exports MeasurerUtils
         */
        var MeasurerUtils = {

            /**
             * Subdivide a list of positions so that no segment is longer then the provided maxLength.
             * <p>If needed, new intermediate positions will be created along lines that follow the given pathType one
             * of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE.
             * All position elevations will be either at the terrain surface if followTerrain is true, or interpolated
             * according to the original elevations.</p>
             *
             * @param {Globe} globe
             * @param {Position[]} positions
             * @param {Boolean} followTerrain
             * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
             * @param {Number} maxLength The maximum length for one segment
             *
             * @return {Position[]} a list of positions with no segment longer then maxLength and elevations following
             * terrain or not.
             */
            subdividePositions: function (globe, positions, followTerrain, pathType, maxLength) {
                var subdividedPositions = [];
                var loc = new Location(0, 0);
                var destLatLon = new Location(0, 0);
                var pos1 = positions[0];
                var elevation;

                this.addPosition(globe, subdividedPositions, pos1, followTerrain);

                for (var i = 1; i < positions.length; i++) {
                    var pos2 = positions[i];
                    var arcLengthRadians = Location.greatCircleDistance(pos1, pos2);
                    loc = Location.interpolateAlongPath(pathType, 0.5, pos1, pos2, loc);
                    var arcLength = arcLengthRadians * globe.radiusAt(loc.latitude, loc.longitude);
                    if (arcLength > maxLength) {
                        // if necessary subdivide segment at regular intervals smaller then maxLength
                        var segmentAzimuth = null;
                        var segmentDistance = null;
                        var steps = Math.ceil(arcLength / maxLength); // number of intervals - at least two
                        for (var j = 1; j < steps; j++) {
                            var s = j / steps;
                            if (pathType === WorldWind.LINEAR) {
                                destLatLon = Location.interpolateLinear(s, pos1, pos2, destLatLon);
                            }
                            else if (pathType === WorldWind.RHUMB_LINE) {
                                if (segmentAzimuth == null) {
                                    segmentAzimuth = Location.rhumbAzimuth(pos1, pos2);
                                    segmentDistance = Location.rhumbDistance(pos1, pos2);
                                }
                                destLatLon = Location.rhumbLocation(pos1, segmentAzimuth, s * segmentDistance,
                                    destLatLon);
                            }
                            else {
                                //GREAT_CIRCLE
                                if (segmentAzimuth == null) {
                                    segmentAzimuth = Location.greatCircleAzimuth(pos1, pos2); //degrees
                                    segmentDistance = Location.greatCircleDistance(pos1, pos2); //radians
                                }
                                //Location, degrees, radians, Location
                                destLatLon = Location.greatCircleLocation(pos1, segmentAzimuth, s * segmentDistance,
                                    destLatLon);
                            }

                            // Set elevation
                            if (followTerrain) {
                                elevation = globe.elevationAtLocation(destLatLon.latitude, destLatLon.longitude);
                            }
                            else {
                                elevation = pos1.altitude * (1 - s) + pos2.altitude * s;
                            }

                            subdividedPositions.push(new Position(destLatLon.latitude, destLatLon.longitude, elevation));
                        }
                    }

                    // Finally add the segment end position
                    this.addPosition(globe, subdividedPositions, pos2, followTerrain);

                    // Prepare for next segment
                    pos1 = pos2;
                }

                return subdividedPositions;
            },

            /**
             * Adds a position to a list of positions.
             * If the path is following the terrain the elevation is also computed.
             *
             * @param {Globe} globe
             * @param {Position[]} positions The list of positions to add to
             * @param {Position} position The position to add to the list
             * @param {Boolean} followTerrain
             *
             * @return {Position[]} The list of positions
             */
            addPosition: function (globe, positions, position, followTerrain) {
                var elevation = position.altitude;
                if (followTerrain) {
                    elevation = globe.elevationAtLocation(position.latitude, position.longitude);
                }
                positions.push(new Position(position.latitude, position.longitude, elevation));
                return positions;
            },

            /**
             * Determines whether a location is located inside a given polygon.
             *
             * @param {Location} location
             * @param {Location[]}locations The list of positions describing the polygon.
             * Last one should be the same as the first one.
             *
             * @return {Boolean} true if the location is inside the polygon.
             */
            isLocationInside: function (location, locations) {
                var result = false;
                var p1 = locations[0];
                for (var i = 1, len = locations.length; i < len; i++) {
                    var p2 = locations[i];
                    if (((p2.latitude <= location.latitude && location.latitude < p1.latitude) ||
                        (p1.latitude <= location.latitude && location.latitude < p2.latitude)) &&
                        (location.longitude < (p1.longitude - p2.longitude) * (location.latitude - p2.latitude) /
                        (p1.latitude - p2.latitude) + p2.longitude)) {
                        result = !result;
                    }
                    p1 = p2;
                }
                return result;
            },

            /**
             * Computes the angle between two Vec3 in radians.
             *
             * @param {Vec3} v1
             * @param {Vec3} v2
             *
             * @return {Number} The ange in radians
             */
            angleBetweenVectors: function (v1, v2) {
                var dot = v1.dot(v2);
                // Compute the sum of magnitudes.
                var length = v1.magnitude() * v2.magnitude();
                // Normalize the dot product, if necessary.
                if (!(length === 0) && (length !== 1.0)) {
                    dot /= length;
                }

                // The normalized dot product should be in the range [-1, 1]. Otherwise the result is an error from
                // floating point roundoff. So if dot is less than -1 or greater than +1, we treat it as -1 and +1
                // respectively.
                if (dot < -1.0) {
                    dot = -1.0;
                }
                else if (dot > 1.0) {
                    dot = 1.0;
                }

                // Angle is arc-cosine of normalized dot product.
                return Math.acos(dot);
            }

        };

        return MeasurerUtils;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Sector
 */
define('geom/Sector',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a Sector from specified minimum and maximum latitudes and longitudes in degrees.
         * @alias Sector
         * @constructor
         * @classdesc Represents a rectangular region in geographic coordinates in degrees.
         * @param {Number} minLatitude The sector's minimum latitude in degrees.
         * @param {Number} maxLatitude The sector's maximum latitude in degrees.
         * @param {Number} minLongitude The sector's minimum longitude in degrees.
         * @param {Number} maxLongitude The sector's maximum longitude in degrees.
         */
        var Sector = function (minLatitude, maxLatitude, minLongitude, maxLongitude) {
            /**
             * This sector's minimum latitude in degrees.
             * @type {Number}
             */
            this.minLatitude = minLatitude;
            /**
             * This sector's maximum latitude in degrees.
             * @type {Number}
             */
            this.maxLatitude = maxLatitude;
            /**
             * This sector's minimum longitude in degrees.
             * @type {Number}
             */
            this.minLongitude = minLongitude;
            /**
             * This sector's maximum longitude in degrees.
             * @type {Number}
             */
            this.maxLongitude = maxLongitude;
        };

        /**
         * A sector with minimum and maximum latitudes and minimum and maximum longitudes all zero.
         * @constant
         * @type {Sector}
         */
        Sector.ZERO = new Sector(0, 0, 0, 0);

        /**
         * A sector that encompasses the full range of latitude ([-90, 90]) and longitude ([-180, 180]).
         * @constant
         * @type {Sector}
         */
        Sector.FULL_SPHERE = new Sector(-90, 90, -180, 180);

        /**
         * Sets this sector's latitudes and longitudes to those of a specified sector.
         * @param {Sector} sector The sector to copy.
         * @returns {Sector} This sector, set to the values of the specified sector.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        Sector.prototype.copy = function (sector) {
            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "copy", "missingSector"));
            }

            this.minLatitude = sector.minLatitude;
            this.maxLatitude = sector.maxLatitude;
            this.minLongitude = sector.minLongitude;
            this.maxLongitude = sector.maxLongitude;

            return this;
        };

        /**
         * Indicates whether this sector has width or height.
         * @returns {Boolean} true if this sector's minimum and maximum latitudes or minimum and maximum
         * longitudes do not differ, otherwise false.
         */
        Sector.prototype.isEmpty = function () {
            return this.minLatitude === this.maxLatitude && this.minLongitude === this.maxLongitude;
        };

        /**
         * Returns the angle between this sector's minimum and maximum latitudes, in degrees.
         * @returns {Number} The difference between this sector's minimum and maximum latitudes, in degrees.
         */
        Sector.prototype.deltaLatitude = function () {
            return this.maxLatitude - this.minLatitude;
        };

        /**
         * Returns the angle between this sector's minimum and maximum longitudes, in degrees.
         * @returns {Number} The difference between this sector's minimum and maximum longitudes, in degrees.
         */
        Sector.prototype.deltaLongitude = function () {
            return this.maxLongitude - this.minLongitude;
        };

        /**
         * Returns the angle midway between this sector's minimum and maximum latitudes.
         * @returns {Number} The mid-angle of this sector's minimum and maximum latitudes, in degrees.
         */
        Sector.prototype.centroidLatitude = function () {
            return 0.5 * (this.minLatitude + this.maxLatitude);
        };

        /**
         * Returns the angle midway between this sector's minimum and maximum longitudes.
         * @returns {Number} The mid-angle of this sector's minimum and maximum longitudes, in degrees.
         */
        Sector.prototype.centroidLongitude = function () {
            return 0.5 * (this.minLongitude + this.maxLongitude);
        };

        /**
         * Computes the location of the angular center of this sector, which is the mid-angle of each of this sector's
         * latitude and longitude dimensions.
         * @param {Location} result A pre-allocated {@link Location} in which to return the computed centroid.
         * @returns {Location} The specified result argument containing the computed centroid.
         * @throws {ArgumentError} If the result argument is null or undefined.
         */
        Sector.prototype.centroid = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "centroid", "missingResult"));
            }

            result.latitude = this.centroidLatitude();
            result.longitude = this.centroidLongitude();

            return result;
        };

        /**
         * Returns this sector's minimum latitude in radians.
         * @returns {Number} This sector's minimum latitude in radians.
         */
        Sector.prototype.minLatitudeRadians = function () {
            return this.minLatitude * Angle.DEGREES_TO_RADIANS;
        };

        /**
         * Returns this sector's maximum latitude in radians.
         * @returns {Number} This sector's maximum latitude in radians.
         */
        Sector.prototype.maxLatitudeRadians = function () {
            return this.maxLatitude * Angle.DEGREES_TO_RADIANS;
        };

        /**
         * Returns this sector's minimum longitude in radians.
         * @returns {Number} This sector's minimum longitude in radians.
         */
        Sector.prototype.minLongitudeRadians = function () {
            return this.minLongitude * Angle.DEGREES_TO_RADIANS;
        };

        /**
         * Returns this sector's maximum longitude in radians.
         * @returns {Number} This sector's maximum longitude in radians.
         */
        Sector.prototype.maxLongitudeRadians = function () {
            return this.maxLongitude * Angle.DEGREES_TO_RADIANS;
        };

        /**
         * Modifies this sector to encompass an array of specified locations.
         * @param {Location[]} locations An array of locations. The array may be sparse.
         * @returns {Sector} This sector, modified to encompass all locations in the specified array.
         * @throws {ArgumentError} If the specified array is null, undefined or empty or has fewer than two locations.
         */
        Sector.prototype.setToBoundingSector = function (locations) {
            if (!locations || locations.length < 2) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "setToBoundingSector",
                    "missingArray"));
            }

            var minLatitude = 90,
                maxLatitude = -90,
                minLongitude = 180,
                maxLongitude = -180;

            for (var idx = 0, len = locations.length; idx < len; idx += 1) {
                var location = locations[idx];

                if (!location) {
                    continue;
                }

                minLatitude = Math.min(minLatitude, location.latitude);
                maxLatitude = Math.max(maxLatitude, location.latitude);
                minLongitude = Math.min(minLongitude, location.longitude);
                maxLongitude = Math.max(maxLongitude, location.longitude);
            }

            this.minLatitude = minLatitude;
            this.maxLatitude = maxLatitude;
            this.minLongitude = minLongitude;
            this.maxLongitude = maxLongitude;

            return this;
        };

        /**
         * Computes bounding sectors from a list of locations that span the dateline.
         * @param {Location[]} locations The locations to bound.
         * @returns {Sector[]} Two sectors, one in the eastern hemisphere and one in the western hemisphere.
         * Returns null if the computed bounding sector has zero width or height.
         * @throws {ArgumentError} If the specified array is null, undefined or empty or the number of locations
         * is less than 2.
         */
        Sector.splitBoundingSectors = function (locations) {
            if (!locations || locations.length < 2) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "splitBoundingSectors",
                    "missingArray"));
            }

            var minLat = 90;
            var minLon = 180;
            var maxLat = -90;
            var maxLon = -180;

            var lastLocation = null;

            for (var idx = 0, len = locations.length; idx < len; idx += 1) {
                var location = locations[idx];

                var lat = location.latitude;
                if (lat < minLat) {
                    minLat = lat;
                }
                if (lat > maxLat) {
                    maxLat = lat;
                }

                var lon = location.longitude;
                if (lon >= 0 && lon < minLon) {
                    minLon = lon;
                }
                if (lon <= 0 && lon > maxLon) {
                    maxLon = lon;
                }

                if (lastLocation != null) {
                    var lastLon = lastLocation.longitude;
                    if (WWMath.signum(lon) != WWMath.signum(lastLon)) {
                        if (Math.abs(lon - lastLon) < 180) {
                            // Crossing the zero longitude line too
                            maxLon = 0;
                            minLon = 0;
                        }
                    }
                }
                lastLocation = location;
            }

            if (minLat === maxLat && minLon === maxLon) {
                return null;
            }

            return [
                new Sector(minLat, maxLat, minLon, 180), // Sector on eastern hemisphere.
                new Sector(minLat, maxLat, -180, maxLon) // Sector on western hemisphere.
            ];
        };

        /**
         * Indicates whether this sector intersects a specified sector.
         * This sector intersects the specified sector when each sector's boundaries either overlap with the specified
         * sector or are adjacent to the specified sector.
         * The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
         * [-180, 180] longitude).
         * @param {Sector} sector The sector to test intersection with. May be null or undefined, in which case this
         * function returns false.
         * @returns {Boolean} true if the specifies sector intersections this sector, otherwise false.
         */
        Sector.prototype.intersects = function (sector) {
            // Assumes normalized angles: [-90, 90], [-180, 180].
            return sector
                && this.minLongitude <= sector.maxLongitude
                && this.maxLongitude >= sector.minLongitude
                && this.minLatitude <= sector.maxLatitude
                && this.maxLatitude >= sector.minLatitude;
        };

        /**
         * Indicates whether this sector intersects a specified sector exclusive of the sector boundaries.
         * This sector overlaps the specified sector when the union of the two sectors defines a non-empty sector.
         * The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
         * [-180, 180] longitude).
         * @param {Sector} sector The sector to test overlap with. May be null or undefined, in which case this
         * function returns false.
         * @returns {Boolean} true if the specified sector overlaps this sector, otherwise false.
         */
        Sector.prototype.overlaps = function (sector) {
            // Assumes normalized angles: [-90, 90], [-180, 180].
            return sector
                && this.minLongitude < sector.maxLongitude
                && this.maxLongitude > sector.minLongitude
                && this.minLatitude < sector.maxLatitude
                && this.maxLatitude > sector.minLatitude;
        };

        /**
         * Indicates whether this sector fully contains a specified sector.
         * This sector contains the specified sector when the specified sector's boundaries are completely contained
         * within this sector's boundaries, or are equal to this sector's boundaries.
         * The sectors are assumed to have normalized angles (angles within the range [-90, 90] latitude and
         * [-180, 180] longitude).
         * @param {Sector} sector The sector to test containment with. May be null or undefined, in which case this
         * function returns false.
         * @returns {Boolean} true if the specified sector contains this sector, otherwise false.
         */
        Sector.prototype.contains = function (sector) {
            // Assumes normalized angles: [-90, 90], [-180, 180].
            return sector
                && this.minLatitude <= sector.minLatitude
                && this.maxLatitude >= sector.maxLatitude
                && this.minLongitude <= sector.minLongitude
                && this.maxLongitude >= sector.maxLongitude;
        };

        /**
         * Indicates whether this sector contains a specified geographic location.
         * @param {Number} latitude The location's latitude in degrees.
         * @param {Number} longitude The location's longitude in degrees.
         * @returns {Boolean} true if this sector contains the location, otherwise false.
         */
        Sector.prototype.containsLocation = function (latitude, longitude) {
            // Assumes normalized angles: [-90, 90], [-180, 180].
            return this.minLatitude <= latitude
                && this.maxLatitude >= latitude
                && this.minLongitude <= longitude
                && this.maxLongitude >= longitude;
        };

        /**
         * Sets this sector to the intersection of itself and a specified sector.
         * @param {Sector} sector The sector to intersect with this one.
         * @returns {Sector} This sector, set to its intersection with the specified sector.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        Sector.prototype.intersection = function (sector) {
            if (!sector instanceof Sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "intersection", "missingSector"));
            }

            // Assumes normalized angles: [-180, 180], [-90, 90].
            if (this.minLatitude < sector.minLatitude)
                this.minLatitude = sector.minLatitude;
            if (this.maxLatitude > sector.maxLatitude)
                this.maxLatitude = sector.maxLatitude;
            if (this.minLongitude < sector.minLongitude)
                this.minLongitude = sector.minLongitude;
            if (this.maxLongitude > sector.maxLongitude)
                this.maxLongitude = sector.maxLongitude;

            // If the sectors do not overlap in either latitude or longitude, then the result of the above logic results in
            // the max being greater than the min. In this case, set the max to indicate that the sector is empty in
            // that dimension.
            if (this.maxLatitude < this.minLatitude)
                this.maxLatitude = this.minLatitude;
            if (this.maxLongitude < this.minLongitude)
                this.maxLongitude = this.minLongitude;

            return this;
        };

        /**
         * Returns a list of the Lat/Lon coordinates of a Sector's corners.
         *
         * @returns {Array} an array of the four corner locations, in the order SW, SE, NE, NW
         */
        Sector.prototype.getCorners = function () {
            var corners = [];

            corners.push(new Location(this.minLatitude, this.minLongitude));
            corners.push(new Location(this.minLatitude, this.maxLongitude));
            corners.push(new Location(this.maxLatitude, this.maxLongitude));
            corners.push(new Location(this.maxLatitude, this.minLongitude));

            return corners;
        };

        /**
         * Returns an array of {@link Vec3} that bounds the specified sector on the surface of the specified
         * {@link Globe}. The returned points enclose the globe's surface terrain in the sector,
         * according to the specified vertical exaggeration, minimum elevation, and maximum elevation. If the minimum and
         * maximum elevation are equal, this assumes a maximum elevation of 10 + the minimum.
         *
         * @param {Globe} globe the globe the extent relates to.
         * @param {Number} verticalExaggeration the globe's vertical surface exaggeration.
         *
         * @returns {Vec3} a set of points that enclose the globe's surface on the specified sector. Can be turned into a {@link BoundingBox}
         * with the setToVec3Points method.
         *
         * @throws {ArgumentError} if the globe is null.
         */
        Sector.prototype.computeBoundingPoints = function (globe, verticalExaggeration) {
            // TODO: Refactor this method back to computeBoundingBox.
            // This method was originally computeBoundingBox and returned a BoundingBox. This created a circular dependency between
            // Sector and BoundingBox that the Karma unit test suite doesn't appear to like. If we discover a way to make Karma handle this
            // situation, we should refactor this method.
            if (globe === null) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "computeBoundingBox", "missingGlobe"));
            }

            var minAndMaxElevations = globe.minAndMaxElevationsForSector(this);

            // Compute the exaggerated minimum and maximum heights.
            var minHeight = minAndMaxElevations[0] * verticalExaggeration;
            var maxHeight = minAndMaxElevations[1] * verticalExaggeration;

            if (minHeight === maxHeight)
                maxHeight = minHeight + 10; // Ensure the top and bottom heights are not equal.

            var points = [];
            var corners = this.getCorners();
            for (var i = 0; i < corners.length; i++) {
                points.push(globe.computePointFromPosition(corners[i].latitude, corners[i].longitude, minHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(corners[i].latitude, corners[i].longitude, maxHeight, new Vec3(0, 0, 0)));
            }

            // A point at the centroid captures the maximum vertical dimension.
            var centroid = this.centroid(new Location(0, 0));
            points.push(globe.computePointFromPosition(centroid.latitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));

            // If the sector spans the equator, then the curvature of all four edges need to be taken into account. The
            // extreme points along the top and bottom edges are located at their mid-points, and the extreme points along
            // the left and right edges are on the equator. Add points with the longitude of the sector's centroid but with
            // the sector's min and max latitude, and add points with the sector's min and max longitude but with latitude
            // at the equator. See WWJINT-225.
            if (this.minLatitude < 0 && this.maxLatitude > 0) {
                points.push(globe.computePointFromPosition(this.minLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(this.maxLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(0, this.minLongitude, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(0, this.maxLongitude, maxHeight, new Vec3(0, 0, 0)));
            }
            // If the sector is located entirely in the southern hemisphere, then the curvature of its top edge needs to be
            // taken into account. The extreme point along the top edge is located at its mid-point. Add a point with the
            // longitude of the sector's centroid but with the sector's max latitude. See WWJINT-225.
            else if (this.minLatitude < 0) {
                points.push(globe.computePointFromPosition(this.maxLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
            }
            // If the sector is located entirely in the northern hemisphere, then the curvature of its bottom edge needs to
            // be taken into account. The extreme point along the bottom edge is located at its mid-point. Add a point with
            // the longitude of the sector's centroid but with the sector's min latitude. See WWJINT-225.
            else {
                points.push(globe.computePointFromPosition(this.minLatitude, centroid.longitude, maxHeight, new Vec3(0, 0, 0)));
            }

            // If the sector spans 360 degrees of longitude then is a band around the entire globe. (If one edge is a pole
            // then the sector looks like a circle around the pole.) Add points at the min and max latitudes and longitudes
            // 0, 180, 90, and -90 to capture full extent of the band.
            if (this.deltaLongitude() >= 360) {
                var minLat = this.minLatitude;
                points.push(globe.computePointFromPosition(minLat, 0, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(minLat, 90, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(minLat, -90, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(minLat, 180, maxHeight, new Vec3(0, 0, 0)));

                var maxLat = this.maxLatitude;
                points.push(globe.computePointFromPosition(maxLat, 0, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(maxLat, 90, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(maxLat, -90, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(maxLat, 180, maxHeight, new Vec3(0, 0, 0)));
            }
            else if (this.deltaLongitude() > 180) {
                // Need to compute more points to ensure the box encompasses the full sector.
                var cLon = centroid.longitude;
                var cLat = centroid.latitude;

                // centroid latitude, longitude midway between min longitude and centroid longitude
                var lon = (this.minLongitude + cLon) / 2;
                points.push(globe.computePointFromPosition(cLat, lon, maxHeight, new Vec3(0, 0, 0)));

                // centroid latitude, longitude midway between centroid longitude and max longitude
                lon = (cLon + this.maxLongitude) / 2;
                points.push(globe.computePointFromPosition(cLat, lon, maxHeight, new Vec3(0, 0, 0)));

                // centroid latitude, longitude at min longitude and max longitude
                points.push(globe.computePointFromPosition(cLat, this.minLongitude, maxHeight, new Vec3(0, 0, 0)));
                points.push(globe.computePointFromPosition(cLat, this.maxLongitude, maxHeight, new Vec3(0, 0, 0)));
            }

            return points;
        };

        /**
         * Sets this sector to the union of itself and a specified sector.
         * @param {Sector} sector The sector to union with this one.
         * @returns {Sector} This sector, set to its union with the specified sector.
         * @throws {ArgumentError} if the specified sector is null or undefined.
         */
        Sector.prototype.union = function (sector) {
            if (!sector instanceof Sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Sector", "union", "missingSector"));
            }

            // Assumes normalized angles: [-180, 180], [-90, 90].
            if (this.minLatitude > sector.minLatitude)
                this.minLatitude = sector.minLatitude;
            if (this.maxLatitude < sector.maxLatitude)
                this.maxLatitude = sector.maxLatitude;
            if (this.minLongitude > sector.minLongitude)
                this.minLongitude = sector.minLongitude;
            if (this.maxLongitude < sector.maxLongitude)
                this.maxLongitude = sector.maxLongitude;

            return this;
        };

        return Sector;
    });
/*
 * @license
 * Copyright 2000, Silicon Graphics, Inc. All Rights Reserved.
 * Copyright 2014, Google Inc. All Rights Reserved.
 *
 * 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 including the dates of first publication and
 * either this permission notice or a reference to http://oss.sgi.com/projects/FreeB/
 * 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
 * SILICON GRAPHICS, INC. 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.
 *
 * Original Code. The Original Code is: OpenGL Sample Implementation,
 * Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
 * Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
 * Copyright in any portions created by third parties is as indicated
 * elsewhere herein. All Rights Reserved.
 */
/*
 * @author ericv@cs.stanford.edu (Eric Veach)
 * @author bckenny@google.com (Brendan Kenny)
 */

/*
 * Base namespace.
 * @const
 */
var libtess = {};

/**
 * Whether to run asserts and extra debug checks.
 * @define {boolean}
 */
libtess.DEBUG = false;

/**
 * Checks if the condition evaluates to true if libtess.DEBUG is true.
 * @param {*} condition The condition to check.
 * @param {string=} opt_message Error message in case of failure.
 * @throws {Error} Assertion failed, the condition evaluates to false.
 */
libtess.assert = function(condition, opt_message) {
  if (libtess.DEBUG && !condition) {
    throw new Error('Assertion failed' +
        (opt_message ? ': ' + opt_message : ''));
  }
};

/**
 * The maximum vertex coordinate size, 1e150. Anything larger will trigger a
 * GLU_TESS_COORD_TOO_LARGE error callback and the vertex will be clamped to
 * this value for all tessellation calculations.
 * @const {number}
 */
libtess.GLU_TESS_MAX_COORD = 1e150;
// NOTE(bckenny): value from glu.pl generator

/**
 * Normally the polygon is projected to a plane perpendicular to one of the
 * three coordinate axes before tessellating in 2d. This helps numerical
 * accuracy by forgoing a transformation step by simply dropping one coordinate
 * dimension.
 *
 * However, this can affect the placement of intersection points for non-axis-
 * aligned polygons. Setting TRUE_PROJECT to true will instead project onto a
 * plane actually perpendicular to the polygon's normal.
 *
 * NOTE(bckenny): I can find no instances on the internet in which this mode has
 * been used, but it's difficult to search for. This was a compile-time setting
 * in the original, so setting this as constant. If this is exposed in the
 * public API, remove the ignore coverage directives on
 * libtess.normal.projectPolygon and libtess.normal.normalize_.
 * @const {boolean}
 */
libtess.TRUE_PROJECT = false;

/**
 * We cache vertex data for single-contour polygons so that we can try a
 * quick-and-dirty decomposition first.
 * @const {number}
 */
libtess.TESS_MAX_CACHE = 100;

/**
 * The default tolerance for merging features, 0, meaning vertices are only
 * merged if they are exactly coincident
 * If a higher tolerance is needed, significant rewriting will need to occur.
 * See libtess.sweep.TOLERANCE_NONZERO_ as a starting place.
 * @const {number}
 */
libtess.GLU_TESS_DEFAULT_TOLERANCE = 0;

/**
 * The input contours parition the plane into regions. A winding
 * rule determines which of these regions are inside the polygon.
 *
 * For a single contour C, the winding number of a point x is simply
 * the signed number of revolutions we make around x as we travel
 * once around C (where CCW is positive). When there are several
 * contours, the individual winding numbers are summed. This
 * procedure associates a signed integer value with each point x in
 * the plane. Note that the winding number is the same for all
 * points in a single region.
 *
 * The winding rule classifies a region as "inside" if its winding
 * number belongs to the chosen category (odd, nonzero, positive,
 * negative, or absolute value of at least two). The current GLU
 * tesselator implements the "odd" rule. The "nonzero" rule is another
 * common way to define the interior. The other three rules are
 * useful for polygon CSG operations.
 * @enum {number}
 */
libtess.windingRule = {
  // NOTE(bckenny): values from enumglu.spec
  GLU_TESS_WINDING_ODD: 100130,
  GLU_TESS_WINDING_NONZERO: 100131,
  GLU_TESS_WINDING_POSITIVE: 100132,
  GLU_TESS_WINDING_NEGATIVE: 100133,
  GLU_TESS_WINDING_ABS_GEQ_TWO: 100134
};

/**
 * The type of primitive return from a "begin" callback. GL_LINE_LOOP is only
 * returned when GLU_TESS_BOUNDARY_ONLY is true. GL_TRIANGLE_STRIP and
 * GL_TRIANGLE_FAN are no longer returned since 1.1.0 (see release notes).
 * @enum {number}
 */
libtess.primitiveType = {
  GL_LINE_LOOP: 2,
  GL_TRIANGLES: 4,
  GL_TRIANGLE_STRIP: 5,
  GL_TRIANGLE_FAN: 6
};

/**
 * The types of errors provided in the error callback.
 * @enum {number}
 */
libtess.errorType = {
  // TODO(bckenny) doc types
  // NOTE(bckenny): values from enumglu.spec
  GLU_TESS_MISSING_BEGIN_POLYGON: 100151,
  GLU_TESS_MISSING_END_POLYGON: 100153,
  GLU_TESS_MISSING_BEGIN_CONTOUR: 100152,
  GLU_TESS_MISSING_END_CONTOUR: 100154,
  GLU_TESS_COORD_TOO_LARGE: 100155,
  GLU_TESS_NEED_COMBINE_CALLBACK: 100156
};

/**
 * Enum values necessary for providing settings and callbacks. See the readme
 * for details.
 * @enum {number}
 */
libtess.gluEnum = {
  // TODO(bckenny): rename so not always typing libtess.gluEnum.*?

  // NOTE(bckenny): values from enumglu.spec
  GLU_TESS_BEGIN: 100100,
  GLU_TESS_VERTEX: 100101,
  GLU_TESS_END: 100102,
  GLU_TESS_ERROR: 100103,
  GLU_TESS_EDGE_FLAG: 100104,
  GLU_TESS_COMBINE: 100105,
  GLU_TESS_BEGIN_DATA: 100106,
  GLU_TESS_VERTEX_DATA: 100107,
  GLU_TESS_END_DATA: 100108,
  GLU_TESS_ERROR_DATA: 100109,
  GLU_TESS_EDGE_FLAG_DATA: 100110,
  GLU_TESS_COMBINE_DATA: 100111,

  GLU_TESS_MESH: 100112,  //  NOTE(bckenny): from tess.c
  GLU_TESS_TOLERANCE: 100142,
  GLU_TESS_WINDING_RULE: 100140,
  GLU_TESS_BOUNDARY_ONLY: 100141,

  // TODO(bckenny): move this to libtess.errorType?
  GLU_INVALID_ENUM: 100900,
  GLU_INVALID_VALUE: 100901
};

/** @typedef {number} */
libtess.PQHandle;

// TODO(bckenny): better typing on key?
/** @typedef {Object} */
libtess.PQKey;


/* global libtess */

/** @const */
libtess.geom = {};

/**
 * Returns whether vertex u and vertex v are equal.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @return {boolean}
 */
libtess.geom.vertEq = function(u, v) {
  return u.s === v.s && u.t === v.t;
};

/**
 * Returns whether vertex u is lexicographically less than or equal to vertex v.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @return {boolean}
 */
libtess.geom.vertLeq = function(u, v) {
  return (u.s < v.s) || (u.s === v.s && u.t <= v.t);
};

/**
 * Given three vertices u,v,w such that geom.vertLeq(u,v) && geom.vertLeq(v,w),
 * evaluates the t-coord of the edge uw at the s-coord of the vertex v.
 * Returns v.t - (uw)(v.s), ie. the signed distance from uw to v.
 * If uw is vertical (and thus passes thru v), the result is zero.
 *
 * The calculation is extremely accurate and stable, even when v
 * is very close to u or w.  In particular if we set v.t = 0 and
 * let r be the negated result (this evaluates (uw)(v.s)), then
 * r is guaranteed to satisfy MIN(u.t,w.t) <= r <= MAX(u.t,w.t).
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @param {libtess.GluVertex} w
 * @return {number}
 */
libtess.geom.edgeEval = function(u, v, w) {

  var gapL = v.s - u.s;
  var gapR = w.s - v.s;

  if (gapL + gapR > 0) {
    if (gapL < gapR) {
      return (v.t - u.t) + (u.t - w.t) * (gapL / (gapL + gapR));
    } else {
      return (v.t - w.t) + (w.t - u.t) * (gapR / (gapL + gapR));
    }
  }

  // vertical line
  return 0;
};

/**
 * Returns a number whose sign matches geom.edgeEval(u,v,w) but which
 * is cheaper to evaluate.  Returns > 0, == 0 , or < 0
 * as v is above, on, or below the edge uw.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @param {libtess.GluVertex} w
 * @return {number}
 */
libtess.geom.edgeSign = function(u, v, w) {

  var gapL = v.s - u.s;
  var gapR = w.s - v.s;

  if (gapL + gapR > 0) {
    return (v.t - w.t) * gapL + (v.t - u.t) * gapR;
  }

  // vertical line
  return 0;
};

/**
 * Version of VertLeq with s and t transposed.
 * Returns whether vertex u is lexicographically less than or equal to vertex v.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @return {boolean}
 */
libtess.geom.transLeq = function(u, v) {
  return (u.t < v.t) || (u.t === v.t && u.s <= v.s);
};

/**
 * Version of geom.edgeEval with s and t transposed.
 * Given three vertices u,v,w such that geom.transLeq(u,v) &&
 * geom.transLeq(v,w), evaluates the t-coord of the edge uw at the s-coord of
 * the vertex v. Returns v.s - (uw)(v.t), ie. the signed distance from uw to v.
 * If uw is vertical (and thus passes thru v), the result is zero.
 *
 * The calculation is extremely accurate and stable, even when v
 * is very close to u or w.  In particular if we set v.s = 0 and
 * let r be the negated result (this evaluates (uw)(v.t)), then
 * r is guaranteed to satisfy MIN(u.s,w.s) <= r <= MAX(u.s,w.s).
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @param {libtess.GluVertex} w
 * @return {number}
 */
libtess.geom.transEval = function(u, v, w) {

  var gapL = v.t - u.t;
  var gapR = w.t - v.t;

  if (gapL + gapR > 0) {
    if (gapL < gapR) {
      return (v.s - u.s) + (u.s - w.s) * (gapL / (gapL + gapR));
    } else {
      return (v.s - w.s) + (w.s - u.s) * (gapR / (gapL + gapR));
    }
  }

  // vertical line
  return 0;
};

/**
 * Version of geom.edgeSign with s and t transposed.
 * Returns a number whose sign matches geom.transEval(u,v,w) but which
 * is cheaper to evaluate.  Returns > 0, == 0 , or < 0
 * as v is above, on, or below the edge uw.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @param {libtess.GluVertex} w
 * @return {number}
 */
libtess.geom.transSign = function(u, v, w) {

  var gapL = v.t - u.t;
  var gapR = w.t - v.t;

  if (gapL + gapR > 0) {
    return (v.s - w.s) * gapL + (v.s - u.s) * gapR;
  }

  // vertical line
  return 0;
};

/**
 * Returns whether edge is directed from right to left.
 * @param {libtess.GluHalfEdge} e
 * @return {boolean}
 */
libtess.geom.edgeGoesLeft = function(e) {
  return libtess.geom.vertLeq(e.dst(), e.org);
};

/**
 * Returns whether edge is directed from left to right.
 * @param {libtess.GluHalfEdge} e
 * @return {boolean}
 */
libtess.geom.edgeGoesRight = function(e) {
  return libtess.geom.vertLeq(e.org, e.dst());
};

/**
 * Calculates the L1 distance between vertices u and v.
 * @param {libtess.GluVertex} u
 * @param {libtess.GluVertex} v
 * @return {number}
 */
libtess.geom.vertL1dist = function(u, v) {
  return Math.abs(u.s - v.s) + Math.abs(u.t - v.t);
};

// NOTE(bckenny): vertCCW is called nowhere in libtess and isn't part of the
// public API.
/* istanbul ignore next */
/**
 * For almost-degenerate situations, the results are not reliable.
 * Unless the floating-point arithmetic can be performed without
 * rounding errors, *any* implementation will give incorrect results
 * on some degenerate inputs, so the client must have some way to
 * handle this situation.
 * @param {!libtess.GluVertex} u
 * @param {!libtess.GluVertex} v
 * @param {!libtess.GluVertex} w
 * @return {boolean}
 */
libtess.geom.vertCCW = function(u, v, w) {
  return (u.s * (v.t - w.t) + v.s * (w.t - u.t) + w.s * (u.t - v.t)) >= 0;
};

/**
 * Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b),
 * or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces
 * this in the rare case that one argument is slightly negative.
 * The implementation is extremely stable numerically.
 * In particular it guarantees that the result r satisfies
 * MIN(x,y) <= r <= MAX(x,y), and the results are very accurate
 * even when a and b differ greatly in magnitude.
 * @private
 * @param {number} a
 * @param {number} x
 * @param {number} b
 * @param {number} y
 * @return {number}
 */
libtess.geom.interpolate_ = function(a, x, b, y) {
  // from Macro RealInterpolate:
  //(a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, ((a <= b) ? ((b == 0) ? ((x+y) / 2) : (x + (y-x) * (a/(a+b)))) : (y + (x-y) * (b/(a+b)))))
  a = (a < 0) ? 0 : a;
  b = (b < 0) ? 0 : b;

  if (a <= b) {
    if (b === 0) {
      return (x + y) / 2;
    } else {
      return x + (y - x) * (a / (a + b));
    }
  } else {
    return y + (x - y) * (b / (a + b));
  }
};

/**
 * Given edges (o1,d1) and (o2,d2), compute their point of intersection.
 * The computed point is guaranteed to lie in the intersection of the
 * bounding rectangles defined by each edge.
 * @param {!libtess.GluVertex} o1
 * @param {!libtess.GluVertex} d1
 * @param {!libtess.GluVertex} o2
 * @param {!libtess.GluVertex} d2
 * @param {!libtess.GluVertex} v
 */
libtess.geom.edgeIntersect = function(o1, d1, o2, d2, v) {
  // This is certainly not the most efficient way to find the intersection
  // of two line segments, but it is very numerically stable.

  // Strategy: find the two middle vertices in the VertLeq ordering,
  // and interpolate the intersection s-value from these. Then repeat
  // using the TransLeq ordering to find the intersection t-value.
  var z1;
  var z2;
  var tmp;
  if (!libtess.geom.vertLeq(o1, d1)) {
    // Swap(o1, d1);
    tmp = o1;
    o1 = d1;
    d1 = tmp;
  }
  if (!libtess.geom.vertLeq(o2, d2)) {
    // Swap(o2, d2);
    tmp = o2;
    o2 = d2;
    d2 = tmp;
  }
  if (!libtess.geom.vertLeq(o1, o2)) {
    // Swap(o1, o2);
    tmp = o1;
    o1 = o2;
    o2 = tmp;
    // Swap(d1, d2);
    tmp = d1;
    d1 = d2;
    d2 = tmp;
  }

  if (!libtess.geom.vertLeq(o2, d1)) {
    // Technically, no intersection -- do our best
    v.s = (o2.s + d1.s) / 2;

  } else if (libtess.geom.vertLeq(d1, d2)) {
    // Interpolate between o2 and d1
    z1 = libtess.geom.edgeEval(o1, o2, d1);
    z2 = libtess.geom.edgeEval(o2, d1, d2);
    if (z1 + z2 < 0) { z1 = -z1; z2 = -z2; }
    v.s = libtess.geom.interpolate_(z1, o2.s, z2, d1.s);

  } else {
    // Interpolate between o2 and d2
    z1 = libtess.geom.edgeSign(o1, o2, d1);
    z2 = -libtess.geom.edgeSign(o1, d2, d1);
    if (z1 + z2 < 0) { z1 = -z1; z2 = -z2; }
    v.s = libtess.geom.interpolate_(z1, o2.s, z2, d2.s);
  }

  // Now repeat the process for t
  if (!libtess.geom.transLeq(o1, d1)) {
    // Swap(o1, d1);
    tmp = o1;
    o1 = d1;
    d1 = tmp;
  }
  if (!libtess.geom.transLeq(o2, d2)) {
    // Swap(o2, d2);
    tmp = o2;
    o2 = d2;
    d2 = tmp;
  }
  if (!libtess.geom.transLeq(o1, o2)) {
    // Swap(o1, o2);
    tmp = o1;
    o1 = o2;
    o2 = tmp;
    // Swap(d1, d2);
    tmp = d1;
    d1 = d2;
    d2 = tmp;
  }

  if (!libtess.geom.transLeq(o2, d1)) {
    // Technically, no intersection -- do our best
    v.t = (o2.t + d1.t) / 2;

  } else if (libtess.geom.transLeq(d1, d2)) {
    // Interpolate between o2 and d1
    z1 = libtess.geom.transEval(o1, o2, d1);
    z2 = libtess.geom.transEval(o2, d1, d2);
    if (z1 + z2 < 0) { z1 = -z1; z2 = -z2; }
    v.t = libtess.geom.interpolate_(z1, o2.t, z2, d1.t);

  } else {
    // Interpolate between o2 and d2
    z1 = libtess.geom.transSign(o1, o2, d1);
    z2 = -libtess.geom.transSign(o1, d2, d1);
    if (z1 + z2 < 0) { z1 = -z1; z2 = -z2; }
    v.t = libtess.geom.interpolate_(z1, o2.t, z2, d2.t);
  }
};



/* global libtess */

// TODO(bckenny): could maybe merge GluMesh and mesh.js since these are
// operations on the mesh

/** @const */
libtess.mesh = {};

/****************** Basic Edge Operations **********************/


/**
 * makeEdge creates one edge, two vertices, and a loop (face).
 * The loop consists of the two new half-edges.
 *
 * @param {libtess.GluMesh} mesh [description].
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.mesh.makeEdge = function(mesh) {
  // TODO(bckenny): probably move to GluMesh, but needs Make* methods with it

  var e = libtess.mesh.makeEdgePair_(mesh.eHead);

  // complete edge with vertices and face (see mesh.makeEdgePair_)
  libtess.mesh.makeVertex_(e, mesh.vHead);
  libtess.mesh.makeVertex_(e.sym, mesh.vHead);
  libtess.mesh.makeFace_(e, mesh.fHead);

  return e;
};


/**
 * meshSplice(eOrg, eDst) is the basic operation for changing the
 * mesh connectivity and topology. It changes the mesh so that
 *  eOrg.oNext <- OLD( eDst.oNext )
 *  eDst.oNext <- OLD( eOrg.oNext )
 * where OLD(...) means the value before the meshSplice operation.
 *
 * This can have two effects on the vertex structure:
 *  - if eOrg.org != eDst.org, the two vertices are merged together
 *  - if eOrg.org == eDst.org, the origin is split into two vertices
 * In both cases, eDst.org is changed and eOrg.org is untouched.
 *
 * Similarly (and independently) for the face structure,
 *  - if eOrg.lFace == eDst.lFace, one loop is split into two
 *  - if eOrg.lFace != eDst.lFace, two distinct loops are joined into one
 * In both cases, eDst.lFace is changed and eOrg.lFace is unaffected.
 *
 * Some special cases:
 * If eDst == eOrg, the operation has no effect.
 * If eDst == eOrg.lNext, the new face will have a single edge.
 * If eDst == eOrg.lPrev(), the old face will have a single edge.
 * If eDst == eOrg.oNext, the new vertex will have a single edge.
 * If eDst == eOrg.oPrev(), the old vertex will have a single edge.
 *
 * @param {libtess.GluHalfEdge} eOrg [description].
 * @param {libtess.GluHalfEdge} eDst [description].
 */
libtess.mesh.meshSplice = function(eOrg, eDst) {
  // TODO: more descriptive name?

  var joiningLoops = false;
  var joiningVertices = false;

  if (eOrg === eDst) {
    return;
  }

  if (eDst.org !== eOrg.org) {
    // We are merging two disjoint vertices -- destroy eDst.org
    joiningVertices = true;
    libtess.mesh.killVertex_(eDst.org, eOrg.org);
  }

  if (eDst.lFace !== eOrg.lFace) {
    // We are connecting two disjoint loops -- destroy eDst.lFace
    joiningLoops = true;
    libtess.mesh.killFace_(eDst.lFace, eOrg.lFace);
  }

  // Change the edge structure
  libtess.mesh.splice_(eDst, eOrg);

  if (!joiningVertices) {
    // We split one vertex into two -- the new vertex is eDst.org.
    // Make sure the old vertex points to a valid half-edge.
    libtess.mesh.makeVertex_(eDst, eOrg.org);
    eOrg.org.anEdge = eOrg;
  }

  if (!joiningLoops) {
    // We split one loop into two -- the new loop is eDst.lFace.
    // Make sure the old face points to a valid half-edge.
    libtess.mesh.makeFace_(eDst, eOrg.lFace);
    eOrg.lFace.anEdge = eOrg;
  }
};


/**
 * deleteEdge(eDel) removes the edge eDel. There are several cases:
 * if (eDel.lFace != eDel.rFace()), we join two loops into one; the loop
 * eDel.lFace is deleted. Otherwise, we are splitting one loop into two;
 * the newly created loop will contain eDel.dst(). If the deletion of eDel
 * would create isolated vertices, those are deleted as well.
 *
 * This function could be implemented as two calls to __gl_meshSplice
 * plus a few calls to memFree, but this would allocate and delete
 * unnecessary vertices and faces.
 *
 * @param {libtess.GluHalfEdge} eDel [description].
 */
libtess.mesh.deleteEdge = function(eDel) {
  var eDelSym = eDel.sym;
  var joiningLoops = false;

  // First step: disconnect the origin vertex eDel.org.  We make all
  // changes to get a consistent mesh in this "intermediate" state.
  if (eDel.lFace !== eDel.rFace()) {
    // We are joining two loops into one -- remove the left face
    joiningLoops = true;
    libtess.mesh.killFace_(eDel.lFace, eDel.rFace());
  }

  if (eDel.oNext === eDel) {
    libtess.mesh.killVertex_(eDel.org, null);

  } else {
    // Make sure that eDel.org and eDel.rFace() point to valid half-edges
    eDel.rFace().anEdge = eDel.oPrev();
    eDel.org.anEdge = eDel.oNext;

    libtess.mesh.splice_(eDel, eDel.oPrev());

    if (!joiningLoops) {
      // We are splitting one loop into two -- create a new loop for eDel.
      libtess.mesh.makeFace_(eDel, eDel.lFace);
    }
  }

  // Claim: the mesh is now in a consistent state, except that eDel.org
  // may have been deleted.  Now we disconnect eDel.dst().
  if (eDelSym.oNext === eDelSym) {
    libtess.mesh.killVertex_(eDelSym.org, null);
    libtess.mesh.killFace_(eDelSym.lFace, null);

  } else {
    // Make sure that eDel.dst() and eDel.lFace point to valid half-edges
    eDel.lFace.anEdge = eDelSym.oPrev();
    eDelSym.org.anEdge = eDelSym.oNext;
    libtess.mesh.splice_(eDelSym, eDelSym.oPrev());
  }

  // Any isolated vertices or faces have already been freed.
  libtess.mesh.killEdge_(eDel);
};

/******************** Other Edge Operations **********************/

/* All these routines can be implemented with the basic edge
 * operations above.  They are provided for convenience and efficiency.
 */


/**
 * addEdgeVertex(eOrg) creates a new edge eNew such that
 * eNew == eOrg.lNext, and eNew.dst() is a newly created vertex.
 * eOrg and eNew will have the same left face.
 *
 * @param {libtess.GluHalfEdge} eOrg [description].
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.mesh.addEdgeVertex = function(eOrg) {
  // TODO(bckenny): why is it named this?

  var eNew = libtess.mesh.makeEdgePair_(eOrg);
  var eNewSym = eNew.sym;

  // Connect the new edge appropriately
  libtess.mesh.splice_(eNew, eOrg.lNext);

  // Set the vertex and face information
  eNew.org = eOrg.dst();

  libtess.mesh.makeVertex_(eNewSym, eNew.org);

  eNew.lFace = eNewSym.lFace = eOrg.lFace;

  return eNew;
};


/**
 * splitEdge(eOrg) splits eOrg into two edges eOrg and eNew,
 * such that eNew == eOrg.lNext. The new vertex is eOrg.dst() == eNew.org.
 * eOrg and eNew will have the same left face.
 *
 * @param {libtess.GluHalfEdge} eOrg [description].
 * @return {!libtess.GluHalfEdge} [description].
 */
libtess.mesh.splitEdge = function(eOrg) {
  var tempHalfEdge = libtess.mesh.addEdgeVertex(eOrg);
  var eNew = tempHalfEdge.sym;

  // Disconnect eOrg from eOrg.dst() and connect it to eNew.org
  libtess.mesh.splice_(eOrg.sym, eOrg.sym.oPrev());
  libtess.mesh.splice_(eOrg.sym, eNew);

  // Set the vertex and face information
  eOrg.sym.org = eNew.org; // NOTE(bckenny): assignment to dst
  eNew.dst().anEdge = eNew.sym;  // may have pointed to eOrg.sym
  eNew.sym.lFace = eOrg.rFace(); // NOTE(bckenny): assignment to rFace
  eNew.winding = eOrg.winding;  // copy old winding information
  eNew.sym.winding = eOrg.sym.winding;

  return eNew;
};


/**
 * connect(eOrg, eDst) creates a new edge from eOrg.dst()
 * to eDst.org, and returns the corresponding half-edge eNew.
 * If eOrg.lFace == eDst.lFace, this splits one loop into two,
 * and the newly created loop is eNew.lFace. Otherwise, two disjoint
 * loops are merged into one, and the loop eDst.lFace is destroyed.
 *
 * If (eOrg == eDst), the new face will have only two edges.
 * If (eOrg.lNext == eDst), the old face is reduced to a single edge.
 * If (eOrg.lNext.lNext == eDst), the old face is reduced to two edges.
 *
 * @param {libtess.GluHalfEdge} eOrg [description].
 * @param {libtess.GluHalfEdge} eDst [description].
 * @return {!libtess.GluHalfEdge} [description].
 */
libtess.mesh.connect = function(eOrg, eDst) {
  var joiningLoops = false;
  var eNew = libtess.mesh.makeEdgePair_(eOrg);
  var eNewSym = eNew.sym;

  if (eDst.lFace !== eOrg.lFace) {
    // We are connecting two disjoint loops -- destroy eDst.lFace
    joiningLoops = true;
    libtess.mesh.killFace_(eDst.lFace, eOrg.lFace);
  }

  // Connect the new edge appropriately
  libtess.mesh.splice_(eNew, eOrg.lNext);
  libtess.mesh.splice_(eNewSym, eDst);

  // Set the vertex and face information
  eNew.org = eOrg.dst();
  eNewSym.org = eDst.org;
  eNew.lFace = eNewSym.lFace = eOrg.lFace;

  // Make sure the old face points to a valid half-edge
  eOrg.lFace.anEdge = eNewSym;

  if (!joiningLoops) {
    // We split one loop into two -- the new loop is eNew.lFace
    libtess.mesh.makeFace_(eNew, eOrg.lFace);
  }
  return eNew;
};

/******************** Other Operations **********************/


/**
 * zapFace(fZap) destroys a face and removes it from the
 * global face list. All edges of fZap will have a null pointer as their
 * left face. Any edges which also have a null pointer as their right face
 * are deleted entirely (along with any isolated vertices this produces).
 * An entire mesh can be deleted by zapping its faces, one at a time,
 * in any order. Zapped faces cannot be used in further mesh operations!
 *
 * @param {libtess.GluFace} fZap [description].
 */
libtess.mesh.zapFace = function(fZap) {
  var eStart = fZap.anEdge;

  // walk around face, deleting edges whose right face is also NULL
  var eNext = eStart.lNext;
  var e;
  do {
    e = eNext;
    eNext = e.lNext;

    e.lFace = null;
    if (e.rFace() === null) {
      // delete the edge -- see mesh.deleteEdge above
      if (e.oNext === e) {
        libtess.mesh.killVertex_(e.org, null);

      } else {
        // Make sure that e.org points to a valid half-edge
        e.org.anEdge = e.oNext;
        libtess.mesh.splice_(e, e.oPrev());
      }

      var eSym = e.sym;

      if (eSym.oNext === eSym) {
        libtess.mesh.killVertex_(eSym.org, null);

      } else {
        // Make sure that eSym.org points to a valid half-edge
        eSym.org.anEdge = eSym.oNext;
        libtess.mesh.splice_(eSym, eSym.oPrev());
      }
      libtess.mesh.killEdge_(e);
    }
  } while (e !== eStart);

  // delete from circular doubly-linked list
  var fPrev = fZap.prev;
  var fNext = fZap.next;
  fNext.prev = fPrev;
  fPrev.next = fNext;

  // TODO(bckenny): memFree( fZap );
  // TODO(bckenny): probably null at callsite
};

// TODO(bckenny): meshUnion isn't called within libtess and isn't part of the
// public API. Could be useful if more mesh manipulation functions are exposed.
/* istanbul ignore next */
/**
 * meshUnion() forms the union of all structures in
 * both meshes, and returns the new mesh (the old meshes are destroyed).
 *
 * @param {!libtess.GluMesh} mesh1
 * @param {!libtess.GluMesh} mesh2
 * @return {!libtess.GluMesh}
 */
libtess.mesh.meshUnion = function(mesh1, mesh2) {
  // TODO(bceknny): probably move to GluMesh method
  var f1 = mesh1.fHead;
  var v1 = mesh1.vHead;
  var e1 = mesh1.eHead;

  var f2 = mesh2.fHead;
  var v2 = mesh2.vHead;
  var e2 = mesh2.eHead;

  // Add the faces, vertices, and edges of mesh2 to those of mesh1
  if (f2.next !== f2) {
    f1.prev.next = f2.next;
    f2.next.prev = f1.prev;
    f2.prev.next = f1;
    f1.prev = f2.prev;
  }

  if (v2.next !== v2) {
    v1.prev.next = v2.next;
    v2.next.prev = v1.prev;
    v2.prev.next = v1;
    v1.prev = v2.prev;
  }

  if (e2.next !== e2) {
    e1.sym.next.sym.next = e2.next;
    e2.next.sym.next = e1.sym.next;
    e2.sym.next.sym.next = e1;
    e1.sym.next = e2.sym.next;
  }

  // TODO(bckenny): memFree(mesh2);
  // TODO(bckenny): If function is kept, remove mesh2's data to enforce.
  return mesh1;
};


/**
 * deleteMesh(mesh) will free all storage for any valid mesh.
 * @param {libtess.GluMesh} mesh [description].
 */
libtess.mesh.deleteMesh = function(mesh) {
  // TODO(bckenny): unnecessary, I think.
  // TODO(bckenny): might want to explicitly null at callsite
  // lots of memFrees. see also DELETE_BY_ZAPPING
};

/************************ Utility Routines ************************/


/**
 * Creates a new pair of half-edges which form their own loop.
 * No vertex or face structures are allocated, but these must be assigned
 * before the current edge operation is completed.
 *
 * TODO(bckenny): warning about eNext strictly being first of pair? (see code)
 *
 * @private
 * @param {libtess.GluHalfEdge} eNext [description].
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.mesh.makeEdgePair_ = function(eNext) {
  var e = new libtess.GluHalfEdge();
  var eSym = new libtess.GluHalfEdge();

  // TODO(bckenny): how do we ensure this? see above comment in jsdoc
  // Make sure eNext points to the first edge of the edge pair
  // if (eNext->Sym < eNext ) { eNext = eNext->Sym; }

  // NOTE(bckenny): check this for bugs in current implementation!

  // Insert in circular doubly-linked list before eNext.
  // Note that the prev pointer is stored in sym.next.
  var ePrev = eNext.sym.next;
  eSym.next = ePrev;
  ePrev.sym.next = e;
  e.next = eNext;
  eNext.sym.next = eSym;

  e.sym = eSym;
  e.oNext = e;
  e.lNext = eSym;

  eSym.sym = e;
  eSym.oNext = eSym;
  eSym.lNext = e;

  return e;
};


/**
 * splice_ is best described by the Guibas/Stolfi paper or the
 * CS348a notes. Basically, it modifies the mesh so that
 * a.oNext and b.oNext are exchanged. This can have various effects
 * depending on whether a and b belong to different face or vertex rings.
 * For more explanation see mesh.meshSplice below.
 *
 * @private
 * @param {libtess.GluHalfEdge} a [description].
 * @param {libtess.GluHalfEdge} b [description].
 */
libtess.mesh.splice_ = function(a, b) {
  var aONext = a.oNext;
  var bONext = b.oNext;

  aONext.sym.lNext = b;
  bONext.sym.lNext = a;
  a.oNext = bONext;
  b.oNext = aONext;
};


/**
 * makeVertex_(eOrig, vNext) attaches a new vertex and makes it the
 * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
 * a place to insert the new vertex in the global vertex list.  We insert
 * the new vertex *before* vNext so that algorithms which walk the vertex
 * list will not see the newly created vertices.
 *
 * NOTE: unlike original, acutally allocates new vertex.
 *
 * @private
 * @param {libtess.GluHalfEdge} eOrig [description].
 * @param {libtess.GluVertex} vNext [description].
 */
libtess.mesh.makeVertex_ = function(eOrig, vNext) {
  // insert in circular doubly-linked list before vNext
  var vPrev = vNext.prev;
  var vNew = new libtess.GluVertex(vNext, vPrev);
  vPrev.next = vNew;
  vNext.prev = vNew;

  vNew.anEdge = eOrig;
  // leave coords, s, t undefined
  // TODO(bckenny): does above line mean 0 specifically, or does it matter?

  // fix other edges on this vertex loop
  var e = eOrig;
  do {
    e.org = vNew;
    e = e.oNext;
  } while (e !== eOrig);
};


/**
 * makeFace_(eOrig, fNext) attaches a new face and makes it the left
 * face of all edges in the face loop to which eOrig belongs. "fNext" gives
 * a place to insert the new face in the global face list.  We insert
 * the new face *before* fNext so that algorithms which walk the face
 * list will not see the newly created faces.
 *
 * NOTE: unlike original, acutally allocates new face.
 *
 * @private
 * @param {libtess.GluHalfEdge} eOrig [description].
 * @param {libtess.GluFace} fNext [description].
 */
libtess.mesh.makeFace_ = function(eOrig, fNext) {
  // insert in circular doubly-linked list before fNext
  var fPrev = fNext.prev;
  var fNew = new libtess.GluFace(fNext, fPrev);
  fPrev.next = fNew;
  fNext.prev = fNew;

  fNew.anEdge = eOrig;

  // The new face is marked "inside" if the old one was.  This is a
  // convenience for the common case where a face has been split in two.
  fNew.inside = fNext.inside;

  // fix other edges on this face loop
  var e = eOrig;
  do {
    e.lFace = fNew;
    e = e.lNext;
  } while (e !== eOrig);
};


/**
 * killEdge_ destroys an edge (the half-edges eDel and eDel.sym),
 * and removes from the global edge list.
 *
 * @private
 * @param {libtess.GluHalfEdge} eDel [description].
 */
libtess.mesh.killEdge_ = function(eDel) {
  // TODO(bckenny): in this case, no need to worry(?), but check when checking mesh.makeEdgePair_
  // Half-edges are allocated in pairs, see EdgePair above
  // if (eDel->Sym < eDel ) { eDel = eDel->Sym; }

  // delete from circular doubly-linked list
  var eNext = eDel.next;
  var ePrev = eDel.sym.next;
  eNext.sym.next = ePrev;
  ePrev.sym.next = eNext;

  // TODO(bckenny): memFree( eDel ); (which also frees eDel.sym)
  // TODO(bckenny): need to null at callsites?
};


/**
 * killVertex_ destroys a vertex and removes it from the global
 * vertex list. It updates the vertex loop to point to a given new vertex.
 *
 * @private
 * @param {libtess.GluVertex} vDel [description].
 * @param {libtess.GluVertex} newOrg [description].
 */
libtess.mesh.killVertex_ = function(vDel, newOrg) {
  var eStart = vDel.anEdge;

  // change the origin of all affected edges
  var e = eStart;
  do {
    e.org = newOrg;
    e = e.oNext;
  } while (e !== eStart);

  // delete from circular doubly-linked list
  var vPrev = vDel.prev;
  var vNext = vDel.next;
  vNext.prev = vPrev;
  vPrev.next = vNext;

  // TODO(bckenny): memFree( vDel );
  // TODO(bckenny): need to null at callsites?
};


/**
 * killFace_ destroys a face and removes it from the global face
 * list. It updates the face loop to point to a given new face.
 *
 * @private
 * @param {libtess.GluFace} fDel [description].
 * @param {libtess.GluFace} newLFace [description].
 */
libtess.mesh.killFace_ = function(fDel, newLFace) {
  var eStart = fDel.anEdge;

  // change the left face of all affected edges
  var e = eStart;
  do {
    e.lFace = newLFace;
    e = e.lNext;
  } while (e !== eStart);

  // delete from circular doubly-linked list
  var fPrev = fDel.prev;
  var fNext = fDel.next;
  fNext.prev = fPrev;
  fPrev.next = fNext;

  // TODO(bckenny): memFree( fDel );
  // TODO(bckenny): need to null at callsites?
};


/* global libtess */

/** @const */
libtess.normal = {};

// TODO(bckenny): Integrate SLANTED_SWEEP somehow?
/* The "feature merging" is not intended to be complete. There are
 * special cases where edges are nearly parallel to the sweep line
 * which are not implemented. The algorithm should still behave
 * robustly (ie. produce a reasonable tesselation) in the presence
 * of such edges, however it may miss features which could have been
 * merged. We could minimize this effect by choosing the sweep line
 * direction to be something unusual (ie. not parallel to one of the
 * coordinate axes).
 * #if defined(SLANTED_SWEEP)
 * #define S_UNIT_X  0.50941539564955385 // Pre-normalized
 * #define S_UNIT_Y  0.86052074622010633
 * #endif
 */

/**
 * X coordinate of local basis for polygon projection.
 * @private
 * @const
 */
libtess.normal.S_UNIT_X_ = 1.0;

/**
 * Y coordinate of local basis for polygon projection.
 * @private
 * @const
 */
libtess.normal.S_UNIT_Y_ = 0.0;

/**
 * Determines a polygon normal and projects vertices onto the plane of the
 * polygon.
 * @param {!libtess.GluTesselator} tess
 */
libtess.normal.projectPolygon = function(tess) {
  var computedNormal = false;

  var norm = [
    tess.normal[0],
    tess.normal[1],
    tess.normal[2]
  ];
  if (norm[0] === 0 && norm[1] === 0 && norm[2] === 0) {
    libtess.normal.computeNormal_(tess, norm);
    computedNormal = true;
  }

  var sUnit = tess.sUnit;
  var tUnit = tess.tUnit;
  var i = libtess.normal.longAxis_(norm);

  // NOTE(bckenny): This branch is never taken. See comment on
  // libtess.TRUE_PROJECT.
  /* istanbul ignore if */
  if (libtess.TRUE_PROJECT) {
    // Choose the initial sUnit vector to be approximately perpendicular
    // to the normal.
    libtess.normal.normalize_(norm);

    sUnit[i] = 0;
    sUnit[(i + 1) % 3] = libtess.normal.S_UNIT_X_;
    sUnit[(i + 2) % 3] = libtess.normal.S_UNIT_Y_;

    // Now make it exactly perpendicular
    var w = libtess.normal.dot_(sUnit, norm);
    sUnit[0] -= w * norm[0];
    sUnit[1] -= w * norm[1];
    sUnit[2] -= w * norm[2];
    libtess.normal.normalize_(sUnit);

    // Choose tUnit so that (sUnit,tUnit,norm) form a right-handed frame
    tUnit[0] = norm[1] * sUnit[2] - norm[2] * sUnit[1];
    tUnit[1] = norm[2] * sUnit[0] - norm[0] * sUnit[2];
    tUnit[2] = norm[0] * sUnit[1] - norm[1] * sUnit[0];
    libtess.normal.normalize_(tUnit);

  } else {
    // Project perpendicular to a coordinate axis -- better numerically
    sUnit[i] = 0;
    sUnit[(i + 1) % 3] = libtess.normal.S_UNIT_X_;
    sUnit[(i + 2) % 3] = libtess.normal.S_UNIT_Y_;

    tUnit[i] = 0;
    tUnit[(i + 1) % 3] = (norm[i] > 0) ?
        -libtess.normal.S_UNIT_Y_ : libtess.normal.S_UNIT_Y_;
    tUnit[(i + 2) % 3] = (norm[i] > 0) ?
        libtess.normal.S_UNIT_X_ : -libtess.normal.S_UNIT_X_;
  }

  // Project the vertices onto the sweep plane
  var vHead = tess.mesh.vHead;
  for (var v = vHead.next; v !== vHead; v = v.next) {
    v.s = libtess.normal.dot_(v.coords, sUnit);
    v.t = libtess.normal.dot_(v.coords, tUnit);
  }

  if (computedNormal) {
    libtess.normal.checkOrientation_(tess);
  }
};

/**
 * Computes the dot product of vectors u and v.
 * @private
 * @param {!Array.<number>} u
 * @param {!Array.<number>} v
 * @return {number}
 */
libtess.normal.dot_ = function(u, v) {
  return u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
};

// NOTE(bckenny): only called from within libtess.normal.projectPolygon's
// TRUE_PROJECT branch, so ignoring for code coverage.
/* istanbul ignore next */
/**
 * Normalize vector v.
 * @private
 * @param {!Array.<number>} v
 */
libtess.normal.normalize_ = function(v) {
  var len = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];

  len = Math.sqrt(len);
  v[0] /= len;
  v[1] /= len;
  v[2] /= len;
};

/**
 * Returns the index of the longest component of vector v.
 * @private
 * @param {!Array.<number>} v
 * @return {number}
 */
libtess.normal.longAxis_ = function(v) {
  var i = 0;

  if (Math.abs(v[1]) > Math.abs(v[0])) {
    i = 1;
  }
  if (Math.abs(v[2]) > Math.abs(v[i])) {
    i = 2;
  }

  return i;
};

/**
 * Compute an approximate normal of the polygon from the vertices themselves.
 * Result returned in norm.
 * @private
 * @param {!libtess.GluTesselator} tess
 * @param {!Array.<number>} norm
 */
libtess.normal.computeNormal_ = function(tess, norm) {
  var maxVal = [
    -2 * libtess.GLU_TESS_MAX_COORD,
    -2 * libtess.GLU_TESS_MAX_COORD,
    -2 * libtess.GLU_TESS_MAX_COORD
  ];
  var minVal = [
    2 * libtess.GLU_TESS_MAX_COORD,
    2 * libtess.GLU_TESS_MAX_COORD,
    2 * libtess.GLU_TESS_MAX_COORD
  ];
  var maxVert = [];
  var minVert = [];

  var v;
  var vHead = tess.mesh.vHead;
  for (v = vHead.next; v !== vHead; v = v.next) {
    for (var i = 0; i < 3; ++i) {
      var c = v.coords[i];
      if (c < minVal[i]) { minVal[i] = c; minVert[i] = v; }
      if (c > maxVal[i]) { maxVal[i] = c; maxVert[i] = v; }
    }
  }

  // Find two vertices separated by at least 1/sqrt(3) of the maximum
  // distance between any two vertices
  var index = 0;
  if (maxVal[1] - minVal[1] > maxVal[0] - minVal[0]) { index = 1; }
  if (maxVal[2] - minVal[2] > maxVal[index] - minVal[index]) { index = 2; }
  if (minVal[index] >= maxVal[index]) {
    // All vertices are the same -- normal doesn't matter
    norm[0] = 0; norm[1] = 0; norm[2] = 1;
    return;
  }

  // Look for a third vertex which forms the triangle with maximum area
  // (Length of normal == twice the triangle area)
  var maxLen2 = 0;
  var v1 = minVert[index];
  var v2 = maxVert[index];
  var tNorm = [0, 0, 0];
  var d1 = [
    v1.coords[0] - v2.coords[0],
    v1.coords[1] - v2.coords[1],
    v1.coords[2] - v2.coords[2]
  ];
  var d2 = [0, 0, 0];
  for (v = vHead.next; v !== vHead; v = v.next) {
    d2[0] = v.coords[0] - v2.coords[0];
    d2[1] = v.coords[1] - v2.coords[1];
    d2[2] = v.coords[2] - v2.coords[2];
    tNorm[0] = d1[1] * d2[2] - d1[2] * d2[1];
    tNorm[1] = d1[2] * d2[0] - d1[0] * d2[2];
    tNorm[2] = d1[0] * d2[1] - d1[1] * d2[0];
    var tLen2 = tNorm[0] * tNorm[0] + tNorm[1] * tNorm[1] + tNorm[2] * tNorm[2];
    if (tLen2 > maxLen2) {
      maxLen2 = tLen2;
      norm[0] = tNorm[0];
      norm[1] = tNorm[1];
      norm[2] = tNorm[2];
    }
  }

  if (maxLen2 <= 0) {
    // All points lie on a single line -- any decent normal will do
    norm[0] = norm[1] = norm[2] = 0;
    norm[libtess.normal.longAxis_(d1)] = 1;
  }
};

/**
 * Check that the sum of the signed area of all projected contours is
 * non-negative. If not, negate the t-coordinates to reverse the orientation and
 * make it so.
 * @private
 * @param {!libtess.GluTesselator} tess
 */
libtess.normal.checkOrientation_ = function(tess) {
  var area = 0;
  var fHead = tess.mesh.fHead;
  for (var f = fHead.next; f !== fHead; f = f.next) {
    var e = f.anEdge;
    if (e.winding <= 0) { continue; }
    do {
      area += (e.org.s - e.dst().s) * (e.org.t + e.dst().t);
      e = e.lNext;
    } while (e !== f.anEdge);
  }

  if (area < 0) {
    // Reverse the orientation by flipping all the t-coordinates
    var vHead = tess.mesh.vHead;
    for (var v = vHead.next; v !== vHead; v = v.next) {
      v.t = -v.t;
    }
    tess.tUnit[0] = -tess.tUnit[0];
    tess.tUnit[1] = -tess.tUnit[1];
    tess.tUnit[2] = -tess.tUnit[2];
  }
};


/* global libtess */

/** @const */
libtess.render = {};

/**
 * Takes a mesh, breaks it into separate triangles, and renders them. The
 * rendering output is provided as callbacks (see the API). Set flagEdges to
 * true to get edgeFlag callbacks (tess.flagBoundary in original libtess).
 * @param {!libtess.GluTesselator} tess
 * @param {!libtess.GluMesh} mesh
 * @param {boolean} flagEdges
 */
libtess.render.renderMesh = function(tess, mesh, flagEdges) {
  var beginOrBeginDataCalled = false;

  // TODO(bckenny): edgeState needs to be boolean, but !== on first call
  // force edge state output for first vertex
  var edgeState = -1;

  // We examine all faces in an arbitrary order. Whenever we find
  // an inside triangle f, we render f.
  // NOTE(bckenny): go backwards through face list to match original libtess
  // triangle order
  for (var f = mesh.fHead.prev; f !== mesh.fHead; f = f.prev) {
    if (f.inside) {
      // We're going to emit a triangle, so call begin callback once
      if (!beginOrBeginDataCalled) {
        tess.callBeginOrBeginData(libtess.primitiveType.GL_TRIANGLES);
        beginOrBeginDataCalled = true;
      }

      // check that face has only three edges
      var e = f.anEdge;
      // Loop once for each edge (there will always be 3 edges)
      do {
        if (flagEdges) {
          // Set the "edge state" to true just before we output the
          // first vertex of each edge on the polygon boundary.
          var newState = !e.rFace().inside ? 1 : 0; // TODO(bckenny): total hack to get edgeState working. fix me.
          if (edgeState !== newState) {
            edgeState = newState;
            // TODO(bckenny): edgeState should be boolean now
            tess.callEdgeFlagOrEdgeFlagData(!!edgeState);
          }
        }

        // emit vertex
        tess.callVertexOrVertexData(e.org.data);

        e = e.lNext;
      } while (e !== f.anEdge);
    }
  }

  // only call end callback if begin was called
  if (beginOrBeginDataCalled) {
    tess.callEndOrEndData();
  }
};

/**
 * Takes a mesh, and outputs one contour for each face marked "inside". The
 * rendering output is provided as callbacks (see the API).
 * @param {!libtess.GluTesselator} tess
 * @param {!libtess.GluMesh} mesh
 */
libtess.render.renderBoundary = function(tess, mesh) {
  for (var f = mesh.fHead.next; f !== mesh.fHead; f = f.next) {
    if (f.inside) {
      tess.callBeginOrBeginData(libtess.primitiveType.GL_LINE_LOOP);

      var e = f.anEdge;
      do {
        tess.callVertexOrVertexData(e.org.data);
        e = e.lNext;
      } while (e !== f.anEdge);

      tess.callEndOrEndData();
    }
  }
};


/* global libtess */

// TODO(bckenny): a number of these never return null (as opposed to original) and should be typed appropriately

/*
 * Invariants for the Edge Dictionary.
 * - each pair of adjacent edges e2=succ(e1) satisfies edgeLeq_(e1,e2)
 *   at any valid location of the sweep event
 * - if edgeLeq_(e2,e1) as well (at any valid sweep event), then e1 and e2
 *   share a common endpoint
 * - for each e, e.dst() has been processed, but not e.org
 * - each edge e satisfies vertLeq(e.dst(),event) && vertLeq(event,e.org)
 *   where "event" is the current sweep line event.
 * - no edge e has zero length
 *
 * Invariants for the Mesh (the processed portion).
 * - the portion of the mesh left of the sweep line is a planar graph,
 *   ie. there is *some* way to embed it in the plane
 * - no processed edge has zero length
 * - no two processed vertices have identical coordinates
 * - each "inside" region is monotone, ie. can be broken into two chains
 *   of monotonically increasing vertices according to VertLeq(v1,v2)
 *   - a non-invariant: these chains may intersect (very slightly)
 *
 * Invariants for the Sweep.
 * - if none of the edges incident to the event vertex have an activeRegion
 *   (ie. none of these edges are in the edge dictionary), then the vertex
 *   has only right-going edges.
 * - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced
 *   by ConnectRightVertex), then it is the only right-going edge from
 *   its associated vertex.  (This says that these edges exist only
 *   when it is necessary.)
 */

/** @const */
libtess.sweep = {};


/**
 * Make the sentinel coordinates big enough that they will never be
 * merged with real input features.  (Even with the largest possible
 * input contour and the maximum tolerance of 1.0, no merging will be
 * done with coordinates larger than 3 * libtess.GLU_TESS_MAX_COORD).
 * @private
 * @const
 * @type {number}
 */
libtess.sweep.SENTINEL_COORD_ = 4 * libtess.GLU_TESS_MAX_COORD;


/**
 * Because vertices at exactly the same location are merged together
 * before we process the sweep event, some degenerate cases can't occur.
 * However if someone eventually makes the modifications required to
 * merge features which are close together, the cases below marked
 * TOLERANCE_NONZERO will be useful.  They were debugged before the
 * code to merge identical vertices in the main loop was added.
 * @private
 * @const
 * @type {boolean}
 */
libtess.sweep.TOLERANCE_NONZERO_ = false;


/**
 * computeInterior(tess) computes the planar arrangement specified
 * by the given contours, and further subdivides this arrangement
 * into regions. Each region is marked "inside" if it belongs
 * to the polygon, according to the rule given by tess.windingRule.
 * Each interior region is guaranteed be monotone.
 *
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.computeInterior = function(tess) {
  tess.fatalError = false;

  // Each vertex defines an event for our sweep line. Start by inserting
  // all the vertices in a priority queue. Events are processed in
  // lexicographic order, ie.
  // e1 < e2  iff  e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y)
  libtess.sweep.removeDegenerateEdges_(tess);
  libtess.sweep.initPriorityQ_(tess);
  libtess.sweep.initEdgeDict_(tess);

  // TODO(bckenny): don't need the cast if pq's key is better typed
  var v;
  while ((v = /** @type {libtess.GluVertex} */(tess.pq.extractMin())) !== null) {
    for (;;) {
      var vNext = /** @type {libtess.GluVertex} */(tess.pq.minimum());
      if (vNext === null || !libtess.geom.vertEq(vNext, v)) {
        break;
      }

      /* Merge together all vertices at exactly the same location.
       * This is more efficient than processing them one at a time,
       * simplifies the code (see connectLeftDegenerate), and is also
       * important for correct handling of certain degenerate cases.
       * For example, suppose there are two identical edges A and B
       * that belong to different contours (so without this code they would
       * be processed by separate sweep events).  Suppose another edge C
       * crosses A and B from above.  When A is processed, we split it
       * at its intersection point with C.  However this also splits C,
       * so when we insert B we may compute a slightly different
       * intersection point.  This might leave two edges with a small
       * gap between them.  This kind of error is especially obvious
       * when using boundary extraction (GLU_TESS_BOUNDARY_ONLY).
       */
      vNext = /** @type {libtess.GluVertex} */(tess.pq.extractMin());
      libtess.sweep.spliceMergeVertices_(tess, v.anEdge, vNext.anEdge);
    }
    libtess.sweep.sweepEvent_(tess, v);
  }

  // TODO(bckenny): what does the next comment mean? can we eliminate event except when debugging?
  // Set tess.event for debugging purposes
  var minRegion = tess.dict.getMin().getKey();
  tess.event = minRegion.eUp.org;
  libtess.sweep.doneEdgeDict_(tess);
  libtess.sweep.donePriorityQ_(tess);

  libtess.sweep.removeDegenerateFaces_(tess.mesh);
  tess.mesh.checkMesh();
};


/**
 * When we merge two edges into one, we need to compute the combined
 * winding of the new edge.
 * @private
 * @param {libtess.GluHalfEdge} eDst [description].
 * @param {libtess.GluHalfEdge} eSrc [description].
 */
libtess.sweep.addWinding_ = function(eDst, eSrc) {
  // NOTE(bckenny): from AddWinding macro
  eDst.winding += eSrc.winding;
  eDst.sym.winding += eSrc.sym.winding;
};


/**
 * Both edges must be directed from right to left (this is the canonical
 * direction for the upper edge of each region).
 *
 * The strategy is to evaluate a "t" value for each edge at the
 * current sweep line position, given by tess.event.  The calculations
 * are designed to be very stable, but of course they are not perfect.
 *
 * Special case: if both edge destinations are at the sweep event,
 * we sort the edges by slope (they would otherwise compare equally).
 *
 * @private
 * @param {!libtess.GluTesselator} tess
 * @param {!libtess.ActiveRegion} reg1
 * @param {!libtess.ActiveRegion} reg2
 * @return {boolean}
 */
libtess.sweep.edgeLeq_ = function(tess, reg1, reg2) {
  var event = tess.event;
  var e1 = reg1.eUp;
  var e2 = reg2.eUp;

  if (e1.dst() === event) {
    if (e2.dst() === event) {
      // Two edges right of the sweep line which meet at the sweep event.
      // Sort them by slope.
      if (libtess.geom.vertLeq(e1.org, e2.org)) {
        return libtess.geom.edgeSign(e2.dst(), e1.org, e2.org) <= 0;
      }

      return libtess.geom.edgeSign(e1.dst(), e2.org, e1.org) >= 0;
    }

    return libtess.geom.edgeSign(e2.dst(), event, e2.org) <= 0;
  }

  if (e2.dst() === event) {
    return libtess.geom.edgeSign(e1.dst(), event, e1.org) >= 0;
  }

  // General case - compute signed distance *from* e1, e2 to event
  var t1 = libtess.geom.edgeEval(e1.dst(), event, e1.org);
  var t2 = libtess.geom.edgeEval(e2.dst(), event, e2.org);
  return (t1 >= t2);
};


/**
 * [deleteRegion_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} reg [description].
 */
libtess.sweep.deleteRegion_ = function(tess, reg) {
  if (reg.fixUpperEdge) {
    // It was created with zero winding number, so it better be
    // deleted with zero winding number (ie. it better not get merged
    // with a real edge).
  }

  reg.eUp.activeRegion = null;

  tess.dict.deleteNode(reg.nodeUp);
  reg.nodeUp = null;

  // memFree( reg ); TODO(bckenny)
  // TODO(bckenny): may need to null at callsite
};


/**
 * Replace an upper edge which needs fixing (see connectRightVertex).
 * @private
 * @param {libtess.ActiveRegion} reg [description].
 * @param {libtess.GluHalfEdge} newEdge [description].
 */
libtess.sweep.fixUpperEdge_ = function(reg, newEdge) {
  libtess.mesh.deleteEdge(reg.eUp);

  reg.fixUpperEdge = false;
  reg.eUp = newEdge;
  newEdge.activeRegion = reg;
};


/**
 * Find the region above the uppermost edge with the same origin.
 * @private
 * @param {libtess.ActiveRegion} reg [description].
 * @return {libtess.ActiveRegion} [description].
 */
libtess.sweep.topLeftRegion_ = function(reg) {
  var org = reg.eUp.org;

  // Find the region above the uppermost edge with the same origin
  do {
    reg = reg.regionAbove();
  } while (reg.eUp.org === org);

  // If the edge above was a temporary edge introduced by connectRightVertex,
  // now is the time to fix it.
  if (reg.fixUpperEdge) {
    var e = libtess.mesh.connect(reg.regionBelow().eUp.sym, reg.eUp.lNext);
    libtess.sweep.fixUpperEdge_(reg, e);
    reg = reg.regionAbove();
  }

  return reg;
};


/**
 * Find the region above the uppermost edge with the same destination.
 * @private
 * @param {libtess.ActiveRegion} reg [description].
 * @return {libtess.ActiveRegion} [description].
 */
libtess.sweep.topRightRegion_ = function(reg) {
  var dst = reg.eUp.dst();

  do {
    reg = reg.regionAbove();
  } while (reg.eUp.dst() === dst);

  return reg;
};


/**
 * Add a new active region to the sweep line, *somewhere* below "regAbove"
 * (according to where the new edge belongs in the sweep-line dictionary).
 * The upper edge of the new region will be "eNewUp".
 * Winding number and "inside" flag are not updated.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regAbove [description].
 * @param {libtess.GluHalfEdge} eNewUp [description].
 * @return {libtess.ActiveRegion} regNew.
 */
libtess.sweep.addRegionBelow_ = function(tess, regAbove, eNewUp) {
  var regNew = new libtess.ActiveRegion();

  regNew.eUp = eNewUp;
  regNew.nodeUp = tess.dict.insertBefore(regAbove.nodeUp, regNew);
  eNewUp.activeRegion = regNew;

  return regNew;
};


/**
 * [isWindingInside_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {number} n int.
 * @return {boolean} [description].
 */
libtess.sweep.isWindingInside_ = function(tess, n) {
  switch (tess.windingRule) {
    case libtess.windingRule.GLU_TESS_WINDING_ODD:
      return ((n & 1) !== 0);
    case libtess.windingRule.GLU_TESS_WINDING_NONZERO:
      return (n !== 0);
    case libtess.windingRule.GLU_TESS_WINDING_POSITIVE:
      return (n > 0);
    case libtess.windingRule.GLU_TESS_WINDING_NEGATIVE:
      return (n < 0);
    case libtess.windingRule.GLU_TESS_WINDING_ABS_GEQ_TWO:
      return (n >= 2) || (n <= -2);
  }

  // TODO(bckenny): not reached
  return false;
};


/**
 * [computeWinding_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} reg [description].
 */
libtess.sweep.computeWinding_ = function(tess, reg) {
  reg.windingNumber = reg.regionAbove().windingNumber + reg.eUp.winding;
  reg.inside = libtess.sweep.isWindingInside_(tess, reg.windingNumber);
};


/**
 * Delete a region from the sweep line. This happens when the upper
 * and lower chains of a region meet (at a vertex on the sweep line).
 * The "inside" flag is copied to the appropriate mesh face (we could
 * not do this before -- since the structure of the mesh is always
 * changing, this face may not have even existed until now).
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} reg [description].
 */
libtess.sweep.finishRegion_ = function(tess, reg) {
  // TODO(bckenny): may need to null reg at callsite

  var e = reg.eUp;
  var f = e.lFace;

  f.inside = reg.inside;
  f.anEdge = e;   // optimization for tessmono.tessellateMonoRegion() // TODO(bckenny): how so?
  libtess.sweep.deleteRegion_(tess, reg);
};


/**
 * We are given a vertex with one or more left-going edges. All affected
 * edges should be in the edge dictionary. Starting at regFirst.eUp,
 * we walk down deleting all regions where both edges have the same
 * origin vOrg. At the same time we copy the "inside" flag from the
 * active region to the face, since at this point each face will belong
 * to at most one region (this was not necessarily true until this point
 * in the sweep). The walk stops at the region above regLast; if regLast
 * is null we walk as far as possible. At the same time we relink the
 * mesh if necessary, so that the ordering of edges around vOrg is the
 * same as in the dictionary.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regFirst [description].
 * @param {libtess.ActiveRegion} regLast [description].
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.sweep.finishLeftRegions_ = function(tess, regFirst, regLast) {
  var regPrev = regFirst;
  var ePrev = regFirst.eUp;
  while (regPrev !== regLast) {
    // placement was OK
    regPrev.fixUpperEdge = false;
    var reg = regPrev.regionBelow();
    var e = reg.eUp;
    if (e.org !== ePrev.org) {
      if (!reg.fixUpperEdge) {
        /* Remove the last left-going edge. Even though there are no further
         * edges in the dictionary with this origin, there may be further
         * such edges in the mesh (if we are adding left edges to a vertex
         * that has already been processed). Thus it is important to call
         * finishRegion rather than just deleteRegion.
         */
        libtess.sweep.finishRegion_(tess, regPrev);
        break;
      }

      // If the edge below was a temporary edge introduced by
      // connectRightVertex, now is the time to fix it.
      e = libtess.mesh.connect(ePrev.lPrev(), e.sym);
      libtess.sweep.fixUpperEdge_(reg, e);
    }

    // Relink edges so that ePrev.oNext === e
    if (ePrev.oNext !== e) {
      libtess.mesh.meshSplice(e.oPrev(), e);
      libtess.mesh.meshSplice(ePrev, e);
    }

    // may change reg.eUp
    libtess.sweep.finishRegion_(tess, regPrev);
    ePrev = reg.eUp;
    regPrev = reg;
  }

  return ePrev;
};


/**
 * Purpose: insert right-going edges into the edge dictionary, and update
 * winding numbers and mesh connectivity appropriately. All right-going
 * edges share a common origin vOrg. Edges are inserted CCW starting at
 * eFirst; the last edge inserted is eLast.oPrev. If vOrg has any
 * left-going edges already processed, then eTopLeft must be the edge
 * such that an imaginary upward vertical segment from vOrg would be
 * contained between eTopLeft.oPrev and eTopLeft; otherwise eTopLeft
 * should be null.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @param {libtess.GluHalfEdge} eFirst [description].
 * @param {libtess.GluHalfEdge} eLast [description].
 * @param {libtess.GluHalfEdge} eTopLeft [description].
 * @param {boolean} cleanUp [description].
 */
libtess.sweep.addRightEdges_ = function(tess, regUp, eFirst, eLast, eTopLeft,
    cleanUp) {

  var firstTime = true;

  // Insert the new right-going edges in the dictionary
  var e = eFirst;
  do {
    libtess.sweep.addRegionBelow_(tess, regUp, e.sym);
    e = e.oNext;
  } while (e !== eLast);

  // Walk *all* right-going edges from e.org, in the dictionary order,
  // updating the winding numbers of each region, and re-linking the mesh
  // edges to match the dictionary ordering (if necessary).
  if (eTopLeft === null) {
    eTopLeft = regUp.regionBelow().eUp.rPrev();
  }
  var regPrev = regUp;
  var ePrev = eTopLeft;
  var reg;
  for (;;) {
    reg = regPrev.regionBelow();
    e = reg.eUp.sym;
    if (e.org !== ePrev.org) {
      break;
    }

    if (e.oNext !== ePrev) {
      // Unlink e from its current position, and relink below ePrev
      libtess.mesh.meshSplice(e.oPrev(), e);
      libtess.mesh.meshSplice(ePrev.oPrev(), e);
    }
    // Compute the winding number and "inside" flag for the new regions
    reg.windingNumber = regPrev.windingNumber - e.winding;
    reg.inside = libtess.sweep.isWindingInside_(tess, reg.windingNumber);

    // Check for two outgoing edges with same slope -- process these
    // before any intersection tests (see example in libtess.sweep.computeInterior).
    regPrev.dirty = true;
    if (!firstTime && libtess.sweep.checkForRightSplice_(tess, regPrev)) {
      libtess.sweep.addWinding_(e, ePrev);
      libtess.sweep.deleteRegion_(tess, regPrev); // TODO(bckenny): need to null regPrev anywhere else?
      libtess.mesh.deleteEdge(ePrev);
    }
    firstTime = false;
    regPrev = reg;
    ePrev = e;
  }

  regPrev.dirty = true;

  if (cleanUp) {
    // Check for intersections between newly adjacent edges.
    libtess.sweep.walkDirtyRegions_(tess, regPrev);
  }
};


/**
 * [callCombine_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.GluVertex} isect [description].
 * @param {Array.<Object>} data [description].
 * @param {Array.<number>} weights [description].
 * @param {boolean} needed [description].
 */
libtess.sweep.callCombine_ = function(tess, isect, data, weights, needed) {
  // Copy coord data in case the callback changes it.
  var coords = [
    isect.coords[0],
    isect.coords[1],
    isect.coords[2]
  ];

  isect.data = null;
  isect.data = tess.callCombineOrCombineData(coords, data, weights);
  if (isect.data === null) {
    if (!needed) {
      // not needed, so just use data from first vertex
      isect.data = data[0];

    } else if (!tess.fatalError) {
      // The only way fatal error is when two edges are found to intersect,
      // but the user has not provided the callback necessary to handle
      // generated intersection points.
      tess.callErrorOrErrorData(
          libtess.errorType.GLU_TESS_NEED_COMBINE_CALLBACK);
      tess.fatalError = true;
    }
  }
};


/**
 * Two vertices with idential coordinates are combined into one.
 * e1.org is kept, while e2.org is discarded.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.GluHalfEdge} e1 [description].
 * @param {libtess.GluHalfEdge} e2 [description].
 */
libtess.sweep.spliceMergeVertices_ = function(tess, e1, e2) {
  // TODO(bckenny): better way to init these? save them?
  var data = [null, null, null, null];
  var weights = [0.5, 0.5, 0, 0];

  data[0] = e1.org.data;
  data[1] = e2.org.data;
  libtess.sweep.callCombine_(tess, e1.org, data, weights, false);
  libtess.mesh.meshSplice(e1, e2);
};


/**
 * Find some weights which describe how the intersection vertex is
 * a linear combination of org and dst. Each of the two edges
 * which generated "isect" is allocated 50% of the weight; each edge
 * splits the weight between its org and dst according to the
 * relative distance to "isect".
 *
 * @private
 * @param {libtess.GluVertex} isect [description].
 * @param {libtess.GluVertex} org [description].
 * @param {libtess.GluVertex} dst [description].
 * @param {Array.<number>} weights [description].
 * @param {number} weightIndex Index into weights for first weight to supply.
 */
libtess.sweep.vertexWeights_ = function(isect, org, dst, weights, weightIndex) {
  // TODO(bckenny): think through how we can use L1dist here and be correct for coords
  var t1 = libtess.geom.vertL1dist(org, isect);
  var t2 = libtess.geom.vertL1dist(dst, isect);

  // TODO(bckenny): introduced weightIndex to mimic addressing in original
  // 1) document (though it is private and only used from getIntersectData)
  // 2) better way? manually inline into getIntersectData? supply two two-length tmp arrays?
  var i0 = weightIndex;
  var i1 = weightIndex + 1;
  weights[i0] = 0.5 * t2 / (t1 + t2);
  weights[i1] = 0.5 * t1 / (t1 + t2);
  isect.coords[0] += weights[i0] * org.coords[0] + weights[i1] * dst.coords[0];
  isect.coords[1] += weights[i0] * org.coords[1] + weights[i1] * dst.coords[1];
  isect.coords[2] += weights[i0] * org.coords[2] + weights[i1] * dst.coords[2];
};


/**
 * We've computed a new intersection point, now we need a "data" pointer
 * from the user so that we can refer to this new vertex in the
 * rendering callbacks.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.GluVertex} isect [description].
 * @param {libtess.GluVertex} orgUp [description].
 * @param {libtess.GluVertex} dstUp [description].
 * @param {libtess.GluVertex} orgLo [description].
 * @param {libtess.GluVertex} dstLo [description].
 */
libtess.sweep.getIntersectData_ = function(tess, isect, orgUp, dstUp, orgLo,
    dstLo) {

  // TODO(bckenny): called for every intersection event, should these be from a pool?
  // TODO(bckenny): better way to init these?
  var weights = [0, 0, 0, 0];
  var data = [
    orgUp.data,
    dstUp.data,
    orgLo.data,
    dstLo.data
  ];

  // TODO(bckenny): it appears isect is a reappropriated vertex, so does need to be zeroed.
  // double check this.
  isect.coords[0] = isect.coords[1] = isect.coords[2] = 0;

  // TODO(bckenny): see note in libtess.sweep.vertexWeights_ for explanation of weightIndex. fix?
  libtess.sweep.vertexWeights_(isect, orgUp, dstUp, weights, 0);
  libtess.sweep.vertexWeights_(isect, orgLo, dstLo, weights, 2);

  libtess.sweep.callCombine_(tess, isect, data, weights, true);
};


/**
 * Check the upper and lower edge of regUp, to make sure that the
 * eUp.org is above eLo, or eLo.org is below eUp (depending on which
 * origin is leftmost).
 *
 * The main purpose is to splice right-going edges with the same
 * dest vertex and nearly identical slopes (ie. we can't distinguish
 * the slopes numerically). However the splicing can also help us
 * to recover from numerical errors. For example, suppose at one
 * point we checked eUp and eLo, and decided that eUp.org is barely
 * above eLo. Then later, we split eLo into two edges (eg. from
 * a splice operation like this one). This can change the result of
 * our test so that now eUp.org is incident to eLo, or barely below it.
 * We must correct this condition to maintain the dictionary invariants.
 *
 * One possibility is to check these edges for intersection again
 * (i.e. checkForIntersect). This is what we do if possible. However
 * checkForIntersect requires that tess.event lies between eUp and eLo,
 * so that it has something to fall back on when the intersection
 * calculation gives us an unusable answer. So, for those cases where
 * we can't check for intersection, this routine fixes the problem
 * by just splicing the offending vertex into the other edge.
 * This is a guaranteed solution, no matter how degenerate things get.
 * Basically this is a combinatorial solution to a numerical problem.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @return {boolean} [description].
 */
libtess.sweep.checkForRightSplice_ = function(tess, regUp) {
  // TODO(bckenny): fully learn how these two checks work

  var regLo = regUp.regionBelow();
  var eUp = regUp.eUp;
  var eLo = regLo.eUp;

  if (libtess.geom.vertLeq(eUp.org, eLo.org)) {
    if (libtess.geom.edgeSign(eLo.dst(), eUp.org, eLo.org) > 0) {
      return false;
    }

    // eUp.org appears to be below eLo
    if (!libtess.geom.vertEq(eUp.org, eLo.org)) {
      // Splice eUp.org into eLo
      libtess.mesh.splitEdge(eLo.sym);
      libtess.mesh.meshSplice(eUp, eLo.oPrev());
      regUp.dirty = regLo.dirty = true;

    } else if (eUp.org !== eLo.org) {
      // merge the two vertices, discarding eUp.org
      // TODO(bckenny): fix pqHandle null situation
      tess.pq.remove(/** @type {libtess.PQHandle} */(eUp.org.pqHandle));
      libtess.sweep.spliceMergeVertices_(tess, eLo.oPrev(), eUp);
    }

  } else {
    if (libtess.geom.edgeSign(eUp.dst(), eLo.org, eUp.org) < 0) {
      return false;
    }

    // eLo.org appears to be above eUp, so splice eLo.org into eUp
    regUp.regionAbove().dirty = regUp.dirty = true;
    libtess.mesh.splitEdge(eUp.sym);
    libtess.mesh.meshSplice(eLo.oPrev(), eUp);
  }

  return true;
};


/**
 * Check the upper and lower edge of regUp to make sure that the
 * eUp.dst() is above eLo, or eLo.dst() is below eUp (depending on which
 * destination is rightmost).
 *
 * Theoretically, this should always be true. However, splitting an edge
 * into two pieces can change the results of previous tests. For example,
 * suppose at one point we checked eUp and eLo, and decided that eUp.dst()
 * is barely above eLo. Then later, we split eLo into two edges (eg. from
 * a splice operation like this one). This can change the result of
 * the test so that now eUp.dst() is incident to eLo, or barely below it.
 * We must correct this condition to maintain the dictionary invariants
 * (otherwise new edges might get inserted in the wrong place in the
 * dictionary, and bad stuff will happen).
 *
 * We fix the problem by just splicing the offending vertex into the
 * other edge.
 *
 * @private
 * @param {libtess.GluTesselator} tess description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @return {boolean} [description].
 */
libtess.sweep.checkForLeftSplice_ = function(tess, regUp) {
  var regLo = regUp.regionBelow();
  var eUp = regUp.eUp;
  var eLo = regLo.eUp;
  var e;

  if (libtess.geom.vertLeq(eUp.dst(), eLo.dst())) {
    if (libtess.geom.edgeSign(eUp.dst(), eLo.dst(), eUp.org) < 0) {
      return false;
    }

    // eLo.dst() is above eUp, so splice eLo.dst() into eUp
    regUp.regionAbove().dirty = regUp.dirty = true;
    e = libtess.mesh.splitEdge(eUp);
    libtess.mesh.meshSplice(eLo.sym, e);
    e.lFace.inside = regUp.inside;

  } else {
    if (libtess.geom.edgeSign(eLo.dst(), eUp.dst(), eLo.org) > 0) {
      return false;
    }

    // eUp.dst() is below eLo, so splice eUp.dst() into eLo
    regUp.dirty = regLo.dirty = true;
    e = libtess.mesh.splitEdge(eLo);
    libtess.mesh.meshSplice(eUp.lNext, eLo.sym);
    e.rFace().inside = regUp.inside;
  }

  return true;
};


/**
 * Check the upper and lower edges of the given region to see if
 * they intersect. If so, create the intersection and add it
 * to the data structures.
 *
 * Returns true if adding the new intersection resulted in a recursive
 * call to addRightEdges_(); in this case all "dirty" regions have been
 * checked for intersections, and possibly regUp has been deleted.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @return {boolean} [description].
 */
libtess.sweep.checkForIntersect_ = function(tess, regUp) {
  var regLo = regUp.regionBelow();
  var eUp = regUp.eUp;
  var eLo = regLo.eUp;
  var orgUp = eUp.org;
  var orgLo = eLo.org;
  var dstUp = eUp.dst();
  var dstLo = eLo.dst();

  var isect = new libtess.GluVertex();

  if (orgUp === orgLo) {
    // right endpoints are the same
    return false;
  }

  var tMinUp = Math.min(orgUp.t, dstUp.t);
  var tMaxLo = Math.max(orgLo.t, dstLo.t);
  if (tMinUp > tMaxLo) {
    // t ranges do not overlap
    return false;
  }

  if (libtess.geom.vertLeq(orgUp, orgLo)) {
    if (libtess.geom.edgeSign(dstLo, orgUp, orgLo) > 0) {
      return false;
    }
  } else {
    if (libtess.geom.edgeSign(dstUp, orgLo, orgUp) < 0) {
      return false;
    }
  }

  // At this point the edges intersect, at least marginally
  libtess.geom.edgeIntersect(dstUp, orgUp, dstLo, orgLo, isect);

  // The following properties are guaranteed:

  if (libtess.geom.vertLeq(isect, tess.event)) {
    /* The intersection point lies slightly to the left of the sweep line,
     * so move it until it's slightly to the right of the sweep line.
     * (If we had perfect numerical precision, this would never happen
     * in the first place). The easiest and safest thing to do is
     * replace the intersection by tess.event.
     */
    isect.s = tess.event.s;
    isect.t = tess.event.t;
  }

  // TODO(bckenny): try to find test54.d
  /* Similarly, if the computed intersection lies to the right of the
   * rightmost origin (which should rarely happen), it can cause
   * unbelievable inefficiency on sufficiently degenerate inputs.
   * (If you have the test program, try running test54.d with the
   * "X zoom" option turned on).
   */
  var orgMin = libtess.geom.vertLeq(orgUp, orgLo) ? orgUp : orgLo;
  if (libtess.geom.vertLeq(orgMin, isect)) {
    isect.s = orgMin.s;
    isect.t = orgMin.t;
  }

  if (libtess.geom.vertEq(isect, orgUp) || libtess.geom.vertEq(isect, orgLo)) {
    // Easy case -- intersection at one of the right endpoints
    libtess.sweep.checkForRightSplice_(tess, regUp);
    return false;
  }

  // TODO(bckenny): clean this up; length is distracting
  if ((!libtess.geom.vertEq(dstUp, tess.event) &&
      libtess.geom.edgeSign(dstUp, tess.event, isect) >= 0) ||
      (!libtess.geom.vertEq(dstLo, tess.event) &&
      libtess.geom.edgeSign(dstLo, tess.event, isect) <= 0)) {

    /* Very unusual -- the new upper or lower edge would pass on the
     * wrong side of the sweep event, or through it. This can happen
     * due to very small numerical errors in the intersection calculation.
     */
    if (dstLo === tess.event) {
      // Splice dstLo into eUp, and process the new region(s)
      libtess.mesh.splitEdge(eUp.sym);
      libtess.mesh.meshSplice(eLo.sym, eUp);
      regUp = libtess.sweep.topLeftRegion_(regUp);
      eUp = regUp.regionBelow().eUp;
      libtess.sweep.finishLeftRegions_(tess, regUp.regionBelow(), regLo);
      libtess.sweep.addRightEdges_(tess, regUp, eUp.oPrev(), eUp, eUp, true);
      return true;
    }

    if (dstUp === tess.event) {
      // Splice dstUp into eLo, and process the new region(s)
      libtess.mesh.splitEdge(eLo.sym);
      libtess.mesh.meshSplice(eUp.lNext, eLo.oPrev());
      regLo = regUp;
      regUp = libtess.sweep.topRightRegion_(regUp);
      var e = regUp.regionBelow().eUp.rPrev();
      regLo.eUp = eLo.oPrev();
      eLo = libtess.sweep.finishLeftRegions_(tess, regLo, null);
      libtess.sweep.addRightEdges_(tess, regUp, eLo.oNext, eUp.rPrev(), e,
          true);
      return true;
    }

    /* Special case: called from connectRightVertex. If either
     * edge passes on the wrong side of tess.event, split it
     * (and wait for connectRightVertex to splice it appropriately).
     */
    if (libtess.geom.edgeSign(dstUp, tess.event, isect) >= 0) {
      regUp.regionAbove().dirty = regUp.dirty = true;
      libtess.mesh.splitEdge(eUp.sym);
      eUp.org.s = tess.event.s;
      eUp.org.t = tess.event.t;
    }

    if (libtess.geom.edgeSign(dstLo, tess.event, isect) <= 0) {
      regUp.dirty = regLo.dirty = true;
      libtess.mesh.splitEdge(eLo.sym);
      eLo.org.s = tess.event.s;
      eLo.org.t = tess.event.t;
    }

    // leave the rest for connectRightVertex
    return false;
  }

  /* General case -- split both edges, splice into new vertex.
   * When we do the splice operation, the order of the arguments is
   * arbitrary as far as correctness goes. However, when the operation
   * creates a new face, the work done is proportional to the size of
   * the new face. We expect the faces in the processed part of
   * the mesh (ie. eUp.lFace) to be smaller than the faces in the
   * unprocessed original contours (which will be eLo.oPrev.lFace).
   */
  libtess.mesh.splitEdge(eUp.sym);
  libtess.mesh.splitEdge(eLo.sym);
  libtess.mesh.meshSplice(eLo.oPrev(), eUp);
  eUp.org.s = isect.s;
  eUp.org.t = isect.t;
  eUp.org.pqHandle = tess.pq.insert(eUp.org);
  libtess.sweep.getIntersectData_(tess, eUp.org, orgUp, dstUp, orgLo, dstLo);
  regUp.regionAbove().dirty = regUp.dirty = regLo.dirty = true;

  return false;
};


/**
 * When the upper or lower edge of any region changes, the region is
 * marked "dirty". This routine walks through all the dirty regions
 * and makes sure that the dictionary invariants are satisfied
 * (see the comments at the beginning of this file). Of course,
 * new dirty regions can be created as we make changes to restore
 * the invariants.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 */
libtess.sweep.walkDirtyRegions_ = function(tess, regUp) {
  var regLo = regUp.regionBelow();

  for (;;) {
    // Find the lowest dirty region (we walk from the bottom up).
    while (regLo.dirty) {
      regUp = regLo;
      regLo = regLo.regionBelow();
    }
    if (!regUp.dirty) {
      regLo = regUp;
      regUp = regUp.regionAbove();
      if (regUp === null || !regUp.dirty) {
        // We've walked all the dirty regions
        return;
      }
    }

    regUp.dirty = false;
    var eUp = regUp.eUp;
    var eLo = regLo.eUp;

    if (eUp.dst() !== eLo.dst()) {
      // Check that the edge ordering is obeyed at the dst vertices.
      if (libtess.sweep.checkForLeftSplice_(tess, regUp)) {
        // If the upper or lower edge was marked fixUpperEdge, then
        // we no longer need it (since these edges are needed only for
        // vertices which otherwise have no right-going edges).
        if (regLo.fixUpperEdge) {
          libtess.sweep.deleteRegion_(tess, regLo);
          libtess.mesh.deleteEdge(eLo);
          regLo = regUp.regionBelow();
          eLo = regLo.eUp;

        } else if (regUp.fixUpperEdge) {
          libtess.sweep.deleteRegion_(tess, regUp);
          libtess.mesh.deleteEdge(eUp);
          regUp = regLo.regionAbove();
          eUp = regUp.eUp;
        }
      }
    }

    if (eUp.org !== eLo.org) {
      if (eUp.dst() !== eLo.dst() && !regUp.fixUpperEdge &&
          !regLo.fixUpperEdge &&
          (eUp.dst() === tess.event || eLo.dst() === tess.event)) {
        /* When all else fails in checkForIntersect(), it uses tess.event
         * as the intersection location. To make this possible, it requires
         * that tess.event lie between the upper and lower edges, and also
         * that neither of these is marked fixUpperEdge (since in the worst
         * case it might splice one of these edges into tess.event, and
         * violate the invariant that fixable edges are the only right-going
         * edge from their associated vertex).
         */
        if (libtess.sweep.checkForIntersect_(tess, regUp)) {
          // walkDirtyRegions() was called recursively; we're done
          return;
        }

      } else {
        // Even though we can't use checkForIntersect(), the org vertices
        // may violate the dictionary edge ordering. Check and correct this.
        libtess.sweep.checkForRightSplice_(tess, regUp);
      }
    }

    if (eUp.org === eLo.org && eUp.dst() === eLo.dst()) {
      // A degenerate loop consisting of only two edges -- delete it.
      libtess.sweep.addWinding_(eLo, eUp);
      libtess.sweep.deleteRegion_(tess, regUp);
      libtess.mesh.deleteEdge(eUp);
      regUp = regLo.regionAbove();
    }
  }
};


/**
 * Purpose: connect a "right" vertex vEvent (one where all edges go left)
 * to the unprocessed portion of the mesh. Since there are no right-going
 * edges, two regions (one above vEvent and one below) are being merged
 * into one. regUp is the upper of these two regions.
 *
 * There are two reasons for doing this (adding a right-going edge):
 *  - if the two regions being merged are "inside", we must add an edge
 *    to keep them separated (the combined region would not be monotone).
 *  - in any case, we must leave some record of vEvent in the dictionary,
 *    so that we can merge vEvent with features that we have not seen yet.
 *    For example, maybe there is a vertical edge which passes just to
 *    the right of vEvent; we would like to splice vEvent into this edge.
 *
 * However, we don't want to connect vEvent to just any vertex. We don't
 * want the new edge to cross any other edges; otherwise we will create
 * intersection vertices even when the input data had no self-intersections.
 * (This is a bad thing; if the user's input data has no intersections,
 * we don't want to generate any false intersections ourselves.)
 *
 * Our eventual goal is to connect vEvent to the leftmost unprocessed
 * vertex of the combined region (the union of regUp and regLo).
 * But because of unseen vertices with all right-going edges, and also
 * new vertices which may be created by edge intersections, we don't
 * know where that leftmost unprocessed vertex is. In the meantime, we
 * connect vEvent to the closest vertex of either chain, and mark the region
 * as "fixUpperEdge". This flag says to delete and reconnect this edge
 * to the next processed vertex on the boundary of the combined region.
 * Quite possibly the vertex we connected to will turn out to be the
 * closest one, in which case we won't need to make any changes.
 *
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @param {libtess.GluHalfEdge} eBottomLeft [description].
 */
libtess.sweep.connectRightVertex_ = function(tess, regUp, eBottomLeft) {
  var eTopLeft = eBottomLeft.oNext;
  var regLo = regUp.regionBelow();
  var eUp = regUp.eUp;
  var eLo = regLo.eUp;
  var degenerate = false;

  if (eUp.dst() !== eLo.dst()) {
    libtess.sweep.checkForIntersect_(tess, regUp);
  }

  // Possible new degeneracies: upper or lower edge of regUp may pass
  // through vEvent, or may coincide with new intersection vertex
  if (libtess.geom.vertEq(eUp.org, tess.event)) {
    libtess.mesh.meshSplice(eTopLeft.oPrev(), eUp);
    regUp = libtess.sweep.topLeftRegion_(regUp);
    eTopLeft = regUp.regionBelow().eUp;
    libtess.sweep.finishLeftRegions_(tess, regUp.regionBelow(), regLo);
    degenerate = true;
  }
  if (libtess.geom.vertEq(eLo.org, tess.event)) {
    libtess.mesh.meshSplice(eBottomLeft, eLo.oPrev());
    eBottomLeft = libtess.sweep.finishLeftRegions_(tess, regLo, null);
    degenerate = true;
  }
  if (degenerate) {
    libtess.sweep.addRightEdges_(tess, regUp, eBottomLeft.oNext, eTopLeft,
        eTopLeft, true);
    return;
  }

  // Non-degenerate situation -- need to add a temporary, fixable edge.
  // Connect to the closer of eLo.org, eUp.org.
  var eNew;
  if (libtess.geom.vertLeq(eLo.org, eUp.org)) {
    eNew = eLo.oPrev();
  } else {
    eNew = eUp;
  }
  eNew = libtess.mesh.connect(eBottomLeft.lPrev(), eNew);

  // Prevent cleanup, otherwise eNew might disappear before we've even
  // had a chance to mark it as a temporary edge.
  libtess.sweep.addRightEdges_(tess, regUp, eNew, eNew.oNext, eNew.oNext,
      false);
  eNew.sym.activeRegion.fixUpperEdge = true;
  libtess.sweep.walkDirtyRegions_(tess, regUp);
};


/**
 * The event vertex lies exacty on an already-processed edge or vertex.
 * Adding the new vertex involves splicing it into the already-processed
 * part of the mesh.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.ActiveRegion} regUp [description].
 * @param {libtess.GluVertex} vEvent [description].
 */
libtess.sweep.connectLeftDegenerate_ = function(tess, regUp, vEvent) {
  var e = regUp.eUp;
  /* istanbul ignore if */
  if (libtess.geom.vertEq(e.org, vEvent)) {
    // NOTE(bckenny): this code is unreachable but remains for a hypothetical
    // future extension of libtess. See docs on libtess.sweep.TOLERANCE_NONZERO_
    // for more information. Conditional on TOLERANCE_NONZERO_ to help Closure
    // Compiler eliminate dead code.
    // e.org is an unprocessed vertex - just combine them, and wait
    // for e.org to be pulled from the queue
    if (libtess.sweep.TOLERANCE_NONZERO_) {
      libtess.sweep.spliceMergeVertices_(tess, e, vEvent.anEdge);
    }
    return;
  }

  if (!libtess.geom.vertEq(e.dst(), vEvent)) {
    // General case -- splice vEvent into edge e which passes through it
    libtess.mesh.splitEdge(e.sym);

    if (regUp.fixUpperEdge) {
      // This edge was fixable -- delete unused portion of original edge
      libtess.mesh.deleteEdge(e.oNext);
      regUp.fixUpperEdge = false;
    }

    libtess.mesh.meshSplice(vEvent.anEdge, e);

    // recurse
    libtess.sweep.sweepEvent_(tess, vEvent);
    return;
  }

  // NOTE(bckenny): this code is unreachable but remains for a hypothetical
  // future extension of libtess. See docs on libtess.sweep.TOLERANCE_NONZERO_
  // for more information. Conditional on TOLERANCE_NONZERO_ to help Closure
  // Compiler eliminate dead code.
  // vEvent coincides with e.dst(), which has already been processed.
  // Splice in the additional right-going edges.
  /* istanbul ignore next */

  /* istanbul ignore next */
  if (libtess.sweep.TOLERANCE_NONZERO_) {
    regUp = libtess.sweep.topRightRegion_(regUp);
    var reg = regUp.regionBelow();
    var eTopRight = reg.eUp.sym;
    var eTopLeft = eTopRight.oNext;
    var eLast = eTopLeft;

    if (reg.fixUpperEdge) {
      // Here e.dst() has only a single fixable edge going right.
      // We can delete it since now we have some real right-going edges.

      // there are some left edges too
      libtess.sweep.deleteRegion_(tess, reg); // TODO(bckenny): something to null?
      libtess.mesh.deleteEdge(eTopRight);
      eTopRight = eTopLeft.oPrev();
    }

    libtess.mesh.meshSplice(vEvent.anEdge, eTopRight);
    if (!libtess.geom.edgeGoesLeft(eTopLeft)) {
      // e.dst() had no left-going edges -- indicate this to addRightEdges()
      eTopLeft = null;
    }

    libtess.sweep.addRightEdges_(tess, regUp, eTopRight.oNext, eLast, eTopLeft,
        true);
  }
};


/**
 * Connect a "left" vertex (one where both edges go right)
 * to the processed portion of the mesh. Let R be the active region
 * containing vEvent, and let U and L be the upper and lower edge
 * chains of R. There are two possibilities:
 *
 * - the normal case: split R into two regions, by connecting vEvent to
 *   the rightmost vertex of U or L lying to the left of the sweep line
 *
 * - the degenerate case: if vEvent is close enough to U or L, we
 *   merge vEvent into that edge chain. The subcases are:
 *  - merging with the rightmost vertex of U or L
 *  - merging with the active edge of U or L
 *  - merging with an already-processed portion of U or L
 *
 * @private
 * @param {libtess.GluTesselator} tess   [description].
 * @param {libtess.GluVertex} vEvent [description].
 */
libtess.sweep.connectLeftVertex_ = function(tess, vEvent) {
  // TODO(bckenny): tmp only used for sweep. better to keep tmp across calls?
  var tmp = new libtess.ActiveRegion();

  // NOTE(bckenny): this was commented out in the original
  // libtess.assert(vEvent.anEdge.oNext.oNext === vEvent.anEdge);

  // Get a pointer to the active region containing vEvent
  tmp.eUp = vEvent.anEdge.sym;
  var regUp = tess.dict.search(tmp).getKey();
  var regLo = regUp.regionBelow();
  var eUp = regUp.eUp;
  var eLo = regLo.eUp;

  // try merging with U or L first
  if (libtess.geom.edgeSign(eUp.dst(), vEvent, eUp.org) === 0) {
    libtess.sweep.connectLeftDegenerate_(tess, regUp, vEvent);
    return;
  }

  // Connect vEvent to rightmost processed vertex of either chain.
  // e.dst() is the vertex that we will connect to vEvent.
  var reg = libtess.geom.vertLeq(eLo.dst(), eUp.dst()) ? regUp : regLo;
  var eNew;
  if (regUp.inside || reg.fixUpperEdge) {
    if (reg === regUp) {
      eNew = libtess.mesh.connect(vEvent.anEdge.sym, eUp.lNext);

    } else {
      var tempHalfEdge = libtess.mesh.connect(eLo.dNext(), vEvent.anEdge);
      eNew = tempHalfEdge.sym;
    }

    if (reg.fixUpperEdge) {
      libtess.sweep.fixUpperEdge_(reg, eNew);

    } else {
      libtess.sweep.computeWinding_(tess,
          libtess.sweep.addRegionBelow_(tess, regUp, eNew));
    }
    libtess.sweep.sweepEvent_(tess, vEvent);

  } else {
    // The new vertex is in a region which does not belong to the polygon.
    // We don''t need to connect this vertex to the rest of the mesh.
    libtess.sweep.addRightEdges_(tess, regUp, vEvent.anEdge, vEvent.anEdge,
        null, true);
  }
};


/**
 * Does everything necessary when the sweep line crosses a vertex.
 * Updates the mesh and the edge dictionary.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {libtess.GluVertex} vEvent [description].
 */
libtess.sweep.sweepEvent_ = function(tess, vEvent) {
  tess.event = vEvent; // for access in edgeLeq_ // TODO(bckenny): wuh?

  /* Check if this vertex is the right endpoint of an edge that is
   * already in the dictionary.  In this case we don't need to waste
   * time searching for the location to insert new edges.
   */
  var e = vEvent.anEdge;
  while (e.activeRegion === null) {
    e = e.oNext;
    if (e === vEvent.anEdge) {
      // All edges go right -- not incident to any processed edges
      libtess.sweep.connectLeftVertex_(tess, vEvent);
      return;
    }
  }

  /* Processing consists of two phases: first we "finish" all the
   * active regions where both the upper and lower edges terminate
   * at vEvent (ie. vEvent is closing off these regions).
   * We mark these faces "inside" or "outside" the polygon according
   * to their winding number, and delete the edges from the dictionary.
   * This takes care of all the left-going edges from vEvent.
   */
  var regUp = libtess.sweep.topLeftRegion_(e.activeRegion);
  var reg = regUp.regionBelow();
  var eTopLeft = reg.eUp;
  var eBottomLeft = libtess.sweep.finishLeftRegions_(tess, reg, null);

  /* Next we process all the right-going edges from vEvent. This
   * involves adding the edges to the dictionary, and creating the
   * associated "active regions" which record information about the
   * regions between adjacent dictionary edges.
   */
  if (eBottomLeft.oNext === eTopLeft) {
    // No right-going edges -- add a temporary "fixable" edge
    libtess.sweep.connectRightVertex_(tess, regUp, eBottomLeft);

  } else {
    libtess.sweep.addRightEdges_(tess, regUp, eBottomLeft.oNext, eTopLeft,
        eTopLeft, true);
  }
};


/**
 * We add two sentinel edges above and below all other edges,
 * to avoid special cases at the top and bottom.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 * @param {number} t [description].
 */
libtess.sweep.addSentinel_ = function(tess, t) {
  var reg = new libtess.ActiveRegion();

  var e = libtess.mesh.makeEdge(tess.mesh);

  e.org.s = libtess.sweep.SENTINEL_COORD_;
  e.org.t = t;
  e.dst().s = -libtess.sweep.SENTINEL_COORD_;
  e.dst().t = t;
  tess.event = e.dst(); //initialize it

  reg.eUp = e;
  reg.windingNumber = 0;
  reg.inside = false;
  reg.fixUpperEdge = false;
  reg.sentinel = true;
  reg.dirty = false;
  reg.nodeUp = tess.dict.insert(reg);
};


/**
 * We maintain an ordering of edge intersections with the sweep line.
 * This order is maintained in a dynamic dictionary.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.initEdgeDict_ = function(tess) {
  tess.dict = new libtess.Dict(tess, libtess.sweep.edgeLeq_);

  libtess.sweep.addSentinel_(tess, -libtess.sweep.SENTINEL_COORD_);
  libtess.sweep.addSentinel_(tess, libtess.sweep.SENTINEL_COORD_);
};


/**
 * [doneEdgeDict_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.doneEdgeDict_ = function(tess) {
  // NOTE(bckenny): fixedEdges is only used in the assert below, so ignore so
  // when asserts are removed jshint won't error.
  /* jshint unused:false */
  var fixedEdges = 0;

  var reg;
  while ((reg = tess.dict.getMin().getKey()) !== null) {
    // At the end of all processing, the dictionary should contain
    // only the two sentinel edges, plus at most one "fixable" edge
    // created by connectRightVertex().
    if (!reg.sentinel) {
    }
    libtess.sweep.deleteRegion_(tess, reg);
  }

  // NOTE(bckenny): see tess.dict.deleteDict_() for old delete dict function
  tess.dict = null;
};


/**
 * Remove zero-length edges, and contours with fewer than 3 vertices.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.removeDegenerateEdges_ = function(tess) {
  var eHead = tess.mesh.eHead;

  var eNext;
  for (var e = eHead.next; e !== eHead; e = eNext) {
    eNext = e.next;
    var eLNext = e.lNext;

    if (libtess.geom.vertEq(e.org, e.dst()) && e.lNext.lNext !== e) {
      // Zero-length edge, contour has at least 3 edges
      libtess.sweep.spliceMergeVertices_(tess, eLNext, e); // deletes e.org
      libtess.mesh.deleteEdge(e); // e is a self-loop TODO(bckenny): does this comment really apply here?
      e = eLNext;
      eLNext = e.lNext;
    }

    if (eLNext.lNext === e) {
      // Degenerate contour (one or two edges)
      if (eLNext !== e) {
        if (eLNext === eNext || eLNext === eNext.sym) {
          eNext = eNext.next;
        }
        libtess.mesh.deleteEdge(eLNext);
      }

      if (e === eNext || e === eNext.sym) {
        eNext = eNext.next;
      }
      libtess.mesh.deleteEdge(e);
    }
  }
};


/**
 * Construct priority queue and insert all vertices into it, which determines
 * the order in which vertices cross the sweep line.
 * @private
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.initPriorityQ_ = function(tess) {
  // TODO(bckenny): libtess.geom.vertLeq needs cast?
  var pq = new libtess.PriorityQ(
      /** @type {function(Object, Object): boolean} */(libtess.geom.vertLeq));
  tess.pq = pq;

  var vHead = tess.mesh.vHead;
  var v;
  for (v = vHead.next; v !== vHead; v = v.next) {
    v.pqHandle = pq.insert(v);
  }

  pq.init();
};


/**
 * [donePriorityQ_ description]
 * @private
 * @param {libtess.GluTesselator} tess [description].
 */
libtess.sweep.donePriorityQ_ = function(tess) {
  // TODO(bckenny): probably don't need deleteQ. check that function for comment
  tess.pq.deleteQ();
  tess.pq = null;
};


/**
 * Delete any degenerate faces with only two edges. walkDirtyRegions()
 * will catch almost all of these, but it won't catch degenerate faces
 * produced by splice operations on already-processed edges.
 * The two places this can happen are in finishLeftRegions(), when
 * we splice in a "temporary" edge produced by connectRightVertex(),
 * and in checkForLeftSplice(), where we splice already-processed
 * edges to ensure that our dictionary invariants are not violated
 * by numerical errors.
 *
 * In both these cases it is *very* dangerous to delete the offending
 * edge at the time, since one of the routines further up the stack
 * will sometimes be keeping a pointer to that edge.
 *
 * @private
 * @param {libtess.GluMesh} mesh [description].
 */
libtess.sweep.removeDegenerateFaces_ = function(mesh) {
  var fNext;
  for (var f = mesh.fHead.next; f !== mesh.fHead; f = fNext) {
    fNext = f.next;
    var e = f.anEdge;

    if (e.lNext.lNext === e) {
      // A face with only two edges
      libtess.sweep.addWinding_(e.oNext, e);
      libtess.mesh.deleteEdge(e);
    }
  }
};


/* global libtess */

/** @const */
libtess.tessmono = {};

/**
 * Tessellates a monotone region (what else would it do??). The region must
 * consist of a single loop of half-edges (see mesh.js) oriented CCW. "Monotone"
 * in this case means that any vertical line intersects the interior of the
 * region in a single interval.
 *
 * Tessellation consists of adding interior edges (actually pairs of
 * half-edges), to split the region into non-overlapping triangles.
 * @private
 * @param {!libtess.GluFace} face
 */
libtess.tessmono.tessellateMonoRegion_ = function(face) {
  /* The basic idea is explained in Preparata and Shamos (which I don't
   * have handy right now), although their implementation is more
   * complicated than this one. The are two edge chains, an upper chain
   * and a lower chain. We process all vertices from both chains in order,
   * from right to left.
   *
   * The algorithm ensures that the following invariant holds after each
   * vertex is processed: the untessellated region consists of two
   * chains, where one chain (say the upper) is a single edge, and
   * the other chain is concave. The left vertex of the single edge
   * is always to the left of all vertices in the concave chain.
   *
   * Each step consists of adding the rightmost unprocessed vertex to one
   * of the two chains, and forming a fan of triangles from the rightmost
   * of two chain endpoints. Determining whether we can add each triangle
   * to the fan is a simple orientation test. By making the fan as large
   * as possible, we restore the invariant (check it yourself).
   *
   * All edges are oriented CCW around the boundary of the region.
   * First, find the half-edge whose origin vertex is rightmost.
   * Since the sweep goes from left to right, face.anEdge should
   * be close to the edge we want.
   */
  var up = face.anEdge;

  for (; libtess.geom.vertLeq(up.dst(), up.org); up = up.lPrev()) { }
  for (; libtess.geom.vertLeq(up.org, up.dst()); up = up.lNext) { }

  var lo = up.lPrev();

  var tempHalfEdge;
  while (up.lNext !== lo) {
    if (libtess.geom.vertLeq(up.dst(), lo.org)) {
      // up.dst() is on the left. It is safe to form triangles from lo.org.
      // The edgeGoesLeft test guarantees progress even when some triangles
      // are CW, given that the upper and lower chains are truly monotone.
      while (lo.lNext !== up && (libtess.geom.edgeGoesLeft(lo.lNext) ||
          libtess.geom.edgeSign(lo.org, lo.dst(), lo.lNext.dst()) <= 0)) {

        tempHalfEdge = libtess.mesh.connect(lo.lNext, lo);
        lo = tempHalfEdge.sym;
      }
      lo = lo.lPrev();

    } else {
      // lo.org is on the left. We can make CCW triangles from up.dst().
      while (lo.lNext !== up && (libtess.geom.edgeGoesRight(up.lPrev()) ||
          libtess.geom.edgeSign(up.dst(), up.org, up.lPrev().org) >= 0)) {

        tempHalfEdge = libtess.mesh.connect(up, up.lPrev());
        up = tempHalfEdge.sym;
      }
      up = up.lNext;
    }
  }

  // Now lo.org == up.dst() == the leftmost vertex. The remaining region
  // can be tessellated in a fan from this leftmost vertex.
  while (lo.lNext.lNext !== up) {
    tempHalfEdge = libtess.mesh.connect(lo.lNext, lo);
    lo = tempHalfEdge.sym;
  }
};

/**
 * Tessellates each region of the mesh which is marked "inside" the polygon.
 * Each such region must be monotone.
 * @param {!libtess.GluMesh} mesh
 */
libtess.tessmono.tessellateInterior = function(mesh) {
  var next;
  for (var f = mesh.fHead.next; f !== mesh.fHead; f = next) {
    // Make sure we don't try to tessellate the new triangles.
    next = f.next;
    if (f.inside) {
      libtess.tessmono.tessellateMonoRegion_(f);
    }
  }
};

/**
 * Zaps (i.e. sets to null) all faces which are not marked "inside" the polygon.
 * Since further mesh operations on null faces are not allowed, the main purpose
 * is to clean up the mesh so that exterior loops are not represented in the
 * data structure.
 * @param {!libtess.GluMesh} mesh
 */
libtess.tessmono.discardExterior = function(mesh) {
  var next;
  for (var f = mesh.fHead.next; f !== mesh.fHead; f = next) {
    // Since f will be destroyed, save its next pointer.
    next = f.next;
    if (!f.inside) {
      libtess.mesh.zapFace(f);
    }
  }
};

/**
 * Resets the winding numbers on all edges so that regions marked "inside" the
 * polygon have a winding number of "value", and regions outside have a winding
 * number of 0.
 *
 * If keepOnlyBoundary is true, it also deletes all edges which do not separate
 * an interior region from an exterior one.
 *
 * @param {!libtess.GluMesh} mesh
 * @param {number} value
 * @param {boolean} keepOnlyBoundary
 */
libtess.tessmono.setWindingNumber = function(mesh, value, keepOnlyBoundary) {
  var eNext;
  for (var e = mesh.eHead.next; e !== mesh.eHead; e = eNext) {
    eNext = e.next;

    if (e.rFace().inside !== e.lFace.inside) {
      // This is a boundary edge (one side is interior, one is exterior).
      e.winding = (e.lFace.inside) ? value : -value;

    } else {
      // Both regions are interior, or both are exterior.
      if (!keepOnlyBoundary) {
        e.winding = 0;

      } else {
        libtess.mesh.deleteEdge(e);
      }
    }
  }
};


/* global libtess */

/**
 * A list of edges crossing the sweep line, sorted from top to bottom.
 * Implementation is a doubly-linked list, sorted by the injected edgeLeq
 * comparator function. Here it is a simple ordering, but see libtess.sweep for
 * the list of invariants on the edge dictionary this ordering creates.
 * @constructor
 * @struct
 * @param {!libtess.GluTesselator} frame
 * @param {function(!libtess.GluTesselator, !libtess.ActiveRegion, !libtess.ActiveRegion): boolean} leq
 */
libtess.Dict = function(frame, leq) {

  /**
   * The head of the doubly-linked DictNode list. At creation time, links back
   * and forward only to itself.
   * @private
   */
  this.head_ = new libtess.DictNode();

  /**
   * The GluTesselator used as the frame for edge/event comparisons.
   * @private
   */
  this.frame_ = frame;

  /**
   * Comparison function to maintain the invariants of the Dict. See
   * libtess.sweep.edgeLeq_ for source.
   * @private
   * @type {function(!libtess.GluTesselator, !libtess.ActiveRegion, !libtess.ActiveRegion): boolean}
   */
  this.leq_ = leq;
};

/* istanbul ignore next */
/**
 * Formerly used to delete the dict.
 * NOTE(bckenny): No longer called but left for memFree documentation. Nulled at
 * former callsite instead (sweep.doneEdgeDict_)
 * @private
 */
libtess.Dict.prototype.deleteDict_ = function() {
  // for (var node = this.head_.next; node !== this.head_; node = node.next) {
  //   memFree(node);
  // }
  // memFree(dict);
};

/**
 * Insert the supplied key into the edge list and return its new node.
 * @param {libtess.DictNode} node
 * @param {!libtess.ActiveRegion} key
 * @return {!libtess.DictNode}
 */
libtess.Dict.prototype.insertBefore = function(node, key) {
  do {
    node = node.prev;
  } while (node.key !== null && !this.leq_(this.frame_, node.key, key));

  // insert the new node and update the surrounding nodes to point to it
  var newNode = new libtess.DictNode(key, node.next, node);
  node.next.prev = newNode;
  node.next = newNode;

  return newNode;
};

/**
 * Insert key into the dict and return the new node that contains it.
 * @param {!libtess.ActiveRegion} key
 * @return {!libtess.DictNode}
 */
libtess.Dict.prototype.insert = function(key) {
  // NOTE(bckenny): from a macro in dict.h/dict-list.h
  return this.insertBefore(this.head_, key);
};

/**
 * Remove node from the list.
 * @param {libtess.DictNode} node
 */
libtess.Dict.prototype.deleteNode = function(node) {
  node.next.prev = node.prev;
  node.prev.next = node.next;

  // NOTE(bckenny): nulled at callsite (sweep.deleteRegion_)
  // memFree( node );
};

/**
 * Search returns the node with the smallest key greater than or equal
 * to the given key. If there is no such key, returns a node whose
 * key is null. Similarly, max(d).getSuccessor() has a null key, etc.
 * @param {!libtess.ActiveRegion} key
 * @return {!libtess.DictNode}
 */
libtess.Dict.prototype.search = function(key) {
  var node = this.head_;

  do {
    node = node.next;
  } while (node.key !== null && !this.leq_(this.frame_, key, node.key));

  return node;
};

/**
 * Return the node with the smallest key.
 * @return {!libtess.DictNode}
 */
libtess.Dict.prototype.getMin = function() {
  // NOTE(bckenny): from a macro in dict.h/dict-list.h
  return this.head_.next;
};

// NOTE(bckenny): libtess.Dict.getMax isn't called within libtess and isn't part
// of the public API. For now, leaving in but ignoring for coverage.
/* istanbul ignore next */
/**
 * Returns the node with the greatest key.
 * @return {!libtess.DictNode}
 */
libtess.Dict.prototype.getMax = function() {
  // NOTE(bckenny): from a macro in dict.h/dict-list.h
  return this.head_.prev;
};


/* global libtess */

/**
 * A doubly-linked-list node with a libtess.ActiveRegion payload.
 * The key for this node and the next and previous nodes in the parent Dict list
 * can be provided to insert it into an existing list (or all can be omitted if
 * this is to be the founding node of the list).
 * @param {!libtess.ActiveRegion=} opt_key
 * @param {!libtess.DictNode=} opt_nextNode
 * @param {!libtess.DictNode=} opt_prevNode
 * @constructor
 * @struct
 */
libtess.DictNode = function(opt_key, opt_nextNode, opt_prevNode) {
  /**
   * The ActiveRegion key for this node, or null if the head of the list.
   * @type {libtess.ActiveRegion}
   */
  this.key = opt_key || null;

  /**
   * Link to next DictNode in parent list or to self if this is the first node.
   * @type {!libtess.DictNode}
   */
  this.next = opt_nextNode || this;

  /**
   * Link to previous DictNode in parent list or to self if this is the first
   * node.
   * @type {!libtess.DictNode}
   */
  this.prev = opt_prevNode || this;
};

/**
 * Get the key from this node.
 * @return {libtess.ActiveRegion}
 */
libtess.DictNode.prototype.getKey = function() {
  return this.key;
};

/**
 * Get the successor node to this one.
 * @return {!libtess.DictNode}
 */
libtess.DictNode.prototype.getSuccessor = function() {
  return this.next;
};

/**
 * Get the predecessor node to this one.
 * @return {!libtess.DictNode}
 */
libtess.DictNode.prototype.getPredecessor = function() {
  return this.prev;
};



/* global libtess */

/**
 * Cached vertex data for single-countour polygons for quick-and-dirty
 * decomposition.
 * @constructor
 * @struct
 */
libtess.CachedVertex = function() {
  /**
   * [coords description]
   * @type {Array.<number>}
   */
  this.coords = [0, 0, 0];
  // TODO(bckenny): better way to init?

  /**
   * [data description]
   * @type {Object}
   */
  this.data = null;
};



/* global libtess */

// TODO(bckenny): create more javascript-y API, e.g. make gluTessEndPolygon
// async, don't require so many temp objects created

/**
 * The tesselator main class, providing the public API.
 * @constructor
 * @struct
 */
libtess.GluTesselator = function() {
  // Only initialize fields which can be changed by the api. Other fields
  // are initialized where they are used.

  // TODO(bckenny): many of these can be made private
  // TODO(bckenny): can we combine call* and call*Data functions?

  /*** state needed for collecting the input data ***/

  /**
   * what begin/end calls have we seen?
   * @type {libtess.GluTesselator.tessState_}
   */
  this.state = libtess.GluTesselator.tessState_.T_DORMANT;

  /**
   * lastEdge_.org is the most recent vertex
   * @private
   * @type {libtess.GluHalfEdge}
   */
  this.lastEdge_ = null;

  /**
   * stores the input contours, and eventually the tessellation itself
   * @type {libtess.GluMesh}
   */
  this.mesh = null;
  // NOTE(bckenny): initialized in this.emptyCache_

  /**
   * Error callback.
   * @private
   * @type {?function((libtess.errorType|libtess.gluEnum))}
   */
  this.callError_ = null;


  /*** state needed for projecting onto the sweep plane ***/

  /**
   * user-specified normal (if provided)
   * @type {!Array.<number>}
   */
  this.normal = [0, 0, 0];
  // TODO(bckenny): better way to init these arrays?

  /**
   * unit vector in s-direction (debugging)
   * @type {!Array.<number>}
   */
  this.sUnit = [0, 0, 0];

  /**
   * unit vector in t-direction (debugging)
   * @type {!Array.<number>}
   */
  this.tUnit = [0, 0, 0];

  /*** state needed for the line sweep ***/
  // TODO(bckenny): this could be moved to a sweep state object of some sort

  /**
   * tolerance for merging features
   * @type {number}
   */
  this.relTolerance = libtess.GLU_TESS_DEFAULT_TOLERANCE;

  /**
   * rule for determining polygon interior
   * @type {libtess.windingRule}
   */
  this.windingRule = libtess.windingRule.GLU_TESS_WINDING_ODD;

  /**
   * fatal error: needed combine callback
   * @type {boolean}
   */
  this.fatalError = false;

  /**
   * edge dictionary for sweep line
   * @type {libtess.Dict}
   */
  this.dict = null;
  // NOTE(bckenny): dict initialized in sweep.initEdgeDict_, removed in sweep.doneEdgeDict_

  /**
   * priority queue of vertex events
   * @type {libtess.PriorityQ}
   */
  this.pq = null;
  // NOTE(bckenny): pq initialized in sweep.initPriorityQ

  /**
   * current sweep event being processed
   * @type {libtess.GluVertex}
   */
  this.event = null;

  /**
   * Combine callback.
   * @private
   * @type {?function(Array.<number>, Array.<Object>, Array.<number>): Object}
   */
  this.callCombine_ = null;

  /*** state needed for rendering callbacks (see render.js) ***/

  /**
   * Extract contours, not triangles
   * @type {boolean}
   */
  this.boundaryOnly = false;

  /**
   * Begin callback.
   * @private
   * @type {?function(libtess.primitiveType)}
   */
  this.callBegin_ = null;

  /**
   * Edge flag callback.
   * @private
   * @type {?function(boolean)}
   */
  this.callEdgeFlag_ = null;

  /**
   * Vertex callback.
   * @private
   * @type {?function(Object)}
   */
  this.callVertex_ = null;

  /**
   * End callback.
   * @private
   * @type {?function()}
   */
  this.callEnd_ = null;

  /**
   * Mesh callback.
   * @private
   * @type {?function(libtess.GluMesh)}
   */
  this.callMesh_ = null;

  /*** rendering callbacks that also pass polygon data  ***/
  /**
   * BeginData callback.
   * @private
   * @type {?function(libtess.primitiveType, Object)}
   */
  this.callBeginData_ = null;

  /**
   * EdgeFlagData callback.
   * @private
   * @type {?function(boolean, Object)}
   */
  this.callEdgeFlagData_ = null;

  /**
   * VertexData callback.
   * @private
   * @type {?function(Object, Object)}
   */
  this.callVertexData_ = null;

  /**
   * EndData callback.
   * @private
   * @type {?function(Object)}
   */
  this.callEndData_ = null;

  /**
   * ErrorData callback.
   * @private
   * @type {?function((libtess.errorType|libtess.gluEnum), Object)}
   */
  this.callErrorData_ = null;

  /**
   * CombineData callback.
   * @private
   * @type {?function(Array.<number>, Array.<Object>, Array.<number>, Object): Object}
   */
  this.callCombineData_ = null;

  /**
   * client data for current polygon
   * @private
   * @type {Object}
   */
  this.polygonData_ = null;

  /*** state needed to cache single-contour polygons for renderCache() ***/
  /**
   * empty cache on next vertex() call
   * @type {boolean}
   */
  this.emptyCache = false;
  // TODO(bckenny): possibly rename to be clear it's a boolean

  /**
   * number of cached vertices
   * @type {number}
   */
  this.cacheCount = 0;

  /**
   * the vertex data
   * @type {Array.<libtess.CachedVertex>}
   */
  this.cache = new Array(libtess.TESS_MAX_CACHE);

  // TODO(bckenny): fill now? or init on demand
  for (var i = 0; i < libtess.TESS_MAX_CACHE; i++) {
    this.cache[i] = new libtess.CachedVertex();
  }
};

/**
 * The begin/end calls must be properly nested. We keep track of the current
 * state to enforce the ordering.
 * @enum {number}
 * @private
 */
libtess.GluTesselator.tessState_ = {
  T_DORMANT: 0,
  T_IN_POLYGON: 1,
  T_IN_CONTOUR: 2
};

/**
 * Destory the tesselator object. See README.
 */
libtess.GluTesselator.prototype.gluDeleteTess = function() {
  // TODO(bckenny): This does nothing but assert that it isn't called while
  // building the polygon since we rely on GC to handle memory. *If* the public
  // API changes, this should go.
  this.requireState_(libtess.GluTesselator.tessState_.T_DORMANT);
  // memFree(tess); TODO(bckenny)
};


/**
 * Set properties for control over tesselation. See README.
 * @param {libtess.gluEnum} which [description].
 * @param {number|boolean} value [description].
 */
libtess.GluTesselator.prototype.gluTessProperty = function(which, value) {
  // TODO(bckenny): split into more setters?
  // TODO(bckenny): in any case, we can do better than this switch statement

  switch (which) {
    case libtess.gluEnum.GLU_TESS_TOLERANCE:
      if (value < 0 || value > 1) {
        break;
      }
      // TODO(bckenny): libtess doesn't support any tolerance but 0. This should
      // reject any non-zero tolerance accordingly.
      this.relTolerance = /** @type {number} */(value);
      return;

    case libtess.gluEnum.GLU_TESS_WINDING_RULE:
      var windingRule = /** @type {libtess.windingRule} */(value);

      switch (windingRule) {
        case libtess.windingRule.GLU_TESS_WINDING_ODD:
        case libtess.windingRule.GLU_TESS_WINDING_NONZERO:
        case libtess.windingRule.GLU_TESS_WINDING_POSITIVE:
        case libtess.windingRule.GLU_TESS_WINDING_NEGATIVE:
        case libtess.windingRule.GLU_TESS_WINDING_ABS_GEQ_TWO:
          this.windingRule = windingRule;
          return;
        default:
      }
      break;

    case libtess.gluEnum.GLU_TESS_BOUNDARY_ONLY:
      // TODO(bckenny): added boolean param type. make sure ok.
      this.boundaryOnly = !!value;
      return;

    default:
      this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
      return;
  }
  this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_VALUE);
};


/**
 * Returns tessellator property
 * @param {libtess.gluEnum} which [description].
 * @return {number|boolean} [description].
 */
libtess.GluTesselator.prototype.gluGetTessProperty = function(which) {
  // TODO(bckenny): as above, split into more getters? and improve on switch statement
  // why are these being asserted in getter but not setter?

  switch (which) {
    case libtess.gluEnum.GLU_TESS_TOLERANCE:
      // tolerance should be in range [0..1]
      return this.relTolerance;

    case libtess.gluEnum.GLU_TESS_WINDING_RULE:
      var rule = this.windingRule;
      return rule;

    case libtess.gluEnum.GLU_TESS_BOUNDARY_ONLY:
      return this.boundaryOnly;

    default:
      this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
      break;
  }
  return false;
};


/**
 * Lets the user supply the polygon normal, if known.  All input data
 * is projected into a plane perpendicular to the normal before
 * tesselation. All output triangles are oriented CCW with
 * respect to the normal (CW orientation can be obtained by
 * reversing the sign of the supplied normal). For example, if
 * you know that all polygons lie in the x-y plane, call
 * "tess.gluTessNormal(0.0, 0.0, 1.0)" before rendering any polygons.
 *
 * @param {number} x [description].
 * @param {number} y [description].
 * @param {number} z [description].
 */
libtess.GluTesselator.prototype.gluTessNormal = function(x, y, z) {
  this.normal[0] = x;
  this.normal[1] = y;
  this.normal[2] = z;
};


/**
 * Specify callbacks. See README. A null or undefined opt_fn removes current callback.
 *
 * @param {libtess.gluEnum} which [description].
 * @param {?Function=} opt_fn [description].
 */
libtess.GluTesselator.prototype.gluTessCallback = function(which, opt_fn) {
  var fn = !opt_fn ? null : opt_fn;
  // TODO(bckenny): better opt_fn typing?

  switch (which) {
    case libtess.gluEnum.GLU_TESS_BEGIN:
      this.callBegin_ = /** @type {function(libtess.primitiveType)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_BEGIN_DATA:
      this.callBeginData_ =
          /** @type {function(libtess.primitiveType, Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_EDGE_FLAG:
      this.callEdgeFlag_ = /** @type {function(boolean)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_EDGE_FLAG_DATA:
      this.callEdgeFlagData_ = /** @type {function(boolean, Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_VERTEX:
      this.callVertex_ = /** @type {function(Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_VERTEX_DATA:
      this.callVertexData_ = /** @type {function(Object, Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_END:
      this.callEnd_ = /** @type {function()} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_END_DATA:
      this.callEndData_ = /** @type {function(Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_ERROR:
      this.callError_ = /** @type {function((libtess.errorType|libtess.gluEnum))} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_ERROR_DATA:
      this.callErrorData_ =
          /** @type {function((libtess.errorType|libtess.gluEnum), Object)} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_COMBINE:
      this.callCombine_ = /** @type {function(Array.<number>, Array.<Object>, Array.<number>): Object} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_COMBINE_DATA:
      this.callCombineData_ = /** @type {function(Array.<number>, Array.<Object>, Array.<number>, Object): Object} */ (fn);
      return;

    case libtess.gluEnum.GLU_TESS_MESH:
      this.callMesh_ = /** @type {function(libtess.GluMesh)} */ (fn);
      return;

    default:
      this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
      return;
  }
};


/**
 * Specify a vertex and associated data. Must be within calls to
 * beginContour/endContour. See README.
 *
 * @param {Array.<number>} coords [description].
 * @param {Object} data [description].
 */
libtess.GluTesselator.prototype.gluTessVertex = function(coords, data) {
  var tooLarge = false;

  // TODO(bckenny): pool allocation?
  var clamped = [0, 0, 0];

  this.requireState_(libtess.GluTesselator.tessState_.T_IN_CONTOUR);

  if (this.emptyCache) {
    this.emptyCache_();
    this.lastEdge_ = null;
  }

  for (var i = 0; i < 3; ++i) {
    var x = coords[i];
    if (x < -libtess.GLU_TESS_MAX_COORD) {
      x = -libtess.GLU_TESS_MAX_COORD;
      tooLarge = true;
    }
    if (x > libtess.GLU_TESS_MAX_COORD) {
      x = libtess.GLU_TESS_MAX_COORD;
      tooLarge = true;
    }
    clamped[i] = x;
  }

  if (tooLarge) {
    this.callErrorOrErrorData(libtess.errorType.GLU_TESS_COORD_TOO_LARGE);
  }

  if (this.mesh === null) {
    if (this.cacheCount < libtess.TESS_MAX_CACHE) {
      this.cacheVertex_(clamped, data);
      return;
    }

    // cache is full, create mesh and add cached verts to it
    this.emptyCache_();
  }

  this.addVertex_(clamped, data);
};


/**
 * [gluTessBeginPolygon description]
 * @param {Object} data Client data for current polygon.
 */
libtess.GluTesselator.prototype.gluTessBeginPolygon = function(data) {
  this.requireState_(libtess.GluTesselator.tessState_.T_DORMANT);

  this.state = libtess.GluTesselator.tessState_.T_IN_POLYGON;
  this.cacheCount = 0;
  this.emptyCache = false;
  this.mesh = null;

  this.polygonData_ = data;
};


/**
 * [gluTessBeginContour description]
 */
libtess.GluTesselator.prototype.gluTessBeginContour = function() {
  this.requireState_(libtess.GluTesselator.tessState_.T_IN_POLYGON);

  this.state = libtess.GluTesselator.tessState_.T_IN_CONTOUR;
  this.lastEdge_ = null;
  if (this.cacheCount > 0) {
    // Just set a flag so we don't get confused by empty contours
    // -- these can be generated accidentally with the obsolete
    // NextContour() interface.
    // TODO(bckenny): we aren't implementing NextContour() interface.
    this.emptyCache = true;
  }
};


/**
 * [gluTessEndContour description]
 */
libtess.GluTesselator.prototype.gluTessEndContour = function() {
  this.requireState_(libtess.GluTesselator.tessState_.T_IN_CONTOUR);
  this.state = libtess.GluTesselator.tessState_.T_IN_POLYGON;
};


/**
 * [gluTessEndPolygon description]
 */
libtess.GluTesselator.prototype.gluTessEndPolygon = function() {
  this.requireState_(libtess.GluTesselator.tessState_.T_IN_POLYGON);
  this.state = libtess.GluTesselator.tessState_.T_DORMANT;

  if (this.mesh === null) {
    // TODO(bckenny): can we eliminate more cache functionality?
    this.emptyCache_();
  }

  // Determine the polygon normal and project vertices onto the plane
  // of the polygon.
  libtess.normal.projectPolygon(this);

  // computeInterior(tess) computes the planar arrangement specified
  // by the given contours, and further subdivides this arrangement
  // into regions. Each region is marked "inside" if it belongs
  // to the polygon, according to the rule given by this.windingRule.
  // Each interior region is guaranteed be monotone.
  libtess.sweep.computeInterior(this);

  if (!this.fatalError) {
    // If the user wants only the boundary contours, we throw away all edges
    // except those which separate the interior from the exterior.
    // Otherwise we tessellate all the regions marked "inside".
    // NOTE(bckenny): we know this.mesh has been initialized, so help closure out.
    var mesh = /** @type {!libtess.GluMesh} */(this.mesh);
    if (this.boundaryOnly) {
      libtess.tessmono.setWindingNumber(mesh, 1, true);
    } else {
      libtess.tessmono.tessellateInterior(mesh);
    }

    this.mesh.checkMesh();

    if (this.callBegin_ || this.callEnd_ || this.callVertex_ ||
        this.callEdgeFlag_ || this.callBeginData_ || this.callEndData_ ||
        this.callVertexData_ || this.callEdgeFlagData_) {

      if (this.boundaryOnly) {
        // output boundary contours
        libtess.render.renderBoundary(this, this.mesh);

      } else {
        // output triangles (with edge callback if one is set)
        var flagEdges = !!(this.callEdgeFlag_ || this.callEdgeFlagData_);
        libtess.render.renderMesh(this, this.mesh, flagEdges);
      }
    }

    if (this.callMesh_) {
      // Throw away the exterior faces, so that all faces are interior.
      // This way the user doesn't have to check the "inside" flag,
      // and we don't need to even reveal its existence. It also leaves
      // the freedom for an implementation to not generate the exterior
      // faces in the first place.
      libtess.tessmono.discardExterior(this.mesh);
      // user wants the mesh itself
      this.callMesh_(this.mesh);

      this.mesh = null;
      this.polygonData_ = null;
      return;
    }
  }

  libtess.mesh.deleteMesh(this.mesh);
  this.polygonData_ = null;
  this.mesh = null;
};


/**
 * Return the tessellator to its original dormant state.
 * @private
 */
libtess.GluTesselator.prototype.makeDormant_ = function() {
  if (this.mesh) {
    libtess.mesh.deleteMesh(this.mesh);
  }
  this.state = libtess.GluTesselator.tessState_.T_DORMANT;
  this.lastEdge_ = null;
  this.mesh = null;
};


/**
 * [requireState_ description]
 * @private
 * @param {libtess.GluTesselator.tessState_} state [description].
 */
libtess.GluTesselator.prototype.requireState_ = function(state) {
  if (this.state !== state) {
    this.gotoState_(state);
  }
};


/**
 * [gotoState_ description]
 * @private
 * @param  {libtess.GluTesselator.tessState_} newState [description].
 */
libtess.GluTesselator.prototype.gotoState_ = function(newState) {
  while (this.state !== newState) {
    // We change the current state one level at a time, to get to the desired
    // state.
    if (this.state < newState) {
      switch (this.state) {
        case libtess.GluTesselator.tessState_.T_DORMANT:
          this.callErrorOrErrorData(
              libtess.errorType.GLU_TESS_MISSING_BEGIN_POLYGON);
          this.gluTessBeginPolygon(null);
          break;

        case libtess.GluTesselator.tessState_.T_IN_POLYGON:
          this.callErrorOrErrorData(
              libtess.errorType.GLU_TESS_MISSING_BEGIN_CONTOUR);
          this.gluTessBeginContour();
          break;
      }

    } else {
      switch (this.state) {
        case libtess.GluTesselator.tessState_.T_IN_CONTOUR:
          this.callErrorOrErrorData(
              libtess.errorType.GLU_TESS_MISSING_END_CONTOUR);
          this.gluTessEndContour();
          break;

        case libtess.GluTesselator.tessState_.T_IN_POLYGON:
          this.callErrorOrErrorData(
              libtess.errorType.GLU_TESS_MISSING_END_POLYGON);
          // this.gluTessEndPolygon() is too much work!
          this.makeDormant_();
          break;
      }
    }
  }
};


/**
 * [addVertex_ description]
 * @private
 * @param {Array.<number>} coords [description].
 * @param {Object} data [description].
 */
libtess.GluTesselator.prototype.addVertex_ = function(coords, data) {
  var e = this.lastEdge_;
  if (e === null) {
    // Make a self-loop (one vertex, one edge).
    e = libtess.mesh.makeEdge(this.mesh);
    libtess.mesh.meshSplice(e, e.sym);

  } else {
    // Create a new vertex and edge which immediately follow e
    // in the ordering around the left face.
    libtess.mesh.splitEdge(e);
    e = e.lNext;
  }

  // The new vertex is now e.org.
  e.org.data = data;
  e.org.coords[0] = coords[0];
  e.org.coords[1] = coords[1];
  e.org.coords[2] = coords[2];

  // The winding of an edge says how the winding number changes as we
  // cross from the edge''s right face to its left face.  We add the
  // vertices in such an order that a CCW contour will add +1 to
  // the winding number of the region inside the contour.
  e.winding = 1;
  e.sym.winding = -1;

  this.lastEdge_ = e;
};


/**
 * [cacheVertex_ description]
 * @private
 * @param {Array.<number>} coords [description].
 * @param {Object} data [description].
 */
libtess.GluTesselator.prototype.cacheVertex_ = function(coords, data) {
  var v = this.cache[this.cacheCount];

  v.data = data;
  v.coords[0] = coords[0];
  v.coords[1] = coords[1];
  v.coords[2] = coords[2];
  ++this.cacheCount;
};


/**
 * [emptyCache_ description]
 * @private
 */
libtess.GluTesselator.prototype.emptyCache_ = function() {
  // NOTE(bckenny): surprise!
  this.mesh = new libtess.GluMesh();

  for (var i = 0; i < this.cacheCount; i++) {
    var v = this.cache[i];
    this.addVertex_(v.coords, v.data);
  }

  this.cacheCount = 0;
  this.emptyCache = false;
};


// TODO(bckenny): all following conditional callbacks could be simplified
// TODO(bckenny): using null for now, but may rework
// TODO(bckenny): should add documentation that references in callback are volatile (or make a copy)
// see README callback descriptions
/**
 * [callBeginOrBeginData description]
 * @param {libtess.primitiveType} type [description].
 */
libtess.GluTesselator.prototype.callBeginOrBeginData = function(type) {
  if (this.callBeginData_) {
    this.callBeginData_(type, this.polygonData_);

  } else if (this.callBegin_) {
    this.callBegin_(type);
  }
};


/**
 * [callVertexOrVertexData description]
 * @param {Object} data [description].
 */
libtess.GluTesselator.prototype.callVertexOrVertexData = function(data) {
  if (this.callVertexData_) {
    this.callVertexData_(data, this.polygonData_);

  } else if (this.callVertex_) {
    this.callVertex_(data);
  }
};


/**
 * [callEdgeFlagOrEdgeFlagData description]
 * @param {boolean} flag [description].
 */
libtess.GluTesselator.prototype.callEdgeFlagOrEdgeFlagData = function(flag) {
  if (this.callEdgeFlagData_) {
    this.callEdgeFlagData_(flag, this.polygonData_);

  } else if (this.callEdgeFlag_) {
    this.callEdgeFlag_(flag);
  }
};


/**
 * [callEndOrEndData description]
 */
libtess.GluTesselator.prototype.callEndOrEndData = function() {
  if (this.callEndData_) {
    this.callEndData_(this.polygonData_);

  } else if (this.callEnd_) {
    this.callEnd_();
  }
};


/**
 * [callCombineOrCombineData description]
 * @param {Array.<number>} coords [description].
 * @param {Array.<Object>} data [description].
 * @param {Array.<number>} weight [description].
 * @return {Object} Interpolated vertex.
 */
libtess.GluTesselator.prototype.callCombineOrCombineData =
    function(coords, data, weight) {

  var interpData;
  if (this.callCombineData_) {
    interpData = this.callCombineData_(coords, data, weight, this.polygonData_);

  } else if (this.callCombine_) {
    interpData = this.callCombine_(coords, data, weight);
  }

  // TODO(bckenny): can't be undefined
  if (interpData === undefined) {
    interpData = null;
  }
  return interpData;
};


// TODO(bckenny): combine the enums in libtess
/**
 * [callErrorOrErrorData description]
 * @param {(libtess.errorType|libtess.gluEnum)} errno [description].
 */
libtess.GluTesselator.prototype.callErrorOrErrorData = function(errno) {
  if (this.callErrorData_) {
    this.callErrorData_(errno, this.polygonData_);

  } else if (this.callError_) {
    this.callError_(errno);
  }
};



/* global libtess */

/**
 * Each face has a pointer to the next and previous faces in the
 * circular list, and a pointer to a half-edge with this face as
 * the left face (null if this is the dummy header). There is also
 * a field "data" for client data.
 *
 * @param {libtess.GluFace=} opt_nextFace
 * @param {libtess.GluFace=} opt_prevFace
 * @constructor
 * @struct
 */
libtess.GluFace = function(opt_nextFace, opt_prevFace) {
  // TODO(bckenny): reverse order of params?

  /**
   * next face (never null)
   * @type {!libtess.GluFace}
   */
  this.next = opt_nextFace || this;

  /**
   * previous face (never NULL)
   * @type {!libtess.GluFace}
   */
  this.prev = opt_prevFace || this;

  /**
   * A half edge with this left face.
   * @type {libtess.GluHalfEdge}
   */
  this.anEdge = null;

  /**
   * room for client's data
   * @type {Object}
   */
  this.data = null;

  /**
   * This face is in the polygon interior.
   * @type {boolean}
   */
  this.inside = false;
};



/* global libtess */

/**
 * The fundamental data structure is the "half-edge". Two half-edges
 * go together to make an edge, but they point in opposite directions.
 * Each half-edge has a pointer to its mate (the "symmetric" half-edge sym),
 * its origin vertex (org), the face on its left side (lFace), and the
 * adjacent half-edges in the CCW direction around the origin vertex
 * (oNext) and around the left face (lNext). There is also a "next"
 * pointer for the global edge list (see below).
 *
 * The notation used for mesh navigation:
 *  sym   = the mate of a half-edge (same edge, but opposite direction)
 *  oNext = edge CCW around origin vertex (keep same origin)
 *  dNext = edge CCW around destination vertex (keep same dest)
 *  lNext = edge CCW around left face (dest becomes new origin)
 *  rNext = edge CCW around right face (origin becomes new dest)
 *
 * "prev" means to substitute CW for CCW in the definitions above.
 *
 * The circular edge list is special; since half-edges always occur
 * in pairs (e and e.sym), each half-edge stores a pointer in only
 * one direction. Starting at eHead and following the e.next pointers
 * will visit each *edge* once (ie. e or e.sym, but not both).
 * e.sym stores a pointer in the opposite direction, thus it is
 * always true that e.sym.next.sym.next === e.
 *
 * @param {libtess.GluHalfEdge=} opt_nextEdge
 * @constructor
 * @struct
 */
libtess.GluHalfEdge = function(opt_nextEdge) {
  // TODO(bckenny): are these the right defaults? (from gl_meshNewMesh requirements)

  /**
   * doubly-linked list (prev==sym->next)
   * @type {!libtess.GluHalfEdge}
   */
  this.next = opt_nextEdge || this;

  // TODO(bckenny): how can this be required if created in pairs? move to factory creation only?
  /**
   * same edge, opposite direction
   * @type {libtess.GluHalfEdge}
   */
  this.sym = null;

  /**
   * next edge CCW around origin
   * @type {libtess.GluHalfEdge}
   */
  this.oNext = null;

  /**
   * next edge CCW around left face
   * @type {libtess.GluHalfEdge}
   */
  this.lNext = null;

  /**
   * origin vertex (oVertex too long)
   * @type {libtess.GluVertex}
   */
  this.org = null;

  /**
   * left face
   * @type {libtess.GluFace}
   */
  this.lFace = null;

  // Internal data (keep hidden)
  // NOTE(bckenny): can't be private, though...

  /**
   * a region with this upper edge (see sweep.js)
   * @type {libtess.ActiveRegion}
   */
  this.activeRegion = null;

  /**
   * change in winding number when crossing from the right face to the left face
   * @type {number}
   */
  this.winding = 0;
};

// NOTE(bckenny): the following came from macros in mesh
// TODO(bckenny): using methods as aliases for sym connections for now.
// not sure about this approach. getters? renames?


/**
 * [rFace description]
 * @return {libtess.GluFace} [description].
 */
libtess.GluHalfEdge.prototype.rFace = function() {
  return this.sym.lFace;
};


/**
 * [dst description]
 * @return {libtess.GluVertex} [description].
 */
libtess.GluHalfEdge.prototype.dst = function() {
  return this.sym.org;
};


/**
 * [oPrev description]
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.GluHalfEdge.prototype.oPrev = function() {
  return this.sym.lNext;
};


/**
 * [lPrev description]
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.GluHalfEdge.prototype.lPrev = function() {
  return this.oNext.sym;
};

// NOTE(bckenny): libtess.GluHalfEdge.dPrev is called nowhere in libtess and
// isn't part of the current public API. It could be useful for mesh traversal
// and manipulation if made public, however.
/* istanbul ignore next */
/**
 * The edge clockwise around destination vertex (keep same dest).
 * @return {libtess.GluHalfEdge}
 */
libtess.GluHalfEdge.prototype.dPrev = function() {
  return this.lNext.sym;
};


/**
 * [rPrev description]
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.GluHalfEdge.prototype.rPrev = function() {
  return this.sym.oNext;
};


/**
 * [dNext description]
 * @return {libtess.GluHalfEdge} [description].
 */
libtess.GluHalfEdge.prototype.dNext = function() {
  return this.rPrev().sym;
};


// NOTE(bckenny): libtess.GluHalfEdge.rNext is called nowhere in libtess and
// isn't part of the current public API. It could be useful for mesh traversal
// and manipulation if made public, however.
/* istanbul ignore next */
/**
 * The edge CCW around the right face (origin of this becomes new dest).
 * @return {libtess.GluHalfEdge}
 */
libtess.GluHalfEdge.prototype.rNext = function() {
  return this.oPrev().sym;
};



/* global libtess */

/**
 * Creates a new mesh with no edges, no vertices,
 * and no loops (what we usually call a "face").
 *
 * @constructor
 * @struct
 */
libtess.GluMesh = function() {
  /**
   * dummy header for vertex list
   * @type {libtess.GluVertex}
   */
  this.vHead = new libtess.GluVertex();

  /**
   * dummy header for face list
   * @type {libtess.GluFace}
   */
  this.fHead = new libtess.GluFace();

  /**
   * dummy header for edge list
   * @type {libtess.GluHalfEdge}
   */
  this.eHead = new libtess.GluHalfEdge();

  /**
   * and its symmetric counterpart
   * @type {libtess.GluHalfEdge}
   */
  this.eHeadSym = new libtess.GluHalfEdge();

  // TODO(bckenny): better way to pair these?
  this.eHead.sym = this.eHeadSym;
  this.eHeadSym.sym = this.eHead;
};


// TODO(bckenny): #ifndef NDEBUG
/**
 * Checks mesh for self-consistency.
 */
libtess.GluMesh.prototype.checkMesh = function() {
  if (!libtess.DEBUG) {
    return;
  }

  var fHead = this.fHead;
  var vHead = this.vHead;
  var eHead = this.eHead;

  var e;

  // faces
  var f;
  var fPrev = fHead;
  for (fPrev = fHead; (f = fPrev.next) !== fHead; fPrev = f) {
    e = f.anEdge;
    do {
      e = e.lNext;
    } while (e !== f.anEdge);
  }

  // vertices
  var v;
  var vPrev = vHead;
  for (vPrev = vHead; (v = vPrev.next) !== vHead; vPrev = v) {
    e = v.anEdge;
    do {
      e = e.oNext;
    } while (e !== v.anEdge);
  }

  // edges
  var ePrev = eHead;
  for (ePrev = eHead; (e = ePrev.next) !== eHead; ePrev = e) {
  }
};



/* global libtess */

/**
 * Each vertex has a pointer to next and previous vertices in the
 * circular list, and a pointer to a half-edge with this vertex as
 * the origin (null if this is the dummy header). There is also a
 * field "data" for client data.
 *
 * @param {libtess.GluVertex=} opt_nextVertex [description].
 * @param {libtess.GluVertex=} opt_prevVertex [description].
 * @constructor
 * @struct
 */
libtess.GluVertex = function(opt_nextVertex, opt_prevVertex) {
  // TODO(bckenny): reverse order of params?

  /**
   * Next vertex (never null).
   * @type {!libtess.GluVertex}
   */
  this.next = opt_nextVertex || this;

  /**
   * Previous vertex (never null).
   * @type {!libtess.GluVertex}
   */
  this.prev = opt_prevVertex || this;

  /**
   * A half-edge with this origin.
   * @type {libtess.GluHalfEdge}
   */
  this.anEdge = null;

  /**
   * The client's data.
   * @type {Object}
   */
  this.data = null;

  /**
   * The vertex location in 3D.
   * @type {!Array.<number>}
   */
  this.coords = [0, 0, 0];
  // TODO(bckenny): we may want to rethink coords, either eliminate (using s
  // and t and user data) or index into contiguous storage?

  /**
   * Component of projection onto the sweep plane.
   * @type {number}
   */
  this.s = 0;

  /**
   * Component of projection onto the sweep plane.
   * @type {number}
   */
  this.t = 0;

  /**
   * To allow deletion from priority queue.
   * @type {?libtess.PQHandle}
   */
  this.pqHandle = null;
  // NOTE(bckenny): pqHandle inited in sweep
  // TODO(bckenny): can we have a numeric default value? null may do bad things
};



/* global libtess */

// TODO(bckenny): more specific typing on key

/**
 * [PQHandleElem description]
 * @constructor
 * @struct
 */
libtess.PQHandleElem = function() {
  // TODO(bckenny): if key could instead be an indexed into another store, makes heap storage a lot easier

  /**
   * [key description]
   * @type {libtess.PQKey}
   */
  this.key = null;

  /**
   * [node description]
   * @type {libtess.PQHandle}
   */
  this.node = 0;
};


/**
 * Allocate a PQHandleElem array of size size. If oldArray is not null, its
 * contents are copied to the beginning of the new array. The rest of the array
 * is filled with new PQHandleElems.
 *
 * @param {?Array.<libtess.PQHandleElem>} oldArray [description].
 * @param {number} size [description].
 * @return {Array.<libtess.PQHandleElem>} [description].
 */
libtess.PQHandleElem.realloc = function(oldArray, size) {
  var newArray = new Array(size);

  // TODO(bckenny): better to reallocate array? or grow array?
  var index = 0;
  if (oldArray !== null) {
    for (; index < oldArray.length; index++) {
      newArray[index] = oldArray[index];
    }
  }

  for (; index < size; index++) {
    newArray[index] = new libtess.PQHandleElem();
  }

  return newArray;
};



/* global libtess */

// TODO(bckenny): maybe just have these created inline as literals
// (or unboxed directly - PQHandle is just an array index number)

/**
 * [PQNode description]
 * @constructor
 * @struct
 */
libtess.PQNode = function() {
  /**
   * [handle description]
   * @type {libtess.PQHandle}
   */
  this.handle = 0;
};


/**
 * Allocate a PQNode array of size size. If oldArray is not null, its contents
 * are copied to the beginning of the new array. The rest of the array is
 * filled with new PQNodes.
 *
 * @param {?Array.<libtess.PQNode>} oldArray [description].
 * @param {number} size [description].
 * @return {Array.<libtess.PQNode>} [description].
 */
libtess.PQNode.realloc = function(oldArray, size) {
  var newArray = new Array(size);

  // TODO(bckenny): better to reallocate array? or grow array?
  var index = 0;
  if (oldArray !== null) {
    for (; index < oldArray.length; index++) {
      newArray[index] = oldArray[index];
    }
  }

  for (; index < size; index++) {
    newArray[index] = new libtess.PQNode();
  }

  return newArray;
};



/* global libtess */

// TODO(bckenny): preallocating arrays may actually be hurting us in sort
// performance (esp if theres some undefs in there)

/**
 * [PriorityQ description]
 * @constructor
 * @struct
 * @param {function(Object, Object): boolean} leq [description].
 */
libtess.PriorityQ = function(leq) {
  /**
   * [keys description]
   * @private
   * @type {Array.<libtess.PQKey>}
   */
  this.keys_ = libtess.PriorityQ.prototype.PQKeyRealloc_(null,
      libtess.PriorityQ.INIT_SIZE_);

  /**
   * Array of indexes into this.keys_
   * @private
   * @type {Array.<number>}
   */
  this.order_ = null;

  /**
   * [size description]
   * @private
   * @type {number}
   */
  this.size_ = 0;

  /**
   * [max_ description]
   * @private
   * @type {number}
   */
  this.max_ = libtess.PriorityQ.INIT_SIZE_;

  /**
   * [initialized description]
   * @private
   * @type {boolean}
   */
  this.initialized_ = false;

  // TODO(bckenny): leq was inlined by define in original, but appears to just
  // be vertLeq, as passed. keep an eye on this as to why its not used.
  /**
   * [leq description]
   * @private
   * @type {function(libtess.PQKey, libtess.PQKey): boolean}
   */
  this.leq_ =
      /** @type {function(libtess.PQKey, libtess.PQKey): boolean} */(leq);

  /**
   * [heap_ description]
   * @private
   * @type {libtess.PriorityQHeap}
   */
  this.heap_ = new libtess.PriorityQHeap(this.leq_);
};


/**
 * [INIT_SIZE_ description]
 * @private
 * @const
 * @type {number}
 */
libtess.PriorityQ.INIT_SIZE_ = 32;


/**
 * [deleteQ description]
 */
libtess.PriorityQ.prototype.deleteQ = function() {
  // TODO(bckenny): unnecessary, I think.
  this.heap_.deleteHeap();
  this.heap_ = null;
  this.order_ = null;
  this.keys_ = null;
  // NOTE(bckenny): nulled at callsite (sweep.donePriorityQ_)
};


/**
 * [init description]
 */
libtess.PriorityQ.prototype.init = function() {
  // TODO(bckenny): reuse. in theory, we don't have to empty this, as access is
  // dictated by this.size_, but array.sort doesn't know that
  this.order_ = [];

  // Create an array of indirect pointers to the keys, so that
  // the handles we have returned are still valid.
  // TODO(bckenny): valid for when? it appears we can just store indexes into
  // keys_, but what did this mean?
  for (var i = 0; i < this.size_; i++) {
    this.order_[i] = i;
  }

  // sort the indirect pointers in descending order of the keys themselves
  // TODO(bckenny): make sure it's ok that keys[a] === keys[b] returns 1
  // TODO(bckenny): unstable sort means we may get slightly different polys in
  // different browsers, but only when passing in equal points
  // TODO(bckenny): make less awkward closure?
  var comparator = (function(keys, leq) {
    return function(a, b) {
      return leq(keys[a], keys[b]) ? 1 : -1;
    };
  })(this.keys_, this.leq_);
  this.order_.sort(comparator);

  this.max_ = this.size_;
  this.initialized_ = true;
  this.heap_.init();

  // TODO(bckenny):
  // #ifndef NDEBUG
  if (libtess.DEBUG) {
    var p = 0;
    var r = p + this.size_ - 1;
    for (i = p; i < r; ++i) {
    }
  }
  // #endif
};


/**
 * [insert description]
 * @param {libtess.PQKey} keyNew [description].
 * @return {libtess.PQHandle} [description].
 */
libtess.PriorityQ.prototype.insert = function(keyNew) {
  // NOTE(bckenny): originally returned LONG_MAX as alloc failure signal. no
  // longer does.
  if (this.initialized_) {
    return this.heap_.insert(keyNew);
  }

  var curr = this.size_;
  if (++this.size_ >= this.max_) {
    // If the heap overflows, double its size.
    this.max_ *= 2;
    this.keys_ =
        libtess.PriorityQ.prototype.PQKeyRealloc_(this.keys_, this.max_);
  }

  this.keys_[curr] = keyNew;

  // Negative handles index the sorted array.
  return -(curr + 1);
};


/**
 * Allocate a PQKey array of size size. If oldArray is not null, its
 * contents are copied to the beginning of the new array. The rest of the array
 * is filled with nulls.
 *
 * @private
 * @param {?Array.<libtess.PQKey>} oldArray [description].
 * @param {number} size [description].
 * @return {Array.<(?libtess.PQKey)>} [description].
 */
libtess.PriorityQ.prototype.PQKeyRealloc_ = function(oldArray, size) {
  // TODO(bckenny): double check return type. can we have ? there?
  var newArray = new Array(size);

  // TODO(bckenny): better to reallocate array? or grow array?
  var index = 0;
  if (oldArray !== null) {
    for (; index < oldArray.length; index++) {
      newArray[index] = oldArray[index];
    }
  }

  for (; index < size; index++) {
    newArray[index] = null;
  }

  return newArray;
};

// NOTE(bckenny): libtess.PriorityQ.keyLessThan_ is called nowhere in libtess
// and isn't part of the public API.
/* istanbul ignore next */
/**
 * Whether x is less than y according to this.leq_.
 * @private
 * @param {number} x
 * @param {number} y
 * @return {boolean}
 */
libtess.PriorityQ.prototype.keyLessThan_ = function(x, y) {
  // NOTE(bckenny): was macro LT
  var keyX = this.keys_[x];
  var keyY = this.keys_[y];
  return !this.leq_(keyY, keyX);
};

// NOTE(bckenny): libtess.PriorityQ.keyGreaterThan_ is called nowhere in libtess
// and isn't part of the public API.
/* istanbul ignore next */
/**
 * Whether x is greater than y according to this.leq_.
 * @private
 * @param {number} x
 * @param {number} y
 * @return {boolean}
 */
libtess.PriorityQ.prototype.keyGreaterThan_ = function(x, y) {
  // NOTE(bckenny): was macro GT
  var keyX = this.keys_[x];
  var keyY = this.keys_[y];
  return !this.leq_(keyX, keyY);
};


/**
 * [extractMin description]
 * @return {libtess.PQKey} [description].
 */
libtess.PriorityQ.prototype.extractMin = function() {
  if (this.size_ === 0) {
    return this.heap_.extractMin();
  }

  var sortMin = this.keys_[this.order_[this.size_ - 1]];
  if (!this.heap_.isEmpty()) {
    var heapMin = this.heap_.minimum();
    if (this.leq_(heapMin, sortMin)) {
      return this.heap_.extractMin();
    }
  }

  do {
    --this.size_;
  } while (this.size_ > 0 && this.keys_[this.order_[this.size_ - 1]] === null);

  return sortMin;
};


/**
 * [minimum description]
 * @return {libtess.PQKey} [description].
 */
libtess.PriorityQ.prototype.minimum = function() {
  if (this.size_ === 0) {
    return this.heap_.minimum();
  }

  var sortMin = this.keys_[this.order_[this.size_ - 1]];
  if (!this.heap_.isEmpty()) {
    var heapMin = this.heap_.minimum();
    if (this.leq_(heapMin, sortMin)) {
      return heapMin;
    }
  }

  return sortMin;
};

// NOTE(bckenny): libtess.PriorityQ.isEmpty_ isn't called within libtess and
// isn't part of the public API. For now, leaving in but ignoring for coverage.
/* istanbul ignore next */
/**
 * Returns whether the priority queue is empty.
 * @private
 * @return {boolean}
 */
libtess.PriorityQ.prototype.isEmpty_ = function() {
  return (this.size_ === 0) && this.heap_.isEmpty();
};


/**
 * [remove description]
 * @param {libtess.PQHandle} curr [description].
 */
libtess.PriorityQ.prototype.remove = function(curr) {
  if (curr >= 0) {
    this.heap_.remove(curr);
    return;
  }
  curr = -(curr + 1);

  this.keys_[curr] = null;
  while (this.size_ > 0 && this.keys_[this.order_[this.size_ - 1]] === null) {
    --this.size_;
  }
};



/* global libtess */

// TODO(bckenny): keys appear to always be GluVertex in this case?

/**
 * [PriorityQHeap description]
 * @constructor
 * @struct
 * @param {function(libtess.PQKey, libtess.PQKey): boolean} leq [description].
 */
libtess.PriorityQHeap = function(leq) {
  /**
   * The heap itself. Active nodes are stored in the range 1..size. Each node
   * stores only an index into handles.
   * @private
   * @type {Array.<libtess.PQNode>}
   */
  this.nodes_ = libtess.PQNode.realloc(null,
      libtess.PriorityQHeap.INIT_SIZE_ + 1);

  /**
   * Each handle stores a key, plus a pointer back to the node which currently
   * represents that key (ie. nodes[handles[i].node].handle == i).
   * @private
   * @type {Array.<libtess.PQHandleElem>}
   */
  this.handles_ = libtess.PQHandleElem.realloc(null,
      libtess.PriorityQHeap.INIT_SIZE_ + 1);

  // TODO(bckenny): size and max should probably be libtess.PQHandle for correct
  // typing (see PriorityQ.js)
  /**
   * The size of the queue.
   * @private
   * @type {number}
   */
  this.size_ = 0;

  /**
   * The queue's current allocated space.
   * @private
   * @type {number}
   */
  this.max_ = libtess.PriorityQHeap.INIT_SIZE_;

  /**
   * The index of the next free hole in the handles array. Handle in that slot
   * has next item in freeList in its node propert. If there are no holes,
   * freeList === 0 and one at the end of handles must be use.
   * @private
   * @type {libtess.PQHandle}
   */
  this.freeList_ = 0;

  /**
   * Indicates that the heap has been initialized via init. If false, inserts
   * are fast insertions at the end of a list. If true, all inserts will now be
   * correctly ordered in the queue before returning.
   * @private
   * @type {boolean}
   */
  this.initialized_ = false;

  // TODO(bckenny): leq was inlined by define in original, but appears to
  // be vertLeq, as passed. Using injected version, but is it better just to
  // manually inline?
  /**
   * [leq description]
   * @private
   * @type {function(libtess.PQKey, libtess.PQKey): boolean}
   */
  this.leq_ = leq;

  // so that minimum returns null
  this.nodes_[1].handle = 1;
};


/**
 * [INIT_SIZE_ description]
 * @private
 * @const
 * @type {number}
 */
libtess.PriorityQHeap.INIT_SIZE_ = 32;


/**
 * [deleteHeap description]
 */
libtess.PriorityQHeap.prototype.deleteHeap = function() {
  // TODO(bckenny): unnecessary, I think.
  this.handles_ = null;
  this.nodes_ = null;
  // NOTE(bckenny): nulled at callsite in PriorityQ.deleteQ
};


/**
 * Initializing ordering of the heap. Must be called before any method other
 * than insert is called to ensure correctness when removing or querying.
 */
libtess.PriorityQHeap.prototype.init = function() {
  // This method of building a heap is O(n), rather than O(n lg n).
  for (var i = this.size_; i >= 1; --i) {
    this.floatDown_(i);
  }

  this.initialized_ = true;
};


/**
 * Insert a new key into the heap.
 * @param {libtess.PQKey} keyNew The key to insert.
 * @return {libtess.PQHandle} A handle that can be used to remove the key.
 */
libtess.PriorityQHeap.prototype.insert = function(keyNew) {
  var curr = ++this.size_;

  // if the heap overflows, double its size.
  if ((curr * 2) > this.max_) {
    this.max_ *= 2;
    this.nodes_ = libtess.PQNode.realloc(this.nodes_, this.max_ + 1);
    this.handles_ = libtess.PQHandleElem.realloc(this.handles_, this.max_ + 1);
  }

  var free;
  if (this.freeList_ === 0) {
    free = curr;
  } else {
    free = this.freeList_;
    this.freeList_ = this.handles_[free].node;
  }

  this.nodes_[curr].handle = free;
  this.handles_[free].node = curr;
  this.handles_[free].key = keyNew;

  if (this.initialized_) {
    this.floatUp_(curr);
  }

  return free;
};


/**
 * @return {boolean} Whether the heap is empty.
 */
libtess.PriorityQHeap.prototype.isEmpty = function() {
  return this.size_ === 0;
};


/**
 * Returns the minimum key in the heap. If the heap is empty, null will be
 * returned.
 * @return {libtess.PQKey} [description].
 */
libtess.PriorityQHeap.prototype.minimum = function() {
  return this.handles_[this.nodes_[1].handle].key;
};


/**
 * Removes the minimum key from the heap and returns it. If the heap is empty,
 * null will be returned.
 * @return {libtess.PQKey} [description].
 */
libtess.PriorityQHeap.prototype.extractMin = function() {
  var n = this.nodes_;
  var h = this.handles_;
  var hMin = n[1].handle;
  var min = h[hMin].key;

  if (this.size_ > 0) {
    n[1].handle = n[this.size_].handle;
    h[n[1].handle].node = 1;

    h[hMin].key = null;
    h[hMin].node = this.freeList_;
    this.freeList_ = hMin;

    if (--this.size_ > 0) {
      this.floatDown_(1);
    }
  }

  return min;
};


/**
 * Remove key associated with handle hCurr (returned from insert) from heap.
 * @param {libtess.PQHandle} hCurr [description].
 */
libtess.PriorityQHeap.prototype.remove = function(hCurr) {
  var n = this.nodes_;
  var h = this.handles_;

  var curr = h[hCurr].node;
  n[curr].handle = n[this.size_].handle;
  h[n[curr].handle].node = curr;

  if (curr <= --this.size_) {
    if (curr <= 1 ||
        this.leq_(h[n[curr >> 1].handle].key, h[n[curr].handle].key)) {

      this.floatDown_(curr);
    } else {
      this.floatUp_(curr);
    }
  }

  h[hCurr].key = null;
  h[hCurr].node = this.freeList_;
  this.freeList_ = hCurr;
};


/**
 * [floatDown_ description]
 * @private
 * @param {libtess.PQHandle} curr [description].
 */
libtess.PriorityQHeap.prototype.floatDown_ = function(curr) {
  var n = this.nodes_;
  var h = this.handles_;

  var hCurr = n[curr].handle;
  for (;;) {
    // The children of node i are nodes 2i and 2i+1.
    // set child to the index of the child with the minimum key
    var child = curr << 1;
    if (child < this.size_ &&
        this.leq_(h[n[child + 1].handle].key, h[n[child].handle].key)) {

      ++child;
    }

    var hChild = n[child].handle;
    if (child > this.size_ || this.leq_(h[hCurr].key, h[hChild].key)) {
      n[curr].handle = hCurr;
      h[hCurr].node = curr;
      break;
    }
    n[curr].handle = hChild;
    h[hChild].node = curr;
    curr = child;
  }
};


/**
 * [floatUp_ description]
 * @private
 * @param {libtess.PQHandle} curr [description].
 */
libtess.PriorityQHeap.prototype.floatUp_ = function(curr) {
  var n = this.nodes_;
  var h = this.handles_;

  var hCurr = n[curr].handle;
  for (;;) {
    var parent = curr >> 1;
    var hParent = n[parent].handle;
    if (parent === 0 || this.leq_(h[hParent].key, h[hCurr].key)) {
      n[curr].handle = hCurr;
      h[hCurr].node = curr;
      break;
    }

    n[curr].handle = hParent;
    h[hParent].node = curr;
    curr = parent;
  }
};


/* global libtess */

// TODO(bckenny): apparently only visible outside of sweep for debugging routines.
// find out if we can hide

/**
 * For each pair of adjacent edges crossing the sweep line, there is
 * an ActiveRegion to represent the region between them. The active
 * regions are kept in sorted order in a dynamic dictionary. As the
 * sweep line crosses each vertex, we update the affected regions.
 * @constructor
 * @struct
 */
libtess.ActiveRegion = function() {
  // TODO(bckenny): I *think* eUp and nodeUp could be passed in as constructor params

  /**
   * The upper edge of the region, directed right to left
   * @type {libtess.GluHalfEdge}
   */
  this.eUp = null;

  /**
   * Dictionary node corresponding to eUp edge.
   * @type {libtess.DictNode}
   */
  this.nodeUp = null;

  /**
   * Used to determine which regions are inside the polygon.
   * @type {number}
   */
  this.windingNumber = 0;

  /**
   * Whether this region is inside the polygon.
   * @type {boolean}
   */
  this.inside = false;

  /**
   * Marks fake edges at t = +/-infinity.
   * @type {boolean}
   */
  this.sentinel = false;

  /**
   * Marks regions where the upper or lower edge has changed, but we haven't
   * checked whether they intersect yet.
   * @type {boolean}
   */
  this.dirty = false;

  /**
   * marks temporary edges introduced when we process a "right vertex" (one
   * without any edges leaving to the right)
   * @type {boolean}
   */
  this.fixUpperEdge = false;
};

/**
 * Returns the ActiveRegion below this one.
 * @return {libtess.ActiveRegion}
 */
libtess.ActiveRegion.prototype.regionBelow = function() {
  return this.nodeUp.getPredecessor().getKey();
};

/**
 * Returns the ActiveRegion above this one.
 * @return {libtess.ActiveRegion}
 */
libtess.ActiveRegion.prototype.regionAbove = function() {
  return this.nodeUp.getSuccessor().getKey();
};

/* global libtess, module */

/**
 * node.js export for non-compiled source
 */
if (typeof module !== 'undefined') {
  module.exports = libtess;
}
;
define("util/libtess", function(){});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AreaMeasurer
 */

define('util/measure/AreaMeasurer',[
        '../../geom/Angle',
        '../../error/ArgumentError',
        '../../geom/Location',
        '../Logger',
        './MeasurerUtils',
        '../../geom/Sector',
        '../../geom/Vec3',
        '../libtess'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger,
              MeasurerUtils,
              Sector,
              Vec3,
              libtessDummy) {
        'use strict';

        /**
         * Utility class to compute approximations of projected and surface (terrain following) area on a globe.
         *
         * <p>To properly compute surface area the measurer must be provided with a list of positions that describe a
         * closed path - one which last position is equal to the first.</p>
         *
         * <p>Segments which are longer then the current maxSegmentLength will be subdivided along lines following the
         * current pathType - WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE.</p>
         *
         * <p>Projected or non terrain following area is computed in a sinusoidal projection which is equivalent or
         * equal area.
         * Surface or terrain following area is approximated by sampling the path bounding sector with square cells
         * along a grid. Cells which center is inside the path  have their area estimated and summed according to the
         * overall slope at the cell south-west corner.</p>
         *
         * @alias AreaMeasurer
         * @constructor
         * @param {WorldWindow} wwd The WorldWindow associated with AreaMeasurer.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var AreaMeasurer = function (wwd) {

            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AreaMeasurer", "constructor", "missingWorldWindow"));
            }

            this.wwd = wwd;

            // Private. Sampling grid max rows or cols
            this.DEFAULT_AREA_SAMPLING_STEPS = 32;

            // Private. Documentation is with the defined property below.
            this._areaTerrainSamplingSteps = this.DEFAULT_AREA_SAMPLING_STEPS;

            // Private. Documentation is with the defined property below.
            this._maxSegmentLength = 100e3;

            // Private. A list of positions with no segment longer then maxLength and elevations following terrain or not.
            this.subdividedPositions = null;

            // Private.
            this.vecZ = new Vec3(0, 0, 1);

            // Private. Reusable Location.
            this.scratchLocation = new Location(0, 0);

        };

        Object.defineProperties(AreaMeasurer.prototype, {
            /**
             * The sampling grid maximum number of rows or columns for terrain following surface area approximation.
             * @type {Number}
             * @memberof AreaMeasurer.prototype
             */
            areaTerrainSamplingSteps: {
                get: function () {
                    return this._areaTerrainSamplingSteps;
                },
                set: function (value) {
                    this._areaTerrainSamplingSteps = value;
                }
            },

            /**
             * The maximum length a segment can have before being subdivided along a line following the current pathType.
             * @type {Number}
             * @memberof AreaMeasurer.prototype
             */
            maxSegmentLength: {
                get: function () {
                    return this._maxSegmentLength;
                },
                set: function (value) {
                    this._maxSegmentLength = value;
                }
            }
        });

        /**
         * Get the sampling grid maximum number of rows or columns for terrain following surface area approximation.
         *
         * @param {Position[]} positions A list of positions describing a polygon
         * @param {Boolean} followTerrain If true, the computed length will account for terrain deformations as if
         * someone was walking along that path
         * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
         *
         * @return {Number} area in square meters
         */
        AreaMeasurer.prototype.getArea = function (positions, followTerrain, pathType) {
            var globe = this.wwd.globe;
            if (followTerrain) {
                return this.computeSurfaceAreaSampling(globe, positions, pathType);
            }
            return this.computeProjectedAreaGeometry(globe, positions, pathType);
        };

        /**
         * Sample the path bounding sector with square cells which area are approximated according to the surface normal
         * at the cell south-west corner.
         *
         * @param {Globe} globe
         * @param {Position[]} positions
         * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
         *
         * @return {Number} area in square meters
         */
        AreaMeasurer.prototype.computeSurfaceAreaSampling = function (globe, positions, pathType) {
            var sector = new Sector(0, 0, 0, 0);
            sector.setToBoundingSector(positions);

            // Subdivide long segments if needed
            this.subdividedPositions = MeasurerUtils.subdividePositions(globe, positions, true, pathType,
                this._maxSegmentLength);

            // Sample the bounding sector with cells about the same length in side - squares
            var steps = Math.max(this.DEFAULT_AREA_SAMPLING_STEPS, this._areaTerrainSamplingSteps);
            var deltaLatRadians = sector.deltaLatitude() * Angle.DEGREES_TO_RADIANS;
            var deltaLonRadians = sector.deltaLongitude() * Angle.DEGREES_TO_RADIANS;
            var stepsRadians = Math.max(deltaLatRadians / steps, deltaLonRadians / steps);
            var latSteps = Math.round(deltaLatRadians / stepsRadians);
            var lonSteps = Math.round(deltaLonRadians / stepsRadians *
                Math.cos(sector.centroidLatitude() * Angle.DEGREES_TO_RADIANS));
            var latStepRadians = deltaLatRadians / latSteps;
            var lonStepRadians = deltaLonRadians / lonSteps;

            var area = 0;
            for (var i = 0; i < latSteps; i++) {
                var lat = sector.minLatitude * Angle.DEGREES_TO_RADIANS + latStepRadians * i;
                // Compute this latitude row cells area
                var radius = globe.radiusAt((lat + latStepRadians / 2) * Angle.RADIANS_TO_DEGREES, sector.centroidLongitude());
                var cellWidth = lonStepRadians * radius * Math.cos(lat + latStepRadians / 2);
                var cellHeight = latStepRadians * radius;
                var cellArea = cellWidth * cellHeight;

                for (var j = 0; j < lonSteps; j++) {
                    var lon = sector.minLongitude * Angle.DEGREES_TO_RADIANS + lonStepRadians * j;
                    var minLat = lat * Angle.RADIANS_TO_DEGREES;
                    var maxLat = (lat + latStepRadians) * Angle.RADIANS_TO_DEGREES;
                    var minLon = lon * Angle.RADIANS_TO_DEGREES;
                    var maxLon = (lon + lonStepRadians) * Angle.RADIANS_TO_DEGREES;
                    var cellSector = new Sector(minLat, maxLat, minLon, maxLon);
                    var isLocationInside = MeasurerUtils.isLocationInside(cellSector.centroid(this.scratchLocation),
                        this.subdividedPositions);
                    if (isLocationInside) {
                        // Compute suface area using terrain normal in SW corner
                        // Corners elevation
                        var eleSW = globe.elevationAtLocation(minLat, minLon);
                        var eleSE = globe.elevationAtLocation(minLat, maxLon);
                        var eleNW = globe.elevationAtLocation(maxLat, minLon);

                        // Compute normal
                        var vx = new Vec3(cellWidth, 0, eleSE - eleSW);
                        var vy = new Vec3(0, cellHeight, eleNW - eleSW);
                        vx.normalize();
                        vy.normalize();
                        var normalSW = vx.cross(vy).normalize(); // point toward positive Z

                        // Compute slope factor
                        var tan = Math.tan(MeasurerUtils.angleBetweenVectors(this.vecZ, normalSW));
                        var slopeFactor = Math.sqrt(1 + tan * tan);

                        // Add cell area
                        area += (cellArea * slopeFactor);
                    }
                }
            }

            return area;
        };

        /**
         * Tessellate the path in lat-lon space, then sum each triangle area.
         *
         * @param {Globe} globe
         * @param {Position[]} positions
         * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
         *
         * @return {Number} area in square meters
         */
        AreaMeasurer.prototype.computeProjectedAreaGeometry = function (globe, positions, pathType) {

            // Subdivide long segments if needed
            this.subdividedPositions = MeasurerUtils.subdividePositions(globe, positions, false, pathType,
                this._maxSegmentLength);

            // First: tessellate polygon
            var verticesCount = this.subdividedPositions.length;
            var firstPos = this.subdividedPositions[0];
            var lastPos = this.subdividedPositions[verticesCount - 1];
            if (firstPos.equals(lastPos)) {
                verticesCount--;
            }

            var verts = [];
            var idx = 0;
            for (var i = 0; i < verticesCount; i++) {
                var pos = this.subdividedPositions[i];
                verts[idx++] = pos.longitude * Angle.DEGREES_TO_RADIANS;
                verts[idx++] = pos.latitude * Angle.DEGREES_TO_RADIANS;
                verts[idx++] = 0;
            }

            var triangles = this.tessellatePolygon(verticesCount, verts);

            // Second: sum triangles area
            var area = 0;
            var triangleCount = triangles.length / 9;
            for (i = 0; i < triangleCount; i++) {
                idx = i * 9;
                var triangle = [
                    triangles[idx + 0], triangles[idx + 1], triangles[idx + 2],
                    triangles[idx + 3], triangles[idx + 4], triangles[idx + 5],
                    triangles[idx + 6], triangles[idx + 7], triangles[idx + 8]
                ];
                area += this.computeTriangleProjectedArea(globe, triangle);
            }

            return area;

        };

        /**
         * Compute triangle area in a sinusoidal projection centered at the triangle center.
         * Note sinusoidal projection is equivalent or equal area.
         *
         * @param {Globe} globe
         * @param {Number[]} verts A list of 9 positions in radians describing a triangle
         *
         * @return {Number} area in square meters
         */
        AreaMeasurer.prototype.computeTriangleProjectedArea = function (globe, verts) {
            // http://www.mathopenref.com/coordtrianglearea.html
            var ax = verts[0];
            var ay = verts[1];
            var bx = verts[3];
            var by = verts[4];
            var cx = verts[6];
            var cy = verts[7];

            var area = Math.abs(
                ax * (by - cy) +
                bx * (cy - ay) +
                cx * (ay - by)
            );
            area /= 2;

            var centerLon = (ax + bx + cx) / 3;
            var centerLat = (ay + by + cy) / 3;

            // Apply globe radius at triangle center and scale down area according to center latitude cosine
            var radius = globe.radiusAt(centerLat * Angle.RADIANS_TO_DEGREES, centerLon * Angle.RADIANS_TO_DEGREES);
            area *= Math.cos(centerLat) * radius * radius; // Square meter

            return area;
        };

        /**
         * Tessellate a Polygon
         *
         * @param {Number} count the number of vertices
         * @param {Number[]} vertices A list of positions in radians
         *
         * @return {Number[]} a list of tessellated vertices
         */
        AreaMeasurer.prototype.tessellatePolygon = function (count, vertices) {
            var tess = new libtess.GluTesselator();
            var triangles = [];
            var coords;

            tess.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, function (prim) {
                if (prim !== libtess.primitiveType.GL_TRIANGLES) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "AreaMeasurer", "tessellatePolygon",
                        "Tessellation error, primitive is not TRIANGLES.");
                }
            });

            tess.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, function (data, tris) {
                tris.push(data[0]);
                tris.push(data[1]);
                tris.push(data[2]);
            });

            //prevents triangle fans and strips
            tess.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, function () {
            });

            tess.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, function (errno) {
                Logger.logMessage(Logger.LEVEL_WARNING, "AreaMeasurer", "tessellatePolygon",
                    "Tessellation error " + errno + ".");
            });

            // Tessellate the polygon.
            tess.gluTessBeginPolygon(triangles);
            tess.gluTessBeginContour();
            for (var i = 0; i < count; i++) {
                coords = vertices.slice(3 * i, 3 * i + 3);
                tess.gluTessVertex(coords, coords);
            }
            tess.gluTessEndContour();
            tess.gluTessEndPolygon();

            return triangles;
        };

        return AreaMeasurer;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AtmosphereProgram
 */
define('shaders/AtmosphereProgram',[
        '../error/ArgumentError',
        '../shaders/GpuProgram',
        '../util/Logger'
    ],
    function (ArgumentError,
              GpuProgram,
              Logger) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
         * This method then compiles the shaders and then links the program if compilation is successful.
         *
         * @alias AtmosphereProgram
         * @constructor
         * @augments GpuProgram
         * @classdesc AtmosphereProgram is a GLSL program that draws the atmosphere.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var AtmosphereProgram = function (gl, vertexShaderSource, fragmentShaderSource, attribute) {

            // Call to the superclass, which performs shader program compiling and linking.
            GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, attribute);


            // Frag color mode indicates the atmospheric scattering color components written to the fragment color.
            this.FRAGMODE_SKY = 1;
            this.FRAGMODE_GROUND_PRIMARY = 2;
            this.FRAGMODE_GROUND_SECONDARY = 3;
            this.FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;

            /**
             * The globe's atmosphere altitude.
             * @type {Number}
             * @default 160000.0 meters
             */
            this.altitude = 160000;

            /**
             * This atmosphere's Rayleigh scale depth.
             * @type {Number}
             * @default 0.25
             */
            this.rayleighScaleDepth = 0.25;

            /**
             * The WebGL location for this program's 'fragMode' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.fragModeLocation = this.uniformLocation(gl, "fragMode");

            /**
             * The WebGL location for this program's 'mvpMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");

            /**
             * The WebGL location for this program's 'texCoordMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.texCoordMatrixLocation = this.uniformLocation(gl, "texCoordMatrix");

            /**
             * The WebGL location for this program's 'vertexOrigin' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.vertexOriginLocation = this.uniformLocation(gl, "vertexOrigin");

            /**
             * The WebGL location for this program's 'eyePoint' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.eyePointLocation = this.uniformLocation(gl, "eyePoint");

            /**
             * The WebGL location for this program's 'eyeMagnitude' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.eyeMagnitudeLocation = this.uniformLocation(gl, "eyeMagnitude");

            /**
             * The WebGL location for this program's 'eyeMagnitude2' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.eyeMagnitude2Location = this.uniformLocation(gl, "eyeMagnitude2");

            /**
             * The WebGL location for this program's 'lightDirection' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.lightDirectionLocation = this.uniformLocation(gl, "lightDirection");

            /**
             * The WebGL location for this program's 'atmosphereRadius' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.atmosphereRadiusLocation = this.uniformLocation(gl, "atmosphereRadius");

            /**
             * The WebGL location for this program's 'atmosphereRadius2' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.atmosphereRadius2Location = this.uniformLocation(gl, "atmosphereRadius2");

            /**
             * The WebGL location for this program's 'globeRadius' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.globeRadiusLocation = this.uniformLocation(gl, "globeRadius");

            /**
             * The WebGL location for this program's 'scale' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.scaleLocation = this.uniformLocation(gl, "scale");

            /**
             * The WebGL location for this program's 'scaleDepth' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.scaleDepthLocation = this.uniformLocation(gl, "scaleDepth");

            /**
             * The WebGL location for this program's 'scaleOverScaleDepth' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.scaleOverScaleDepthLocation = this.uniformLocation(gl, "scaleOverScaleDepth");
            
            this.scratchArray9 = new Float32Array(9);
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        AtmosphereProgram.key = "WorldWindGpuAtmosphereProgram";

        // Inherit from GpuProgram.
        AtmosphereProgram.prototype = Object.create(GpuProgram.prototype);

        /**
         * Returns the atmosphere's altitude.
         * @returns {Number} The atmosphere's altitude in meters.
         */
        AtmosphereProgram.prototype.getAltitude = function () {
            return this.altitude;
        };

        /**
         * Loads the specified number as the value of this program's 'fragMode' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} fragMode The frag mode value.
         * @throws {ArgumentError} If the specified number is null or undefined.
         */
        AtmosphereProgram.prototype.loadFragMode = function (gl, fragMode) {
            if (!fragMode) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadFragMode", "missingFragMode"));
            }

            gl.uniform1i(this.fragModeLocation, fragMode);
        };

        /**
         * Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        AtmosphereProgram.prototype.loadModelviewProjection = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadModelviewProjection",
                        "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
        };

        /**
         * Loads the specified vector as the value of this program's 'vertexOrigin' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Vec3} vector The vector to load.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        AtmosphereProgram.prototype.loadVertexOrigin = function (gl, vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadVertexOrigin", "missingVector"));
            }

            gl.uniform3f(this.vertexOriginLocation, vector[0], vector[1], vector[2]);
        };

        /**
         * Loads the specified vector as the value of this program's 'lightDirection' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Vec3} vector The vector to load.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        AtmosphereProgram.prototype.loadLightDirection = function (gl, vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadLightDirection", "missingVector"));
            }

            gl.uniform3f(this.lightDirectionLocation, vector[0], vector[1], vector[2]);
        };

        /**
         * Loads the specified vector as the value of this program's 'lightDirection' uniform variable,
         * the magnitude's specified vector as the value of this program's 'eyeMagnitude' uniform variable and
         * the squared magnitude's specified vector as the value of this program's 'eyeMagnitude2' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Vec3} vector The vector to load.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        AtmosphereProgram.prototype.loadEyePoint = function (gl, vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadEyePoint", "missingVector"));
            }

            gl.uniform3f(this.eyePointLocation, vector[0], vector[1], vector[2]);
            gl.uniform1f(this.eyeMagnitudeLocation, vector.magnitude());
            gl.uniform1f(this.eyeMagnitude2Location, vector.magnitudeSquared());
        };

        /**
         * Loads the specified number as the value of this program's 'globeRadius' uniform variable and the specified
         * number which add the altitude value as the value of this program's 'atmosphereRadius' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} globeRadius The globe radius value.
         * @throws {ArgumentError} If the specified number is null or undefined.
         */
        AtmosphereProgram.prototype.loadGlobeRadius = function (gl, globeRadius) {
            if (!globeRadius) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadGlobeRadius",
                        "missingGlobeRadius"));
            }

            var gr = globeRadius;
            var ar = gr + this.altitude;

            gl.uniform1f(this.globeRadiusLocation, gr);
            gl.uniform1f(this.atmosphereRadiusLocation, ar);
            gl.uniform1f(this.atmosphereRadius2Location, ar * ar);
        };

        /**
         * Sets the program's 'scale', 'scaleDepth' and 'scaleOverScaleDepth' uniform variables.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         */
        AtmosphereProgram.prototype.setScale = function (gl) {
            gl.uniform1f(this.scaleLocation, 1 / this.getAltitude());
            gl.uniform1f(this.scaleDepthLocation, this.rayleighScaleDepth);
            gl.uniform1f(this.scaleOverScaleDepthLocation, (1 / this.getAltitude()) / this.rayleighScaleDepth);
        };

        /**
         * Loads the specified matrix as the value of this program's 'texCoordMatrix' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix3} matrix The texture coordinate matrix.
         */
        AtmosphereProgram.prototype.loadTexMatrix = function(gl, matrix){
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AtmosphereProgram", "loadTexMatrix",
                        "missingMatrix"));
            }

            matrix.columnMajorComponents(this.scratchArray9);
            gl.uniformMatrix3fv(this.texCoordMatrixLocation, false, this.scratchArray9);
        };

        return AtmosphereProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GroundProgram
 */
define('shaders/GroundProgram',[
        '../shaders/AtmosphereProgram'
    ],
    function (AtmosphereProgram) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
         * This method then compiles the shaders and then links the program if compilation is successful. Use the bind
         * method to make the program current during rendering.
         *
         * @alias GroundProgram
         * @constructor
         * @augments AtmosphereProgram
         * @classdesc GroundProgram is a GLSL program that draws the ground component of the atmosphere.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var GroundProgram = function (gl) {
            var vertexShaderSource =
                    'precision mediump int;\n' +

                    'const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;\n' +
                    'const int SAMPLE_COUNT = 2;\n' +
                    'const float SAMPLES = 2.0;\n' +

                    'const float PI = 3.141592653589;\n' +
                    'const float Kr = 0.0025;\n' +
                    'const float Kr4PI = Kr * 4.0 * PI;\n' +
                    'const float Km = 0.0015;\n' +
                    'const float Km4PI = Km * 4.0 * PI;\n' +
                    'const float ESun = 15.0;\n' +
                    'const float KmESun = Km * ESun;\n' +
                    'const float KrESun = Kr * ESun;\n' +
                    'const vec3 invWavelength = vec3(5.60204474633241, 9.473284437923038, 19.643802610477206);\n' +
                    'const float rayleighScaleDepth = 0.25;\n' +

                    'uniform int fragMode;\n' +
                    'uniform mat4 mvpMatrix;\n' +
                    'uniform mat3 texCoordMatrix;\n' +
                    'uniform vec3 vertexOrigin;\n' +
                    'uniform vec3 eyePoint;\n' +
                    'uniform float eyeMagnitude;\n' + /* The eye point's magnitude */
                    'uniform float eyeMagnitude2;\n' + /* eyeMagnitude^2 */
                    'uniform vec3 lightDirection;\n' + /* The direction vector to the light source */
                    'uniform float atmosphereRadius;\n' + /* The outer (atmosphere) radius */
                    'uniform float atmosphereRadius2;\n' + /* atmosphereRadius^2 */
                    'uniform float globeRadius;\n' + /* The inner (planetary) radius */
                    'uniform float scale;\n' + /* 1 / (atmosphereRadius - globeRadius) */
                    'uniform float scaleDepth;\n' + /* The scale depth (i.e. the altitude at which
                     the atmosphere's average density is found) */
                    'uniform float scaleOverScaleDepth;\n' + /* fScale / fScaleDepth */

                    'attribute vec4 vertexPoint;\n' +
                    'attribute vec2 vertexTexCoord;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec2 texCoord;\n' +

                    'float scaleFunc(float cos) {\n' +
                    '    float x = 1.0 - cos;\n' +
                    '    return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n' +
                    '}\n' +

                    'void sampleGround() {\n' +
                    /* Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the
                     atmosphere) */
                    '    vec3 point = vertexPoint.xyz + vertexOrigin;\n' +
                    '    vec3 ray = point - eyePoint;\n' +
                    '    float far = length(ray);\n' +
                    '    ray /= far;\n' +

                    '    vec3 start;\n' +
                    '    if (eyeMagnitude < atmosphereRadius) {\n' +
                    '        start = eyePoint;\n' +
                    '    } else {\n' +
                    /* Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray
                     passing through the atmosphere) */
                    '        float B = 2.0 * dot(eyePoint, ray);\n' +
                    '        float C = eyeMagnitude2 - atmosphereRadius2;\n' +
                    '        float det = max(0.0, B*B - 4.0 * C);\n' +
                    '        float near = 0.5 * (-B - sqrt(det));\n' +

                    /* Calculate the ray's starting point, then calculate its scattering offset */
                    '        start = eyePoint + ray * near;\n' +
                    '        far -= near;\n' +
                    '}\n' +

                    '    float depth = exp((globeRadius - atmosphereRadius) / scaleDepth);\n' +
                    '    float eyeAngle = dot(-ray, point) / length(point);\n' +
                    '    float lightAngle = dot(lightDirection, point) / length(point);\n' +
                    '    float eyeScale = scaleFunc(eyeAngle);\n' +
                    '    float lightScale = scaleFunc(lightAngle);\n' +
                    '    float eyeOffset = depth*eyeScale;\n' +
                    '    float temp = (lightScale + eyeScale);\n' +

                    /* Initialize the scattering loop variables */
                    '    float sampleLength = far / SAMPLES;\n' +
                    '    float scaledLength = sampleLength * scale;\n' +
                    '    vec3 sampleRay = ray * sampleLength;\n' +
                    '    vec3 samplePoint = start + sampleRay * 0.5;\n' +

                    /* Now loop through the sample rays */
                    '    vec3 frontColor = vec3(0.0, 0.0, 0.0);\n' +
                    '    vec3 attenuate = vec3(0.0, 0.0, 0.0);\n' +
                    '    for(int i=0; i<SAMPLE_COUNT; i++)\n' +
                    '    {\n' +
                    '        float height = length(samplePoint);\n' +
                    '        float depth = exp(scaleOverScaleDepth * (globeRadius - height));\n' +
                    '        float scatter = depth*temp - eyeOffset;\n' +
                    '        attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));\n' +
                    '        frontColor += attenuate * (depth * scaledLength);\n' +
                    '        samplePoint += sampleRay;\n' +
                    '    }\n' +

                    '    primaryColor = frontColor * (invWavelength * KrESun + KmESun);\n' +
                    '    secondaryColor = attenuate;\n' + /* Calculate the attenuation factor for the ground */
                    '}\n' +

                    'void main()\n ' +
                    '{\n' +
                    '    sampleGround();\n' +
                    /* Transform the vertex point by the modelview-projection matrix */
                    '    gl_Position = mvpMatrix * vertexPoint;\n' +
                    '    if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {\n' +
                    /* Transform the vertex texture coordinate by the tex coord matrix */
                    '        texCoord = (texCoordMatrix * vec3(vertexTexCoord, 1.0)).st;\n' +
                    '    }\n' +
                    '}',
                fragmentShaderSource =
                    'precision mediump float;\n' +
                    'precision mediump int;\n' +

                    'const int FRAGMODE_GROUND_PRIMARY = 2;\n' +
                    'const int FRAGMODE_GROUND_SECONDARY = 3;\n' +
                    'const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;\n' +

                    'uniform int fragMode;\n' +
                    'uniform sampler2D texSampler;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec2 texCoord;\n' +

                    'void main (void)\n' +
                    '{\n' +
                    '    if (fragMode == FRAGMODE_GROUND_PRIMARY) {\n' +
                    '        gl_FragColor = vec4(primaryColor, 1.0);\n' +
                    '    } else if (fragMode == FRAGMODE_GROUND_SECONDARY) {\n' +
                    '        gl_FragColor = vec4(secondaryColor, 1.0);\n' +
                    '    } else if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {\n' +
                    '        vec4 texColor = texture2D(texSampler, texCoord);\n' +
                    '        gl_FragColor = vec4(primaryColor + texColor.rgb * (1.0 - secondaryColor), 1.0);\n' +
                    '    }\n' +
                    '}';

            // Call to the superclass, which performs shader program compiling and linking.
            AtmosphereProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint", "vertexTexCoord"]);
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        GroundProgram.key = "WorldWindGroundProgram";

        // Inherit from AtmosphereProgram.
        GroundProgram.prototype = Object.create(AtmosphereProgram.prototype);

        return GroundProgram;
    });



/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Layer
 */
define('layer/Layer',[
        '../util/Logger'
    ],
    function (Logger) {
        "use strict";

        /**
         * Constructs a layer. This constructor is meant to be called by subclasses and not directly by an application.
         * @alias Layer
         * @constructor
         * @classdesc Provides an abstract base class for layer implementations. This class is not meant to be instantiated
         * directly.
         */
        var Layer = function (displayName) {

            /**
             * This layer's display name.
             * @type {String}
             * @default "Layer"
             */
            this.displayName = displayName ? displayName : "Layer";

            /**
             * Indicates whether to display this layer.
             * @type {Boolean}
             * @default true
             */
            this.enabled = true;

            /**
             * Indicates whether this layer is pickable.
             * @type {Boolean}
             * @default true
             */
            this.pickEnabled = true;

            /**
             * This layer's opacity, which is combined with the opacity of shapes within layers.
             * Opacity is in the range [0, 1], with 1 indicating fully opaque.
             * @type {Number}
             * @default 1
             */
            this.opacity = 1;

            /**
             * The eye altitude above which this layer is displayed, in meters.
             * @type {Number}
             * @default -Number.MAX_VALUE (always displayed)
             */
            this.minActiveAltitude = -Number.MAX_VALUE;

            /**
             * The eye altitude below which this layer is displayed, in meters.
             * @type {Number}
             * @default Number.MAX_VALUE (always displayed)
             */
            this.maxActiveAltitude = Number.MAX_VALUE;

            /**
             * Indicates whether elements of this layer were drawn in the most recently generated frame.
             * @type {Boolean}
             * @readonly
             */
            this.inCurrentFrame = false;

            /**
             * The time to display. This property selects the layer contents that represents the specified time.
             * If null, layer-type dependent contents are displayed.
             * @type {Date}
             */
            this.time = null;
        };

        /**
         * Refreshes the data associated with this layer. The behavior of this function varies with the layer
         * type. For image layers, it causes the images to be re-retrieved from their origin.
         */
        Layer.prototype.refresh = function () {
            // Default implementation does nothing.
        };

        /**
         * Displays this layer. Subclasses should generally not override this method but should instead override the
         * [doRender]{@link Layer#doRender} method. This method calls that method after verifying that the layer is
         * enabled, the eye point is within this layer's active altitudes and the layer is in view.
         * @param {DrawContext} dc The current draw context.
         */
        Layer.prototype.render = function (dc) {
            this.inCurrentFrame = false;

            if (!this.enabled)
                return;

            if (dc.pickingMode && !this.pickEnabled)
                return;

            if (!this.withinActiveAltitudes(dc))
                return;

            if (!this.isLayerInView(dc))
                return;

            this.doRender(dc);
        };

        /**
         * Subclass method called to display this layer. Subclasses should implement this method rather than the
         * [render]{@link Layer#render} method, which determines enable, pick and active altitude status and does not
         * call this doRender method if the layer should not be displayed.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        Layer.prototype.doRender = function (dc) {
            // Default implementation does nothing.
        };

        /* INTENTIONALLY NOT DOCUMENTED
         * Indicates whether the current eye distance is within this layer's active-altitude range.
         * @param {DrawContext} dc The current draw context.
         * @returns {boolean} true If the eye distance is greater than or equal to this layer's minimum active
         * altitude and less than or equal to this layer's maximum active altitude, otherwise false.
         * @protected
         */
        Layer.prototype.withinActiveAltitudes = function (dc) {
            var eyePosition = dc.eyePosition;
            if (!eyePosition)
                return false;

            return eyePosition.altitude >= this.minActiveAltitude && eyePosition.altitude <= this.maxActiveAltitude;
       };

        /**
         * Indicates whether this layer is within the current view. Subclasses may override this method and
         * when called determine whether the layer contents are visible in the current view frustum. The default
         * implementation always returns true.
         * @param {DrawContext} dc The current draw context.
         * @returns {boolean} true If this layer is within the current view, otherwise false.
         * @protected
         */
        Layer.prototype.isLayerInView = function (dc) {
            return true; // default implementation always returns true
        };

        return Layer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Matrix3
 */

define('geom/Matrix3',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a 3 x 3  matrix.
         * @alias Matrix3
         * @constructor
         * @classdesc Represents a 3 x 3 double precision matrix stored in a Float64Array in row-major order.
         * @param {Number} m11 matrix element at row 1, column 1.
         * @param {Number} m12 matrix element at row 1, column 2.
         * @param {Number} m13 matrix element at row 1, column 3.
         * @param {Number} m21 matrix element at row 2, column 1.
         * @param {Number} m22 matrix element at row 2, column 2.
         * @param {Number} m23 matrix element at row 2, column 3.
         * @param {Number} m31 matrix element at row 3, column 1.
         * @param {Number} m32 matrix element at row 3, column 2.
         * @param {Number} m33 matrix element at row 3, column 3.
         */
        var Matrix3 = function (m11, m12, m13,
                                m21, m22, m23,
                                m31, m32, m33) {
            this[0] = m11;
            this[1] = m12;
            this[2] = m13;
            this[3] = m21;
            this[4] = m22;
            this[5] = m23;
            this[6] = m31;
            this[7] = m32;
            this[8] = m33;
        };

        // Derives from Float64Array.
        Matrix3.prototype = new Float64Array(9);

        /**
         * Creates an identity matrix.
         * @returns {Matrix3} A new identity matrix.
         */
        Matrix3.fromIdentity = function () {
            return new Matrix3(
                1, 0, 0,
                0, 1, 0,
                0, 0, 1
            );
        };

        /**
         * Sets this matrix to one that flips and shifts the y-axis.
         * <p>
         * The resultant matrix maps Y=0 to Y=1 and Y=1 to Y=0. All existing values are overwritten. This matrix is
         * usually used to change the coordinate origin from an upper left coordinate origin to a lower left coordinate
         * origin. This is typically necessary to align the coordinate system of images (top-left origin) with that of
         * OpenGL (bottom-left origin).
         * @returns {Matrix3} This matrix set to values described above.
         */
        Matrix3.prototype.setToUnitYFlip = function () {
            this[0] = 1;
            this[1] = 0;
            this[2] = 0;
            this[3] = 0;
            this[4] = -1;
            this[5] = 1;
            this[6] = 0;
            this[7] = 0;
            this[8] = 1;
            return this;
        };

        /**
         * Multiplies this matrix by a specified matrix.
         *
         * @param {Matrix3} matrix The matrix to multiply with this matrix.
         * @returns {Matrix3} This matrix after multiplying it by the specified matrix.
         * @throws {ArgumentError} if the specified matrix is null or undefined.
         */
        Matrix3.prototype.multiplyMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix3", "multiplyMatrix", "missingMatrix"));
            }

            var ma = this,
                mb = matrix,
                ma0, ma1, ma2;

            // Row 1
            ma0 = ma[0];
            ma1 = ma[1];
            ma2 = ma[2];
            ma[0] = (ma0 * mb[0]) + (ma1 * mb[3]) + (ma2 * mb[6]);
            ma[1] = (ma0 * mb[1]) + (ma1 * mb[4]) + (ma2 * mb[7]);
            ma[2] = (ma0 * mb[2]) + (ma1 * mb[5]) + (ma2 * mb[8]);

            // Row 2
            ma0 = ma[3];
            ma1 = ma[4];
            ma2 = ma[5];
            ma[3] = (ma0 * mb[0]) + (ma1 * mb[3]) + (ma2 * mb[6]);
            ma[4] = (ma0 * mb[1]) + (ma1 * mb[4]) + (ma2 * mb[7]);
            ma[5] = (ma0 * mb[2]) + (ma1 * mb[5]) + (ma2 * mb[8]);

            // Row 3
            ma0 = ma[6];
            ma1 = ma[7];
            ma2 = ma[8];
            ma[6] = (ma0 * mb[0]) + (ma1 * mb[3]) + (ma2 * mb[6]);
            ma[7] = (ma0 * mb[1]) + (ma1 * mb[4]) + (ma2 * mb[7]);
            ma[8] = (ma0 * mb[2]) + (ma1 * mb[5]) + (ma2 * mb[8]);

            return this;

        };

        /**
         * Multiplies this matrix by a matrix that transforms normalized coordinates from a source sector to a destination
         * sector. Normalized coordinates within a sector range from 0 to 1, with (0, 0) indicating the lower left corner
         * and (1, 1) indicating the upper right. The resultant matrix maps a normalized source coordinate (X, Y) to its
         * corresponding normalized destination coordinate (X', Y').
         * <p/>
         * This matrix typically necessary to transform texture coordinates from one geographic region to another. For
         * example, the texture coordinates for a terrain tile spanning one region must be transformed to coordinates
         * appropriate for an image tile spanning a potentially different region.
         *
         * @param {Sector} src the source sector
         * @param {Sector} dst the destination sector
         *
         * @returns {Matrix3} this matrix multiplied by the transform matrix implied by values described above
         */
        Matrix3.prototype.multiplyByTileTransform = function (src, dst) {

            var srcDeltaLat = src.deltaLatitude();
            var srcDeltaLon = src.deltaLongitude();
            var dstDeltaLat = dst.deltaLatitude();
            var dstDeltaLon = dst.deltaLongitude();

            var xs = srcDeltaLon / dstDeltaLon;
            var ys = srcDeltaLat / dstDeltaLat;
            var xt = (src.minLongitude - dst.minLongitude) / dstDeltaLon;
            var yt = (src.minLatitude - dst.minLatitude) / dstDeltaLat;

            // This is equivalent to the following operation, but is potentially much faster:
            /*var m = new Matrix3(
                xs, 0, xt,
                0, ys, yt,
                0, 0, 1);
            this.multiplyMatrix(m);*/

            // This inline version eliminates unnecessary multiplication by 1 and 0 in the matrix's components, reducing
            // the total number of primitive operations from 63 to 18.

            var m = this;

             // Must be done before modifying m0, m1, etc. below.
             m[2] += (m[0] * xt) + (m[1] * yt);
             m[5] += (m[3] * xt) + (m[4] * yt);
             m[8] += (m[6] * xt) + (m[6] * yt);

             m[0] *= xs;
             m[1] *= ys;

             m[3] *= xs;
             m[4] *= ys;

             m[6] *= xs;
             m[7] *= ys;

            return this;
        };

        /**
         * Stores this matrix's components in column-major order in a specified array.
         * <p>
         * The array must have space for at least 9 elements. This matrix's components are stored in the array
         * starting with row 0 column 0 in index 0, row 1 column 0 in index 1, row 2 column 0 in index 2, and so on.
         *
         * @param {Float32Array | Float64Array | Number[]} result An array of at least 9 elements. Upon return,
         * contains this matrix's components in column-major.
         * @returns {Float32Array} The specified result array.
         * @throws {ArgumentError} If the specified result array in null or undefined.
         */
        Matrix3.prototype.columnMajorComponents = function (result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Matrix3", "columnMajorComponents", "missingResult"));
            }

            // Column 1
            result[0] = this[0];
            result[1] = this[3];
            result[2] = this[6];

            // Column 2
            result[3] = this[1];
            result[4] = this[4];
            result[5] = this[7];

            // Column 3
            result[6] = this[2];
            result[7] = this[5];
            result[8] = this[8];

            return result;
        };

        return Matrix3;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SkyProgram
 */
define('shaders/SkyProgram',[
        '../shaders/AtmosphereProgram'
    ],
    function (AtmosphereProgram) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
         * This method then compiles the shaders and then links the program if compilation is successful. Use the bind
         * method to make the program current during rendering.
         *
         * @alias SkyProgram
         * @constructor
         * @augments AtmosphereProgram
         * @classdesc SkyProgram is a GLSL program that draws the sky component of the atmosphere.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var  SkyProgram = function (gl) {
            var vertexShaderSource =
                    'precision mediump int;\n' +

                    'const int SAMPLE_COUNT = 2;\n' +
                    'const float SAMPLES = 2.0;\n' +

                    'const float PI = 3.141592653589;\n' +
                    'const float Kr = 0.0025;\n' +
                    'const float Kr4PI = Kr * 4.0 * PI;\n' +
                    'const float Km = 0.0015;\n' +
                    'const float Km4PI = Km * 4.0 * PI;\n' +
                    'const float ESun = 15.0;\n' +
                    'const float KmESun = Km * ESun;\n' +
                    'const float KrESun = Kr * ESun;\n' +
                    'const vec3 invWavelength = vec3(5.60204474633241, 9.473284437923038, 19.643802610477206);\n' +
                    'const float rayleighScaleDepth = 0.25;\n' +

                    'uniform mat4 mvpMatrix;\n' +
                    'uniform vec3 vertexOrigin;\n' +
                    'uniform vec3 eyePoint;\n' +
                    'uniform float eyeMagnitude;\n' +        /* The eye point's magnitude */
                    'uniform float eyeMagnitude2;\n' +       /* eyeMagnitude^2 */
                    'uniform mediump vec3 lightDirection;\n' +       /* The direction vector to the light source */
                    'uniform float atmosphereRadius;\n' +    /* The outer (atmosphere) radius */
                    'uniform float atmosphereRadius2;\n' +   /* atmosphereRadius^2 */
                    'uniform float globeRadius;\n' +         /* The inner (planetary) radius */
                    'uniform float scale;\n' +               /* 1 / (atmosphereRadius - globeRadius) */
                    'uniform float scaleDepth;\n' +          /* The scale depth (i.e. the altitude at which the
                     atmosphere's average density is found) */
                    'uniform float scaleOverScaleDepth;\n' + /* fScale / fScaleDepth */

                    'attribute vec4 vertexPoint;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec3 direction;\n' +

                    'float scaleFunc(float cos)\n' +
                    '{\n' +
                    '    float x = 1.0 - cos;\n' +
                    '    return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n' +
                    '}\n' +

                    'void sampleSky() {\n' +
                        /* Get the ray from the camera to the vertex and its length (which is the far point of
                         the ray passing through the atmosphere) */
                    '    vec3 point = vertexPoint.xyz + vertexOrigin;\n' +
                    '    vec3 ray = point - eyePoint;\n' +
                    '    float far = length(ray);\n' +
                    '    ray /= far;\n' +

                    '    vec3 start;\n' +
                    '    float startOffset;\n' +

                    '    if (eyeMagnitude < atmosphereRadius) {\n' +
                        /* Calculate the ray's starting point, then calculate its scattering offset */
                    '        start = eyePoint;\n' +
                    '        float height = length(start);\n' +
                    '        float depth = exp(scaleOverScaleDepth * (globeRadius - eyeMagnitude));\n' +
                    '        float startAngle = dot(ray, start) / height;\n' +
                    '        startOffset = depth*scaleFunc(startAngle);\n' +
                    '    } else {\n' +
                        /* Calculate the closest intersection of the ray with the outer atmosphere (which is the near
                         point of the ray passing through the atmosphere) */
                    '        float B = 2.0 * dot(eyePoint, ray);\n' +
                    '        float C = eyeMagnitude2 - atmosphereRadius2;\n' +
                    '        float det = max(0.0, B*B - 4.0 * C);\n' +
                    '        float near = 0.5 * (-B - sqrt(det));\n' +

                        /* Calculate the ray's starting point, then calculate its scattering offset */
                    '        start = eyePoint + ray * near;\n' +
                    '        far -= near;\n' +
                    '        float startAngle = dot(ray, start) / atmosphereRadius;\n' +
                    '        float startDepth = exp(-1.0 / scaleDepth);\n' +
                    '        startOffset = startDepth*scaleFunc(startAngle);\n' +
                    '    }\n' +

                        /* Initialize the scattering loop variables */
                    '    float sampleLength = far / SAMPLES;\n' +
                    '    float scaledLength = sampleLength * scale;\n' +
                    '    vec3 sampleRay = ray * sampleLength;\n' +
                    '    vec3 samplePoint = start + sampleRay * 0.5;\n' +

                        /* Now loop through the sample rays */
                    '    vec3 frontColor = vec3(0.0, 0.0, 0.0);\n' +
                    '    for(int i=0; i<SAMPLE_COUNT; i++)\n' +
                    '    {\n' +
                    '       float height = length(samplePoint);\n' +
                    '       float depth = exp(scaleOverScaleDepth * (globeRadius - height));\n' +
                    '       float lightAngle = dot(lightDirection, samplePoint) / height;\n' +
                    '       float cameraAngle = dot(ray, samplePoint) / height;\n' +
                    '       float scatter = (startOffset + depth*(scaleFunc(lightAngle) - scaleFunc(cameraAngle)));\n'+
                    '       vec3 attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));\n' +
                    '       frontColor += attenuate * (depth * scaledLength);\n' +
                    '       samplePoint += sampleRay;\n' +
                    '    }\n' +

                        /* Finally, scale the Mie and Rayleigh colors and set up the varying variables for the fragment
                         shader */
                    '    primaryColor = frontColor * (invWavelength * KrESun);\n' +
                    '    secondaryColor = frontColor * KmESun;\n' +
                    '    direction = eyePoint - point;\n' +
                    '}\n' +

                    'void main()\n' +
                    '{\n' +
                    '    sampleSky();\n' +

                        /* Transform the vertex point by the modelview-projection matrix */
                    '    gl_Position = mvpMatrix * vertexPoint;\n' +
                    '}',
                fragmentShaderSource =
                    '#ifdef GL_FRAGMENT_PRECISION_HIGH\n'+
                    'precision highp float;\n'+
                    '#else\n'+
                    'precision mediump float;\n'+
                    '#endif\n'+

                    'const float g = -0.95;\n' +
                    'const float g2 = g * g;\n' +
                        
                    'uniform mediump vec3 lightDirection;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec3 direction;\n' +

                    'void main (void)\n' +
                    '{\n' +
                    '    float cos = dot(lightDirection, direction) / length(direction);\n' +
                    '    float rayleighPhase = 0.75 * (1.0 + cos * cos);\n' +
                    '    float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cos*cos) / ' +
                    '        pow(1.0 + g2 - 2.0*g*cos, 1.5);\n' +
                    '    const float exposure = 2.0;\n' +
                    '    vec3 color = primaryColor * rayleighPhase + secondaryColor * miePhase;\n' +
                    '    color = vec3(1.0) - exp(-exposure * color);\n' +
                    '    gl_FragColor = vec4(color, color.b);\n' +
                    '}';

            // Call to the superclass, which performs shader program compiling and linking.
            AtmosphereProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint"]);
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        SkyProgram.key = "WorldWindSkyProgram";

        // Inherit from AtmosphereProgram.
        SkyProgram.prototype = Object.create(AtmosphereProgram.prototype);

        return SkyProgram;
    });




/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */

define('util/SunPosition',[
        '../geom/Angle',
        '../error/ArgumentError',
        './Logger',
        './WWMath'
    ],
    function (Angle,
              ArgumentError,
              Logger,
              WWMath) {
        'use strict';

        /**
         * Provides utilities for determining the Sun geographic and celestial location.
         * @exports SunPosition
         */
        var SunPosition = {

            /**
             * Computes the geographic location of the sun for a given date
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {{latitude: Number, longitude: Number}} the geographic location
             */
            getAsGeographicLocation: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "getAsGeographicLocation",
                            "missingDate"));
                }

                var celestialLocation = this.getAsCelestialLocation(date);
                return this.celestialToGeographic(celestialLocation, date);
            },

            /**
             * Computes the celestial location of the sun for a given julianDate
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {{declination: Number, rightAscension: Number}} the celestial location
             */
            getAsCelestialLocation: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "getAsCelestialLocation",
                            "missingDate"));
                }

                var julianDate = this.computeJulianDate(date);

                //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
                var numDays = julianDate - 2451545;

                var meanLongitude = WWMath.normalizeAngle360(280.460 + 0.9856474 * numDays);

                var meanAnomaly = WWMath.normalizeAngle360(357.528 + 0.9856003 * numDays) * Angle.DEGREES_TO_RADIANS;

                var eclipticLongitude = meanLongitude + 1.915 * Math.sin(meanAnomaly) + 0.02 * Math.sin(2 * meanAnomaly);
                var eclipticLongitudeRad = eclipticLongitude * Angle.DEGREES_TO_RADIANS;

                var obliquityOfTheEcliptic = (23.439 - 0.0000004 * numDays) * Angle.DEGREES_TO_RADIANS;

                var declination = Math.asin(Math.sin(obliquityOfTheEcliptic) * Math.sin(eclipticLongitudeRad)) *
                    Angle.RADIANS_TO_DEGREES;

                var rightAscension = Math.atan(Math.cos(obliquityOfTheEcliptic) * Math.tan(eclipticLongitudeRad)) *
                    Angle.RADIANS_TO_DEGREES;

                //compensate for atan result
                if (eclipticLongitude >= 90 && eclipticLongitude < 270) {
                    rightAscension += 180;
                }
                rightAscension = WWMath.normalizeAngle360(rightAscension);

                return {
                    declination: declination,
                    rightAscension: rightAscension
                };
            },

            /**
             * Converts from celestial coordinates (declination and right ascension) to geographic coordinates
             * (latitude, longitude) for a given julian date
             * @param {{declination: Number, rightAscension: Number}} celestialLocation
             * @param {Date} date
             * @throws {ArgumentError} if celestialLocation or julianDate are missing
             * @return {{latitude: Number, longitude: Number}} the geographic location
             */
            celestialToGeographic: function (celestialLocation, date) {
                if (!celestialLocation) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "celestialToGeographic",
                            "missingCelestialLocation"));
                }
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "celestialToGeographic", "missingDate"));
                }

                var julianDate = this.computeJulianDate(date);

                //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
                var numDays = julianDate - 2451545;

                //Greenwich Mean Sidereal Time
                var GMST = WWMath.normalizeAngle360(280.46061837 + 360.98564736629 * numDays);

                //Greenwich Hour Angle
                var GHA = WWMath.normalizeAngle360(GMST - celestialLocation.rightAscension);

                var longitude = Angle.normalizedDegreesLongitude(-GHA);

                return {
                    latitude: celestialLocation.declination,
                    longitude: longitude
                };
            },

            /**
             * Computes the julian date from a javascript date object
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {Number} the julian date
             */
            computeJulianDate: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "computeJulianDate", "missingDate"));
                }

                var year = date.getUTCFullYear();
                var month = date.getUTCMonth() + 1;
                var day = date.getUTCDate();
                var hour = date.getUTCHours();
                var minute = date.getUTCMinutes();
                var second = date.getUTCSeconds();

                var dayFraction = (hour + minute / 60 + second / 3600) / 24;

                if (month <= 2) {
                    year -= 1;
                    month += 12;
                }

                var A = Math.floor(year / 100);
                var B = 2 - A + Math.floor(A / 4);
                var JD0h = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5;

                return JD0h + dayFraction;
            }

        };

        return SunPosition;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/WWUtil',[
        '../error/ArgumentError',
        '../geom/Line',
        '../util/Logger',
        '../geom/Rectangle',
        '../geom/Vec3'],
    function (ArgumentError,
              Line,
              Logger,
              Rectangle,
              Vec3) {
        "use strict";
        /**
         * Provides math constants and functions.
         * @exports WWUtil
         */
        var WWUtil = {
            // A regular expression that matches latitude followed by a comma and possible white space followed by
            // longitude. Latitude and longitude ranges are not considered.
            latLonRegex: /^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/,

            /**
             * Returns the suffix for a specified mime type.
             * @param {String} mimeType The mime type to determine a suffix for.
             * @returns {String} The suffix for the specified mime type, or null if the mime type is not recognized.
             */
            suffixForMimeType: function (mimeType) {
                if (mimeType === "image/png")
                    return "png";

                if (mimeType === "image/jpeg")
                    return "jpg";

                if (mimeType === "application/bil16")
                    return "bil";

                if (mimeType === "application/bil32")
                    return "bil";

                return null;
            },

            /**
             * Returns the current location URL as obtained from window.location with the last path component
             * removed.
             * @returns {String} The current location URL with the last path component removed.
             */
            currentUrlSansFilePart: function () {
                var protocol = window.location.protocol,
                    host = window.location.host,
                    path = window.location.pathname,
                    pathParts = path.split("/"),
                    newPath = "";

                for (var i = 0, len = pathParts.length; i < len - 1; i++) {
                    if (pathParts[i].length > 0) {
                        newPath = newPath + "/" + pathParts[i];
                    }
                }

                return protocol + "//" + host + newPath;
            },

            /**
             * Returns the URL of the directory containing the WorldWind library.
             * @returns {String} The URL of the directory containing the WorldWind library, or null if that directory
             * cannot be determined.
             */
            worldwindlibLocation: function () {
                var scripts = document.getElementsByTagName("script"),
                    libraryName = "/worldwind.";

                for (var i = 0; i < scripts.length; i++) {
                    var index = scripts[i].src.indexOf(libraryName);
                    if (index >= 0) {
                        return scripts[i].src.substring(0, index) + "/";
                    }
                }

                return null;
            },

            /**
             * Returns the path component of a specified URL.
             * @param {String} url The URL from which to determine the path component.
             * @returns {String} The path component, or the empty string if the specified URL is null, undefined
             * or empty.
             */
            urlPath: function (url) {
                if (!url)
                    return "";

                var urlParts = url.split("/"),
                    newPath = "";

                for (var i = 0, len = urlParts.length; i < len; i++) {
                    var part = urlParts[i];

                    if (!part || part.length === 0
                        || part.indexOf(":") != -1
                        || part === "."
                        || part === ".."
                        || part === "null"
                        || part === "undefined") {
                        continue;
                    }

                    if (newPath.length !== 0) {
                        newPath = newPath + "/";
                    }

                    newPath = newPath + part;
                }

                return newPath;
            },

            /**
             * Sets each element of an array to a specified value. This function is intentionally generic, and works
             * with any data structure with a length property whose elements may be referenced using array index syntax.
             * @param array The array to fill.
             * @param {*} value The value to assign to each array element.
             */
            fillArray: function (array, value) {
                if (!array) {
                    return;
                }

                for (var i = 0, len = array.length; i < len; i++) {
                    array[i] = value;
                }
            },

            /**
             * Multiplies each element of an array by a specified value and assigns each element to the result. This
             * function is intentionally generic, and works with any data structure with a length property whose
             * elements may be referenced using array index syntax.
             * @param array The array to fill.
             * @param {*} value The value to multiply by each array element.
             */
            multiplyArray: function (array, value) {
                if (!array) {
                    return;
                }

                for (var i = 0, len = array.length; i < len; i++) {
                    array[i] *= value;
                }
            },

            // Used to form unique function names for JSONP callback functions.
            jsonpCounter: 0,

            /**
             * Request a resource using JSONP.
             * @param {String} url The url to receive the request.
             * @param {String} parameterName The JSONP callback function key required by the server. Typically
             * "jsonp" or "callback".
             * @param {Function} callback The function to invoke when the request succeeds. The function receives
             * one argument, the JSON payload of the JSONP request.
             */
            jsonp: function (url, parameterName, callback) {

                // Generate a unique function name for the JSONP callback.
                var functionName = "gov_nasa_worldwind_jsonp_" + WWUtil.jsonpCounter++;

                // Define a JSONP callback function. Assign it to global scope the browser can find it.
                window[functionName] = function (jsonData) {
                    // Remove the JSONP callback from global scope.
                    delete window[functionName];

                    // Call the client's callback function.
                    callback(jsonData);
                };

                // Append the callback query parameter to the URL.
                var jsonpUrl = url + (url.indexOf('?') === -1 ? '?' : '&');
                jsonpUrl += parameterName + "=" + functionName;

                // Create a script element for the browser to invoke.
                var script = document.createElement('script');
                script.async = true;
                script.src = jsonpUrl;

                // Prepare to add the script to the document's head.
                var head = document.getElementsByTagName('head')[0];

                // Set up to remove the script element once it's invoked.
                var cleanup = function () {
                    script.onload = undefined;
                    script.onerror = undefined;
                    head.removeChild(script);
                };

                script.onload = cleanup;
                script.onerror = cleanup;

                // Add the script element to the document, causing the browser to invoke it.
                head.appendChild(script);
            },

            arrayEquals: function (array1, array2) {
                return (array1.length == array2.length) && array1.every(function (element, index) {
                        return element === array2[index] || element.equals && element.equals(array2[index]);
                    });
            },

            /**
             * It transforms given item to the boolean. It respects that 0, "0" and "false" are percieved as false
             * on top of the standard Boolean function.
             * @param item {String} Item to transform
             * @returns {boolean} Value transformed to the boolean.
             */
            transformToBoolean: function (item) {
                if (item == 0 || item == "0" || item == "false") {
                    return false;
                } else {
                    return Boolean(item);
                }
            },

            /**
             * It clones original object into the new one. It is necessary to retain the options information valid
             * for all nodes.
             * @param original Object to clone
             * @returns {Object} Cloned object
             */
            clone: function (original) {
                var clone = {};
                var i, keys = Object.keys(original);

                for (i = 0; i < keys.length; i++) {
                    // copy each property into the clone
                    clone[keys[i]] = original[keys[i]];
                }

                return clone;
            },

            /**
             * It returns unique GUID.
             * @returns {string} String representing unique identifier in the application.
             */
            guid: function () {
                function s4() {
                    return Math.floor((1 + Math.random()) * 0x10000)
                        .toString(16)
                        .substring(1);
                }

                return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
                    s4() + '-' + s4() + s4() + s4();
            },

            /**
             * Transforms item to date. It accepts ISO-8601 format.
             * @param item {String} To transform.
             * @returns {Date} Date extracted from the current information.
             */
            date: function(item) {
                return new Date(item);
            },

            /**
             * Determines whether subjectString begins with the characters of searchString.
             * @param {String} subjectString The string to analyse.
             * @param {String} searchString The characters to be searched for at the start of subjectString.
             * @param {Number} position The position in subjectString at which to begin searching for searchString; defaults to 0.
             * @return {Boolean} true if the given characters are found at the beginning of the string; otherwise, false.
             */
            startsWith: function(subjectString, searchString, position) {
                position = position || 0;
                return subjectString.substr(position, searchString.length) === searchString;
            },

            /**
             * Determines whether subjectString ends with the characters of searchString.
             * @param {String} subjectString The string to analyse.
             * @param {String} searchString The characters to be searched for at the end of subjectString.
             * @param {Number} length Optional. If provided overwrites the considered length of the string to search in. If omitted, the default value is the length of the string.
             * @return {Boolean} true if the given characters are found at the end of the string; otherwise, false.
             */
            endsWith: function(subjectString, searchString, length) {
                if (typeof length !== 'number' || !isFinite(length) || Math.floor(length) !== length || length > subjectString.length) {
                    length = subjectString.length;
                }
                length -= searchString.length;
                var lastIndex = subjectString.lastIndexOf(searchString, length);
                return lastIndex !== -1 && lastIndex === length;
            }
        };

        return WWUtil;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AtmosphereLayer
 */
define('layer/AtmosphereLayer',[
        '../error/ArgumentError',
        '../shaders/GroundProgram',
        '../layer/Layer',
        '../util/Logger',
        '../geom/Matrix',
        '../geom/Matrix3',
        '../geom/Sector',
        '../shaders/SkyProgram',
        '../util/SunPosition',
        '../geom/Vec3',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              GroundProgram,
              Layer,
              Logger,
              Matrix,
              Matrix3,
              Sector,
              SkyProgram,
              SunPosition,
              Vec3,
              WWUtil) {
        "use strict";

        /**
         * Constructs a layer showing the Earth's atmosphere.
         * @alias AtmosphereLayer
         * @constructor
         * @classdesc Provides a layer showing the Earth's atmosphere.
         * @param {URL} nightImageSource optional url for the night texture.
         * @augments Layer
         */
        var AtmosphereLayer = function (nightImageSource) {
            Layer.call(this, "Atmosphere");

            // The atmosphere layer is not pickable.
            this.pickEnabled = false;

            //Documented in defineProperties below.
            this._nightImageSource = nightImageSource ||
                WorldWind.configuration.baseUrl + 'images/dnb_land_ocean_ice_2012.png';

            //Internal use only.
            //The light direction in cartesian space, computed from the layer time or defaults to the eyePoint.
            this._activeLightDirection = new Vec3(0, 0, 0);

            this._fullSphereSector = Sector.FULL_SPHERE;

            //Internal use only. Intentionally not documented.
            this._skyData = {};

            //Internal use only. The number of longitudinal points in the grid for the sky geometry.
            this._skyWidth = 128;

            //Internal use only. The number of latitudinal points in the grid for the sky geometry.
            this._skyHeight = 128;

            //Internal use only. Number of indices for the sky geometry.
            this._numIndices = 0;

            //Internal use only. Texture coordinate matrix used for the night texture.
            this._texMatrix = Matrix3.fromIdentity();

            //Internal use only. The night texture.
            this._activeTexture = null;
        };

        AtmosphereLayer.prototype = Object.create(Layer.prototype);

        Object.defineProperties(AtmosphereLayer.prototype, {

            /**
             * Url for the night texture.
             * @memberof AtmosphereLayer.prototype
             * @type {URL}
             */
            nightImageSource: {
                get: function () {
                    return this._nightImageSource;
                },
                set: function (value) {
                    this._nightImageSource = value;
                }
            }

        });

        // Documented in superclass.
        AtmosphereLayer.prototype.doRender = function (dc) {
            if (dc.globe.is2D()) {
                return;
            }

            this.determineLightDirection(dc);
            this.drawSky(dc);
            this.drawGround(dc);
        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.applySkyVertices = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                skyData = this._skyData,
                skyPoints, vboId;

            if (!skyData.verticesVboCacheKey) {
                skyData.verticesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(skyData.verticesVboCacheKey);
            
            if (!vboId) {
                skyPoints = this.assembleVertexPoints(dc, this._skyHeight, this._skyWidth, program.getAltitude());
                
                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, skyPoints, gl.STATIC_DRAW);
                gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
                
                dc.gpuResourceCache.putResource(skyData.verticesVboCacheKey, vboId,
                    skyPoints.length * 4);
                dc.frameStatistics.incrementVboLoadCount(1);
            }
            else {
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
            }

        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.applySkyIndices = function (dc) {
            var gl = dc.currentGlContext,
                skyData = this._skyData,
                skyIndices, vboId;

            if (!skyData.indicesVboCacheKey) {
                skyData.indicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(skyData.indicesVboCacheKey);
            
            if (!vboId) {
                skyIndices = this.assembleTriStripIndices(this._skyWidth, this._skyHeight);
                
                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, skyIndices, gl.STATIC_DRAW);
                
                dc.frameStatistics.incrementVboLoadCount(1);
                dc.gpuResourceCache.putResource(skyData.indicesVboCacheKey, vboId, skyIndices.length * 2);
            }
            else {
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
            }

        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.drawSky = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.findAndBindProgram(SkyProgram);

            program.loadGlobeRadius(gl, dc.globe.equatorialRadius);

            program.loadEyePoint(gl, dc.navigatorState.eyePoint);

            program.loadVertexOrigin(gl, Vec3.ZERO);

            program.loadModelviewProjection(gl, dc.navigatorState.modelviewProjection);

            program.loadLightDirection(gl, this._activeLightDirection);

            program.setScale(gl);

            this.applySkyVertices(dc);
            this.applySkyIndices(dc);

            gl.depthMask(false);
            gl.frontFace(gl.CW);
            gl.enableVertexAttribArray(0);
            gl.drawElements(gl.TRIANGLE_STRIP, this._numIndices, gl.UNSIGNED_SHORT, 0);

            gl.depthMask(true);
            gl.frontFace(gl.CCW);
            gl.disableVertexAttribArray(0);
        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.drawGround = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.findAndBindProgram(GroundProgram),
                terrain = dc.terrain,
                textureBound;

            program.loadGlobeRadius(gl, dc.globe.equatorialRadius);

            program.loadEyePoint(gl, dc.navigatorState.eyePoint);

            program.loadLightDirection(gl, this._activeLightDirection);

            program.setScale(gl);

            // Use this layer's night image when the layer has time value defined
            if (this.nightImageSource && (this.time !== null)) {
                
                this._activeTexture = dc.gpuResourceCache.resourceForKey(this.nightImageSource);
                
                if (!this._activeTexture) {
                    this._activeTexture = dc.gpuResourceCache.retrieveTexture(gl, this.nightImageSource);
                }
                
                textureBound = this._activeTexture && this._activeTexture.bind(dc);
            }

            terrain.beginRendering(dc);

            for (var idx = 0, len = terrain.surfaceGeometry.length; idx < len; idx++) {
                var currentTile = terrain.surfaceGeometry[idx];
                
                // Use the vertex origin for the terrain tile.
                var terrainOrigin = currentTile.referencePoint;
                program.loadVertexOrigin(gl, terrainOrigin);

                // Use a tex coord matrix that registers the night texture correctly on each terrain.
                if (textureBound) {
                    this._texMatrix.setToUnitYFlip();
                    this._texMatrix.multiplyByTileTransform(currentTile.sector, this._fullSphereSector);
                    program.loadTexMatrix(gl, this._texMatrix);
                }

                terrain.beginRenderingTile(dc, currentTile);

                // Draw the tile, multiplying the current fragment color by the program's secondary color.
                program.loadFragMode(gl, program.FRAGMODE_GROUND_SECONDARY);
                gl.blendFunc(gl.DST_COLOR, gl.ZERO);
                terrain.renderTile(dc, currentTile);

                // Draw the terrain as triangles, adding the current fragment color to the program's primary color.
                var fragMode = textureBound ?
                    program.FRAGMODE_GROUND_PRIMARY_TEX_BLEND : program.FRAGMODE_GROUND_PRIMARY;
                program.loadFragMode(gl, fragMode);
                gl.blendFunc(gl.ONE, gl.ONE);
                terrain.renderTile(dc, currentTile);

                terrain.endRenderingTile(dc, currentTile);
            }

            // Restore the default WorldWind OpenGL state.
            terrain.endRendering(dc);
            gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

            // Clear references to Gpu resources.
            this._activeTexture = null;
        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.assembleVertexPoints = function (dc, numLat, numLon, altitude) {
            var count = numLat * numLon;
            var altitudes = new Array(count);
            WWUtil.fillArray(altitudes, altitude);
            var result = new Float32Array(count * 3);

            return dc.globe.computePointsForGrid(this._fullSphereSector, numLat, numLon, altitudes, Vec3.ZERO, result);
        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.assembleTriStripIndices = function (numLat, numLon) {
            var result = [];
            var vertex = 0;

            for (var latIndex = 0; latIndex < numLat - 1; latIndex++) {
                // Create a triangle strip joining each adjacent column of vertices, starting in the bottom left corner and
                // proceeding to the right. The first vertex starts with the left row of vertices and moves right to create
                // a counterclockwise winding order.
                for (var lonIndex = 0; lonIndex < numLon; lonIndex++) {
                    vertex = lonIndex + latIndex * numLon;
                    result.push(vertex + numLon);
                    result.push(vertex);
                }

                // Insert indices to create 2 degenerate triangles:
                // - one for the end of the current row, and
                // - one for the beginning of the next row
                if (latIndex < numLat - 2) {
                    result.push(vertex);
                    result.push((latIndex + 2) * numLon);
                }
            }

            this._numIndices = result.length;
            
            return new Uint16Array(result);
        };

        // Internal. Intentionally not documented.
        AtmosphereLayer.prototype.determineLightDirection = function (dc) {
            if (this.time !== null) {
                var sunLocation = SunPosition.getAsGeographicLocation(this.time);
                dc.globe.computePointFromLocation(sunLocation.latitude, sunLocation.longitude,
                    this._activeLightDirection);
            } else {
                this._activeLightDirection.copy(dc.navigatorState.eyePoint);
            }
            this._activeLightDirection.normalize();
        };

        return AtmosphereLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BasicProgram
 */
define('shaders/BasicProgram',[
        '../error/ArgumentError',
        '../util/Color',
        '../shaders/GpuProgram',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              GpuProgram,
              Logger) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
         * method then compiles the shaders and then links the program if compilation is successful. Use the bind method to make the
         * program current during rendering.
         *
         * @alias BasicProgram
         * @constructor
         * @augments GpuProgram
         * @classdesc BasicProgram is a GLSL program that draws geometry in a solid color.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var BasicProgram = function (gl) {
            var vertexShaderSource =
                    'attribute vec4 vertexPoint;\n' +
                    'uniform mat4 mvpMatrix;\n' +
                    'void main() {gl_Position = mvpMatrix * vertexPoint;}',
                fragmentShaderSource =
                    'precision mediump float;\n' +
                    'uniform vec4 color;\n' +
                    'void main() {gl_FragColor = color;}';

            // Call to the superclass, which performs shader program compiling and linking.
            GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource);

            /**
             * The WebGL location for this program's 'vertexPoint' attribute.
             * @type {Number}
             * @readonly
             */
            this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");

            /**
             * The WebGL location for this program's 'mvpMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");

            /**
             * The WebGL location for this program's 'color' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.colorLocation = this.uniformLocation(gl, "color");
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        BasicProgram.key = "WorldWindGpuBasicProgram";

        // Inherit from GpuProgram.
        BasicProgram.prototype = Object.create(GpuProgram.prototype);

        /**
         * Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        BasicProgram.prototype.loadModelviewProjection = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicProgram", "loadModelviewProjection", "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
        };

        /**
         * Loads the specified color as the value of this program's 'color' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Color} color The color to load.
         * @throws {ArgumentError} If the specified color is null or undefined.
         */
        BasicProgram.prototype.loadColor = function (gl, color) {
            if (!color) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicProgram", "loadColor", "missingColor"));
            }

            this.loadUniformColor(gl, color, this.colorLocation);
        };

        /**
         * Loads the specified RGBA color components as the value of this program's 'color' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} red The red component, a number between 0 and 1.
         * @param {Number} green The green component, a number between 0 and 1.
         * @param {Number} blue The blue component, a number between 0 and 1.
         * @param {Number} alpha The alpha component, a number between 0 and 1.
         */
        BasicProgram.prototype.loadColorComponents = function (gl, red, green, blue, alpha) {
            this.loadUniformColorComponents(gl, red, green, blue, alpha, this.colorLocation);
        };

        return BasicProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BasicTimeSequence
 */

define('util/BasicTimeSequence',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError, Logger) {
        "use strict";

        /**
         * Constructs a time sequence from an array of Dates.
         * @alias BasicTimeSequence
         * @constructor
         * @classdesc Represents a time sequence described as an array of Date objects as required by WMS.
         * This class provides iteration over the sequence in steps
         * specified by the period. If the start and end dates are different, iteration will start at the start
         * date and end at the end date.
         * @param {Date[]} dates An array of Date objects.
         * @throws {ArgumentError} If the specified dates array is null, undefined or has a length less than two.
         */

        var BasicTimeSequence = function (dates) {

            if (!dates && dates.length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BasicTimeSequence", "constructor", "missingDates"));
            }

            /**
             * This sequence's list of Dates.
             * @type {Date[]}
             */
            this.dates = dates;

            /**
             * This sequence's current index.
             * @type {Number}
             * @default 0.
             */
            this.currentIndex = 0;

            /**
             * This sequence's current time.
             * @type {Date}
             * @default This sequence's start time.
             */
            this.currentTime = dates[0];
        };

        Object.defineProperties(BasicTimeSequence.prototype, {
            /**
             * Indicates the position of this sequence's current time relative to the sequence's total interval,
             * in the range [0, 1]. A value of 0 indicates this sequence's start time. A value of 1 indicates
             * this sequence's end time. A value of 0.5 indicates a current time that's exactly mid-way between
             * this sequence's start time and end time.
             * @type {Number}
             * @memberof BasicTimeSequence.prototype
             */
            scaleForCurrentTime: {
                get: function () {
                    if (!this.currentTime) {
                        return 1;
                    }
                    else {
                        return (this.currentIndex / this.dates.length);
                    }
                }
            }

        });

        /**
         * Sets this sequence's current time to the next time in the sequence and returns that time.
         * @returns {Date|null} The next time of this sequence, or null if no more times are in the sequence.
         * Use [reset]{@link BasicTimeSequence#reset} to re-start this sequence.
         * Use [previous]{@link BasicTimeSequence#previous} to step backwards through this sequence.
         */
        BasicTimeSequence.prototype.next = function () {

            if (this.currentIndex >= this.dates.length - 1) {
                return null;
            }

            this.currentIndex++;
            this.currentTime = this.dates[this.currentIndex];

            return this.currentTime;
        };

        /**
         * Sets this sequence's current time to the previous time in the sequence and returns that time.
         * @returns {Date|null} The previous time of this sequence, or null if the sequence is currently at its start
         * time.
         * Use [next]{@link BasicTimeSequence#next} to step forwards through this sequence.
         */
        BasicTimeSequence.prototype.previous = function () {

            if (this.currentIndex <= 0) {
                return null;
            }

            this.currentIndex--;
            this.currentTime = this.dates[this.currentIndex];

            return this.currentTime;
        };

        /**
         * Resets this sequence's current time to its start time.
         * Use [next]{@link BasicTimeSequence#next} to step forwards through this sequence.
         * Use [previous]{@link BasicTimeSequence#previous} to step backwards through this sequence.
         */
        BasicTimeSequence.prototype.reset = function () {
            this.currentIndex = -1;
            this.currentTime = null;
        };

        /**
         * Returns the time associated with a specified value in the range [0, 1]. A value of 0 returns this
         * sequence's start time. A value of 1 returns this sequence's end time. A value of 0.5 returs a time
         * mid-way between this sequence's start and end times.
         * @param scale The scale value. This value is clamped to the range [0, 1] before the time is determined.
         * @returns {Date}
         */
        BasicTimeSequence.prototype.getTimeForScale = function (scale) {

            if (scale <= 0) {
                this.currentIndex = 0;
            }
            else if (scale >= 1) {
                this.currentIndex = this.dates.length - 1;
            }
            else {
                this.currentIndex = Math.floor(this.dates.length * scale);
            }

            this.currentTime = this.dates[this.currentIndex];

            return this.currentTime;

        };

        return BasicTimeSequence;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AbsentResourceList
 */
define('util/AbsentResourceList',[],
    function () {
        "use strict";

        /**
         * Constructs an absent resource list.
         * @alias AbsentResourceList
         * @constructor
         * @classdesc Provides a collection to keep track of resources whose retrieval failed and when retrieval
         * may be tried again. Applications typically do not use this class directly.
         * @param {Number} maxTrys The number of attempts to make before the resource is marked as absent.
         * @param {Number} minCheckInterval The amount of time to wait between attempts, in milliseconds.
         * @constructor
         */
        var AbsentResourceList = function (maxTrys, minCheckInterval) {

            /**
             * The number  of attempts to make before the resource is marked as absent.
             * @type {Number}
             */
            this.maxTrys = maxTrys;

            /**
             * The amount of time to wait before each attempt.
             * @type {Number}
             */
            this.minCheckInterval = minCheckInterval;

            /**
             * The amount of time, in milliseconds, beyond which retrieval attempts should again be allowed.
             * When this time has elapsed from the most recent failed attempt the number of trys attempted is
             * reset to 0. This prevents the resource from being permanently blocked.
             * @type {number}
             * @default 60,000 milliseconds (one minute)
             */
            this.tryAgainInterval = 60e3; // 60 seconds

            this.possiblyAbsent = {};
        };

        /**
         * Indicates whether a specified resource is marked as absent.
         * @param {String} resourceId The resource identifier.
         * @returns {Boolean} true if the resource is marked as absent, otherwise false.
         */
        AbsentResourceList.prototype.isResourceAbsent = function (resourceId) {
            var entry = this.possiblyAbsent[resourceId];

            if (!entry) {
                return false;
            }

            if (entry.permanent) {
                return true;
            }

            var timeSinceLastMark = Date.now() - entry.timeOfLastMark;

            if (timeSinceLastMark > this.tryAgainInterval) {
                delete this.possiblyAbsent[resourceId];
                return false;
            }

            return timeSinceLastMark < this.minCheckInterval || entry.numTrys > this.maxTrys;
        };

        /**
         * Marks a resource attempt as having failed. This increments the number-of-tries counter and sets the time
         * of the last attempt. When this method has been called [this.maxTrys]{@link AbsentResourceList#maxTrys}
         * times the resource is marked as absent until this absent resource list's
         * [try-again-interval]{@link AbsentResourceList#tryAgainInterval} is reached.
         * @param {String} resourceId The resource identifier.
         */
        AbsentResourceList.prototype.markResourceAbsent = function (resourceId) {
            var entry = this.possiblyAbsent[resourceId];

            if (!entry) {
                entry = {
                    timeOfLastMark: Date.now(),
                    numTrys: 0
                };
                this.possiblyAbsent[resourceId] = entry;
            }

            entry.numTrys = entry.numTrys + 1;
            entry.timeOfLastMark = Date.now();
        };

        /**
         * Marks a resource attempt as having failed permanently. No attempt will ever again be made to retrieve
         * the resource.
         * @param {String} resourceId The resource identifier.
         */
        AbsentResourceList.prototype.markResourceAbsentPermanently = function (resourceId) {
            var entry = this.possiblyAbsent[resourceId];

            if (!entry) {
                entry = {
                    timeOfLastMark: Date.now(),
                    numTrys: 0
                };
                this.possiblyAbsent[resourceId] = entry;
            }

            entry.numTrys = entry.numTrys + 1;
            entry.timeOfLastMark = Date.now();
            entry.permanent = true;
        };

        /**
         * Removes the specified resource from this absent resource list. Call this method when retrieval attempts
         * succeed.
         * @param {String} resourceId The resource identifier.
         */
        AbsentResourceList.prototype.unmarkResourceAbsent = function (resourceId) {
            var entry = this.possiblyAbsent[resourceId];

            if (entry) {
                delete this.possiblyAbsent[resourceId];
            }
        };

        return AbsentResourceList;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Frustum
 */
define('geom/Frustum',[
        '../error/ArgumentError',
        '../geom/Matrix',
        '../geom/Plane',
        '../util/Logger'
    ],
    function (ArgumentError,
              Matrix,
              Plane,
              Logger) {
        "use strict";

        /**
         * Constructs a frustum.
         * @alias Frustum
         * @constructor
         * @classdesc Represents a six-sided view frustum in Cartesian coordinates.
         * @param {Plane} left The frustum's left plane.
         * @param {Plane} right The frustum's right plane.
         * @param {Plane} bottom The frustum's bottom plane.
         * @param {Plane} top The frustum's top plane.
         * @param {Plane} near The frustum's near plane.
         * @param {Plane} far The frustum's far plane.
         * @throws {ArgumentError} If any specified plane is null or undefined.
         */
        var Frustum = function (left, right, bottom, top, near, far) {
            if (!left || !right || !bottom || !top || !near || !far) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "constructor", "missingPlane"));
            }

            // Internal. Intentionally not documented. See property accessors below for public interface.
            this._left = left;
            this._right = right;
            this._bottom = bottom;
            this._top = top;
            this._near = near;
            this._far = far;

            // Internal. Intentionally not documented.
            this._planes = [this._left, this._right, this._top, this._bottom, this._near, this._far];
        };

        // These accessors are defined in order to prevent changes that would make the properties inconsistent with the
        // planes array.
        Object.defineProperties(Frustum.prototype, {
            /**
             * This frustum's left plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            left: {
                get: function() {
                    return this._left;
                }
            },
            /**
             * This frustum's right plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            right: {
                get: function() {
                    return this._right;
                }
            },
            /**
             * This frustum's bottom plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            bottom: {
                get: function() {
                    return this._bottom;
                }
            },
            /**
             * This frustum's top plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            top: {
                get: function() {
                    return this._top;
                }
            },
            /**
             * This frustum's near plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            near: {
                get: function() {
                    return this._near;
                }
            },
            /**
             * This frustum's far plane.
             * @memberof Frustum.prototype
             * @type {Plane}
             * @readonly
             */
            far: {
                get: function() {
                    return this._far;
                }
            }
        });

        /**
         * Transforms this frustum by a specified matrix.
         * @param {Matrix} matrix The matrix to apply to this frustum.
         * @returns {Frustum} This frustum set to its original value multiplied by the specified matrix.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Frustum.prototype.transformByMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "transformByMatrix", "missingMatrix"));
            }

            this._left.transformByMatrix(matrix);
            this._right.transformByMatrix(matrix);
            this._bottom.transformByMatrix(matrix);
            this._top.transformByMatrix(matrix);
            this._near.transformByMatrix(matrix);
            this._far.transformByMatrix(matrix);

            return this;
        };

        /**
         * Normalizes the plane vectors of the planes composing this frustum.
         * @returns {Frustum} This frustum with its planes normalized.
         */
        Frustum.prototype.normalize = function () {
            this._left.normalize();
            this._right.normalize();
            this._bottom.normalize();
            this._top.normalize();
            this._near.normalize();
            this._far.normalize();

            return this;
        };

        /**
         * Returns a new frustum with each of its planes 1 meter from the center.
         * @returns {Frustum} The new frustum.
         */
        Frustum.unitFrustum = function () {
            return new Frustum(
                new Plane(1, 0, 0, 1), // left
                new Plane(-1, 0, 0, 1), // right
                new Plane(0, 1, 1, 1), // bottom
                new Plane(0, -1, 0, 1), // top
                new Plane(0, 0, -1, 1), // near
                new Plane(0, 0, 1, 1) // far
            );
        };

        /**
         * Extracts a frustum from a projection matrix.
         * <p>
         * This method assumes that the specified matrix represents a projection matrix. If it does not represent a projection matrix
         * the results are undefined.
         * <p>
         * A projection matrix's view frustum is a Cartesian volume that contains everything visible in a scene displayed
         * using that projection matrix.
         *
         * @param {Matrix} matrix The projection matrix to extract the frustum from.
         * @return {Frustum} A new frustum containing the projection matrix's view frustum, in eye coordinates.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Frustum.fromProjectionMatrix = function (matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "fromProjectionMatrix", "missingMatrix"));
            }

            var x, y, z, w, d, left, right, top, bottom, near, far;

            // Left Plane = row 4 + row 1:
            x = matrix[12] + matrix[0];
            y = matrix[13] + matrix[1];
            z = matrix[14] + matrix[2];
            w = matrix[15] + matrix[3];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            left = new Plane(x / d, y / d, z / d, w / d);

            // Right Plane = row 4 - row 1:
            x = matrix[12] - matrix[0];
            y = matrix[13] - matrix[1];
            z = matrix[14] - matrix[2];
            w = matrix[15] - matrix[3];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            right = new Plane(x / d, y / d, z / d, w / d);

            // Bottom Plane = row 4 + row 2:
            x = matrix[12] + matrix[4];
            y = matrix[13] + matrix[5];
            z = matrix[14] + matrix[6];
            w = matrix[15] + matrix[7];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            bottom = new Plane(x / d, y / d, z / d, w / d);

            // Top Plane = row 4 - row 2:
            x = matrix[12] - matrix[4];
            y = matrix[13] - matrix[5];
            z = matrix[14] - matrix[6];
            w = matrix[15] - matrix[7];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            top = new Plane(x / d, y / d, z / d, w / d);

            // Near Plane = row 4 + row 3:
            x = matrix[12] + matrix[8];
            y = matrix[13] + matrix[9];
            z = matrix[14] + matrix[10];
            w = matrix[15] + matrix[11];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            near = new Plane(x / d, y / d, z / d, w / d);

            // Far Plane = row 4 - row 3:
            x = matrix[12] - matrix[8];
            y = matrix[13] - matrix[9];
            z = matrix[14] - matrix[10];
            w = matrix[15] - matrix[11];
            d = Math.sqrt(x * x + y * y + z * z); // for normalizing the coordinates
            far = new Plane(x / d, y / d, z / d, w / d);

            return new Frustum(left, right, bottom, top, near, far);
        };

        Frustum.prototype.containsPoint = function (point) {
            if (!point) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "containsPoint", "missingPoint"));
            }

            // See if the point is entirely within the frustum. The dot product of the point with each plane's vector
            // provides a distance to each plane. If this distance is less than 0, the point is clipped by that plane and
            // neither intersects nor is contained by the space enclosed by this Frustum.

            if (this._far.dot(point) <= 0)
                return false;
            if (this._left.dot(point) <= 0)
                return false;
            if (this._right.dot(point) <= 0)
                return false;
            if (this._top.dot(point) <= 0)
                return false;
            if (this._bottom.dot(point) <= 0)
                return false;
            if (this._near.dot(point) <= 0)
                return false;

            return true;
        };

        /**
         * Determines whether a line segment intersects this frustum.
         *
         * @param {Vec3} pointA One end of the segment.
         * @param {Vec3} pointB The other end of the segment.
         *
         * @return {boolean} <code>true</code> if the segment intersects or is contained in this frustum,
         * otherwise <code>false</code>.
         *
         * @throws {ArgumentError} If either point is null or undefined.
         */
        Frustum.prototype.intersectsSegment = function (pointA, pointB) {
            if (!pointA || !pointB) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Frustum", "containsPoint", "missingPoint"));
            }

            // First do a trivial accept test.
            if (this.containsPoint(pointA) || this.containsPoint(pointB))
                return true;

            if (pointA.equals(pointB))
                return false;

            for (var i = 0, len = this._planes.length; i < len; i++) {

                // See if both points are behind the plane and therefore not in the frustum.
                if (this._planes[i].onSameSide(pointA, pointB) < 0)
                    return false;

                // See if the segment intersects the plane.
                if (this._planes[i].clip(pointA, pointB) != null)
                    return true;
            }

            return false; // segment does not intersect frustum
        };

        return Frustum;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports NotYetImplementedError
 */
define('error/NotYetImplementedError',['../error/AbstractError'],
    function (AbstractError) {
        "use strict";

        /**
         * Constructs a not-yet-implemented error with a specified message.
         * @alias NotYetImplementedError
         * @constructor
         * @classdesc Represents an error associated with an operation that is not yet implemented.
         * @augments AbstractError
         * @param {String} message The message.
         */
        var NotYetImplementedError = function (message) {
            AbstractError.call(this, "NotYetImplementedError", message);

            var stack;
            try {
                //noinspection ExceptionCaughtLocallyJS
                throw new Error();
            } catch (e) {
                stack = e.stack;
            }
            this.stack = stack;
        };

        NotYetImplementedError.prototype = Object.create(AbstractError.prototype);

        return NotYetImplementedError;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BoundingBox
 */
define('geom/BoundingBox',[
        '../error/ArgumentError',
        '../shaders/BasicProgram',
        '../geom/Frustum',
        '../util/Logger',
        '../geom/Matrix',
        '../error/NotYetImplementedError',
        '../geom/Plane',
        '../geom/Sector',
        '../geom/Vec3',
        '../util/WWMath',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              BasicProgram,
              Frustum,
              Logger,
              Matrix,
              NotYetImplementedError,
              Plane,
              Sector,
              Vec3,
              WWMath,
              WWUtil) {
        "use strict";

        /**
         * Constructs a unit bounding box.
         * The unit box has its R, S and T axes aligned with the X, Y and Z axes, respectively, and has its length,
         * width and height set to 1.
         * @alias BoundingBox
         * @constructor
         * @classdesc Represents a bounding box in Cartesian coordinates. Typically used as a bounding volume.
         */
        var BoundingBox = function () {

            /**
             * The box's center point.
             * @type {Vec3}
             * @default (0, 0, 0)
             */
            this.center = new Vec3(0, 0, 0);

            /**
             * The center point of the box's bottom. (The origin of the R axis.)
             * @type {Vec3}
             * @default (-0.5, 0, 0)
             */
            this.bottomCenter = new Vec3(-0.5, 0, 0);

            /**
             * The center point of the box's top. (The end of the R axis.)
             * @type {Vec3}
             * @default (0.5, 0, 0)
             */
            this.topCenter = new Vec3(0.5, 0, 0);

            /**
             * The box's R axis, its longest axis.
             * @type {Vec3}
             * @default (1, 0, 0)
             */
            this.r = new Vec3(1, 0, 0);

            /**
             * The box's S axis, its mid-length axis.
             * @type {Vec3}
             * @default (0, 1, 0)
             */
            this.s = new Vec3(0, 1, 0);

            /**
             * The box's T axis, its shortest axis.
             * @type {Vec3}
             * @default (0, 0, 1)
             */
            this.t = new Vec3(0, 0, 1);

            /**
             * The box's radius. (The half-length of its diagonal.)
             * @type {number}
             * @default sqrt(3)
             */
            this.radius = Math.sqrt(3);

            // Internal use only. Intentionally not documented.
            this.tmp1 = new Vec3(0, 0, 0);
            this.tmp2 = new Vec3(0, 0, 0);
            this.tmp3 = new Vec3(0, 0, 0);

            // Internal use only. Intentionally not documented.
            this.scratchElevations = new Float64Array(9);
            this.scratchPoints = new Float64Array(3 * this.scratchElevations.length);
        };

        // Internal use only. Intentionally not documented.
        BoundingBox.scratchMatrix = Matrix.fromIdentity();

        /**
         * Returns the eight {@link Vec3} corners of the box.
         *
         * @returns {Array} the eight box corners in the order bottom-lower-left, bottom-lower-right, bottom-upper-right,
         *         bottom-upper-left, top-lower-left, top-lower-right, top-upper-right, top-upper-left.
         */
        BoundingBox.prototype.getCorners = function () {
            var ll = new Vec3(this.s[0], this.s[1], this.s[2]);
            var lr = new Vec3(this.t[0], this.t[1], this.t[2]);
            var ur = new Vec3(this.s[0], this.s[1], this.s[2]);
            var ul = new Vec3(this.s[0], this.s[1], this.s[2]);

            ll.add(this.t).multiply(-0.5);     // Lower left.
            lr.subtract(this.s).multiply(0.5); // Lower right.
            ur.add(this.t).multiply(0.5);      // Upper right.
            ul.subtract(this.t).multiply(0.5); // Upper left.

            var corners = [];
            for (var i = 0; i < 4; i++) {
                corners.push(new Vec3(this.bottomCenter[0], this.bottomCenter[1], this.bottomCenter[2]));
            }

            for (i = 0; i < 4; i++) {
                corners.push(new Vec3(this.topCenter[0], this.topCenter[1], this.topCenter[2]));
            }

            corners[0].add(ll);
            corners[1].add(lr);
            corners[2].add(ur);
            corners[3].add(ul);
            corners[4].add(ll);
            corners[5].add(lr);
            corners[6].add(ur);
            corners[7].add(ul);

            return corners;
        };

        /**
         * Sets this bounding box such that it minimally encloses a specified collection of points.
         * @param {Float32Array} points The points to contain.
         * @returns {BoundingBox} This bounding box set to contain the specified points.
         * @throws {ArgumentError} If the specified list of points is null, undefined or empty.
         */
        BoundingBox.prototype.setToPoints = function (points) {
            if (!points || points.length < 3) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToPoints", "missingArray"));
            }

            var rMin = +Number.MAX_VALUE,
                rMax = -Number.MAX_VALUE,
                sMin = +Number.MAX_VALUE,
                sMax = -Number.MAX_VALUE,
                tMin = +Number.MAX_VALUE,
                tMax = -Number.MAX_VALUE,
                r = this.r, s = this.s, t = this.t,
                p = new Vec3(0, 0, 0),
                pdr, pds, pdt, rLen, sLen, tLen, rSum, sSum, tSum,
                rx_2, ry_2, rz_2, cx, cy, cz;

            Matrix.principalAxesFromPoints(points, r, s, t);

            for (var i = 0, len = points.length / 3; i < len; i++) {
                p[0] = points[i * 3];
                p[1] = points[i * 3 + 1];
                p[2] = points[i * 3 + 2];

                pdr = p.dot(r);
                if (rMin > pdr)
                    rMin = pdr;
                if (rMax < pdr)
                    rMax = pdr;

                pds = p.dot(s);
                if (sMin > pds)
                    sMin = pds;
                if (sMax < pds)
                    sMax = pds;

                pdt = p.dot(t);
                if (tMin > pdt)
                    tMin = pdt;
                if (tMax < pdt)
                    tMax = pdt;
            }

            if (rMax === rMin)
                rMax = rMin + 1;
            if (sMax === sMin)
                sMax = sMin + 1;
            if (tMax === tMin)
                tMax = tMin + 1;

            rLen = rMax - rMin;
            sLen = sMax - sMin;
            tLen = tMax - tMin;
            rSum = rMax + rMin;
            sSum = sMax + sMin;
            tSum = tMax + tMin;

            rx_2 = 0.5 * r[0] * rLen;
            ry_2 = 0.5 * r[1] * rLen;
            rz_2 = 0.5 * r[2] * rLen;

            cx = 0.5 * (r[0] * rSum + s[0] * sSum + t[0] * tSum);
            cy = 0.5 * (r[1] * rSum + s[1] * sSum + t[1] * tSum);
            cz = 0.5 * (r[2] * rSum + s[2] * sSum + t[2] * tSum);

            this.center[0] = cx;
            this.center[1] = cy;
            this.center[2] = cz;

            this.topCenter[0] = cx + rx_2;
            this.topCenter[1] = cy + ry_2;
            this.topCenter[2] = cz + rz_2;

            this.bottomCenter[0] = cx - rx_2;
            this.bottomCenter[1] = cy - ry_2;
            this.bottomCenter[2] = cz - rz_2;

            r.multiply(rLen);
            s.multiply(sLen);
            t.multiply(tLen);

            this.radius = 0.5 * Math.sqrt(rLen * rLen + sLen * sLen + tLen * tLen);

            return this;
        };

        /**
         * Sets this bounding box such that it minimally encloses a specified collection of points.
         * @param {Vec3} points The points to contain.
         * @returns {BoundingBox} This bounding box set to contain the specified points.
         * @throws {ArgumentError} If the specified list of points is null, undefined or empty.
         */
        BoundingBox.prototype.setToVec3Points = function (points) {
            if (!points || points.length === 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToVec3Points", "missingArray"));
            }

            var pointList = new Float32Array(points.length * 3);
            for (var i = 0; i < points.length; i++) {
                var point = points[i];
                for (var j = 0; j < 3; j++) {
                    pointList[i * 3 + j] = point[j];
                }
            }

            return this.setToPoints(pointList);
        };

        /**
         * Sets this bounding box such that it contains a specified sector on a specified globe with min and max elevation.
         * <p>
         * To create a bounding box that contains the sector at mean sea level, specify zero for the minimum and maximum
         * elevations.
         * To create a bounding box that contains the terrain surface in this sector, specify the actual minimum and maximum
         * elevation values associated with the sector, multiplied by the model's vertical exaggeration.
         * @param {Sector} sector The sector for which to create the bounding box.
         * @param {Globe} globe The globe associated with the sector.
         * @param {Number} minElevation The minimum elevation within the sector.
         * @param {Number} maxElevation The maximum elevation within the sector.
         * @returns {BoundingBox} This bounding box set to contain the specified sector.
         * @throws {ArgumentError} If either the specified sector or globe is null or undefined.
         */
        BoundingBox.prototype.setToSector = function (sector, globe, minElevation, maxElevation) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToSector", "missingSector"));
            }

            if (!globe) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "setToSector", "missingGlobe"));
            }

            // Compute the cartesian points for a 3x3 geographic grid. This grid captures enough detail to bound the
            // sector. Use minimum elevation at the corners and max elevation everywhere else.
            var elevations = this.scratchElevations,
                points = this.scratchPoints;

            WWUtil.fillArray(elevations, maxElevation);
            elevations[0] = elevations[2] = elevations[6] = elevations[8] = minElevation;
            globe.computePointsForGrid(sector, 3, 3, elevations, Vec3.ZERO, points);

            // Compute the local coordinate axes. Since we know this box is bounding a geographic sector, we use the
            // local coordinate axes at its centroid as the box axes. Using these axes results in a box that has +-10%
            // the volume of a box with axes derived from a principal component analysis, but is faster to compute.
            var index = 12; // index to the center point's X coordinate
            this.tmp1.set(points[index], points[index + 1], points[index + 2]);
            WWMath.localCoordinateAxesAtPoint(this.tmp1, globe, this.r, this.s, this.t);

            // Find the extremes along each axis.
            var rExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
                sExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
                tExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];

            for (var i = 0, len = points.length; i < len; i += 3) {
                this.tmp1.set(points[i], points[i + 1], points[i + 2]);
                this.adjustExtremes(this.r, rExtremes, this.s, sExtremes, this.t, tExtremes, this.tmp1);
            }

            // Sort the axes from most prominent to least prominent. The frustum intersection methods in WWBoundingBox assume
            // that the axes are defined in this way.
            if (rExtremes[1] - rExtremes[0] < sExtremes[1] - sExtremes[0]) {
                this.swapAxes(this.r, rExtremes, this.s, sExtremes);
            }
            if (sExtremes[1] - sExtremes[0] < tExtremes[1] - tExtremes[0]) {
                this.swapAxes(this.s, sExtremes, this.t, tExtremes);
            }
            if (rExtremes[1] - rExtremes[0] < sExtremes[1] - sExtremes[0]) {
                this.swapAxes(this.r, rExtremes, this.s, sExtremes);
            }

            // Compute the box properties from its unit axes and the extremes along each axis.
            var rLen = rExtremes[1] - rExtremes[0],
                sLen = sExtremes[1] - sExtremes[0],
                tLen = tExtremes[1] - tExtremes[0],
                rSum = rExtremes[1] + rExtremes[0],
                sSum = sExtremes[1] + sExtremes[0],
                tSum = tExtremes[1] + tExtremes[0],

                cx = 0.5 * (this.r[0] * rSum + this.s[0] * sSum + this.t[0] * tSum),
                cy = 0.5 * (this.r[1] * rSum + this.s[1] * sSum + this.t[1] * tSum),
                cz = 0.5 * (this.r[2] * rSum + this.s[2] * sSum + this.t[2] * tSum),
                rx_2 = 0.5 * this.r[0] * rLen,
                ry_2 = 0.5 * this.r[1] * rLen,
                rz_2 = 0.5 * this.r[2] * rLen;

            this.center.set(cx, cy, cz);
            this.topCenter.set(cx + rx_2, cy + ry_2, cz + rz_2);
            this.bottomCenter.set(cx - rx_2, cy - ry_2, cz - rz_2);

            this.r.multiply(rLen);
            this.s.multiply(sLen);
            this.t.multiply(tLen);

            this.radius = 0.5 * Math.sqrt(rLen * rLen + sLen * sLen + tLen * tLen);

            return this;
        };

        /**
         * Translates this bounding box by a specified translation vector.
         * @param {Vec3} translation The translation vector.
         * @returns {BoundingBox} This bounding box translated by the specified translation vector.
         * @throws {ArgumentError} If the specified translation vector is null or undefined.
         */
        BoundingBox.prototype.translate = function (translation) {
            if (!translation) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "translate", "missingVector"));
            }

            this.bottomCenter.add(translation);
            this.topCenter.add(translation);
            this.center.add(translation);

            return this;
        };

        /**
         * Computes the approximate distance between this bounding box and a specified point.
         * <p>
         * This calculation treats the bounding box as a sphere with the same radius as the box.
         * @param {Vec3} point The point to compute the distance to.
         * @returns {Number} The distance from the edge of this bounding box to the specified point.
         * @throws {ArgumentError} If the specified point is null or undefined.
         */
        BoundingBox.prototype.distanceTo = function (point) {
            if (!point) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "distanceTo", "missingPoint"));
            }

            var d = this.center.distanceTo(point) - this.radius;

            return d >= 0 ? d : -d;
        };

        /**
         * Computes the effective radius of this bounding box relative to a specified plane.
         * @param {Plane} plane The plane of interest.
         * @returns {Number} The effective radius of this bounding box to the specified plane.
         * @throws {ArgumentError} If the specified plane is null or undefined.
         */
        BoundingBox.prototype.effectiveRadius = function (plane) {
            if (!plane) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "effectiveRadius", "missingPlane"));
            }

            var n = plane.normal;

            return 0.5 * (WWMath.fabs(this.r.dot(n)) + WWMath.fabs(this.s.dot(n)) + WWMath.fabs(this.t.dot(n)));
        };

        /**
         * Indicates whether this bounding box intersects a specified frustum.
         * @param {Frustum} frustum The frustum of interest.
         * @returns {boolean} true if the specified frustum intersects this bounding box, otherwise false.
         * @throws {ArgumentError} If the specified frustum is null or undefined.
         */
        BoundingBox.prototype.intersectsFrustum = function (frustum) {
            if (!frustum) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BoundingBox", "intersectsFrustum", "missingFrustum"));
            }

            this.tmp1.copy(this.bottomCenter);
            this.tmp2.copy(this.topCenter);

            if (this.intersectionPoint(frustum.near) < 0) {
                return false;
            }
            if (this.intersectionPoint(frustum.far) < 0) {
                return false;
            }
            if (this.intersectionPoint(frustum.left) < 0) {
                return false;
            }
            if (this.intersectionPoint(frustum.right) < 0) {
                return false;
            }
            if (this.intersectionPoint(frustum.top) < 0) {
                return false;
            }
            if (this.intersectionPoint(frustum.bottom) < 0) {
                return false;
            }

            return true;
        };

        // Internal. Intentionally not documented.
        BoundingBox.prototype.intersectionPoint = function (plane) {
            var n = plane.normal,
                effectiveRadius = 0.5 * (Math.abs(this.s.dot(n)) + Math.abs(this.t.dot(n)));

            return this.intersectsAt(plane, effectiveRadius, this.tmp1, this.tmp2);
        };

        // Internal. Intentionally not documented.
        BoundingBox.prototype.intersectsAt = function (plane, effRadius, endPoint1, endPoint2) {
            // Test the distance from the first end-point.
            var dq1 = plane.dot(endPoint1);
            var bq1 = dq1 <= -effRadius;

            // Test the distance from the second end-point.
            var dq2 = plane.dot(endPoint2);
            var bq2 = dq2 <= -effRadius;

            if (bq1 && bq2) { // endpoints more distant from plane than effective radius; box is on neg. side of plane
                return -1;
            }

            if (bq1 == bq2) { // endpoints less distant from plane than effective radius; can't draw any conclusions
                return 0;
            }

            // Compute and return the endpoints of the box on the positive side of the plane
            this.tmp3.copy(endPoint1);
            this.tmp3.subtract(endPoint2);
            var t = (effRadius + dq1) / plane.normal.dot(this.tmp3);

            this.tmp3.copy(endPoint2);
            this.tmp3.subtract(endPoint1);
            this.tmp3.multiply(t);
            this.tmp3.add(endPoint1);

            // Truncate the line to only that in the positive halfspace, e.g., inside the frustum.
            if (bq1) {
                endPoint1.copy(this.tmp3);
            }
            else {
                endPoint2.copy(this.tmp3);
            }

            return t;
        };

        // Internal. Intentionally not documented.
        BoundingBox.prototype.adjustExtremes = function (r, rExtremes, s, sExtremes, t, tExtremes, p) {
            var pdr = p.dot(r);
            if (rExtremes[0] > pdr) {
                rExtremes[0] = pdr;
            }
            if (rExtremes[1] < pdr) {
                rExtremes[1] = pdr;
            }

            var pds = p.dot(s);
            if (sExtremes[0] > pds) {
                sExtremes[0] = pds;
            }
            if (sExtremes[1] < pds) {
                sExtremes[1] = pds;
            }

            var pdt = p.dot(t);
            if (tExtremes[0] > pdt) {
                tExtremes[0] = pdt;
            }
            if (tExtremes[1] < pdt) {
                tExtremes[1] = pdt;
            }
        };

        // Internal. Intentionally not documented.
        BoundingBox.prototype.swapAxes = function (a, aExtremes, b, bExtremes) {
            a.swap(b);

            var tmp = aExtremes[0];
            aExtremes[0] = bExtremes[0];
            bExtremes[0] = tmp;

            tmp = aExtremes[1];
            aExtremes[1] = bExtremes[1];
            bExtremes[1] = tmp;
        };

        /**
         * Renders this bounding box in a semi-transparent color with a highlighted outline. This function is intended
         * for diagnostic use only.
         * @param dc {DrawContext} dc The current draw context.
         */
        BoundingBox.prototype.render = function (dc) {
            var gl = dc.currentGlContext,
                matrix = BoundingBox.scratchMatrix,
                program = dc.findAndBindProgram(BasicProgram);

            try {
                // Setup to transform unit cube coordinates to this bounding box's local coordinates, as viewed by the
                // current navigator state.
                matrix.copy(dc.navigatorState.modelviewProjection);
                matrix.multiply(
                    this.r[0], this.s[0], this.t[0], this.center[0],
                    this.r[1], this.s[1], this.t[1], this.center[1],
                    this.r[2], this.s[2], this.t[2], this.center[2],
                    0, 0, 0, 1);
                matrix.multiplyByTranslation(-0.5, -0.5, -0.5);
                program.loadModelviewProjection(gl, matrix);

                // Setup to draw the geometry when the eye point is inside or outside the box.
                gl.disable(gl.CULL_FACE);

                // Bind the shared unit cube vertex buffer and element buffer.
                gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitCubeBuffer());
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, dc.unitCubeElements());
                gl.enableVertexAttribArray(program.vertexPointLocation);
                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);

                // Draw bounding box fragments that are below the terrain.
                program.loadColorComponents(gl, 0, 1, 0, 0.6);
                gl.drawElements(gl.LINES, 24, gl.UNSIGNED_SHORT, 72);
                program.loadColorComponents(gl, 1, 1, 1, 0.3);
                gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);

            } finally {
                // Restore WorldWind's default WebGL state.
                gl.enable(gl.CULL_FACE);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
            }
        };

        return BoundingBox;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Tile
 */
define('util/Tile',[
        '../error/ArgumentError',
        '../geom/BoundingBox',
        '../util/Logger',
        '../geom/Sector',
        '../geom/Vec3',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              BoundingBox,
              Logger,
              Sector,
              Vec3,
              WWUtil) {
        "use strict";

        /**
         * Constructs a tile for a specified sector, level, row and column.
         * @alias Tile
         * @constructor
         * @classdesc Represents a tile of terrain or imagery.
         * Provides a base class for texture tiles used by tiled image layers and elevation tiles used by elevation models.
         * Applications typically do not interact with this class.
         * @param {Sector} sector The sector represented by this tile.
         * @param {Level} level This tile's level in a tile pyramid.
         * @param {Number} row This tile's row in the specified level in a tile pyramid.
         * @param {Number} column This tile's column in the specified level in a tile pyramid.
         * @throws {ArgumentError} If the specified sector or level is null or undefined or the row or column arguments
         * are less than zero.
         */
        var Tile = function (sector, level, row, column) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor", "missingSector"));
            }

            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
                        "The specified level is null or undefined."));
            }

            if (row < 0 || column < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
                        "The specified row or column is less than zero."));
            }

            /**
             * The sector represented by this tile.
             * @type {Sector}
             * @readonly
             */
            this.sector = sector;

            /**
             * The level at which this tile lies in a tile pyramid.
             * @type {Number}
             * @readonly
             */
            this.level = level;

            /**
             * The row in this tile's level in which this tile lies in a tile pyramid.
             * @type {Number}
             * @readonly
             */
            this.row = row;

            /**
             * The column in this tile's level in which this tile lies in a tile pyramid.
             * @type {Number}
             * @readonly
             */
            this.column = column;

            /**
             * The width in pixels or cells of this tile's associated resource.
             * @type {Number}
             */
            this.tileWidth = level.tileWidth;

            /**
             * The height in pixels or cells of this tile's associated resource.
             * @type {Number}
             */
            this.tileHeight = level.tileHeight;

            /**
             * The size in radians of pixels or cells of this tile's associated resource.
             * @type {Number}
             */
            this.texelSize = level.texelSize;

            /**
             * A key that uniquely identifies this tile within a level set.
             * @type {String}
             * @readonly
             */
            this.tileKey = level.levelNumber.toString() + "." + row.toString() + "." + column.toString();

            /**
             * The Cartesian bounding box of this tile.
             * @type {BoundingBox}
             */
            this.extent = null;

            /**
             * The tile's local origin in model coordinates. Any model coordinate points associates with the tile
             * should be relative to this point.
             * @type {Vec3}
             */
            this.referencePoint = null;

            /**
             * This tile's opacity.
             * @type {Number}
             * @default 1
             */
            this.opacity = 1;

            // Internal use only. Intentionally not documented.
            this.samplePoints = null;

            // Internal use only. Intentionally not documented.
            this.sampleElevations = null;

            // Internal use only. Intentionally not documented.
            this.updateTimestamp = null;

            // Internal use only. Intentionally not documented.
            this.updateVerticalExaggeration = null;

            // Internal use only. Intentionally not documented.
            this.updateGlobeStateKey = null;
        };

        /**
         * Indicates whether this tile is equivalent to a specified tile.
         * @param {Tile} that The tile to check equivalence with.
         * @returns {boolean} true if this tile is equivalent to the specified one, false if
         * they are not equivalent or the specified tile is null or undefined.
         */
        Tile.prototype.isEqual = function (that) {
            if (!that)
                return false;

            if (!that.tileKey)
                return false;

            return this.tileKey == that.tileKey;
        };

        /**
         * Returns the size of this tile in bytes.
         * @returns {Number} The size of this tile in bytes.
         */
        Tile.prototype.size = function () {
            return 4 // child pointer
                + (4 + 32) // sector
                + 4 //level pointer (the level is common to the layer or tessellator so is not included here)
                + 8 // row and column
                + 8 // texel size
                + (4 + 32) // reference point
                + (4 + 676) // bounding box
                + 8 // min and max height
                + (4 + 32) // nearest point
                + 8; // extent timestamp and vertical exaggeration
        };

        /**
         * Computes an approximate distance from this tile to a specified vector.
         * @param {Vec3} vector The vector to compute the distance to.
         * @returns {number} The distance between this tile and the vector.
         * @throws {ArgumentError} If the specified vector is null or undefined.
         */
        Tile.prototype.distanceTo = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "distanceTo", "missingVector"));
            }

            var px = vector[0], py = vector[1], pz = vector[2],
                dx, dy, dz,
                points = this.samplePoints,
                distance = Number.POSITIVE_INFINITY;

            for (var i = 0, len = points.length; i < len; i += 3) {
                dx = px - points[i];
                dy = py - points[i + 1];
                dz = pz - points[i + 2];
                distance = Math.min(distance, dx * dx + dy * dy + dz * dz); // minimum squared distance
            }

            return Math.sqrt(distance);
        };

        /**
         * Returns the four children formed by subdividing this tile.
         * @param {Level} level The level of the children.
         * @param {TileFactory} tileFactory The tile factory to use to create the children.
         * @returns {Tile[]} An array containing the four child tiles.
         * @throws {ArgumentError} If the specified tile factory or level is null or undefined.
         */
        Tile.prototype.subdivide = function (level, tileFactory) {
            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivide",
                        "The specified level is null or undefined."));
            }

            if (!tileFactory) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivide",
                        "The specified tile factory is null or undefined."));
            }

            var latMin = this.sector.minLatitude,
                latMax = this.sector.maxLatitude,
                latMid = this.sector.centroidLatitude(),

                lonMin = this.sector.minLongitude,
                lonMax = this.sector.maxLongitude,
                lonMid = this.sector.centroidLongitude(),

                subRow,
                subCol,
                childSector,
                children = [];

            subRow = 2 * this.row;
            subCol = 2 * this.column;
            childSector = new Sector(latMin, latMid, lonMin, lonMid);
            children.push(tileFactory.createTile(childSector, level, subRow, subCol));

            subRow = 2 * this.row;
            subCol = 2 * this.column + 1;
            childSector = new Sector(latMin, latMid, lonMid, lonMax);
            children.push(tileFactory.createTile(childSector, level, subRow, subCol));

            subRow = 2 * this.row + 1;
            subCol = 2 * this.column;
            childSector = new Sector(latMid, latMax, lonMin, lonMid);
            children.push(tileFactory.createTile(childSector, level, subRow, subCol));

            subRow = 2 * this.row + 1;
            subCol = 2 * this.column + 1;
            childSector = new Sector(latMid, latMax, lonMid, lonMax);
            children.push(tileFactory.createTile(childSector, level, subRow, subCol));

            return children;
        };

        /**
         * Returns the four children formed by subdividing this tile, drawing those children from a specified cache
         * if they exist there.
         * @param {Level} level The level of the children.
         * @param {TileFactory} tileFactory The tile factory to use to create the children.
         * @param {MemoryCache} cache A memory cache that may contain pre-existing child tiles. If non-null, the
         * cache is checked for a child collection prior to creating that tile. If one exists
         * in the cache it is returned rather than creating a new collection of children. If a new collection is
         * created, it is added to the cache.
         * @returns {Tile[]} An array containing the four tiles.
         * @throws {ArgumentError} If the specified tile factory or level is null or undefined.
         */
        Tile.prototype.subdivideToCache = function (level, tileFactory, cache) {
            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivideToCache",
                        "The specified level is null or undefined."));
            }

            if (!tileFactory) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivideToCache",
                        "The specified tile factory is null or undefined."));
            }

            var childList = cache ? cache.entryForKey(this.tileKey) : null;
            if (!childList) {
                childList = this.subdivide(level, tileFactory);
                if (childList && cache) {
                    cache.putEntry(this.tileKey, childList, 4 * childList[0].size());
                }
            }

            return childList;
        };

        /**
         * Indicates whether this tile should be subdivided based on the current navigation state and a specified
         * detail factor.
         * @param {DrawContext} dc The current draw context.
         * @param {Number} detailFactor The detail factor to consider.
         * @returns {boolean} true If the tile should be subdivided, otherwise false.
         */
        Tile.prototype.mustSubdivide = function (dc, detailFactor) {
            // Split when the cell height (length of a texel) becomes greater than the specified fraction of the eye
            // distance. The fraction is specified as a power of 10. For example, a detail factor of 3 means split when
            // the cell height becomes more than one thousandth of the eye distance. Another way to say it is, use the
            // current tile if the cell height is less than the specified fraction of the eye distance.
            //
            // Note: It's tempting to instead compare a screen pixel size to the texel size, but that calculation is
            // window-size dependent and results in selecting an excessive number of tiles when the window is large.

            var cellSize = dc.globe.equatorialRadius * this.texelSize,
                distance = this.distanceTo(dc.navigatorState.eyePoint),
                pixelSize = dc.navigatorState.pixelSizeAtDistance(distance);

            return cellSize > Math.max(detailFactor * pixelSize, 0.5);
        };

        /**
         * Updates this tile's frame-dependent properties as necessary, according to the specified draw context.
         * <p>
         * The tile's frame-dependent properties, include the extent (bounding volume). These properties are dependent
         * on the tile's sector and the elevation values currently in memory, and change when those dependencies change.
         * Therefore <code>update</code> must be called once per frame before the extent and any other frame-dependent
         * properties are used. <code>update</code> intelligently determines when it is necessary to recompute these
         * properties, and does nothing if the state of all dependencies has not changed since the last call.
         * @param {DrawContext} dc The current draw context.
         */
        Tile.prototype.update = function (dc) {
            var elevationTimestamp = dc.globe.elevationTimestamp(),
                verticalExaggeration = dc.verticalExaggeration,
                globeStateKey = dc.globeStateKey;

            if (this.updateTimestamp != elevationTimestamp
                || this.updateVerticalExaggeration != verticalExaggeration
                || this.updateGlobeStateKey != globeStateKey) {

                this.doUpdate(dc);
                dc.frameStatistics.incrementTileUpdateCount(1);

                // Set the geometry extent to the globe's elevation timestamp on which the geometry is based. This
                // ensures that the geometry timestamp can be reliably compared to the elevation timestamp in subsequent
                // frames.
                this.updateTimestamp = elevationTimestamp;
                this.updateVerticalExaggeration = verticalExaggeration;
                this.updateGlobeStateKey = globeStateKey;
            }
        };

        /**
         * Updates this tile's frame-dependent properties according to the specified draw context.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        Tile.prototype.doUpdate = function (dc) {
            // Compute the minimum and maximum world coordinate height for this tile's sector by multiplying the minimum
            // and maximum elevations by the scene's vertical exaggeration. This ensures that the elevations to used
            // build the terrain are contained by this tile's extent. Use zero if the globe as no elevations in this
            // tile's sector.
            var globe = dc.globe,
                verticalExaggeration = dc.verticalExaggeration,
                extremes = globe.minAndMaxElevationsForSector(this.sector),
                minHeight = extremes ? (extremes[0] * verticalExaggeration) : 0,
                maxHeight = extremes ? (extremes[1] * verticalExaggeration) : 0;
            if (minHeight == maxHeight) {
                minHeight = maxHeight + 10; // TODO: Determine if this is necessary.
            }

            // Compute a bounding box for this tile that contains the terrain surface in the tile's coverage area.
            if (!this.extent) {
                this.extent = new BoundingBox();
            }
            this.extent.setToSector(this.sector, globe, minHeight, maxHeight);

            // Compute the cartesian points for a 3x3 geographic grid. This grid captures sufficiently close sample
            // points in order to estimate the distance from the viewer to this tile.
            if (!this.samplePoints) {
                this.sampleElevations = new Float64Array(9);
                this.samplePoints = new Float64Array(3 * this.sampleElevations.length);
            }
            WWUtil.fillArray(this.sampleElevations, 0.5 * (minHeight + maxHeight));
            globe.computePointsForGrid(this.sector, 3, 3, this.sampleElevations, Vec3.ZERO, this.samplePoints);

            // Compute the reference point used as a local coordinate origin for the tile.
            if (!this.referencePoint) {
                this.referencePoint = new Vec3(0, 0, 0);
            }

            globe.computePointFromPosition(this.sector.centroidLatitude(), this.sector.centroidLongitude(), 0,
                this.referencePoint);
        };

        /**
         * Computes a row number for a tile within a level given the tile's latitude.
         * @param {Number} delta The level's latitudinal tile delta in degrees.
         * @param {Number} latitude The tile's minimum latitude.
         * @returns {Number} The computed row number.
         */
        Tile.computeRow = function (delta, latitude) {
            var row = Math.floor((latitude + 90) / delta);

            // If latitude is at the end of the grid, subtract 1 from the computed row to return the last row.
            if (latitude == 90) {
                row -= 1;
            }

            return row;
        };

        /**
         * Computes a column number for a tile within a level given the tile's longitude.
         * @param {Number} delta The level's longitudinal tile delta in degrees.
         * @param {Number} longitude The tile's minimum longitude.
         * @returns {Number} The computed column number.
         */
        Tile.computeColumn = function (delta, longitude) {
            var col = Math.floor((longitude + 180) / delta);

            // If longitude is at the end of the grid, subtract 1 from the computed column to return the last column.
            if (longitude == 180) {
                col -= 1;
            }

            return col;
        };

        /**
         * Computes the last row number for a tile within a level given the tile's maximum latitude.
         * @param {Number} delta The level's latitudinal tile delta in degrees.
         * @param {Number} maxLatitude The tile's maximum latitude in degrees.
         * @returns {Number} The computed row number.
         */
        Tile.computeLastRow = function (delta, maxLatitude) {
            var row = Math.ceil((maxLatitude + 90) / delta - 1);

            // If max latitude is in the first row, set the max row to 0.
            if (maxLatitude + 90 < delta) {
                row = 0;
            }

            return row;
        };

        /**
         * Computes the last column number for a tile within a level given the tile's maximum longitude.
         * @param {Number} delta The level's longitudinal tile delta in degrees.
         * @param {Number} maxLongitude The tile's maximum longitude in degrees.
         * @returns {Number} The computed column number.
         */
        Tile.computeLastColumn = function (delta, maxLongitude) {
            var col = Math.ceil((maxLongitude + 180) / delta - 1);

            // If max longitude is in the first column, set the max column to 0.
            if (maxLongitude + 180 < delta) {
                col = 0;
            }

            return col;
        };

        /**
         * Computes a sector spanned by a tile with the specified level number, row and column.
         * @param {Level} level The tile's level number.
         * @param {Number} row The tile's row number.
         * @param {Number} column The tile's column number.
         * @returns {Sector} The sector spanned by the tile.
         * @throws {ArgumentError} If the specified level is null or undefined or the row or column are less than zero.
         */
        Tile.computeSector = function (level, row, column) {
            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "computeSector", "missingLevel"));
            }

            if (row < 0 || column < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "computeSector",
                        "The specified row or column is less than zero."));
            }

            var deltaLat = level.tileDelta.latitude,
                deltaLon = level.tileDelta.longitude,

                minLat = -90 + row * deltaLat,
                minLon = -180 + column * deltaLon,
                maxLat = minLat + deltaLat,
                maxLon = minLon + deltaLon;

            return new Sector(minLat, maxLat, minLon, maxLon);
        };

        /**
         * Creates all tiles for a specified level number.
         * @param {Level} level The level to create the tiles for.
         * @param {TileFactory} tileFactory The tile factory to use for creating tiles.
         * @param {Tile[]} result An array in which to return the results.
         * @throws {ArgumentError} If any argument is null or undefined.
         */
        Tile.createTilesForLevel = function (level, tileFactory, result) {
            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "createTilesForLevel", "missingLevel"));
            }

            if (!tileFactory) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "createTilesForLevel",
                        "The specified tile factory is null or undefined"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "createTilesForLevel", "missingResult"));
            }

            var deltaLat = level.tileDelta.latitude,
                deltaLon = level.tileDelta.longitude,

                sector = level.sector,
                firstRow = Tile.computeRow(deltaLat, sector.minLatitude),
                lastRow = Tile.computeRow(deltaLat, sector.maxLatitude),

                firstCol = Tile.computeColumn(deltaLon, sector.minLongitude),
                lastCol = Tile.computeColumn(deltaLon, sector.maxLongitude),

                firstRowLat = -90 + firstRow * deltaLat,
                firstRowLon = -180 + firstCol * deltaLon,

                minLat = firstRowLat,
                minLon,
                maxLat,
                maxLon;

            for (var row = firstRow; row <= lastRow; row += 1) {
                maxLat = minLat + deltaLat;
                minLon = firstRowLon;

                for (var col = firstCol; col <= lastCol; col += 1) {
                    maxLon = minLon + deltaLon;
                    var tileSector = new Sector(minLat, maxLat, minLon, maxLon),
                        tile = tileFactory.createTile(tileSector, level, row, col);
                    result.push(tile);

                    minLon = maxLon;
                }

                minLat = maxLat;
            }
        };

        return Tile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TextureTile
 */
define('render/TextureTile',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/Tile'
    ],
    function (ArgumentError,
              Logger,
              Tile) {
        "use strict";

        /**
         * Constructs a texture tile.
         * @alias TextureTile
         * @constructor
         * @augments Tile
         * @classdesc Represents an image applied to a portion of a globe's terrain. Applications typically do not
         * interact with this class.
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {Number} row This tile's row in the associated level.
         * @param {Number} column This tile's column in the associated level.
         * @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
         * are less than zero, or the specified image path is null, undefined or empty.
         *
         */
        var TextureTile = function (sector, level, row, column) {
            Tile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            /**
             * GPU cache key
             * @type {string}
             */
            this.gpuCacheKey = null;
        };

        TextureTile.prototype = Object.create(Tile.prototype);

        /**
         * Returns the size of the this tile in bytes.
         * @returns {Number} The size of this tile in bytes, not including the associated texture size.
         */
        TextureTile.prototype.size = function () {
            return Tile.prototype.size.call(this);
        };

        /**
         * Causes this tile's texture to be active. Implements [SurfaceTile.bind]{@link SurfaceTile#bind}.
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if the texture was bound successfully, otherwise false.
         */
        TextureTile.prototype.bind = function (dc) {
            var texture = dc.gpuResourceCache.resourceForKey(this.gpuCacheKey);
            if (texture) {
                return texture.bind(dc);
            }

            return false;
        };

        /**
         * If this tile's fallback texture is used, applies the appropriate texture transform to a specified matrix.
         * Otherwise, this is a no-op.
         * @param {DrawContext} dc The current draw context.
         * @param {Matrix} matrix The matrix to apply the transform to.
         */
        TextureTile.prototype.applyInternalTransform = function (dc, matrix) {
            // Override this method if the tile has a fallback texture.
        };

        return TextureTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ImageTile
 */
define('render/ImageTile',[
        '../error/ArgumentError',
        '../util/Logger',
        '../render/TextureTile',
        '../util/Tile'
    ],
    function (ArgumentError,
              Logger,
              TextureTile,
              Tile) {
        "use strict";

        /**
         * Constructs an image tile.
         * @alias ImageTile
         * @constructor
         * @classdesc Represents an image applied to a portion of a globe's terrain. Applications typically do not
         * interact with this class.
         * @augments TextureTile
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {Number} row This tile's row in the associated level.
         * @param {Number} column This tile's column in the associated level.
         * @param {String} imagePath The full path to the image.
         * @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
         * are less than zero, or the specified image path is null, undefined or empty.
         *
         */
        var ImageTile = function (sector, level, row, column, imagePath) {
            if (!imagePath || (imagePath.length < 1)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ImageTile", "constructor",
                        "The specified image path is null, undefined or zero length."));
            }

            TextureTile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            /**
             * This tile's image path.
             * @type {String}
             */
            this.imagePath = imagePath;

            /**
             * The tile whose texture to use when this tile's texture is not available.
             * @type {Matrix}
             */
            this.fallbackTile = null;

            // Assign imagePath to gpuCacheKey (inherited from TextureTile).
            this.gpuCacheKey = imagePath;
        };

        ImageTile.prototype = Object.create(TextureTile.prototype);

        /**
         * Returns the size of the this tile in bytes.
         * @returns {Number} The size of this tile in bytes, not including the associated texture size.
         */
        ImageTile.prototype.size = function () {
            return this.__proto__.__proto__.size.call(this) + this.imagePath.length + 8;
        };

        /**
         * Causes this tile's texture to be active. Implements [SurfaceTile.bind]{@link SurfaceTile#bind}.
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if the texture was bound successfully, otherwise false.
         */
        ImageTile.prototype.bind = function (dc) {
            // Attempt to bind in TextureTile first.
            var isBound = this.__proto__.__proto__.bind.call(this, dc);
            if (isBound) {
                return true;
            }

            if (this.fallbackTile) {
                return this.fallbackTile.bind(dc);
            }

            return false;
        };

        /**
         * If this tile's fallback texture is used, applies the appropriate texture transform to a specified matrix.
         * @param {DrawContext} dc The current draw context.
         * @param {Matrix} matrix The matrix to apply the transform to.
         */
        ImageTile.prototype.applyInternalTransform = function (dc, matrix) {
            if (this.fallbackTile && !(dc.gpuResourceCache.resourceForKey(this.imagePath))) {
                // Must apply a texture transform to map the tile's sector into its fallback's image.
                this.applyFallbackTransform(matrix);
            }
        };

        // Intentionally not documented.
        ImageTile.prototype.applyFallbackTransform = function (matrix) {
            var deltaLevel = this.level.levelNumber - this.fallbackTile.level.levelNumber;
            if (deltaLevel <= 0)
                return;

            var fbTileDeltaLat = this.fallbackTile.sector.deltaLatitude(),
                fbTileDeltaLon = this.fallbackTile.sector.deltaLongitude(),
                sx = this.sector.deltaLongitude() / fbTileDeltaLon,
                sy = this.sector.deltaLatitude() / fbTileDeltaLat,
                tx = (this.sector.minLongitude - this.fallbackTile.sector.minLongitude) / fbTileDeltaLon,
                ty = (this.sector.minLatitude - this.fallbackTile.sector.minLatitude) / fbTileDeltaLat;

            // Apply a transform to the matrix that maps texture coordinates for this tile to texture coordinates for the
            // fallback tile. Rather than perform the full set of matrix operations, a single multiply is performed with the
            // precomputed non-zero values:
            //
            // Matrix trans = Matrix.fromTranslation(tx, ty, 0);
            // Matrix scale = Matrix.fromScale(sxy, sxy, 1);
            // matrix.multiply(trans);
            // matrix.multiply(scale);

            matrix.multiply(
                sx, 0, 0, tx,
                0, sy, 0, ty,
                0, 0, 1, 0,
                0, 0, 0, 1);
        };

        return ImageTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Level
 */
define('util/Level',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger) {
        "use strict";

        /**
         * Constructs a Level within a [LevelSet]{@link LevelSet}. Applications typically do not interact with this
         * class.
         * @alias Level
         * @constructor
         * @classdesc Represents a level in a tile pyramid.
         * @throws {ArgumentError} If either the specified tile delta or parent level set is null or undefined.
         */
        var Level = function (levelNumber, tileDelta, parent) {
            if (!tileDelta) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Level", "constructor",
                        "The specified tile delta is null or undefined"));
            }

            if (!parent) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Level", "constructor",
                        "The specified parent level set is null or undefined"));
            }

            /**
             * The level's ordinal in its parent level set.
             * @type {Number}
             */
            this.levelNumber = levelNumber;

            /**
             * The geographic size of tiles within this level.
             * @type {Location}
             */
            this.tileDelta = tileDelta;

            /**
             * The level set that this level is a member of.
             * @type {LevelSet}
             */
            this.parent = parent;

            /**
             * The size of pixels or elevation cells within this level, in radians per pixel or per cell.
             * @type {Number}
             */
            this.texelSize = (tileDelta.latitude * Angle.DEGREES_TO_RADIANS) / parent.tileHeight;

            /**
             * The width in pixels or cells of the resource associated with tiles within this level.
             * @type {Number}
             */
            this.tileWidth = parent.tileWidth;

            /**
             * The height in pixels or cells of the resource associated with tiles within this level.
             * @type {Number}
             */
            this.tileHeight = parent.tileHeight;

            /**
             * The sector spanned by this level.
             * @type {Sector}
             */
            this.sector = parent.sector;
        };

        /**
         * Indicates whether this level is the lowest resolution level (level 0) within its parent's level set.
         * @returns {Boolean} true If this tile is the lowest resolution in the parent level set,
         * otherwise false.
         */
        Level.prototype.isFirstLevel = function () {
            return this.parent.firstLevel() == this;
        };

        /**
         * Indicates whether this level is the highest resolution level within its parent's level set.
         * @returns {Boolean} true If this tile is the highest resolution in the parent level set,
         * otherwise false.
         */
        Level.prototype.isLastLevel = function () {
            return this.parent.lastLevel() == this;
        };

        /**
         * Returns the level whose ordinal occurs immediately before this level's ordinal in the parent level set, or
         * null if this is the fist level.
         * @returns {Level} The previous level, or null if this is the first level.
         */
        Level.prototype.previousLevel = function () {
            return this.parent.level(this.levelNumber - 1);
        };

        /**
         * Returns the level whose ordinal occurs immediately after this level's ordinal in the parent level set, or
         * null if this is the last level.
         * @returns {Level} The next level, or null if this is the last level.
         */
        Level.prototype.nextLevel = function () {
            return this.parent.level(this.levelNumber + 1);
        };

        /**
         * Compare this level's ordinal to that of a specified level.
         * @param {Level} that The level to compare this one to.
         * @returns {Number} 0 if the two ordinals are equivalent. -1 if this level's ordinal is less than the specified
         * level's ordinal. 1 if this level's ordinal is greater than the specified level's ordinal.
         * @throws {ArgumentError} If the specified level is null or undefined.
         */
        Level.prototype.compare = function (that) {
            if (!that) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Level", "compare",
                        "The specified level is null or undefined"));
            }

            if (this.levelNumber < that.levelNumber)
                return -1;

            if (this.levelNumber > that.levelNumber)
                return 1;

            return 0;
        };

        return Level;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports LevelSet
 */
define('util/LevelSet',[
        '../error/ArgumentError',
        '../util/Level',
        '../geom/Location',
        '../util/Logger'
    ],
    function (ArgumentError,
              Level,
              Location,
              Logger) {
        "use strict";

        /**
         * Constructs a level set.
         * @alias Level
         * @constructor
         * @classdesc Represents a multi-resolution, hierarchical collection of tiles. Applications typically do not
         * interact with this class.
         * @param {Sector} sector The sector spanned by this level set.
         * @param {Location} levelZeroDelta The geographic size of tiles in the lowest resolution level of this level set.
         * @param {Number} numLevels The number of levels in the level set.
         * @param {Number} tileWidth The height in pixels of images associated with tiles in this level set, or the number of sample
         * points in the longitudinal direction of elevation tiles associate with this level set.
         * @param {Number} tileHeight The height in pixels of images associated with tiles in this level set, or the number of sample
         * points in the latitudinal direction of elevation tiles associate with this level set.
         * @throws {ArgumentError} If the specified sector or level-zero-delta is null or undefined, the level zero
         * delta values are less than or equal to zero, or any of the number-of-levels, tile-width or tile-height
         * arguments are less than 1.
         */
        var LevelSet = function (sector, levelZeroDelta, numLevels, tileWidth, tileHeight) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor", "missingSector"));
            }

            if (!levelZeroDelta) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified level zero delta is null or undefined"));
            }

            if (levelZeroDelta.latitude <= 0 || levelZeroDelta.longitude <= 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified level zero delta is less than or equal to zero."));
            }

            if (numLevels < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified number of levels is less than one."));
            }

            if (tileWidth < 1 || tileHeight < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified tile width or tile height is less than one."));
            }

            /**
             * The sector spanned by this level set.
             * @type {Sector}
             * @readonly
             */
            this.sector = sector;

            /**
             * The geographic size of the lowest resolution (level 0) tiles in this level set.
             * @type {Location}
             * @readonly
             */
            this.levelZeroDelta = levelZeroDelta;

            /**
             * The number of levels in this level set.
             * @type {Number}
             * @readonly
             */
            this.numLevels = numLevels;

            /**
             *  The width in pixels of images associated with tiles in this level set, or the number of sample points
             *  in the longitudinal direction of elevation tiles associated with this level set.
             * @type {Number}
             * @readonly
             */
            this.tileWidth = tileWidth;

            /**
             *  The height in pixels of images associated with tiles in this level set, or the number of sample points
             *  in the latitudinal direction of elevation tiles associated with this level set.
             * @type {Number}
             * @readonly
             */
            this.tileHeight = tileHeight;

            this.levels = [];

            for (var i = 0; i < numLevels; i += 1) {
                var n = Math.pow(2, i),
                    latDelta = levelZeroDelta.latitude / n,
                    lonDelta = levelZeroDelta.longitude / n,
                    tileDelta = new Location(latDelta, lonDelta),
                    level = new Level(i, tileDelta, this);

                this.levels[i] = level;
            }
        };

        /**
         * Returns the {@link Level} for a specified level number.
         * @param {Number} levelNumber The number of the desired level.
         * @returns {Level} The requested level, or null if the level does not exist.
         */
        LevelSet.prototype.level = function(levelNumber) {
            if (levelNumber < 0 || levelNumber >= this.levels.length) {
                return null;
            } else {
                return this.levels[levelNumber];
            }
        };

        /**
         * Returns the level with a specified texel size.
         * This function returns the first level if the specified texel size is greater than the first level's texel
         * size, and returns the last level if the delta is less than the last level's texel size.
         * @param {Number} texelSize The size of pixels or elevation cells in the level, in radians per pixel or cell.
         */
        LevelSet.prototype.levelForTexelSize = function(texelSize) {
            // TODO: Replace this loop with a computation.
            var lastLevel = this.lastLevel();

            if (lastLevel.texelSize >= texelSize) {
                return lastLevel; // Can't do any better than the last level.
            }

            for (var index = 0, length = this.levels.length; index < length; index += 1) {
                var level = this.levels[index];
                if (level.texelSize <= texelSize) {
                    return level;
                }
            }

            return lastLevel;
        };

        /**
         * Returns the first (lowest resolution) level of this level set.
         * @returns {Level} The first level of this level set.
         */
        LevelSet.prototype.firstLevel = function() {
            return this.levels[0];
        };

        /**
         * Returns the last (highest resolution) level of this level set.
         * @returns {Level} The last level of this level set.
         */
        LevelSet.prototype.lastLevel = function() {
            return this.levels[this.levels.length - 1];
        };

        return LevelSet;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports MemoryCache
 */
define('cache/MemoryCache',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a memory cache of a specified size.
         * @alias MemoryCache
         * @constructor
         * @classdesc Provides a limited-size memory cache of key-value pairs. The meaning of size depends on usage.
         * Some instances of this class work in bytes while others work in counts. See the documentation for the
         * specific use to determine the size units.
         * @param {Number} capacity The cache's capacity.
         * @param {Number} lowWater The size to clear the cache to when its capacity is exceeded.
         * @throws {ArgumentError} If either the capacity is 0 or negative or the low-water value is greater than
         * or equal to the capacity or less than 1.
         */
        var MemoryCache = function (capacity, lowWater) {
            if (!capacity || capacity < 1) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "constructor",
                    "The specified capacity is undefined, zero or negative"));
            }

            if (!lowWater || lowWater >= capacity || lowWater < 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "constructor",
                    "The specified low-water value is undefined, greater than or equal to the capacity, or less than 1"));
            }

            // Documented with its property accessor below.
            this._capacity = capacity;

            // Documented with its property accessor below.
            this._lowWater = lowWater;

            /**
             * The size currently used by this cache.
             * @type {Number}
             * @readonly
             */
            this.usedCapacity = 0;

            /**
             * The size currently unused by this cache.
             * @type {Number}
             * @readonly
             */
            this.freeCapacity = capacity;

            // Private. The cache entries.
            this.entries = {};

            // Private. The cache listeners.
            this.listeners = [];
        };

        Object.defineProperties(MemoryCache.prototype, {
            /**
             * The maximum this cache may hold. When the capacity is explicitly set via this property, and the current
             * low-water value is greater than the specified capacity, the low-water value is adjusted to be 85% of
             * the specified capacity. The specified capacity may not be less than or equal to 0.
             * @type {Number}
             * @memberof MemoryCache.prototype
             */
            capacity: {
                get: function() {
                    return this._capacity;
                },
                set: function (value) {
                    if (!value || value < 1) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "capacity",
                                "Specified cache capacity is undefined, 0 or negative."));
                    }

                    var oldCapacity = this._capacity;

                    this._capacity = value;

                    if (this._capacity <= this.lowWater) {
                        this._lowWater = 0.85 * this._capacity;
                    }

                    // Trim the cache to the low-water mark if it's less than the old capacity
                    if (this._capacity < oldCapacity) {
                        this.makeSpace(0);
                    }
                }
            },

            /**
             * The size to clear this cache to when its capacity is exceeded. It must be less than the current
             * capacity and not negative.
             * @type {Number}
             * @memberof MemoryCache.prototype
             */
            lowWater: {
                get: function () {
                    return this._lowWater;
                },
                set: function (value) {
                    if (!value || value >= this._capacity || value < 0) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "lowWater",
                                "Specified cache low-water value is undefined, negative or not less than the current capacity."));
                    }

                    this._lowWater = value;
                }
            }
        });

        /**
         * Returns the entry for a specified key.
         * @param {String} key The key of the entry to return.
         * @returns {Object} The entry associated with the specified key, or null if the key is not in the cache or
         * is null or undefined.
         */
        MemoryCache.prototype.entryForKey = function (key) {
            if (!key)
                return null;

            var cacheEntry = this.entries[key];
            if (!cacheEntry)
                return null;

            cacheEntry.lastUsed = Date.now();

            return cacheEntry.entry;
        };

        /**
         * Adds a specified entry to this cache.
         * @param {String} key The entry's key.
         * @param {Object} entry The entry.
         * @param {Number} size The entry's size.
         * @throws {ArgumentError} If the specified key or entry is null or undefined or the specified size is less
         * than 1.
         */
        MemoryCache.prototype.putEntry = function (key, entry, size) {
            if (!key) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry", "missingKey."));
            }

            if (!entry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry", "missingEntry."));
            }

            if (size < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "putEntry",
                        "The specified entry size is less than 1."));
            }

            var existing = this.entries[key],
                cacheEntry;

            if (existing) {
                this.removeEntry(key);
            }

            if (this.usedCapacity + size > this._capacity) {
                this.makeSpace(size);
            }

            this.usedCapacity += size;
            this.freeCapacity = this._capacity - this.usedCapacity;

            cacheEntry = {
                key: key,
                entry: entry,
                size: size,
                lastUsed: Date.now()
            };

            this.entries[key] = cacheEntry;
        };

        /**
         * Removes all resources from this cache.
         * @param {Boolean} callListeners If true, the current cache listeners are called for each entry removed.
         * If false, the cache listeners are not called.
         */
        MemoryCache.prototype.clear = function (callListeners) {
            if (callListeners) {
                // Remove each entry individually so that the listeners can be called for each entry.
                for (var key in this.entries) {
                    if (this.entries.hasOwnProperty(key)) {
                        this.removeCacheEntry(key);
                    }
                }
            }

            this.entries = {};
            this.freeCapacity = this._capacity;
            this.usedCapacity = 0;
        };

        /**
         * Remove an entry from this cache.
         * @param {String} key The key of the entry to remove. If null or undefined, this cache is not modified.
         */
        MemoryCache.prototype.removeEntry = function (key) {
            if (!key)
                return;

            var cacheEntry = this.entries[key];
            if (cacheEntry) {
                this.removeCacheEntry(cacheEntry);
            }
        };

        // Private. Removes a specified entry from this cache.
        MemoryCache.prototype.removeCacheEntry = function (cacheEntry) {
            // All removal passes through this function.

            delete this.entries[cacheEntry.key];

            this.usedCapacity -= cacheEntry.size;
            this.freeCapacity = this._capacity - this.usedCapacity;

            for (var i = 0, len = this.listeners.length; i < len; i++) {
                try {
                    this.listeners[i].entryRemoved(cacheEntry.key, cacheEntry.entry);
                } catch (e) {
                    this.listeners[i].removalError(e, cacheEntry.key, cacheEntry.entry);
                }
            }
        };

        /**
         * Indicates whether a specified entry is in this cache.
         * @param {String} key The key of the entry to search for.
         * @returns {Boolean} true if the entry exists, otherwise false.
         */
        MemoryCache.prototype.containsKey = function (key) {
            return key && this.entries[key];
        };

        /**
         * Adds a cache listener to this cache.
         * @param {MemoryCacheListener} listener The listener to add.
         * @throws {ArgumentError} If the specified listener is null or undefined or does not implement both the
         * entryRemoved and removalError functions.
         */
        MemoryCache.prototype.addCacheListener = function (listener) {
            if (!listener) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "addCacheListener", "missingListener"));
            }

            if (typeof listener.entryRemoved != "function" || typeof listener.removalError != "function") {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "addCacheListener",
                        "The specified listener does not implement the required functions."));
            }

            this.listeners.push(listener);
        };

        /**
         * Removes a cache listener from this cache.
         * @param {MemoryCacheListener} listener The listener to remove.
         * @throws {ArgumentError} If the specified listener is null or undefined.
         */
        MemoryCache.prototype.removeCacheListener = function (listener) {
            if (!listener) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCache", "removeCacheListener", "missingListener"));
            }

            var index = this.listeners.indexOf(listener);
            if (index > -1) {
                this.listeners.splice(index, 1);
            }
        };

        // Private. Clears this cache to that necessary to contain a specified amount of free space.
        MemoryCache.prototype.makeSpace = function (spaceRequired) {
            var sortedEntries = [];

            // Sort the entries from least recently used to most recently used, then remove the least recently used entries
            // until the cache capacity reaches the low water and the cache has enough free capacity for the required
            // space.

            var sizeAtStart = this.usedCapacity;
            for (var key in this.entries) {
                if (this.entries.hasOwnProperty(key)) {
                    sortedEntries.push(this.entries[key]);
                }
            }

            sortedEntries.sort(function (a, b) {
                return a.lastUsed - b.lastUsed;
            });

            for (var i = 0, len = sortedEntries.length; i < len; i++) {
                if (this.usedCapacity > this._lowWater || this.freeCapacity < spaceRequired) {
                    this.removeCacheEntry(sortedEntries[i]);
                } else {
                    break;
                }
            }
        };

        return MemoryCache;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TiledImageLayer
 */
define('layer/TiledImageLayer',[
        '../util/AbsentResourceList',
        '../error/ArgumentError',
        '../render/ImageTile',
        '../layer/Layer',
        '../util/LevelSet',
        '../util/Logger',
        '../cache/MemoryCache',
        '../render/Texture',
        '../util/Tile',
        '../util/WWUtil'
    ],
    function (AbsentResourceList,
              ArgumentError,
              ImageTile,
              Layer,
              LevelSet,
              Logger,
              MemoryCache,
              Texture,
              Tile,
              WWUtil) {
        "use strict";

        /**
         * Constructs a tiled image layer.
         * @alias TiledImageLayer
         * @constructor
         * @classdesc
         * Provides a layer that displays multi-resolution imagery arranged as adjacent tiles in a pyramid.
         * This is the primary WorldWind base class for displaying imagery of this type. While it may be used as a
         * stand-alone class, it is typically subclassed by classes that identify the remote image server.
         * <p>
         * While the image tiles for this class are typically drawn from a remote server such as a WMS server. The actual
         * retrieval protocol is independent of this class and encapsulated by a class implementing the {@link UrlBuilder}
         * interface and associated with instances of this class as a property.
         * <p>
         * There is no requirement that image tiles of this class be remote, they may be local or procedurally generated. For
         * such cases the subclass overrides this class' [retrieveTileImage]{@link TiledImageLayer#retrieveTileImage} method.
         * <p>
         * Layers of this type are by default not pickable. Their pick-enabled flag is initialized to false.
         *
         * @augments Layer
         * @param {Sector} sector The sector this layer covers.
         * @param {Location} levelZeroDelta The size in latitude and longitude of level zero (lowest resolution) tiles.
         * @param {Number} numLevels The number of levels to define for the layer. Each level is successively one power
         * of two higher resolution than the next lower-numbered level. (0 is the lowest resolution level, 1 is twice
         * that resolution, etc.)
         * Each level contains four times as many tiles as the next lower-numbered level, each 1/4 the geographic size.
         * @param {String} imageFormat The mime type of the image format for the layer's tiles, e.g., <em>image/png</em>.
         * @param {String} cachePath A string uniquely identifying this layer relative to other layers.
         * @param {Number} tileWidth The horizontal size of image tiles in pixels.
         * @param {Number} tileHeight The vertical size of image tiles in pixels.
         * @throws {ArgumentError} If any of the specified sector, level-zero delta, cache path or image format arguments are
         * null or undefined, or if the specified number of levels, tile width or tile height is less than 1.
         *
         */
        var TiledImageLayer = function (sector, levelZeroDelta, numLevels, imageFormat, cachePath, tileWidth, tileHeight) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor", "missingSector"));
            }

            if (!levelZeroDelta) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
                        "The specified level-zero delta is null or undefined."));
            }

            if (!imageFormat) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
                        "The specified image format is null or undefined."));
            }

            if (!cachePath) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
                        "The specified cache path is null or undefined."));
            }

            if (!numLevels || numLevels < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
                        "The specified number of levels is less than one."));
            }

            if (!tileWidth || !tileHeight || tileWidth < 1 || tileHeight < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "constructor",
                        "The specified tile width or height is less than one."));
            }

            Layer.call(this, "Tiled Image Layer");

            this.retrievalImageFormat = imageFormat;
            this.cachePath = cachePath;

            this.levels = new LevelSet(sector, levelZeroDelta, numLevels, tileWidth, tileHeight);

            /**
             * Controls the level of detail switching for this layer. The next highest resolution level is
             * used when an image's texel size is greater than this number of pixels, up to the maximum resolution
             * of this layer.
             * @type {Number}
             * @default 1.75
             */
            this.detailControl = 1.75;

            /**
             * Indicates whether credentials are sent when requesting images from a different origin.
             *
             * Allowed values are anonymous and use-credentials.
             *
             * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin
             * @type {string}
             * @default anonymous
             */
            this.crossOrigin = 'anonymous';

            /* Intentionally not documented.
             * Indicates the time at which this layer's imagery expire. Expired images are re-retrieved
             * when the current time exceeds the specified expiry time. If null, images do not expire.
             * @type {Date}
             */
            this.expiration = null;

            this.currentTiles = [];
            this.currentTilesInvalid = true;
            this.tileCache = new MemoryCache(500000, 400000);
            this.currentRetrievals = [];
            this.absentResourceList = new AbsentResourceList(3, 50e3);

            this.pickEnabled = false;
        };

        TiledImageLayer.prototype = Object.create(Layer.prototype);

        // Inherited from Layer.
        TiledImageLayer.prototype.refresh = function () {
            this.expiration = new Date();
            this.currentTilesInvalid = true;
        };

        /**
         * Initiates retrieval of this layer's level 0 images. Use
         * [isPrePopulated]{@link TiledImageLayer#isPrePopulated} to determine when the images have been retrieved
         * and associated with the level 0 tiles.
         * Pre-populating is not required. It is used to eliminate the visual effect of loading tiles incrementally,
         * but only for level 0 tiles. An application might pre-populate a layer in order to delay displaying it
         * within a time series until all the level 0 images have been retrieved and added to memory.
         * @param {WorldWindow} wwd The WorldWindow for which to pre-populate this layer.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        TiledImageLayer.prototype.prePopulate = function (wwd) {
            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "prePopulate", "missingWorldWindow"));
            }

            var dc = wwd.drawContext;

            if (!this.topLevelTiles || (this.topLevelTiles.length === 0)) {
                this.createTopLevelTiles(dc);
            }

            for (var i = 0; i < this.topLevelTiles.length; i++) {
                var tile = this.topLevelTiles[i];

                if (!this.isTileTextureInMemory(dc, tile)) {
                    this.retrieveTileImage(dc, tile, true); // suppress redraw upon successful retrieval
                }
            }
        };

        /**
         * Initiates retrieval of this layer's tiles that are visible in the specified WorldWindow. Pre-populating is
         * not required. It is used to eliminate the visual effect of loading tiles incrementally.
         * @param {WorldWindow} wwd The WorldWindow for which to pre-populate this layer.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        TiledImageLayer.prototype.prePopulateCurrentTiles = function (wwd) {
            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "prePopulate", "missingWorldWindow"));
            }

            var dc = wwd.drawContext;
            this.assembleTiles(dc);

            for (var i = 0, len = this.currentTiles.length; i < len; i++) {
                var tile = this.currentTiles[i];

                if (!this.isTileTextureInMemory(dc, tile)) {
                    this.retrieveTileImage(dc, tile, true); // suppress redraw upon successful retrieval
                }
            }
        };

        /**
         * Indicates whether this layer's level 0 tile images have been retrieved and associated with the tiles.
         * Use [prePopulate]{@link TiledImageLayer#prePopulate} to initiate retrieval of level 0 images.
         * @param {WorldWindow} wwd The WorldWindow associated with this layer.
         * @returns {Boolean} true if all level 0 images have been retrieved, otherwise false.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        TiledImageLayer.prototype.isPrePopulated = function (wwd) {
            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "isPrePopulated", "missingWorldWindow"));
            }

            for (var i = 0; i < this.topLevelTiles.length; i++) {
                if (!this.isTileTextureInMemory(wwd.drawContext, this.topLevelTiles[i])) {
                    return false;
                }
            }

            return true;
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.createTile = function (sector, level, row, column) {
            var path = this.cachePath + "-layer/" + level.levelNumber + "/" + row + "/" + row + "_" + column + "."
                + WWUtil.suffixForMimeType(this.retrievalImageFormat);

            return new ImageTile(sector, level, row, column, path);
        };

        // Documented in superclass.
        TiledImageLayer.prototype.doRender = function (dc) {
            if (!dc.terrain)
                return;

            if (this.currentTilesInvalid
                || !this.lasTtMVP || !dc.navigatorState.modelviewProjection.equals(this.lasTtMVP)
                || dc.globeStateKey != this.lastGlobeStateKey) {
                this.currentTilesInvalid = false;

                // Tile fading works visually only when the surface tiles are opaque, otherwise the surface flashes
                // when two tiles are drawn over the same area, even though one of them is semi-transparent.
                // So do not provide fading when the surface opacity is less than 1;
                if (dc.surfaceOpacity >= 1 && this.opacity >= 1) {
                    // Fading of outgoing tiles requires determination of the those tiles. Prepare an object with all of
                    // the preceding frame's tiles so that we can subsequently compare the list of newly selected tiles
                    // with the previously selected tiles.
                    this.previousTiles = {};
                    for (var j = 0; j < this.currentTiles.length; j++) {
                        this.previousTiles[this.currentTiles[j].imagePath] = this.currentTiles[j];
                    }

                    this.assembleTiles(dc);
                    this.fadeOutgoingTiles(dc);
                } else {
                    this.assembleTiles(dc);
                }

            }

            this.lasTtMVP = dc.navigatorState.modelviewProjection;
            this.lastGlobeStateKey = dc.globeStateKey;

            if (this.currentTiles.length > 0) {
                dc.surfaceTileRenderer.renderTiles(dc, this.currentTiles, this.opacity, dc.surfaceOpacity >= 1);
                dc.frameStatistics.incrementImageTileCount(this.currentTiles.length);
                this.inCurrentFrame = true;
            }
        };

        TiledImageLayer.prototype.fadeOutgoingTiles = function (dc) {
            // Determine which files are outgoing and fade their disappearance. Must be called after this frame's
            // current tiles for this layer have been determined.

            var visibilityDelta = (dc.timestamp - dc.previousRedrawTimestamp) / dc.fadeTime;

            // Create a hash table of the current tiles so that we can check for tile inclusion below.
            var current = {};
            for (var i = 0; i < this.currentTiles.length; i++) {
                var tile = this.currentTiles[i];
                current[tile.imagePath] = tile;
            }

            // Determine whether the tile was in the previous frame but is not in this one.  If that's the case,
            // then the tile is outgoing and its opacity needs to be reduced.
            for (var tileImagePath in this.previousTiles) {
                if (this.previousTiles.hasOwnProperty(tileImagePath)) {
                    tile = this.previousTiles[tileImagePath];

                    if (tile.opacity > 0 && !current[tile.imagePath]) {
                        // Compute the reduced.
                        tile.opacity = Math.max(0, tile.opacity - visibilityDelta);

                        // If not fully faded, add the tile to the list of current tiles and request a redraw so that
                        // we'll be called continuously until all tiles have faded completely. Note that order in the
                        // current tiles list is important: the non-opaque tiles must be drawn after the opaque tiles.
                        if (tile.opacity > 0) {
                            this.currentTiles.push(tile);
                            this.currentTilesInvalid = true;
                            dc.redrawRequested = true;
                        }
                    }
                }
            }
        };

        // Documented in superclass.
        TiledImageLayer.prototype.isLayerInView = function (dc) {
            return dc.terrain && dc.terrain.sector && dc.terrain.sector.intersects(this.levels.sector);
        };

        // Documented in superclass.
        TiledImageLayer.prototype.createTopLevelTiles = function (dc) {
            this.topLevelTiles = [];
            Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles);
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.assembleTiles = function (dc) {
            this.currentTiles = [];

            if (!this.topLevelTiles || (this.topLevelTiles.length === 0)) {
                this.createTopLevelTiles(dc);
            }

            for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
                var tile = this.topLevelTiles[i];

                tile.update(dc);

                this.currentAncestorTile = null;

                if (this.isTileVisible(dc, tile)) {
                    this.addTileOrDescendants(dc, tile);
                }
            }
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.addTileOrDescendants = function (dc, tile) {
            if (this.tileMeetsRenderingCriteria(dc, tile)) {
                this.addTile(dc, tile);
                return;
            }

            var ancestorTile = null;

            try {
                if (this.isTileTextureInMemory(dc, tile) || tile.level.levelNumber === 0) {
                    ancestorTile = this.currentAncestorTile;
                    this.currentAncestorTile = tile;
                }

                var nextLevel = this.levels.level(tile.level.levelNumber + 1),
                    subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);

                for (var i = 0, len = subTiles.length; i < len; i++) {
                    var child = subTiles[i];

                    child.update(dc);

                    if (this.levels.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
                        this.addTileOrDescendants(dc, child);
                    }
                }
            } finally {
                if (ancestorTile) {
                    this.currentAncestorTile = ancestorTile;
                }
            }
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.addTile = function (dc, tile) {
            tile.fallbackTile = null;

            var texture = dc.gpuResourceCache.resourceForKey(tile.imagePath);
            if (texture) {
                tile.opacity = 1;;
                this.currentTiles.push(tile);

                // If the tile's texture has expired, cause it to be re-retrieved. Note that the current,
                // expired texture is still used until the updated one arrives.
                if (this.expiration && this.isTextureExpired(texture)) {
                    this.retrieveTileImage(dc, tile);
                }

                return;
            }

            this.retrieveTileImage(dc, tile);

            if (this.currentAncestorTile) {
                if (this.isTileTextureInMemory(dc, this.currentAncestorTile)) {
                    // Set up to map the ancestor tile into the current one.
                    tile.fallbackTile = this.currentAncestorTile;
                    tile.fallbackTile.opacity = 1;
                    this.currentTiles.push(tile);
                }
            }
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.isTileVisible = function (dc, tile) {
            if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
                return false;
            }

            return tile.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
            var s = this.detailControl;
            if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
                s *= 1.2;
            }
            return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.isTileTextureInMemory = function (dc, tile) {
            return dc.gpuResourceCache.containsResource(tile.imagePath);
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.isTextureExpired = function (texture) {
            return this.expiration && (texture.creationTime.getTime() <= this.expiration.getTime());
        };

        /**
         * Retrieves the image for the specified tile. Subclasses should override this method in order to retrieve,
         * compute or otherwise create the image.
         * @param {DrawContext} dc The current draw context.
         * @param {ImageTile} tile The tile for which to retrieve the resource.
         * @param {Boolean} suppressRedraw true to suppress generation of redraw events when an image is successfully
         * retrieved, otherwise false.
         * @protected
         */
        TiledImageLayer.prototype.retrieveTileImage = function (dc, tile, suppressRedraw) {
            if (this.currentRetrievals.indexOf(tile.imagePath) < 0) {
                if (this.absentResourceList.isResourceAbsent(tile.imagePath)) {
                    return;
                }

                var url = this.resourceUrlForTile(tile, this.retrievalImageFormat),
                    image = new Image(),
                    imagePath = tile.imagePath,
                    cache = dc.gpuResourceCache,
                    canvas = dc.currentGlContext.canvas,
                    layer = this;

                if (!url) {
                    this.currentTilesInvalid = true;
                    return;
                }

                image.onload = function () {
                    Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + url);
                    var texture = layer.createTexture(dc, tile, image);
                    layer.removeFromCurrentRetrievals(imagePath);

                    if (texture) {
                        cache.putResource(imagePath, texture, texture.size);

                        layer.currentTilesInvalid = true;
                        layer.absentResourceList.unmarkResourceAbsent(imagePath);

                        if (!suppressRedraw) {
                            // Send an event to request a redraw.
                            var e = document.createEvent('Event');
                            e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                            canvas.dispatchEvent(e);
                        }
                    }
                };

                image.onerror = function () {
                    layer.removeFromCurrentRetrievals(imagePath);
                    layer.absentResourceList.markResourceAbsent(imagePath);
                    Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + url);
                };

                this.currentRetrievals.push(imagePath);
                image.crossOrigin = this.crossOrigin;
                image.src = url;
            }
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.createTexture = function (dc, tile, image) {
            return new Texture(dc.currentGlContext, image);
        };

        // Intentionally not documented.
        TiledImageLayer.prototype.removeFromCurrentRetrievals = function (imagePath) {
            var index = this.currentRetrievals.indexOf(imagePath);
            if (index > -1) {
                this.currentRetrievals.splice(index, 1);
            }
        };

        /**
         * Returns the URL string for the resource.
         * @param {ImageTile} tile The tile whose image is returned
         * @param {String} imageFormat The mime type of the image format desired.
         * @returns {String} The URL string, or null if the string can not be formed.
         * @protected
         */
        TiledImageLayer.prototype.resourceUrlForTile = function (tile, imageFormat) {
            if (this.urlBuilder) {
                return this.urlBuilder.urlForTile(tile, imageFormat);
            } else {
                return null;
            }
        };

        return TiledImageLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports MercatorTiledImageLayer
 */
define('layer/MercatorTiledImageLayer',[
        '../util/Color',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../geom/Vec2',
        '../util/WWMath'
    ],
    function (Color,
              Sector,
              TiledImageLayer,
              Vec2,
              WWMath) {
        "use strict";

        /**
         * Constructs a layer supporting Mercator imagery.
         * @alias MercatorTiledImageLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Provides an abstract layer to support Mercator layers.
         *
         * @param {Sector} sector The sector this layer covers.
         * @param {Location} levelZeroDelta The size in latitude and longitude of level zero (lowest resolution) tiles.
         * @param {Number} numLevels The number of levels to define for the layer. Each level is successively one power
         * of two higher resolution than the next lower-numbered level. (0 is the lowest resolution level, 1 is twice
         * that resolution, etc.)
         * Each level contains four times as many tiles as the next lower-numbered level, each 1/4 the geographic size.
         * @param {String} imageFormat The mime type of the image format for the layer's tiles, e.g., <em>image/png</em>.
         * @param {String} cachePath A string uniquely identifying this layer relative to other layers.
         * @param {Number} tileWidth The horizontal size of image tiles in pixels.
         * @param {Number} tileHeight The vertical size of image tiles in pixels.
         * @throws {ArgumentError} If any of the specified sector, level-zero delta, cache path or image format arguments are
         * null or undefined, or if the specified number of levels, tile width or tile height is less than 1.
         */
        var MercatorTiledImageLayer = function (sector, levelZeroDelta, numLevels, imageFormat, cachePath,
                                                tileWidth, tileHeight) {
            TiledImageLayer.call(this,
                sector, levelZeroDelta, numLevels, imageFormat, cachePath, tileWidth, tileHeight);

            this.detectBlankImages = false;

            // These pixels are tested in retrieved images to determine whether the image is blank.
            this.testPixels = [
                new Vec2(20, 20),
                new Vec2(235, 20),
                new Vec2(20, 235),
                new Vec2(235, 235)
            ];

            // Create a canvas we can use when unprojecting retrieved images.
            this.destCanvas = document.createElement("canvas");
            this.destContext = this.destCanvas.getContext("2d");
        };

        MercatorTiledImageLayer.prototype = Object.create(TiledImageLayer.prototype);

        // Overridden from TiledImageLayer. Computes a tile's sector and creates the tile.
        // Unlike typical tiles, Tiles at the same level do not have the same sector size.
        MercatorTiledImageLayer.prototype.createTile = function (sector, level, row, column) {
            var mapSize = this.mapSizeForLevel(level.levelNumber),
                swX = WWMath.clamp(column * this.imageSize, 0, mapSize),
                neY = WWMath.clamp(row * this.imageSize, 0, mapSize),
                neX = WWMath.clamp(swX + (this.imageSize), 0, mapSize),
                swY = WWMath.clamp(neY + (this.imageSize), 0, mapSize),
                x, y, swLat, swLon, neLat, neLon;

            x = (swX / mapSize) - 0.5;
            y = 0.5 - (swY / mapSize);
            swLat = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
            swLon = 360 * x;

            x = (neX / mapSize) - 0.5;
            y = 0.5 - (neY / mapSize);
            neLat = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
            neLon = 360 * x;

            sector = new Sector(swLat, neLat, swLon, neLon);

            return TiledImageLayer.prototype.createTile.call(this, sector, level, row, column);
        };

        // Overridden from TiledImageLayer to unproject the retrieved image prior to creating a texture for it.
        MercatorTiledImageLayer.prototype.createTexture = function (dc, tile, image) {
            var srcCanvas = dc.canvas2D,
                srcContext = dc.ctx2D,
                srcImageData,
                destCanvas = this.destCanvas,
                destContext = this.destContext,
                destImageData = destContext.createImageData(image.width, image.height),
                sector = tile.sector,
                tMin = WWMath.gudermannianInverse(sector.minLatitude),
                tMax = WWMath.gudermannianInverse(sector.maxLatitude),
                lat, g, srcRow, kSrc, kDest, sy, dy;

            srcCanvas.width = image.width;
            srcCanvas.height = image.height;
            destCanvas.width = image.width;
            destCanvas.height = image.height;

            // Draw the original image to a canvas so image data can be had for it.
            srcContext.drawImage(image, 0, 0, image.width, image.height);
            srcImageData = srcContext.getImageData(0, 0, image.width, image.height);

            // If it's a blank image, mark it as permanently absent.
            if (this.detectBlankImages && this.isBlankImage(image, srcImageData)) {
                this.absentResourceList.markResourceAbsentPermanently(tile.imagePath);
                return null;
            }

            // Unproject the retrieved image.
            for (var n = 0; n < 1; n++) {
                for (var y = 0; y < image.height; y++) {
                    sy = 1 - y / (image.height - 1);
                    lat = sy * sector.deltaLatitude() + sector.minLatitude;
                    g = WWMath.gudermannianInverse(lat);
                    dy = 1 - (g - tMin) / (tMax - tMin);
                    dy = WWMath.clamp(dy, 0, 1);
                    srcRow = Math.floor(dy * (image.height - 1));
                    for (var x = 0; x < image.width; x++) {
                        kSrc = 4 * (x + srcRow * image.width);
                        kDest = 4 * (x + y * image.width);

                        destImageData.data[kDest] = srcImageData.data[kSrc];
                        destImageData.data[kDest + 1] = srcImageData.data[kSrc + 1];
                        destImageData.data[kDest + 2] = srcImageData.data[kSrc + 2];
                        destImageData.data[kDest + 3] = srcImageData.data[kSrc + 3];
                    }
                }
            }

            destContext.putImageData(destImageData, 0, 0);

            return TiledImageLayer.prototype.createTexture.call(this, dc, tile, destCanvas);
        };

        // Determines whether a retrieved image is blank.
        MercatorTiledImageLayer.prototype.isBlankImage = function (image, srcImageData) {
            var pixel, k, pixelValue = null;

            for (var i = 0, len = this.testPixels.length; i < len; i++) {
                pixel = this.testPixels[i];
                k = 4 * (pixel[0] + pixel[1] * image.width);

                if (!pixelValue) {
                    pixelValue = [
                        srcImageData.data[k],
                        srcImageData.data[k + 1],
                        srcImageData.data[k + 2]
                    ];
                } else {
                    if (srcImageData.data[k] != pixelValue[0]
                        || srcImageData.data[k + 1] != pixelValue[1]
                        || srcImageData.data[k + 2] != pixelValue[2]) {
                        return false;
                    }
                }
            }

            return true;
        };

        return MercatorTiledImageLayer;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingTiledImageLayer
 */
define('layer/BingTiledImageLayer',[
        '../geom/Angle',
        '../geom/Location',
        '../geom/Sector',
        '../layer/MercatorTiledImageLayer'
    ],
    function (Angle,
              Location,
              Sector,
              MercatorTiledImageLayer) {
        "use strict";

        /**
         * Constructs a base Bing layer. This constructor is meant to be called only by subclasses.
         * @alias BingTiledImageLayer
         * @constructor
         * @augments MercatorTiledImageLayer
         * @classdesc Provides an abstract base layer for Bing imagery. This class is not intended to be constructed
         * independently but as a base layer for subclasses.
         * See {@link BingAerialLayer}, {@link BingAerialWithLabelsLayer} and {@link BingRoadsLayer}.
         *
         * @param {String} displayName This layer's display name.
         */
        var BingTiledImageLayer = function (displayName) {
            this.imageSize = 256;

            MercatorTiledImageLayer.call(this,
                new Sector(-85.05, 85.05, -180, 180), new Location(85.05, 180), 23, "image/jpeg", displayName,
                this.imageSize, this.imageSize);

            this.displayName = displayName;
            this.pickEnabled = false;

            this.detectBlankImages = true;

            this.creditImage = WorldWind.configuration.baseUrl + "images/powered-by-bing.png"
        };

        BingTiledImageLayer.prototype = Object.create(MercatorTiledImageLayer.prototype);

        BingTiledImageLayer.prototype.doRender = function (dc) {
            MercatorTiledImageLayer.prototype.doRender.call(this, dc);
            if (this.inCurrentFrame) {
                dc.screenCreditController.addImageCredit(this.creditImage);
            }
        };

        // Overridden from TiledImageLayer.
        BingTiledImageLayer.prototype.createTopLevelTiles = function (dc) {
            this.topLevelTiles = [];

            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 1));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 1));
        };

        // Determines the Bing map size for a specified level number.
        BingTiledImageLayer.prototype.mapSizeForLevel = function (levelNumber) {
            return 256 << (levelNumber + 1);
        };

        return BingTiledImageLayer;
    }
)
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingImageryUrlBuilder
 */
define('util/BingImageryUrlBuilder',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              Logger,
              WWUtil) {
        "use strict";

        /**
         * Constructs a URL builder for Bing imagery.
         * @alias BingImageryUrlBuilder
         * @constructor
         * @classdesc Provides a factory to create URLs for Bing image requests.
         * @param {String} imagerySet The name of the imagery set to display.
         * @param {String} bingMapsKey The Bing Maps key to use for the image requests. If null or undefined, the key at
         * [WorldWind.BingMapsKey]{@link WorldWind#BingMapsKey} is used. If that is null or undefined, the default
         * WorldWind Bing Maps key is used,
         * but this fallback is provided only for non-production use. If you are using Web WorldWind in an app or a
         * web page, you must obtain your own key from the
         * [Bing Maps Portal]{@link https://www.microsoft.com/maps/choose-your-bing-maps-API.aspx}
         * and either pass it as a parameter to this constructor or specify it as the property
         * [WorldWind.BingMapsKey]{@link WorldWind#BingMapsKey}.
         */
        var BingImageryUrlBuilder = function (imagerySet, bingMapsKey) {
            var wwBingMapsKey = "AkttWCS8p6qzxvx5RH3qUcCPgwG9nRJ7IwlpFGb14B0rBorB5DvmXr2Y_eCUNIxH";

            // Use key specified for this layer
            this.bingMapsKey = bingMapsKey;

            // If none, fallback to key specified globally
            if (!this.bingMapsKey) {
                this.bingMapsKey = WorldWind.BingMapsKey;
            }

            // If none, fallback to default demo key
            if (!this.bingMapsKey) {
                this.bingMapsKey = wwBingMapsKey;
            }
            
            // If using WorldWind Bing Maps demo key, show warning
            if (this.bingMapsKey === wwBingMapsKey) {
                BingImageryUrlBuilder.showBingMapsKeyWarning();
            }

            this.imagerySet = imagerySet;
        };

        // Intentionally not documented.
        BingImageryUrlBuilder.showBingMapsKeyWarning = function () {
            if (!BingImageryUrlBuilder.keyMessagePrinted) {
                BingImageryUrlBuilder.keyMessagePrinted = true;

                Logger.log(Logger.LEVEL_WARNING, "WARNING: You are using a limited use, non-production Bing Maps key.\n" +
                "If you are developing an app or a web page this violates the Bing Terms of Use.\n" +
                "Please visit https://www.microsoft.com/maps/choose-your-bing-maps-API.aspx to obtain your own key for your application.\n" +
                "Specify that key to WorldWind by setting the WorldWind.BingMapsKey property to your key " +
                "prior to creating any Bing Maps layers.\n");
            }
        };

        BingImageryUrlBuilder.prototype.requestMetadata = function () {
            // Retrieve the metadata for the imagery set.

            if (!this.metadataRetrievalInProcess) {
                this.metadataRetrievalInProcess = true;

                var url = "https://dev.virtualearth.net/REST/V1/Imagery/Metadata/" + this.imagerySet + "/0,0?zl=1&uriScheme=https&key="
                    + this.bingMapsKey;

                // Use JSONP to request the metadata. Can't use XmlHTTPRequest because the virtual earth server doesn't
                // allow cross-origin requests for metadata retrieval.

                var thisObject = this;
                WWUtil.jsonp(url, "jsonp", function (jsonData) {
                    thisObject.imageUrl = jsonData.resourceSets[0].resources[0].imageUrl;

                    // Send an event to request a redraw.
                    var e = document.createEvent('Event');
                    e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                    window.dispatchEvent(e);

                    thisObject.metadataRetrievalInProcess = false;
                });

            }
        };

        /**
         * Creates the URL string for a Bing Maps request.
         * @param {Tile} tile The tile for which to create the URL.
         * @param {String} imageFormat This argument is not used.
         * @return {String} The URL for the specified tile.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        BingImageryUrlBuilder.prototype.urlForTile = function (tile, imageFormat) {
            if (!tile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BingImageryUrlBuilder", "urlForTile", "missingTile"));
            }

            if (!this.imageUrl) {
                // Can't do anything until we get the metadata back from the server.
                this.requestMetadata();
                return null;
            }

            // The quad key identifies the specific image tile for the requested tile.
            var quadKey = this.quadKeyFromLevelRowColumn(tile.level.levelNumber, tile.row, tile.column),
                url;

            // Modify the original image URL to request the tile.
            if (this.imagerySet === "Aerial") {
                url = this.imageUrl.replace(/a3/, "a" + quadKey);
            } else if (this.imagerySet === "AerialWithLabels") {
                url = this.imageUrl.replace(/h3/, "h" + quadKey);
            } else if (this.imagerySet === "Road") {
                url = this.imageUrl.replace(/r3/, "r" + quadKey);
            }

            return url;
        };

        // Intentionally not documented.
        BingImageryUrlBuilder.prototype.quadKeyFromLevelRowColumn = function (levelNumber, row, column) {
            var digit, mask, key = "";

            for (var i = levelNumber + 1; i > 0; i--) {
                digit = 0;
                mask = 1 << (i - 1);

                if ((column & mask) != 0) {
                    digit += 1;
                }

                if ((row & mask) != 0) {
                    digit += 2;
                }

                key += digit.toString();
            }

            return key;
        };

        return BingImageryUrlBuilder;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingAerialLayer
 */
define('layer/BingAerialLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/BingTiledImageLayer',
        '../util/BingImageryUrlBuilder'
    ],
    function (Location,
              Sector,
              BingTiledImageLayer,
              BingImageryUrlBuilder) {
        "use strict";

        /**
         * Constructs a Bing Aerial layer.
         * @alias BingAerialLayer
         * @constructor
         * @augments BingTiledImageLayer
         * @classdesc Displays the Bing Aerial layer.
         * See also {@link BingAerialWithLabelsLayer} and {@link BingRoadsLayer}.
         *
         * @param {String} bingMapsKey The Bing Maps key to use for the image requests. If null or undefined, the key at
         * WorldWind.BingMapsKey is used. If that is null or undefined, the default WorldWind Bing Maps key is used,
         * but this fallback is provided only for non-production use. If you are using Web WorldWind in an app or a
         * web page, you must obtain your own key from the
         * [Bing Maps Portal]{@link https://www.microsoft.com/maps/choose-your-bing-maps-API.aspx}
         * and either pass it as a parameter to this constructor or specify it as the property WorldWind.BingMapsKey.
         */
        var BingAerialLayer = function (bingMapsKey) {
            BingTiledImageLayer.call(this, "Bing Aerial");

            this.urlBuilder = new BingImageryUrlBuilder("Aerial", bingMapsKey);
        };

        BingAerialLayer.prototype = Object.create(BingTiledImageLayer.prototype);

        return BingAerialLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingAerialWithLabelsLayer
 */
define('layer/BingAerialWithLabelsLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/BingTiledImageLayer',
        '../util/BingImageryUrlBuilder'
    ],
    function (Location,
              Sector,
              BingTiledImageLayer,
              BingImageryUrlBuilder) {
        "use strict";

        /**
         * Constructs a Bing Aerial with Labels layer.
         * @alias BingAerialWithLabelsLayer
         * @constructor
         * @augments BingTiledImageLayer
         * @classdesc Displays a Bing Aerial layer with roads and labels.
         * See also {@link BingAerialLayer} and {@link BingRoadsLayer}.
         *
         * @param {String} bingMapsKey The Bing Maps key to use for the image requests. If null or undefined, the key at
         * WorldWind.BingMapsKey is used. If that is null or undefined, the default WorldWind Bing Maps key is used,
         * but this fallback is provided only for non-production use. If you are using Web WorldWind in an app or a
         * web page, you must obtain your own key from the
         * [Bing Maps Portal]{@link https://www.microsoft.com/maps/choose-your-bing-maps-API.aspx}
         * and either pass it as a parameter to this constructor or specify it as the property WorldWind.BingMapsKey.
         */
        var BingAerialWithLabelsLayer = function (bingMapsKey) {
            BingTiledImageLayer.call(this, "Bing Aerial with Labels");

            this.urlBuilder = new BingImageryUrlBuilder("AerialWithLabels", bingMapsKey);
        };

        BingAerialWithLabelsLayer.prototype = Object.create(BingTiledImageLayer.prototype);

        return BingAerialWithLabelsLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingRoadsLayer
 */
define('layer/BingRoadsLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/BingTiledImageLayer',
        '../util/BingImageryUrlBuilder'
    ],
    function (Location,
              Sector,
              BingTiledImageLayer,
              BingImageryUrlBuilder) {
        "use strict";

        /**
         * Constructs a Bing Roads layer.
         * @alias BingRoadsLayer
         * @constructor
         * @augments BingTiledImageLayer
         * @classdesc Displays a Bing Roads layer.
         * See also {@link BingAerialLayer} and {@link BingAerialWithLabelsLayer}.
         *
         * @param {String} bingMapsKey The Bing Maps key to use for the image requests. If null or undefined, the key at
         * WorldWind.BingMapsKey is used. If that is null or undefined, the default WorldWind Bing Maps key is used,
         * but this fallback is provided only for non-production use. If you are using Web WorldWind in an app or a
         * web page, you must obtain your own key from the
         * [Bing Maps Portal]{@link https://www.microsoft.com/maps/choose-your-bing-maps-API.aspx}
         * and either pass it as a parameter to this constructor or specify it as the property WorldWind.BingMapsKey.
         */
        var BingRoadsLayer = function (bingMapsKey) {
            BingTiledImageLayer.call(this, "Bing Roads");

            this.urlBuilder = new BingImageryUrlBuilder("Road", bingMapsKey);

            // Disable blank-image detection.
            this.detectBlankImages = false;
        };

        BingRoadsLayer.prototype = Object.create(BingTiledImageLayer.prototype);

        return BingRoadsLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmsUrlBuilder
 */
define('util/WmsUrlBuilder',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a WMS URL builder.
         * @alias WmsUrlBuilder
         * @constructor
         * @classdesc Provides a factory to create URLs for WMS Get Map requests.
         * @param {String} serviceAddress The address of the WMS server.
         * @param {String} layerNames The comma-separated list of names of the layers to retrieve.
         * @param {String} styleNames The comma-separated list of names of the styles to retrieve. May be null.
         * @param {String} wmsVersion The version of the WMS server. May be null, in which case version 1.3.0 is
         * assumed.
         * @param {String} timeString The time parameter included in GetMap requests.
         * May be null, in which case no time parameter is included in the request.
         * @throws {ArgumentError} If the service address or layer names are null or empty.
         *
         */
        var WmsUrlBuilder = function (serviceAddress, layerNames, styleNames, wmsVersion, timeString) {
            if (!serviceAddress || (serviceAddress.length === 0)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "constructor",
                        "The WMS service address is missing."));
            }

            if (!layerNames || (layerNames.length === 0)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "constructor",
                        "The WMS layer names are not specified."));
            }

            /**
             * The address of the WMS server.
             * @type {String}
             */
            this.serviceAddress = serviceAddress;

            /**
             * The comma-separated list of layer names to retrieve.
             * @type {String}
             */
            this.layerNames = layerNames;

            /**
             * The comma-separated list of style names to retrieve.
             * @type {String}
             */
            this.styleNames = styleNames ? styleNames : "";

            /**
             * Indicates whether the layer should be requested with transparency.
             * @type {Boolean}
             * @default true
             */
            this.transparent = true;

            /**
             * The WMS version to specify when requesting resources.
             * @type {String}
             * @default 1.3.0
             */
            this.wmsVersion = (wmsVersion && wmsVersion.length > 0) ? wmsVersion : "1.3.0";
            this.isWms130OrGreater = this.wmsVersion >= "1.3.0";

            /**
             * The coordinate reference system to use when requesting layers.
             * @type {String}
             * @default EPSG:4326
             */
            this.crs = "EPSG:4326";

            /**
             * The time parameter included in GetMap requests. If null, no time parameter is included in the requests.
             * @type {String}
             */
            this.timeString = timeString;
        };

        /**
         * Creates the URL string for a WMS Get Map request.
         * @param {Tile} tile The tile for which to create the URL.
         * @param {String} imageFormat The image format to request.
         * @throws {ArgumentError} If the specified tile or image format are null or undefined.
         */
        WmsUrlBuilder.prototype.urlForTile = function (tile, imageFormat) {
            if (!tile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "urlForTile", "missingTile"));
            }

            if (!imageFormat) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "urlForTile",
                        "The image format is null or undefined."));
            }

            var sector = tile.sector;

            var sb = WmsUrlBuilder.fixGetMapString(this.serviceAddress);

            if (sb.search(/service=wms/i) < 0) {
                sb = sb + "service=WMS";
            }

            sb = sb + "&request=GetMap";
            sb = sb + "&version=" + this.wmsVersion;
            sb = sb + "&transparent=" + (this.transparent ? "TRUE" : "FALSE");
            sb = sb + "&layers=" + this.layerNames;
            sb = sb + "&styles=" + this.styleNames;
            sb = sb + "&format=" + imageFormat;
            sb = sb + "&width=" + tile.tileWidth;
            sb = sb + "&height=" + tile.tileHeight;

            if (this.timeString) {
                sb = sb + "&time=" + this.timeString;
            }

            if (this.isWms130OrGreater) {
                sb = sb + "&crs=" + this.crs;
                sb = sb + "&bbox=";
                if (this.crs === "CRS:84") {
                    sb = sb + sector.minLongitude + "," + sector.minLatitude + ",";
                    sb = sb + sector.maxLongitude+ "," + sector.maxLatitude;
                } else {
                    sb = sb + sector.minLatitude + "," + sector.minLongitude + ",";
                    sb = sb + sector.maxLatitude+ "," + sector.maxLongitude;
                }
            } else {
                sb = sb + "&srs=" + this.crs;
                sb = sb + "&bbox=";
                sb = sb + sector.minLongitude + "," + sector.minLatitude + ",";
                sb = sb + sector.maxLongitude+ "," + sector.maxLatitude;
            }

            sb = sb.replace(" ", "%20");

            return sb;
        };

        // Intentionally not documented.
        WmsUrlBuilder.fixGetMapString = function (serviceAddress) {
            if (!serviceAddress) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "fixGetMapString",
                        "The specified service address is null or undefined."));
            }

            var index = serviceAddress.indexOf("?");

            if (index < 0) { // if string contains no question mark
                serviceAddress = serviceAddress + "?"; // add one
            } else if (index !== serviceAddress.length - 1) { // else if question mark not at end of string
                index = serviceAddress.search(/&$/);
                if (index < 0) {
                    serviceAddress = serviceAddress + "&"; // add a parameter separator
                }
            }

            return serviceAddress;
        };

        return WmsUrlBuilder;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BingWMSLayer
 */
define('layer/BingWMSLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/WmsUrlBuilder'
    ],
    function (Location,
              Sector,
              TiledImageLayer,
              WmsUrlBuilder) {
        "use strict";

        // Intentionally not documented. For diagnostic use only.
        var BingWMSLayer = function () {
            TiledImageLayer.call(this,
                Sector.FULL_SPHERE, new Location(45, 45), 16, "image/png", "BingWMS", 256, 256);

            this.displayName = "Bing WMS";
            this.pickEnabled = false;
            this.maxActiveAltitude = 10e3;

            this.urlBuilder = new WmsUrlBuilder("https://worldwind27.arc.nasa.gov/wms/virtualearth", "ve", "", "1.3.0");
        };

        BingWMSLayer.prototype = Object.create(TiledImageLayer.prototype);

        return BingWMSLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BMNGLandsatLayer
 */
define('layer/BMNGLandsatLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/WmsUrlBuilder'
    ],
    function (Location,
              Sector,
              TiledImageLayer,
              WmsUrlBuilder) {
        "use strict";

        /**
         * Constructs a combined Blue Marble and Landsat image layer.
         * @alias BMNGLandsatLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a combined Blue Marble and Landsat image layer that spans the entire globe.
         */
        var BMNGLandsatLayer = function () {
            TiledImageLayer.call(this,
                Sector.FULL_SPHERE, new Location(45, 45), 10, "image/jpeg", "BMNGLandsat256", 256, 256);

            this.displayName = "Blue Marble & Landsat";
            this.pickEnabled = false;

            this.urlBuilder = new WmsUrlBuilder("https://worldwind25.arc.nasa.gov/wms",
                "BlueMarble-200405,esat", "", "1.3.0");
        };

        BMNGLandsatLayer.prototype = Object.create(TiledImageLayer.prototype);

        return BMNGLandsatLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BMNGLayer
 */
define('layer/BMNGLayer',[
        '../geom/Location',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/WmsUrlBuilder'
    ],
    function (Location,
              Sector,
              TiledImageLayer,
              WmsUrlBuilder) {
        "use strict";

        /**
         * Constructs a Blue Marble image layer.
         * @alias BMNGLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a Blue Marble image layer that spans the entire globe.
         * @param {String} layerName The name of the layer to display, in the form "BlueMarble-200401"
         * "BlueMarble-200402", ... "BlueMarble-200412". "BlueMarble-200405" is used if the argument is null
         * or undefined.
         */
        var BMNGLayer = function (layerName) {
            TiledImageLayer.call(this,
                Sector.FULL_SPHERE, new Location(45, 45), 5, "image/jpeg", layerName || "BMNG256", 256, 256);

            this.displayName = "Blue Marble";
            this.pickEnabled = false;

            this.urlBuilder = new WmsUrlBuilder("https://worldwind25.arc.nasa.gov/wms",
                layerName || "BlueMarble-200405", "", "1.3.0");
        };

        BMNGLayer.prototype = Object.create(TiledImageLayer.prototype);

        return BMNGLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports RenderableLayer
 */
define('layer/RenderableLayer',[
        '../error/ArgumentError',
        '../layer/Layer',
        '../util/Logger'
    ],
    function (ArgumentError,
              Layer,
              Logger) {
        "use strict";

        /**
         * Constructs a layer that contains shapes and other renderables.
         * @alias RenderableLayer
         * @constructor
         * @augments Layer
         * @classdesc Provides a layer that contains shapes and other renderables.
         * @param {String} displayName This layer's display name.
         */
        var RenderableLayer = function (displayName) {
            Layer.call(this, displayName);

            /**
             * The array of renderables;
             * @type {Array}
             * @readonly
             */
            this.renderables = [];
        };

        RenderableLayer.prototype = Object.create(Layer.prototype);

        /**
         * Adds a renderable to this layer.
         * @param {Renderable} renderable The renderable to add.
         * @throws {ArgumentError} If the specified renderable is null or undefined.
         */
        RenderableLayer.prototype.addRenderable = function (renderable) {
            if (!renderable) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "RenderableLayer", "addRenderable",
                    "missingRenderable"));
            }

            this.renderables.push(renderable);
        };

        /**
         * Adds an array of renderables to this layer.
         * @param {Renderable[]} renderables The renderables to add.
         * @throws {ArgumentError} If the specified renderables array is null or undefined.
         */
        RenderableLayer.prototype.addRenderables = function (renderables) {
            if (!renderables) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "RenderableLayer", "addRenderables",
                    "The renderables array is null or undefined."));
            }

            for (var i = 0, len = renderables.length; i < len; i++) {
                this.addRenderable(renderables[i]);
            }
        };

        /**
         * Removes a renderable from this layer.
         * @param {Renderable} renderable The renderable to remove.
         */
        RenderableLayer.prototype.removeRenderable = function (renderable) {
            var index = this.renderables.indexOf(renderable);
            if (index >= 0) {
                this.renderables.splice(index, 1);
            }
        };

        /**
         * Removes all renderables from this layer. Does not call dispose on those renderables.
         */
        RenderableLayer.prototype.removeAllRenderables = function () {
            this.renderables = [];
        };

        // Documented in superclass.
        RenderableLayer.prototype.doRender = function (dc) {
            var numOrderedRenderablesAtStart = dc.orderedRenderables.length;

            for (var i = 0, len = this.renderables.length; i < len; i++) {
                try {
                    this.renderables[i].render(dc);
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_SEVERE, "RenderableLayer", "doRender",
                        "Error while rendering shape " + this.renderables[i].displayName + ".\n" + e.toString());
                    // Keep going. Render the rest of the shapes.
                }
            }

            if (dc.orderedRenderables.length > numOrderedRenderablesAtStart) {
                this.inCurrentFrame = true;
            }
        };

        return RenderableLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceTile
 */
define('render/SurfaceTile',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Matrix',
        '../geom/Sector',
        '../error/UnsupportedOperationError'
    ],
    function (ArgumentError,
              Logger,
              Matrix,
              Sector,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Constructs a surface tile for a specified sector.
         * @alias SurfaceTile
         * @constructor
         * @classdesc Defines an abstract base class for imagery to be rendered on terrain. Applications typically
         * do not interact with this class.
         * @param {Sector} sector The sector of this surface tile.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        var SurfaceTile = function (sector) {
            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "constructor",
                    "missingSector"));
            }

            /**
             * The sector spanned by this surface tile.
             * @type {Sector}
             */
            this.sector = sector;
        };

        /**
         * Causes this surface tile to be active, typically by binding the tile's texture in WebGL.
         * Subclasses must override this function.
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if the resource was successfully bound, otherwise false.
         */
        SurfaceTile.prototype.bind = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "bind", "abstractInvocation"));
        };

        /**
         * Applies this surface tile's internal transform, typically a texture transform to align the associated
         * resource with the terrain.
         * Subclasses must override this function.
         * @param {DrawContext} dc The current draw context.
         * @param {Matrix} matrix The transform to apply.
         */
        SurfaceTile.prototype.applyInternalTransform = function (dc, matrix) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTile", "applyInternalTransform", "abstractInvocation"));
        };

        return SurfaceTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceImage
 */
define('shapes/SurfaceImage',[
        '../error/ArgumentError',
        '../util/Logger',
        '../pick/PickedObject',
        '../render/SurfaceTile'
    ],
    function (ArgumentError,
              Logger,
              PickedObject,
              SurfaceTile) {
        "use strict";

        /**
         * Constructs a surface image shape for a specified sector and image path.
         * @alias SurfaceImage
         * @constructor
         * @augments SurfaceTile
         * @classdesc Represents an image drawn on the terrain.
         * @param {Sector} sector The sector spanned by this surface image.
         * @param {String|ImageSource} imageSource The image source of the image to draw on the terrain.
         * May be either a string identifying the URL of the image, or an {@link ImageSource} object identifying a
         * dynamically created image.
         * @throws {ArgumentError} If either the specified sector or image source is null or undefined.
         */
        var SurfaceImage = function (sector, imageSource) {
            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceImage", "constructor",
                    "missingSector"));
            }

            if (!imageSource) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceImage", "constructor",
                    "missingImage"));
            }

            SurfaceTile.call(this, sector);

            /**
             * Indicates whether this surface image is drawn.
             * @type {boolean}
             * @default true
             */
            this.enabled = true;

            /**
             * The path to the image.
             * @type {String}
             */
            this._imageSource = imageSource;

            /**
             * This surface image's opacity. When this surface image is drawn, the actual opacity is the product of
             * this opacity and the opacity of the layer containing this surface image.
             * @type {number}
             */
            this.opacity = 1;

            /**
             * This surface image's display name;
             * @type {string}
             */
            this.displayName = "Surface Image";

            // Internal. Indicates whether the image needs to be updated in the GPU resource cache.
            this.imageSourceWasUpdated = true;
        };

        SurfaceImage.prototype = Object.create(SurfaceTile.prototype);

        Object.defineProperties(SurfaceImage.prototype, {
            /**
             * The source of the image to display.
             * May be either a string identifying the URL of the image, or an {@link ImageSource} object identifying a
             * dynamically created image.
             * @type {String|ImageSource}
             * @default null
             * @memberof SurfaceImage.prototype
             */
            imageSource: {
                get: function () {
                    return this._imageSource;
                },
                set: function (imageSource) {
                    if (!imageSource) {
                        throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceImage", "imageSource",
                            "missingImage"));
                    }

                    this._imageSource = imageSource;
                    this.imageSourceWasUpdated = true;
                }
            }
        });

        SurfaceImage.prototype.bind = function (dc) {
            var texture = dc.gpuResourceCache.resourceForKey(this._imageSource);
            if (texture && !this.imageSourceWasUpdated) {
                return texture.bind(dc);
            } else {
                texture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this._imageSource);
                this.imageSourceWasUpdated = false;
                if (texture) {
                    return texture.bind(dc);
                }
            }
        };

        SurfaceImage.prototype.applyInternalTransform = function (dc, matrix) {
            // No need to apply the transform.
        };

        /**
         * Displays this surface image. Called by the layer containing this surface image.
         * @param {DrawContext} dc The current draw context.
         */
        SurfaceImage.prototype.render = function (dc) {
            if (!this.enabled) {
                return;
            }

            if (!dc.terrain) {
                return;
            }

            if (!this.sector.overlaps(dc.terrain.sector)) {
                return;
            }

            if (dc.pickingMode) {
                this.pickColor = dc.uniquePickColor();
            }

            dc.surfaceTileRenderer.renderTiles(dc, [this], this.opacity * dc.currentLayer.opacity);

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this.pickDelegate ? this.pickDelegate : this,
                    null, this.layer, false);
                dc.resolvePick(po);
            }

            dc.currentLayer.inCurrentFrame = true;
        };

        return SurfaceImage;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BMNGOneImageLayer
 */
define('layer/BMNGOneImageLayer',[
        '../layer/RenderableLayer',
        '../geom/Sector',
        '../shapes/SurfaceImage',
        '../util/WWUtil'
    ],
    function (RenderableLayer,
              Sector,
              SurfaceImage,
              WWUtil) {
        "use strict";

        /**
         * Constructs a Blue Marble image layer that spans the entire globe.
         * @alias BMNGOneImageLayer
         * @constructor
         * @augments RenderableLayer
         * @classdesc Displays a Blue Marble image layer that spans the entire globe with a single image.
         */
        var BMNGOneImageLayer = function () {
            RenderableLayer.call(this, "Blue Marble Image");

            var surfaceImage = new SurfaceImage(Sector.FULL_SPHERE,
                WorldWind.configuration.baseUrl + "images/BMNG_world.topo.bathy.200405.3.2048x1024.jpg");

            this.addRenderable(surfaceImage);

            this.pickEnabled = false;
            this.minActiveAltitude = 3e6;
        };

        BMNGOneImageLayer.prototype = Object.create(RenderableLayer.prototype);

        return BMNGOneImageLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PeriodicTimeSequence
 */
define('util/PeriodicTimeSequence',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a time sequence from an ISO 8601 string.
         * @alias PeriodicTimeSequence
         * @constructor
         * @classdesc Represents a time sequence described as an ISO 8601 time-format string as required by WMS.
         * The string must be in the form start/end/period, where start and end are ISO 8601 time values and
         * period is an ISO 8601 period specification. This class provides iteration over the sequence in steps
         * specified by the period. If the start and end dates are different, iteration will start at the start
         * date and end at the end date. If the start and end dates are the same, iteration will start at the
         * specified date and will never end.
         * @param {String} sequenceString The string describing the time sequence.
         * @throws {ArgumentError} If the specified intervalString is null, undefined or not a valid time interval
         * string.
         */
        var PeriodicTimeSequence = function (sequenceString) {
            if (!sequenceString) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PeriodicTimeSequence", "constructor", "missingString"));
            }

            var intervalParts = sequenceString.split("/");
            if (intervalParts.length !== 3) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PeriodicTimeSequence", "constructor",
                        "The interval string " + sequenceString + " does not contain 3 elements."));
            }

            /**
             * This sequence's sequence string, as specified to the constructor.
             * @type {String}
             * @readonly
             */
            this.sequenceString = sequenceString;

            /**
             * This sequence's start time.
             * @type {Date}
             * @readonly
             */
            this.startTime = new Date(intervalParts[0]);

            /**
             * This sequence's end time.
             * @type {Date}
             * @readonly
             */
            this.endTime = new Date(intervalParts[1]);

            // Intentionally not documented.
            this.intervalMilliseconds = this.endTime.getTime() - this.startTime.getTime();

            // Documented with property accessor below.
            this._currentTime = this.startTime;

            /**
             * Indicates whether this sequence is an infinite sequence -- the start and end dates are the same.
             * @type {Boolean}
             * @readonly
             */
            this.infiniteInterval = this.startTime.getTime() == this.endTime.getTime();

            // Intentionally not documented. The array of sequence increments:
            // year, month, week, day, hours, minutes, seconds
            this.period = PeriodicTimeSequence.parsePeriodString(intervalParts[2], false);
        };

        Object.defineProperties(PeriodicTimeSequence.prototype, {
            /**
             * This sequence's current time.
             * @type {Date}
             * @default This sequence's start time.
             * @memberof PeriodicTimeSequence.prototype
             */
            currentTime: {
                get: function () {
                    return this._currentTime;
                },
                set: function (value) {
                    this._currentTime = value;
                }
            },
            /**
             * Indicates the position of this sequence's current time relative to the sequence's total interval,
             * in the range [0, 1]. A value of 0 indicates this sequence's start time. A value of 1 indicates
             * this sequence's end time. A value of 0.5 indicates a current time that's exactly mid-way between
             * this sequence's start time and end time.
             * @type {Number}
             * @memberof PeriodicTimeSequence.prototype
             */
            scaleForCurrentTime: {
                get: function () {
                    if (!this.currentTime) {
                        return 1;
                    } else {
                        return (this.currentTime.getTime() - this.startTime.getTime()) / this.intervalMilliseconds;
                    }
                }
            }
        });

        /**
         * Sets this sequence's current time to the next time in the sequence and returns that time.
         * @returns {Date|null} The next time of this sequence, or null if no more times are in the sequence.
         * Use [reset]{@link PeriodicTimeSequence#reset} to re-start this sequence.
         * Use [previous]{@link PeriodicTimeSequence#previous} to step backwards through this sequence.
         */
        PeriodicTimeSequence.prototype.next = function () {
            if (!this.currentTime) {
                this.currentTime = this.startTime;
            } else if ((this.currentTime.getTime() >= this.endTime.getTime()) && !this.infiniteInterval) {
                this.currentTime = null;
            } else {
                this.currentTime = PeriodicTimeSequence.incrementTime(this.currentTime, this.period);
            }

            return this.currentTime;
        };

        /**
         * Sets this sequence's current time to the previous time in the sequence and returns that time.
         * @returns {Date|null} The previous time of this sequence, or null if the sequence is currently at its start
         * time.
         * Use [next]{@link PeriodicTimeSequence#next} to step forwards through this sequence.
         */
        PeriodicTimeSequence.prototype.previous = function () {
            if (!this.currentTime) {
                this.currentTime = this.endTime;
            } else if (this.currentTime.getTime() === this.startTime.getTime()) {
                this.currentTime = null;
            } else {
                this.currentTime = this.getTimeForScale(0.9999 * this.scaleForCurrentTime);
            }

            return this.currentTime;
        };

        /**
         * Resets this sequence's current time to its start time.
         * Use [next]{@link PeriodicTimeSequence#next} to step forwards through this sequence.
         * Use [previous]{@link PeriodicTimeSequence#previous} to step backwards through this sequence.
         */
        PeriodicTimeSequence.prototype.reset = function () {
            this.currentTime = null;
        };

        /**
         * Returns the time associated with a specified value in the range [0, 1]. A value of 0 returns this
         * sequence's start time. A value of 1 returns this sequence's end time. A value of 0.5 returs a time
         * mid-way between this sequence's start and end times.
         * @param scale The scale value. This value is clamped to the range [0, 1] before the time is determined.
         * @returns {Date}
         */
        PeriodicTimeSequence.prototype.getTimeForScale = function (scale) {
            if (scale <= 0) {
                return this.startTime;
            }

            if (scale >= 1) {
                return this.endTime;
            }

            var time = new Date(this.startTime.getTime()),
                previousTime = time,
                s = 0;

            for (s = 0; s < scale; s = (time.getTime() - this.startTime.getTime()) / this.intervalMilliseconds) {
                previousTime = time;
                time = PeriodicTimeSequence.incrementTime(time, this.period);
            }

            return previousTime;
        };

        // Intentionally not documented. Adds this sequence's period to a specified time.
        PeriodicTimeSequence.incrementTime = function (currentTime, period) {
            var newTime = new Date(currentTime.getTime());

            if (period[0] != 0) {
                newTime.setUTCFullYear(newTime.getUTCFullYear() + period[0]);
            }

            if (period[1] != 0) {
                PeriodicTimeSequence.addMonths(newTime, period[1]);
            }

            if (period[2] != 0) {
                newTime.setUTCDate(newTime.getUTCDate() + 7 * period[2]);
            }

            if (period[3] != 0) {
                newTime.setUTCDate(newTime.getUTCDate() + period[3]);
            }

            if (period[4] != 0) {
                newTime.setUTCHours(newTime.getUTCHours() + period[4]);
            }

            if (period[5] != 0) {
                newTime.setUTCMinutes(newTime.getUTCMinutes() + period[5]);
            }

            if (period[6] != 0) {
                newTime.setUTCSeconds(newTime.getUTCSeconds() + period[6]);
            }

            return newTime;
        };

        // Intentionally not documented.
        PeriodicTimeSequence.isLeapYear = function (year) {
            return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
        };

        // Intentionally not documented.
        PeriodicTimeSequence.getDaysInMonth = function (year, month) {
            return [31, (PeriodicTimeSequence.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
        };

        // Intentionally not documented.
        PeriodicTimeSequence.addMonths = function (date, numMonths) {
            var n = date.getUTCDate();
            date.setUTCDate(1);
            date.setUTCMonth(date.getUTCMonth() + numMonths);
            date.setUTCDate(Math.min(n, PeriodicTimeSequence.getDaysInMonth(date.getUTCFullYear(), date.getUTCMonth())));
            return date;
        };

        /*
         * Parses a ISO8601 period string.
         * @param {String} period iso8601 period string
         * @param {Boolean} distributeOverflow if 'true', the unit overflows are merge into the next higher units.
         */
        PeriodicTimeSequence.parsePeriodString = function (period, distributeOverflow) {
            // Taken from https://github.com/nezasa/iso8601-js-period/blob/master/iso8601.js

            // regex splits as follows
            // grp0 omitted as it is equal to the sample
            //
            // | sample            | grp1   | grp2 | grp3 | grp4 | grp5 | grp6       | grp7 | grp8 | grp9 |
            // --------------------------------------------------------------------------------------------
            // | P1Y2M3W           | 1Y2M3W | 1Y   | 2M   | 3W   | 4D   | T12H30M17S | 12H  | 30M  | 17S  |
            // | P3Y6M4DT12H30M17S | 3Y6M4D | 3Y   | 6M   |      | 4D   | T12H30M17S | 12H  | 30M  | 17S  |
            // | P1M               | 1M     |      | 1M   |      |      |            |      |      |      |
            // | PT1M              | 3Y6M4D |      |      |      |      | T1M        |      | 1M   |      |
            // --------------------------------------------------------------------------------------------

            var _distributeOverflow = (distributeOverflow) ? distributeOverflow : false;
            var valueIndexes = [2, 3, 4, 5, 7, 8, 9];
            var duration = [0, 0, 0, 0, 0, 0, 0];
            var overflowLimits = [0, 12, 4, 7, 24, 60, 60];
            var struct;

            // upcase the string just in case people don't follow the letter of the law
            period = period.toUpperCase().trim();

            // input validation
            if (!period)
                return duration;
            else if (typeof period !== "string") {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PeriodicTimeSequence", "parsePeriodString",
                        "Invalid ISO8601 period string '" + period + "'"));
            }

            // parse the string
            if (struct = /^P((\d+Y)?(\d+M)?(\d+W)?(\d+D)?)?(T(\d+H)?(\d+M)?(\d+S)?)?$/.exec(period)) {

                // remove letters, replace by 0 if not defined
                for (var i = 0; i < valueIndexes.length; i++) {
                    var structIndex = valueIndexes[i];
                    duration[i] = struct[structIndex] ? +struct[structIndex].replace(/[A-Za-z]+/g, '') : 0;
                }
            }
            else {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PeriodicTimeSequence", "parsePeriodString",
                        "String '" + period + "' is not a valid ISO8601 period."));
            }

            if (_distributeOverflow) {
                // note: stop at 1 to ignore overflow of years
                for (i = duration.length - 1; i > 0; i--) {
                    if (duration[i] >= overflowLimits[i]) {
                        duration[i - 1] = duration[i - 1] + Math.floor(duration[i] / overflowLimits[i]);
                        duration[i] = duration[i] % overflowLimits[i];
                    }
                }
            }

            return duration;
        };

        return PeriodicTimeSequence;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports LevelRowColumnUrlBuilder
 */
define('util/LevelRowColumnUrlBuilder',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              Logger,
              WWUtil) {
        "use strict";

        /**
         * Constructs a URL builder for level/row/column tiles.
         * @alias LevelRowColumnUrlBuilder
         * @constructor
         * @classdesc Provides a factory to create URLs for level/row/column tile REST requests.
         * <p>
         * URLs are formed by appending the specified server address with the specified path and appending
         * a path of the form <em>/level/row/row_column.image-format</em>, where image-format is the corresponding
         * suffix to the image mime type specified when a URL is requested. For example, if the specified server
         * address is <em>https://worldwind32.arc.nasa.gov</em> and the specified path-to-data is
         * <em>../standalonedata/Earth/BlueMarble256</em>, and the requested tile's level, row and column are 0, 5 and 9
         * respectively, and the image format is <em>image/jpeg</em>, the composed URL is
         * <em>https://worldwind32.arc.nasa.gov/standalonedata/Earth/BlueMarble256/0/5/5_9.jpg</em>.
         *
         * @param {String} serverAddress The server address. May be null, in which case the address is assumed to be
         * the current location (see <code>window.location</code>) minus the last path component.
         * @param {String} pathToData The path to the dataset on the server. May be null or empty to indicate that
         * the data is directly relative to the specified server address.
         *
         */
        var LevelRowColumnUrlBuilder = function (serverAddress, pathToData) {
            /**
             * The server address.
             * @type {String}
             */
            this.serverAddress = serverAddress;
            if (!serverAddress || serverAddress.length === 0) {
                this.serverAddress = WWUtil.currentUrlSansFilePart();
            }

            /**
             * The server-side path to the dataset.
             * @type {String}
             */
            this.pathToData = pathToData;
        };

        /**
         * Creates the URL string for a WMS Get Map request.
         * @param {Tile} tile The tile for which to create the URL.
         * @param {String} imageFormat The image format to request.
         * @throws {ArgumentError} If the specified tile or image format are null or undefined.
         */
        LevelRowColumnUrlBuilder.prototype.urlForTile = function (tile, imageFormat) {
            if (!tile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "urlForTile", "missingTile"));
            }

            if (!imageFormat) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsUrlBuilder", "urlForTile",
                        "The image format is null or undefined."));
            }

            var sb = this.serverAddress;

            if (this.pathToData) {
                sb = sb + "/" + this.pathToData;
            }

            sb = sb + "/" + tile.level.levelNumber.toString();
            sb = sb + "/" + tile.row.toString();
            sb = sb + "/" + tile.row.toString() + "_" + tile.column.toString();
            sb = sb + "." + WWUtil.suffixForMimeType(imageFormat);

            sb = sb.replace(" ", "%20");

            return sb;
        };

        return LevelRowColumnUrlBuilder;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports RestTiledImageLayer
 */
define('layer/RestTiledImageLayer',[
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/LevelRowColumnUrlBuilder',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              Location,
              Logger,
              Sector,
              TiledImageLayer,
              LevelRowColumnUrlBuilder,
              WWUtil) {
        "use strict";

        /**
         * Constructs a tiled image layer that uses a REST interface to retrieve its imagery.
         * @alias RestTiledImageLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a layer whose imagery is retrieved using a REST interface.
         * See [LevelRowColumnUrlBuilder]{@link LevelRowColumnUrlBuilder} for a description of the REST interface.
         * @param {String} serverAddress The server address of the tile service. May be null, in which case the
         * current origin is used (see window.location).
         * @param {String} pathToData The path to the data directory relative to the specified server address.
         * May be null, in which case the server address is assumed to be the full path to the data directory.
         * @param {String} displayName The display name to associate with this layer.
         * @param {{}} configuration The tiled image layer configuration. May have the following properties:
         * <ul>
         *     <li>sector {Sector}, default is full sphere</li>
         *     <li>levelZerotTileDelta {Location}, default is 45, 45</li>
         *     <li>numLevels {Number}, default is 5</li>
         *     <li>imageFormat {String}, default is image/jpeg</li>
         *     <li>tileWidth {Number}, default is 256</li>
         *     <li>tileHeight {Number}, default is 256</li>
         * </ul>
         * The specified default is used for any property not specified.
         */
        var RestTiledImageLayer = function (serverAddress, pathToData, displayName, configuration) {
            var cachePath = WWUtil.urlPath(serverAddress + "/" + pathToData);

            TiledImageLayer.call(this,
                (configuration && configuration.sector) || Sector.FULL_SPHERE,
                (configuration && configuration.levelZeroTileDelta) || new Location(45, 45),
                (configuration && configuration.numLevels) || 5,
                (configuration && configuration.imageFormat) || "image/jpeg",
                cachePath,
                (configuration && configuration.tileWidth) || 256,
                (configuration && configuration.tileHeight) || 256);

            this.displayName = displayName;
            this.pickEnabled = false;
            this.urlBuilder = new LevelRowColumnUrlBuilder(serverAddress, pathToData);
        };

        RestTiledImageLayer.prototype = Object.create(TiledImageLayer.prototype);

        return RestTiledImageLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports BMNGRestLayer
 */
define('layer/BMNGRestLayer',[
        '../error/ArgumentError',
        '../layer/Layer',
        '../util/Logger',
        '../util/PeriodicTimeSequence',
        '../layer/RestTiledImageLayer'
    ],
    function (ArgumentError,
              Layer,
              Logger,
              PeriodicTimeSequence,
              RestTiledImageLayer) {
        "use strict";

        /**
         * Constructs a Blue Marble layer.
         * @alias BMNGRestLayer
         * @constructor
         * @augments Layer
         * @classdesc Represents the 12 month collection of Blue Marble Next Generation imagery for the year 2004.
         * By default the month of January is displayed, but this can be changed by setting this class' time
         * property to indicate the month to display.
         * @param {String} serverAddress The server address of the tile service. May be null, in which case the
         * current origin is used (see window.location).
         * @param {String} pathToData The path to the data directory relative to the specified server address.
         * May be null, in which case the server address is assumed to be the full path to the data directory.
         * @param {String} displayName The display name to assign this layer. Defaults to "Blue Marble" if null or
         * undefined.
         * @param {Date} initialTime A date value indicating the month to display. The nearest month to the specified
         * time is displayed. January is displayed if this argument is null or undefined, i.e., new Date("2004-01");
         * See {@link RestTiledImageLayer} for a description of its contents. May be null, in which case default
         * values are used.
         */
        var BMNGRestLayer = function (serverAddress, pathToData, displayName, initialTime) {
            Layer.call(this, displayName || "Blue Marble time series");

            /**
             * A value indicating the month to display. The nearest month to the specified time is displayed.
             * @type {Date}
             * @default January 2004 (new Date("2004-01"));
             */
            this.time = initialTime || new Date("2004-01");

            // Intentionally not documented.
            this.timeSequence = new PeriodicTimeSequence("2004-01-01/2004-12-01/P1M");

            // Intentionally not documented.
            this.serverAddress = serverAddress;

            // Intentionally not documented.
            this.pathToData = pathToData;

            // Intentionally not documented.
            this.layers = {}; // holds the layers as they're created.

            // Intentionally not documented.
            this.layerNames = [
                {month: "BlueMarble-200401", time: BMNGRestLayer.availableTimes[0]},
                {month: "BlueMarble-200402", time: BMNGRestLayer.availableTimes[1]},
                {month: "BlueMarble-200403", time: BMNGRestLayer.availableTimes[2]},
                {month: "BlueMarble-200404", time: BMNGRestLayer.availableTimes[3]},
                {month: "BlueMarble-200405", time: BMNGRestLayer.availableTimes[4]},
                {month: "BlueMarble-200406", time: BMNGRestLayer.availableTimes[5]},
                {month: "BlueMarble-200407", time: BMNGRestLayer.availableTimes[6]},
                {month: "BlueMarble-200408", time: BMNGRestLayer.availableTimes[7]},
                {month: "BlueMarble-200409", time: BMNGRestLayer.availableTimes[8]},
                {month: "BlueMarble-200410", time: BMNGRestLayer.availableTimes[9]},
                {month: "BlueMarble-200411", time: BMNGRestLayer.availableTimes[10]},
                {month: "BlueMarble-200412", time: BMNGRestLayer.availableTimes[11]}
            ];

            this.pickEnabled = false;
        };

        BMNGRestLayer.prototype = Object.create(Layer.prototype);

        /**
         * Indicates the available times for this layer.
         * @type {Date[]}
         * @readonly
         */
        BMNGRestLayer.availableTimes = [
            new Date("2004-01"),
            new Date("2004-02"),
            new Date("2004-03"),
            new Date("2004-04"),
            new Date("2004-05"),
            new Date("2004-06"),
            new Date("2004-07"),
            new Date("2004-08"),
            new Date("2004-09"),
            new Date("2004-10"),
            new Date("2004-11"),
            new Date("2004-12")
        ];

        /**
         * Initiates retrieval of this layer's level 0 images for all sub-layers. Use
         * [isPrePopulated]{@link TiledImageLayer#isPrePopulated} to determine when the images have been retrieved
         * and associated with the level 0 tiles.
         * Pre-populating is not required. It is used to eliminate the visual effect of loading tiles incrementally,
         * but only for level 0 tiles. An application might pre-populate a layer in order to delay displaying it
         * within a time series until all the level 0 images have been retrieved and added to memory.
         * @param {WorldWindow} wwd The WorldWindow for which to pre-populate this layer.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        BMNGRestLayer.prototype.prePopulate = function (wwd) {
            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "BMNGRestLayer", "prePopulate", "missingWorldWindow"));
            }

            for (var i = 0; i < this.layerNames.length; i++) {
                var layerName = this.layerNames[i].month;

                if (!this.layers[layerName]) {
                    this.createSubLayer(layerName);
                }

                this.layers[layerName].prePopulate(wwd);
            }
        };

        /**
         * Indicates whether this layer's level 0 tile images for all sub-layers have been retrieved and associated
         * with the tiles.
         * Use [prePopulate]{@link TiledImageLayer#prePopulate} to initiate retrieval of level 0 images.
         * @param {WorldWindow} wwd The WorldWindow associated with this layer.
         * @returns {Boolean} true if all level 0 images have been retrieved, otherwise false.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        BMNGRestLayer.prototype.isPrePopulated = function (wwd) {
            for (var i = 0; i < this.layerNames.length; i++) {
                var layer = this.layers[this.layerNames[i].month];
                if (!layer || !layer.isPrePopulated(wwd)) {
                    return false;
                }
            }

            return true;
        };

        BMNGRestLayer.prototype.doRender = function (dc) {
            var layer = this.nearestLayer(this.time);
            layer.opacity = this.opacity;
            if (this.detailControl) {
                layer.detailControl = this.detailControl;
            }

            layer.doRender(dc);

            this.inCurrentFrame = layer.inCurrentFrame;
        };

        // Intentionally not documented.
        BMNGRestLayer.prototype.nearestLayer = function (time) {
            var nearestName = this.nearestLayerName(time);

            if (!this.layers[nearestName]) {
                this.createSubLayer(nearestName);
            }

            return this.layers[nearestName];
        };

        BMNGRestLayer.prototype.createSubLayer = function (layerName) {
            var subLayerPath = "";
            if (this.pathToData) {
                subLayerPath = this.pathToData + "/" + layerName;
            } else {
                subLayerPath = layerName;
            }

            this.layers[layerName] = new RestTiledImageLayer(this.serverAddress, subLayerPath, this.displayName);
        };

        // Intentionally not documented.
        BMNGRestLayer.prototype.nearestLayerName = function (time) {
            var milliseconds = time.getTime();

            if (milliseconds <= this.layerNames[0].time.getTime()) {
                return this.layerNames[0].month;
            }

            if (milliseconds >= this.layerNames[11].time.getTime()) {
                return this.layerNames[11].month;
            }

            for (var i = 0; i < this.layerNames.length - 1; i++) {
                var leftTime = this.layerNames[i].time.getTime(),
                    rightTime = this.layerNames[i + 1].time.getTime();

                if (milliseconds >= leftTime && milliseconds <= rightTime) {
                    var dLeft = milliseconds - leftTime,
                        dRight = rightTime - milliseconds;

                    return dLeft < dRight ? this.layerNames[i].month : this.layerNames[i + 1].month;
                }
            }
        };

        return BMNGRestLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Touch
 */
define('gesture/Touch',[],
    function () {
        "use strict";

        /**
         * Constructs a touch point.
         * @alias Touch
         * @constructor
         * @classdesc Represents a touch point.
         * @param {Color} identifier A number uniquely identifying the touch point
         * @param {Number} clientX The X coordinate of the touch point's location.
         * @param {Number} clientY The Y coordinate of the touch point's location.
         */
        var Touch = function (identifier, clientX, clientY) {

            /**
             * A number uniquely identifying this touch point.
             * @type {Number}
             * @readonly
             */
            this.identifier = identifier;

            // Intentionally not documented.
            this._clientX = clientX;

            // Intentionally not documented.
            this._clientY = clientY;

            // Intentionally not documented.
            this._clientStartX = clientX;

            // Intentionally not documented.
            this._clientStartY = clientY;
        };

        Object.defineProperties(Touch.prototype, {
            /**
             * Indicates the X coordinate of this touch point's location.
             * @type {Number}
             * @memberof Touch.prototype
             */
            clientX: {
                get: function () {
                    return this._clientX;
                },
                set: function (value) {
                    this._clientX = value;
                }
            },

            /**
             * Indicates the Y coordinate of this touch point's location.
             * @type {Number}
             * @memberof Touch.prototype
             */
            clientY: {
                get: function () {
                    return this._clientY;
                },
                set: function (value) {
                    this._clientY = value;
                }
            },

            /**
             * Indicates this touch point's translation along the X axis since the touch started.
             * @type {Number}
             * @memberof Touch.prototype
             */
            translationX: {
                get: function () {
                    return this._clientX - this._clientStartX;
                },
                set: function (value) {
                    this._clientStartX = this._clientX - value;
                }
            },

            /**
             * Indicates this touch point's translation along the Y axis since the touch started.
             * @type {Number}
             * @memberof Touch.prototype
             */
            translationY: {
                get: function () {
                    return this._clientY - this._clientStartY;
                },
                set: function (value) {
                    this._clientStartY = this._clientY - value;
                }
            }
        });

        return Touch;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GestureRecognizer
 */
define('gesture/GestureRecognizer',[
        '../error/ArgumentError',
        '../util/Logger',
        '../gesture/Touch'
    ],
    function (ArgumentError,
              Logger,
              Touch) {
        "use strict";

        /**
         * Constructs a base gesture recognizer. This is an abstract base class and not intended to be instantiated
         * directly.
         * @alias GestureRecognizer
         * @constructor
         * @classdesc Gesture recognizers translate user input event streams into higher level actions. A gesture
         * recognizer is associated with an event target, which dispatches mouse and keyboard events to the gesture
         * recognizer. When a gesture recognizer has received enough information from the event stream to interpret the
         * action, it calls its callback functions. Callback functions may be specified at construction or added to the
         * [gestureCallbacks]{@link GestureRecognizer#gestureCallbacks} list after construction.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var GestureRecognizer = function (target, callback) {
            if (!target) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "constructor", "missingTarget"));
            }

            /**
             * Indicates the document element this gesture recognizer observes for UI events.
             * @type {EventTarget}
             * @readonly
             */
            this.target = target;

            /**
             * Indicates whether or not this gesture recognizer is enabled. When false, this gesture recognizer will
             * ignore any events dispatched by its target.
             * @type {Boolean}
             * @default true
             */
            this.enabled = true;

            // Documented with its property accessor below.
            this._state = WorldWind.POSSIBLE;

            // Intentionally not documented.
            this._nextState = null;

            // Documented with its property accessor below.
            this._clientX = 0;

            // Documented with its property accessor below.
            this._clientY = 0;

            // Intentionally not documented.
            this._clientStartX = 0;

            // Intentionally not documented.
            this._clientStartY = 0;

            // Documented with its property accessor below.
            this._translationX = 0;

            // Documented with its property accessor below.
            this._translationY = 0;

            // Intentionally not documented.
            this._translationWeight = 0.4;

            // Documented with its property accessor below.
            this._mouseButtonMask = 0;

            // Intentionally not documented.
            this._touches = [];

            // Intentionally not documented.
            this._touchCentroidShiftX = 0;

            // Intentionally not documented.
            this._touchCentroidShiftY = 0;

            // Documented with its property accessor below.
            this._gestureCallbacks = [];

            // Intentionally not documented.
            this._canRecognizeWith = [];

            // Intentionally not documented.
            this._requiresFailureOf = [];

            // Intentionally not documented.
            this._requiredToFailBy = [];

            // Add the optional gesture callback.
            if (callback) {
                this._gestureCallbacks.push(callback);
            }

            // Add this recognizer to the list of all recognizers.
            GestureRecognizer.allRecognizers.push(this);

            // Register listeners on the event target.
            var thisRecognizer = this;

            function eventListener(event) {
                thisRecognizer.handleEvent(event);
            }

            if (window.PointerEvent) {
                target.addEventListener("pointerdown", eventListener, false);
                window.addEventListener("pointermove", eventListener, false); // get pointermove events outside event target
                window.addEventListener("pointercancel", eventListener, false); // get pointercancel events outside event target
                window.addEventListener("pointerup", eventListener, false); // get pointerup events outside event target
            } else {
                target.addEventListener("mousedown", eventListener, false);
                window.addEventListener("mousemove", eventListener, false); // get mousemove events outside event target
                window.addEventListener("mouseup", eventListener, false); // get mouseup events outside event target
                target.addEventListener("touchstart", eventListener, false);
                target.addEventListener("touchmove", eventListener, false);
                target.addEventListener("touchend", eventListener, false);
                target.addEventListener("touchcancel", eventListener, false);
            }
        };

        // Intentionally not documented.
        GestureRecognizer.allRecognizers = [];

        Object.defineProperties(GestureRecognizer.prototype, {
            /**
             * Indicates this gesture's current state. Possible values are WorldWind.POSSIBLE, WorldWind.FAILED,
             * WorldWind.RECOGNIZED, WorldWind.BEGAN, WorldWind.CHANGED, WorldWind.CANCELLED and WorldWind.ENDED.
             * @type {String}
             * @default WorldWind.POSSIBLE
             * @memberof GestureRecognizer.prototype
             */
            state: {
                get: function () {
                    return this._state;
                },
                set: function (value) {
                    this.transitionToState(value);
                }
            },

            /**
             * Indicates the X coordinate of this gesture.
             * @type {Number}
             * @memberof GestureRecognizer.prototype
             */
            clientX: {
                get: function () {
                    return this._clientX;
                },
                set: function (value) {
                    this._clientX = value;
                }
            },

            /**
             * Returns the Y coordinate of this gesture.
             * @type {Number}
             * @memberof GestureRecognizer.prototype
             */
            clientY: {
                get: function () {
                    return this._clientY;
                },
                set: function (value) {
                    this._clientY = value;
                }
            },

            /**
             * Indicates this gesture's translation along the X axis since the gesture started.
             * @type {Number}
             * @memberof GestureRecognizer.prototype
             */
            translationX: {
                get: function () {
                    return this._translationX;
                },
                set: function (value) {
                    this._translationX = value;
                    this._clientStartX = this._clientX;
                    this._touchCentroidShiftX = 0;
                }
            },

            /**
             * Indicates this gesture's translation along the Y axis since the gesture started.
             * @type {Number}
             * @memberof GestureRecognizer.prototype
             */
            translationY: {
                get: function () {
                    return this._translationY;
                },
                set: function (value) {
                    this._translationY = value;
                    this._clientStartY = this._clientY;
                    this._touchCentroidShiftY = 0;
                }
            },

            /**
             * Indicates the currently pressed mouse buttons as a bitmask. A value of 0 indicates that no buttons are
             * pressed. A nonzero value indicates that one or more buttons are pressed as follows: bit 1 indicates the
             * primary button, bit 2 indicates the the auxiliary button, bit 3 indicates the secondary button.
             * @type {Number}
             * @readonly
             * @memberof GestureRecognizer.prototype
             */
            mouseButtonMask: {
                get: function () {
                    return this._mouseButtonMask;
                }
            },

            /**
             * Indicates the number of active touches.
             * @type {Number}
             * @readonly
             * @memberof GestureRecognizer.prototype
             */
            touchCount: {
                get: function () {
                    return this._touches.length;
                }
            },

            /**
             * The list of functions to call when this gesture is recognized. The functions have a single argument:
             * this gesture recognizer, e.g., <code>gestureCallback(recognizer)</code>. Applications may
             * add functions to this array or remove them.
             * @type {Function[]}
             * @readonly
             * @memberof GestureRecognizer.prototype
             */
            gestureCallbacks: {
                get: function () {
                    return this._gestureCallbacks;
                }
            }
        });

        /**
         *
         * @param index
         * @returns {Touch}
         * @throws {ArgumentError} If the index is out of range.
         */
        GestureRecognizer.prototype.touch = function (index) {
            if (index < 0 || index >= this._touches.length) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "touch", "indexOutOfRange"));
            }

            return this._touches[index];
        };

        /**
         *
         * @param recognizer
         */
        GestureRecognizer.prototype.recognizeSimultaneouslyWith = function (recognizer) {
            if (!recognizer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "recognizeSimultaneouslyWith",
                        "The specified gesture recognizer is null or undefined."));
            }

            var index = this._canRecognizeWith.indexOf(recognizer);
            if (index == -1) {
                this._canRecognizeWith.push(recognizer);
                recognizer._canRecognizeWith.push(this);
            }
        };

        /**
         *
         * @param recognizer
         * @returns {Boolean}
         */
        GestureRecognizer.prototype.canRecognizeSimultaneouslyWith = function (recognizer) {
            var index = this._canRecognizeWith.indexOf(recognizer);
            return index != -1;
        };

        /**
         *
         * @param recognizer
         */
        GestureRecognizer.prototype.requireRecognizerToFail = function (recognizer) {
            if (!recognizer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "requireRecognizerToFail",
                        "The specified gesture recognizer is null or undefined"));
            }

            var index = this._requiresFailureOf.indexOf(recognizer);
            if (index == -1) {
                this._requiresFailureOf.push(recognizer);
                recognizer._requiredToFailBy.push(this);
            }
        };

        /**
         *
         * @param recognizer
         * @returns {Boolean}
         */
        GestureRecognizer.prototype.requiresRecognizerToFail = function (recognizer) {
            var index = this._requiresFailureOf.indexOf(recognizer);
            return index != -1;
        };

        /**
         *
         * @param recognizer
         * @returns {Boolean}
         */
        GestureRecognizer.prototype.requiredToFailByRecognizer = function (recognizer) {
            var index = this._requiredToFailBy.indexOf(recognizer);
            return index != -1;
        };

        /**
         * @protected
         */
        GestureRecognizer.prototype.reset = function () {
            this._state = WorldWind.POSSIBLE;
            this._nextState = null;
            this._clientX = 0;
            this._clientY = 0;
            this._clientStartX = 0;
            this._clientStartY = 0;
            this._translationX = 0;
            this._translationY = 0;
            this._mouseButtonMask = 0;
            this._touches = [];
            this._touchCentroidShiftX = 0;
            this._touchCentroidShiftY = 0;
        };

        /**
         * @protected
         */
        GestureRecognizer.prototype.prepareToRecognize = function () {
        };

        /**
         *
         * @param event
         * @protected
         */
        GestureRecognizer.prototype.mouseDown = function (event) {
        };

        /**
         *
         * @param event
         * @protected
         */
        GestureRecognizer.prototype.mouseMove = function (event) {
        };

        /**
         *
         * @param event
         * @protected
         */
        GestureRecognizer.prototype.mouseUp = function (event) {
        };

        /**
         *
         * @param touch
         * @protected
         */
        GestureRecognizer.prototype.touchStart = function (touch) {
        };

        /**
         *
         * @param touch
         * @protected
         */
        GestureRecognizer.prototype.touchMove = function (touch) {
        };

        /**
         *
         * @param touch
         * @protected
         */
        GestureRecognizer.prototype.touchCancel = function (touch) {
        };

        /**
         *
         * @param touch
         * @protected
         */
        GestureRecognizer.prototype.touchEnd = function (touch) {
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.transitionToState = function (newState) {
            this._nextState = null; // clear any pending state transition

            if (newState == WorldWind.FAILED) {
                this._state = newState;
                this.updateRecognizersWaitingForFailure();
                this.resetIfEventsEnded();
            } else if (newState == WorldWind.RECOGNIZED) {
                this.tryToRecognize(newState); // may prevent the transition to Recognized
                if (this._state == newState) {
                    this.prepareToRecognize();
                    this.callGestureCallbacks();
                    this.resetIfEventsEnded();
                }
            } else if (newState == WorldWind.BEGAN) {
                this.tryToRecognize(newState); // may prevent the transition to Began
                if (this._state == newState) {
                    this.prepareToRecognize();
                    this.callGestureCallbacks();
                }
            } else if (newState == WorldWind.CHANGED) {
                this._state = newState;
                this.callGestureCallbacks();
            } else if (newState == WorldWind.CANCELLED) {
                this._state = newState;
                this.callGestureCallbacks();
                this.resetIfEventsEnded();
            } else if (newState == WorldWind.ENDED) {
                this._state = newState;
                this.callGestureCallbacks();
                this.resetIfEventsEnded();
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.updateRecognizersWaitingForFailure = function () {
            // Transition gestures that are waiting for this gesture to transition to Failed.
            for (var i = 0, len = this._requiredToFailBy.length; i < len; i++) {
                var recognizer = this._requiredToFailBy[i];
                if (recognizer._nextState != null) {
                    recognizer.transitionToState(recognizer._nextState);
                }
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.tryToRecognize = function (newState) {
            // Transition to Failed if another gesture can prevent this gesture from recognizing.
            if (GestureRecognizer.allRecognizers.some(this.canBePreventedByRecognizer, this)) {
                this.transitionToState(WorldWind.FAILED);
                return;
            }

            // Delay the transition to Recognized/Began if this gesture is waiting for a gesture in the Possible state.
            if (GestureRecognizer.allRecognizers.some(this.isWaitingForRecognizerToFail, this)) {
                this._nextState = newState;
                return;
            }

            // Transition to Failed all other gestures that can be prevented from recognizing by this gesture.
            var prevented = GestureRecognizer.allRecognizers.filter(this.canPreventRecognizer, this);
            for (var i = 0, len = prevented.length; i < len; i++) {
                prevented[i].transitionToState(WorldWind.FAILED);
            }

            this._state = newState;
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.canPreventRecognizer = function (that) {
            return this != that && this.target == that.target && that.state == WorldWind.POSSIBLE &&
                (this.requiredToFailByRecognizer(that) || !this.canRecognizeSimultaneouslyWith(that));
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.canBePreventedByRecognizer = function (that) {
            return this != that && this.target == that.target && that.state == WorldWind.RECOGNIZED &&
                (this.requiresRecognizerToFail(that) || !this.canRecognizeSimultaneouslyWith(that));
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.isWaitingForRecognizerToFail = function (that) {
            return this != that && this.target == that.target && that.state == WorldWind.POSSIBLE &&
                this.requiresRecognizerToFail(that);
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.callGestureCallbacks = function () {
            for (var i = 0, len = this._gestureCallbacks.length; i < len; i++) {
                this._gestureCallbacks[i](this);
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleEvent = function (event) {
            if (!this.enabled) {
                return;
            }

            if (event.defaultPrevented && this.state == WorldWind.POSSIBLE) {
                return; // ignore cancelled events while in the Possible state
            }

            var i, len;

            try {
                if (event.type == "mousedown") {
                    this.handleMouseDown(event);
                } else if (event.type == "mousemove") {
                    this.handleMouseMove(event);
                } else if (event.type == "mouseup") {
                    this.handleMouseUp(event);
                } else if (event.type == "touchstart") {
                    for (i = 0, len = event.changedTouches.length; i < len; i++) {
                        this.handleTouchStart(event.changedTouches.item(i));
                    }
                } else if (event.type == "touchmove") {
                    for (i = 0, len = event.changedTouches.length; i < len; i++) {
                        this.handleTouchMove(event.changedTouches.item(i));
                    }
                } else if (event.type == "touchcancel") {
                    for (i = 0, len = event.changedTouches.length; i < len; i++) {
                        this.handleTouchCancel(event.changedTouches.item(i));
                    }
                } else if (event.type == "touchend") {
                    for (i = 0, len = event.changedTouches.length; i < len; i++) {
                        this.handleTouchEnd(event.changedTouches.item(i));
                    }
                } else if (event.type == "pointerdown" && event.pointerType == "mouse") {
                    this.handleMouseDown(event);
                } else if (event.type == "pointermove" && event.pointerType == "mouse") {
                    this.handleMouseMove(event);
                } else if (event.type == "pointercancel" && event.pointerType == "mouse") {
                    // Intentionally left blank. The W3C Pointer Events specification is ambiguous on what cancel means
                    // for mouse input, and there is no evidence that this event is actually generated (6/19/2015).
                } else if (event.type == "pointerup" && event.pointerType == "mouse") {
                    this.handleMouseUp(event);
                } else if (event.type == "pointerdown" && event.pointerType == "touch") {
                    this.handleTouchStart(event);
                } else if (event.type == "pointermove" && event.pointerType == "touch") {
                    this.handleTouchMove(event);
                } else if (event.type == "pointercancel" && event.pointerType == "touch") {
                    this.handleTouchCancel(event);
                } else if (event.type == "pointerup" && event.pointerType == "touch") {
                    this.handleTouchEnd(event);
                } else {
                    Logger.logMessage(Logger.LEVEL_INFO, "GestureRecognizer", "handleEvent",
                        "Unrecognized event type: " + event.type);
                }
            } catch (e) {
                Logger.logMessage(Logger.LEVEL_SEVERE, "GestureRecognizer", "handleEvent",
                    "Error handling event.\n" + e.toString());
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleMouseDown = function (event) {
            if (event.type == "mousedown" && this._touches.length > 0) {
                return; // ignore synthesized mouse down events on Android Chrome
            }

            var buttonBit = (1 << event.button);
            if (buttonBit & this._mouseButtonMask != 0) {
                return; // ignore redundant mouse down events
            }

            if (this._mouseButtonMask == 0) { // first button down
                this._clientX = event.clientX;
                this._clientY = event.clientY;
                this._clientStartX = event.clientX;
                this._clientStartY = event.clientY;
                this._translationX = 0;
                this._translationY = 0;
            }

            this._mouseButtonMask |= buttonBit;
            this.mouseDown(event);
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleMouseMove = function (event) {
            if (this._mouseButtonMask == 0) {
                return; // ignore mouse move events when this recognizer does not consider any button to be down
            }

            if (this._clientX == event.clientX && this._clientY == event._clientY) {
                return; // ignore redundant mouse move events
            }

            var dx = event.clientX - this._clientStartX,
                dy = event.clientY - this._clientStartY,
                w = this._translationWeight;
            this._clientX = event.clientX;
            this._clientY = event.clientY;
            this._translationX = this._translationX * (1 - w) + dx * w;
            this._translationY = this._translationY * (1 - w) + dy * w;
            this.mouseMove(event);
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleMouseUp = function (event) {
            var buttonBit = (1 << event.button);
            if (buttonBit & this._mouseButtonMask == 0) {
                return; // ignore mouse up events for buttons this recognizer does not consider to be down
            }

            this._mouseButtonMask &= ~buttonBit;
            this.mouseUp(event);

            if (this._mouseButtonMask == 0) {
                this.resetIfEventsEnded(); // last button up
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleTouchStart = function (event) {
            var touch = new Touch(event.identifier || event.pointerId, event.clientX, event.clientY); // touch events or pointer events
            this._touches.push(touch);

            if (this._touches.length == 1) { // first touch
                this._clientX = event.clientX;
                this._clientY = event.clientY;
                this._clientStartX = event.clientX;
                this._clientStartY = event.clientY;
                this._translationX = 0;
                this._translationY = 0;
                this._touchCentroidShiftX = 0;
                this._touchCentroidShiftY = 0;
            } else {
                this.touchesAddedOrRemoved();
            }

            this.touchStart(touch);
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleTouchMove = function (event) {
            var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
            if (index == -1) {
                return; // ignore events for touches that did not start in this recognizer's target
            }

            var touch = this._touches[index];
            if (touch.clientX == event.clientX && touch.clientY == event.clientY) {
                return; // ignore redundant touch move events, which we've encountered on Android Chrome
            }

            touch.clientX = event.clientX;
            touch.clientY = event.clientY;

            var centroid = this.touchCentroid(),
                dx = centroid.clientX - this._clientStartX + this._touchCentroidShiftX,
                dy = centroid.clientY - this._clientStartY + this._touchCentroidShiftY,
                w = this._translationWeight;
            this._clientX = centroid.clientX;
            this._clientY = centroid.clientY;
            this._translationX = this._translationX * (1 - w) + dx * w;
            this._translationY = this._translationY * (1 - w) + dy * w;

            this.touchMove(touch);
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleTouchCancel = function (event) {
            var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
            if (index == -1) {
                return; // ignore events for touches that did not start in this recognizer's target
            }

            var touch = this._touches[index];
            this._touches.splice(index, 1);
            this.touchesAddedOrRemoved();
            this.touchCancel(touch);
            this.resetIfEventsEnded();
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.handleTouchEnd = function (event) {
            var index = this.indexOfTouchWithId(event.identifier || event.pointerId); // touch events or pointer events
            if (index == -1) {
                return; // ignore events for touches that did not start in this recognizer's target
            }

            var touch = this._touches[index];
            this._touches.splice(index, 1);
            this.touchesAddedOrRemoved();
            this.touchEnd(touch);
            this.resetIfEventsEnded();
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.resetIfEventsEnded = function () {
            if (this._state != WorldWind.POSSIBLE && this._mouseButtonMask == 0 && this._touches.length == 0) {
                this.reset();
            }
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.touchesAddedOrRemoved = function () {
            this._touchCentroidShiftX += this._clientX;
            this._touchCentroidShiftY += this._clientY;
            var centroid = this.touchCentroid();
            this._clientX = centroid.clientX;
            this._clientY = centroid.clientY;
            this._touchCentroidShiftX -= this._clientX;
            this._touchCentroidShiftY -= this._clientY;
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.touchCentroid = function () {
            var x = 0,
                y = 0;

            for (var i = 0, len = this._touches.length; i < len; i++) {
                var touch = this._touches[i];
                x += touch.clientX / len;
                y += touch.clientY / len;
            }

            return {clientX: x, clientY: y};
        };

        // Intentionally not documented.
        GestureRecognizer.prototype.indexOfTouchWithId = function (identifier) {
            for (var i = 0, len = this._touches.length; i < len; i++) {
                if (this._touches[i].identifier == identifier) {
                    return i;
                }
            }

            return -1;
        };

        return GestureRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ClickRecognizer
 */
define('gesture/ClickRecognizer',['../gesture/GestureRecognizer'],
    function (GestureRecognizer) {
        "use strict";

        /**
         * Constructs a mouse click gesture recognizer.
         * @alias ClickRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for single or multiple mouse clicks.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var ClickRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            /**
             *
             * @type {Number}
             */
            this.numberOfClicks = 1;

            /**
             *
             * @type {Number}
             */
            this.button = 0;

            // Intentionally not documented.
            this.maxMouseMovement = 5;

            // Intentionally not documented.
            this.maxClickDuration = 500;

            // Intentionally not documented.
            this.maxClickInterval = 400;

            // Intentionally not documented.
            this.clicks = [];

            // Intentionally not documented.
            this.timeout = null;
        };

        ClickRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        // Documented in superclass.
        ClickRecognizer.prototype.reset = function () {
            GestureRecognizer.prototype.reset.call(this);

            this.clicks = [];
            this.cancelFailAfterDelay();
        };

        // Documented in superclass.
        ClickRecognizer.prototype.mouseDown = function (event) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            if (this.button != event.button) {
                this.state = WorldWind.FAILED;
            } else {
                var click = {
                    clientX: this.clientX,
                    clientY: this.clientY
                };
                this.clicks.push(click);
                this.failAfterDelay(this.maxClickDuration); // fail if the click is down too long
            }
        };

        // Documented in superclass.
        ClickRecognizer.prototype.mouseMove = function (event) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            var dx = this.translationX,
                dy = this.translationY,
                distance = Math.sqrt(dx * dx + dy * dy);
            if (distance > this.maxMouseMovement) {
                this.state = WorldWind.FAILED;
            }
        };

        // Documented in superclass.
        ClickRecognizer.prototype.mouseUp = function (event) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            if (this.mouseButtonMask != 0) {
                return; // wait until the last button is up
            }

            var clickCount = this.clicks.length;
            if (clickCount == this.numberOfClicks) {
                this.clientX = this.clicks[0].clientX;
                this.clientY = this.clicks[0].clientY;
                this.state = WorldWind.RECOGNIZED;
            } else {
                this.failAfterDelay(this.maxClickInterval); // fail if the interval between clicks is too long
            }
        };

        // Documented in superclass.
        ClickRecognizer.prototype.touchStart = function (touch) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            this.state = WorldWind.FAILED; // mouse gestures fail upon receiving a touch event
        };

        // Intentionally not documented.
        ClickRecognizer.prototype.failAfterDelay = function (delay) {
            var self = this;
            if (self.timeout) {
                window.clearTimeout(self.timeout);
            }

            self.timeout = window.setTimeout(function () {
                self.timeout = null;
                if (self.state == WorldWind.POSSIBLE) {
                    self.state = WorldWind.FAILED; // fail if we haven't already reached a terminal state
                }
            }, delay);
        };

        // Intentionally not documented.
        ClickRecognizer.prototype.cancelFailAfterDelay = function () {
            var self = this;
            if (self.timeout) {
                window.clearTimeout(self.timeout);
                self.timeout = null;
            }
        };

        return ClickRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaAsset
 */

define('formats/collada/ColladaAsset',[], function () {
    "use strict";

    /**
     * Constructs a ColladaAsset
     * @alias ColladaAsset
     * @constructor
     * @classdesc Represents a collada asset tag.
     * @param {XML} xmlDoc The raw XML data of the collada file.
     */
    var ColladaAsset = function (xmlDoc) {
        this.xmlAsset = xmlDoc.getElementsByTagName("asset")[0];
        this.asset = {
            daeVersion: xmlDoc.querySelector("COLLADA").getAttribute("version")
        };
    };

    /**
     * Parses the asset tag.
     * Internal. Applications should not call this function.
     */
    ColladaAsset.prototype.parse = function () {

        if (!this.xmlAsset) {
            return null;
        }

        for (var i = 0; i < this.xmlAsset.childNodes.length; i++) {

            var child = this.xmlAsset.childNodes.item(i);

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {
                case "contributor":
                    var tool = child.querySelector("authoring_tool");
                    if (tool) {
                        this.asset["authoring_tool"] = tool.textContext;
                    }
                    break;

                case "unit":
                    this.asset["unit"] = child.getAttribute("meter");
                    break;

                default:
                    this.asset[child.localName] = child.textContent;
                    break;
            }
        }

        this.xmlAsset = null;

        return this.asset;
    };

    return ColladaAsset;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */

define('formats/collada/ColladaUtils',['../../util/Logger'], function (Logger) {
    "use strict";

    /**
     * Provides utilities for the ColladaLoader.
     * @exports ColladaUtils
     */
    var ColladaUtils = {

        /**
         * Packs data from a node in an array.
         * Internal. Applications should not call this function.
         * @param {Node} xmlNode A node from which to extract values.
         */
        getRawValues: function (xmlNode) {
            if (!xmlNode) {
                return null;
            }

            var text = xmlNode.textContent;
            text = text.replace(/\n/gi, " ");
            text = text.replace(/\s+/gi, " ");
            text = text.trim();

            if (text.length === 0) {
                return null;
            }

            return text.split(" ");
        },

        /**
         * Packs data from a node as a Float32Array.
         * Internal. Applications should not call this function.
         * @param {Node} xmlNode A node from which to extract values.
         */
        bufferDataFloat32: function (xmlNode) {

            var rawValues = this.getRawValues(xmlNode);
            if (!rawValues) {
                return null;
            }

            var len = rawValues.length;

            var bufferData = new Float32Array(len);
            for (var i = 0; i < len; i++) {
                bufferData[i] = parseFloat(rawValues[i]);
            }

            return bufferData;
        },

        /**
         * Packs data from a node as a UInt32Array.
         * Internal. Applications should not call this function.
         * @param {Node} xmlNode A node from which to extract values.
         */
        bufferDataUInt32: function (xmlNode) {

            var rawValues = this.getRawValues(xmlNode);
            if (!rawValues) {
                return null;
            }

            var len = rawValues.length;

            var bufferData = new Uint32Array(len);
            for (var i = 0; i < len; i++) {
                bufferData[i] = parseInt(rawValues[i]);
            }

            return bufferData;
        },

        /**
         * Returns the first child of a node.
         * Internal. Applications should not call this function.
         * @param {Node} xmlNode The tag to look in.
         * @param {String} nodeName Optional parameter, the name of the child.
         */
        getFirstChildElement: function (xmlNode, nodeName) {

            var childs = xmlNode.childNodes;

            for (var i = 0; i < childs.length; ++i) {

                var item = childs.item(i);

                if (item.nodeType !== 1) {
                    continue;
                }

                if ((item.nodeName && !nodeName) || (nodeName && nodeName === item.nodeName)) {
                    return item;
                }
            }

            return null;
        },

        /**
         * Returns the filename without slashes.
         * Internal. Applications should not call this function.
         * @param {String} filePath
         */
        getFilename: function (filePath) {

            var pos = filePath.lastIndexOf("\\");
            if (pos !== -1) {
                filePath = filePath.substr(pos + 1);
            }

            pos = filePath.lastIndexOf("/");
            if (pos !== -1) {
                filePath = filePath.substr(pos + 1);
            }

            return filePath;
        },

        /**
         * Replaces the spaces in a string with an "_".
         * Internal. Applications should not call this function.
         * @param {String} str
         */
        replaceSpace: function (str) {
            if (!str) {
                return "";
            }
            return str.replace(/ /g, "_");
        },

        /**
         * Finds a node by id.
         * Internal. Applications should not call this function.
         * @param {NodeList} nodes A list of nodes to look in.
         * @param {String} id The id of the node to search for.
         */
        querySelectorById: function (nodes, id) {
            for (var i = 0; i < nodes.length; i++) {
                var attrId = nodes.item(i).getAttribute("id");
                if (!attrId) {
                    continue;
                }
                if (attrId.toString() === id) {
                    return nodes.item(i);
                }
            }
            return null;
        },

        /**
         * Determines the rendering method for a texture.
         * The method can be CLAMP or REPEAT.
         * Internal. Applications should not call this function.
         * @param {Number[]} uvs The uvs array.
         */
        getTextureType: function (uvs) {
            var clamp = true;

            for (var i = 0, len = uvs.length; i < len; i++) {
                if (uvs[i] < 0 || uvs[i] > 1) {
                    clamp = false;
                    break;
                }
            }

            return clamp;
        },

        /**
         * Fetches a file.
         * @param {String} url The path to the collada file.
         * @param {Function} cb A callback function to call when the collada file loaded.
         */
        fetchFile: function (url, cb) {

            var request = new XMLHttpRequest();

            request.onload = function () {
                if (this.status >= 200 && this.status < 400) {
                    cb(this.response);
                }
                else {
                    Logger.log(Logger.LEVEL_SEVERE, "sever error: " + this.status);
                    cb(null);
                }
            };

            request.onerror = function (e) {
                Logger.log(Logger.LEVEL_SEVERE, "connection error: " + e);
                cb(null);
            };

            request.open("get", url, true);

            request.send();
        }
    };

    return ColladaUtils;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaImage
 */

define('formats/collada/ColladaImage',['./ColladaUtils'],function(ColladaUtils){
    "use strict";

    /**
     * Constructs a ColladaImage
     * @alias ColladaImage
     * @constructor
     * @classdesc Represents a collada image tag.
     * @param {String} imageId The id of an image node
     * @param {String} imageName The name of an image node
     */
    var ColladaImage = function (imageId, imageName) {
        this.filename = '';
        this.map = imageId;
        this.name = imageName;
        this.path = '';
    };

    /**
     * Parses the images of a collada file.
     * Internal. Applications should not call this function.
     * @param {Node} element An image node
     */
    ColladaImage.prototype.parse = function (element) {

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName){

                case 'init_from':

                    this.filename = ColladaUtils.getFilename(child.textContent);
                    this.path = child.textContent;

                    break;

                default:
                    break;
            }
        }

        return this;

    };

    return ColladaImage;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaMaterial
 */

define('formats/collada/ColladaMaterial',['./ColladaUtils'], function (ColladaUtils) {
    "use strict";

    /**
     * Constructs a ColladaMaterial
     * @alias ColladaMaterial
     * @constructor
     * @classdesc Represents a collada material and it's effects.
     * @param {String} materialId The id of a material node
     */
    var ColladaMaterial = function (materialId) {
        this.id = materialId;
        this.newParams = [];
    };

    /**
     * Parses an effect node.
     * Internal. Applications should not call this function.
     * @param {Node} element An effect node.
     */
    ColladaMaterial.prototype.parse = function (element) {

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'profile_COMMON':
                    this.parseProfileCommon(child);
                    break;

                default:
                    break;
            }
        }

        return this;
    };

    /**
     * Parses the profile_COMMON node.
     * Internal. Applications should not call this function.
     * @param {Node} element The profile_COMMON node.
     */
    ColladaMaterial.prototype.parseProfileCommon = function (element) {

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'newparam':
                    this.parseNewparam(child);
                    break;

                case 'image':
                    break;

                case 'technique':
                    this.parseTechnique(child);
                    break;

                default:
                    break;
            }
        }

    };

    /**
     * Parses the newparam node.
     * Internal. Applications should not call this function.
     * @param {Node} element The newparam node.
     */
    ColladaMaterial.prototype.parseNewparam = function (element) {
        var sid = element.getAttribute('sid');

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'surface':
                    var initFrom = child.querySelector("init_from");
                    if (initFrom) {
                        this.newParams.push({
                            sid: sid,
                            type: 'surface',
                            initFrom: initFrom.textContent
                        });
                    }
                    break;

                case 'sampler2D':
                    var source = child.querySelector("source");
                    this.newParams.push({
                        sid: sid,
                        type: 'sampler2D',
                        source: source.textContent
                    });
                    break;

                case 'extra':
                    break;

                default:
                    break;

            }

        }
    };

    /**
     * Parses the technique node.
     * Internal. Applications should not call this function.
     * @param {Node} element The technique node.
     */
    ColladaMaterial.prototype.parseTechnique = function (element) {

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'constant':
                case 'lambert':
                case 'blinn':
                case 'phong':
                    this.techniqueType = child.nodeName;
                    this.parseTechniqueType(child);
                    break;

                case 'extra':
                    break;

                default:
                    break;

            }

        }
    };

    /**
     * Parses the technique type for this effect.
     * Internal. Applications should not call this function.
     * @param {Node} element The technique type node.
     */
    ColladaMaterial.prototype.parseTechniqueType = function (element) {

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1 || !child.nodeName) {
                continue;
            }

            var nodeName = child.nodeName;

            var nodeValue = ColladaUtils.getFirstChildElement(child);

            if (!nodeValue) {
                continue;
            }

            switch (nodeValue.nodeName) {

                case 'color':
                    this[nodeName] = ColladaUtils.bufferDataFloat32(nodeValue).subarray(0, 4);
                    break;

                case 'float':
                    this[nodeName] = ColladaUtils.bufferDataFloat32(nodeValue)[0];
                    break;

                case 'texture':
                    var texture = nodeValue.getAttribute("texture");

                    var pos = this.newParams.map(function (newParam) {
                        return newParam.sid;
                    }).indexOf(texture);

                    var source = this.newParams[pos].source;

                    pos = this.newParams.map(function (newParam) {
                        return newParam.sid;
                    }).indexOf(source);

                    var initFrom = this.newParams[pos].initFrom;

                    if (!this.textures) {
                        this.textures = {};
                    }

                    this.textures[nodeName] = {mapId: initFrom};

                    break;

                default:
                    break;

            }

        }
    };

    return ColladaMaterial;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaMesh
 */

define('formats/collada/ColladaMesh',['./ColladaUtils'], function (ColladaUtils) {
    "use strict";

    /**
     * Constructs a ColladaMesh
     * @alias ColladaMesh
     * @constructor
     * @classdesc Represents a collada mesh tag.
     * @param {String} geometryId The id of a geometry node
     */
    var ColladaMesh = function (geometryId) {
        this.filename = geometryId || "";
        this.name = geometryId || "";
        this.buffers = [];
    };

    /**
     * Parses and computes the geometry of a mesh.
     * Internal. Applications should not call this function.
     * @param {Node} element A mesh node.
     */
    ColladaMesh.prototype.parse = function (element) {

        var sources = {},
            meshData = {},
            verticesInputs = {
                id: '',
                inputs: []
            };

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'source':
                    if (!child.querySelector) {
                        continue;
                    }

                    var floatArray = child.querySelector("float_array");
                    if (!floatArray) {
                        continue;
                    }

                    var values = ColladaUtils.bufferDataFloat32(floatArray);
                    var accessor = child.querySelector("accessor");
                    var stride = parseInt(accessor.getAttribute("stride"));

                    sources[child.getAttribute("id")] = {stride: stride, data: values};
                    break;

                case 'vertices':
                    this.parseVertices(child, verticesInputs);
                    break;

                case 'triangles':
                    meshData = this.parsePolygons(child, sources, verticesInputs, 3);
                    this.buffers.push(meshData);
                    break;

                case 'polygons':
                    meshData = this.parsePolygons(child, sources, verticesInputs, 4);
                    this.buffers.push(meshData);
                    break;

                case 'polylist':
                    meshData = this.parsePolygons(child, sources, verticesInputs, null);
                    this.buffers.push(meshData);
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    /**
     * Parses the vertices tag of a mesh.
     * Internal. Applications should not call this function.
     * @param {Node} element The node containing the primitives and inputs.
     * @param {Object} verticesInputs An object in which to save the inputs of the vertices tag.
     */
    ColladaMesh.prototype.parseVertices = function (element, verticesInputs) {

        verticesInputs.id = element.getAttribute("id");
        var inputs = element.querySelectorAll("input");

        for (var i = 0; i < inputs.length; i++) {

            var input = inputs[i];
            var source = input.getAttribute("source").substr(1);
            var semantic = input.getAttribute("semantic").toUpperCase();

            verticesInputs.inputs.push({
                semantic: semantic,
                source: source
            });

        }

    };

    /**
     * Parses the polygons primitive and computes the indices and vertices.
     * Internal. Applications should not call this function.
     * @param {Node} element The node containing the primitives and inputs.
     * @param {Object} sources An object containing the inputs for vertices, normals and uvs.
     * @param {Object} verticesInputs An object containing the inputs links.
     * @param {Number} vCount Optional parameter, specifies the the vertex count for a polygon
     */
    ColladaMesh.prototype.parsePolygons = function (element, sources, verticesInputs, vCount) {

        var arrVCount = [];
        if (vCount == null) {
            var xmlVCount = element.querySelector("vcount");
            arrVCount = xmlVCount.textContent.trim().split(" ");
        }

        var count = parseInt(element.getAttribute("count"));
        var material = element.getAttribute("material");

        var inputData = this.parseInputs(element, sources, verticesInputs);
        var inputs = inputData.inputs;
        var maxOffset = inputData.maxOffset;

        var primitives = element.querySelector("p");
        var primitiveData = [];
        if (primitives) {
            primitiveData = primitives.textContent.trim().split(" ");
        }

        var nrOfInputs = inputs.length;

        var lastIndex = 0;
        var indexMap = {};
        var indicesArray = [];
        var pos = 0;
        var indexedRendering = false;

        for (var i = 0; i < count; i++) {

            if (arrVCount.length) {
                var numVertices = parseInt(arrVCount[i]);
            }
            else {
                numVertices = vCount;
            }

            var firstIndex = -1;
            var currentIndex = -1;
            var prevIndex = -1;

            for (var k = 0; k < numVertices; k++) {

                var vecId = primitiveData.slice(pos, pos + maxOffset).join(" ");

                prevIndex = currentIndex;
                if (indexMap.hasOwnProperty(vecId)) {
                    currentIndex = indexMap[vecId];
                    indexedRendering = true;
                }
                else {

                    for (var j = 0; j < nrOfInputs; j++) {

                        var input = inputs[j];
                        var offset = input[4];
                        var index = parseInt(primitiveData[pos + offset]);
                        var array = input[1];
                        var source = input[3];
                        index *= input[2];

                        for (var x = 0; x < input[2]; x++) {
                            array.push(source[index + x]);
                        }
                    }

                    currentIndex = lastIndex;
                    lastIndex += 1;
                    indexMap[vecId] = currentIndex;
                }

                if (numVertices > 3) {
                    if (k === 0) {
                        firstIndex = currentIndex;
                    }
                    if (k > 2 * maxOffset) {
                        indicesArray.push(firstIndex);
                        indicesArray.push(prevIndex);
                    }
                }

                indicesArray.push(currentIndex);
                pos += maxOffset;

            }
        }

        var mesh = {
            vertices: new Float32Array(inputs[0][1]),
            indexedRendering: indexedRendering,
            material: material
        };

        this.transformMeshInfo(mesh, inputs, indicesArray);

        return mesh;

    };

    /**
     * Parses the inputs of a mesh.
     * Internal. Applications should not call this function.
     * @param {Node} element The node containing the primitives and inputs.
     * @param {Object} sources An object containing the vertices source and stride.
     * @param {Object} verticesInputs An object containing the inputs links.
     */
    ColladaMesh.prototype.parseInputs = function (element, sources, verticesInputs) {

        var inputs = [], maxOffset = 0;

        var xmlInputs = element.querySelectorAll("input");

        for (var i = 0; i < xmlInputs.length; i++) {
            var xmlInput = xmlInputs.item(i);
            if (!xmlInput.getAttribute) {
                continue;
            }

            var semantic = xmlInput.getAttribute("semantic").toUpperCase();
            var sourceUrl = xmlInput.getAttribute("source").substr(1);
            var offset = parseInt(xmlInput.getAttribute("offset"));

            maxOffset = ( maxOffset < offset + 1 ) ? offset + 1 : maxOffset;

            //indicates which inputs should be grouped together as a single set.
            //multiple inputs may share the same semantics.
            var dataSet = 0;
            if (xmlInput.getAttribute("set")) {
                dataSet = parseInt(xmlInput.getAttribute("set"));
            }

            if (verticesInputs.id === sourceUrl) {
                var vInputs = verticesInputs.inputs;
                for (var j = 0; j < vInputs.length; j++) {
                    var source = sources[vInputs[j].source];
                    if (source) {
                        inputs.push([vInputs[j].semantic, [], source.stride, source.data, offset, dataSet]);
                    }
                }
            }
            else {
                source = sources[sourceUrl];
                inputs.push([semantic, [], source.stride, source.data, offset, dataSet]);
            }

        }

        return {inputs: inputs, maxOffset: maxOffset};
    };

    /**
     * Packs the data in the mesh object.
     * Internal. Applications should not call this function.
     * @param {Object} mesh The mesh that will be returned.
     * @param {Array} inputs The array containing the inputs of the mesh.
     * @param {Number[]} indicesArray An array containing the indices.
     */
    ColladaMesh.prototype.transformMeshInfo = function (mesh, inputs, indicesArray) {
        var translator = {
            "normal": "normals",
            "texcoord": "uvs"
        };

        for (var i = 1; i < inputs.length; i++) {

            var name = inputs[i][0].toLowerCase(); //the semantic
            var data = inputs[i][1]; //the final data (normals, uvs)

            if (!data.length) {
                continue;
            }

            if (translator[name]) {
                name = translator[name];
            }

            if (mesh[name]) {
                name = name + inputs[i][5];
            }

            mesh[name] = new Float32Array(data);

            if (name === 'uvs') {
                mesh.isClamp = ColladaUtils.getTextureType(data);
            }
        }

        if (mesh.indexedRendering) {
            mesh.indices = new Uint16Array(indicesArray);
        }

        return mesh;
    };

    return ColladaMesh;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaNode
 */

define('formats/collada/ColladaNode',['./ColladaUtils', '../../geom/Matrix', '../../geom/Vec3'], function (ColladaUtils, Matrix, Vec3) {
    "use strict";

    /**
     * Constructs a ColladaNode
     * @alias ColladaNode
     * @constructor
     * @classdesc Represents a collada node tag.
     */
    var ColladaNode = function () {
        this.id = "";
        this.name = "";
        this.sid = "";
        this.children = [];
        this.materials = [];
        this.mesh = "";
        this.localMatrix = Matrix.fromIdentity();
        this.worldMatrix = Matrix.fromIdentity();
    };

    /**
     * Parses a visual_scene node.
     * Internal. Applications should not call this function.
     * @param {Node} element A visual_scene node.
     * @param {NodeList} iNodes Nodes from library_nodes.
     * @param {Matrix} parentWorldMatrix The transformation matrix of it's parent.
     */
    ColladaNode.prototype.parse = function (element, iNodes, parentWorldMatrix) {

        this.id = element.getAttribute('id');
        this.sid = element.getAttribute('sid');
        this.name = element.getAttribute('name');

        this.children = [];
        this.materials = [];
        this.mesh = "";
        this.localMatrix = Matrix.fromIdentity();
        this.worldMatrix = Matrix.fromIdentity();

        this.setNodeTransforms(element, parentWorldMatrix);

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'node':
                    this.children.push(( new ColladaNode() ).parse(child, iNodes, this.worldMatrix));
                    break;

                case 'instance_geometry':

                    this.mesh = child.getAttribute("url").substr(1);

                    var materials = child.querySelectorAll("instance_material");

                    for (var j = 0; j < materials.length; j++) {

                        var material = materials.item(j);

                        this.materials.push({
                            id: material.getAttribute("target").substr(1),
                            symbol: material.getAttribute("symbol")
                        });
                    }

                    break;

                case 'instance_node':
                    var iNodeId = child.getAttribute('url').substr(1);
                    var iNode = this.getLibraryNode(iNodes, iNodeId);

                    if (iNode) {
                        this.children.push(( new ColladaNode() ).parse(iNode, iNodes, this.worldMatrix));
                    }
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    /**
     * Computes the transformation and normal matrix of a node
     * Internal. Applications should not call this function.
     * @param {Node} element A visual_scene node.
     * @param {Matrix} parentWorldMatrix The transformation matrix of it's parent.
     */
    ColladaNode.prototype.setNodeTransforms = function (element, parentWorldMatrix) {

        var matrix = Matrix.fromIdentity(),
            rotationMatrix = Matrix.fromIdentity(),
            translationMatrix = Matrix.fromIdentity(),
            scaleMatrix = Matrix.fromIdentity();

        if (!parentWorldMatrix) {
            parentWorldMatrix = Matrix.fromIdentity();
        }

        var transforms = [];

        for (var i = 0; i < element.childNodes.length; i++) {

            var child = element.childNodes[i];

            if (child.nodeType !== 1) {
                continue;
            }

            switch (child.nodeName) {

                case 'matrix':
                    var values = ColladaUtils.bufferDataFloat32(child);
                    matrix.copy(values);
                    transforms.push(matrix);
                    break;

                case 'rotate':
                    values = ColladaUtils.bufferDataFloat32(child);
                    rotationMatrix.multiplyByRotation(values[0], values[1], values[2], values[3]);
                    transforms.push(rotationMatrix);
                    break;

                case 'translate':
                    values = ColladaUtils.bufferDataFloat32(child);
                    translationMatrix.multiplyByTranslation(values[0], values[1], values[2]);
                    transforms.push(translationMatrix);
                    break;

                case 'scale':
                    values = ColladaUtils.bufferDataFloat32(child);
                    scaleMatrix.multiplyByScale(values[0], values[1], values[2]);
                    transforms.push(scaleMatrix);
                    break;

                default:
                    break;

            }
        }

        for (i = 0; i < transforms.length; i++) {
            this.localMatrix.multiplyMatrix(transforms[i]);
        }

        this.worldMatrix.setToMultiply(parentWorldMatrix, this.localMatrix);

        this.normalMatrix = Matrix.fromIdentity();

        var rotationAngles = new Vec3(0,0,0);
        this.worldMatrix.extractRotationAngles(rotationAngles);

        this.normalMatrix.multiplyByRotation(-1, 0, 0, rotationAngles[0]);
        this.normalMatrix.multiplyByRotation(0, -1, 0, rotationAngles[1]);
        this.normalMatrix.multiplyByRotation(0, 0, -1, rotationAngles[2]);

    };

    /**
     * Retrieves a node form library_nodes
     * Internal. Applications should not call this function.
     * @param {NodeList} iNodes Nodes from library_nodes
     * @param {String} id The id of the node to retrieve
     */
    ColladaNode.prototype.getLibraryNode = function (iNodes, id) {

        for (var i = 0; i < iNodes.length; i++) {

            var attObj = iNodes[i].attributes.getNamedItem('id');
            if (attObj && attObj.value === id) {
                return iNodes[i];
            }
        }

        return null;
    };

    return ColladaNode;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaScene
 */

define('formats/collada/ColladaScene',[
        '../../error/ArgumentError',
        '../../shaders/BasicTextureProgram',
        '../../util/Color',
        '../../util/Logger',
        '../../geom/Matrix',
        '../../geom/Position',
        '../../pick/PickedObject',
        '../../render/Renderable',
        '../../geom/Vec3'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              Logger,
              Matrix,
              Position,
              PickedObject,
              Renderable,
              Vec3) {
        "use strict";

        /**
         * Constructs a collada scene
         * @alias ColladaScene
         * @constructor
         * @augments Renderable
         * @classdesc Represents a scene. A scene is a collection of nodes with meshes, materials and textures.
         * @param {Position} position The scene's geographic position.
         * @param {Object} sceneData The scene's data containing the nodes, meshes, materials, textures and other
         * info needed to render the scene.
         */
        var ColladaScene = function (position, sceneData) {

            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ColladaScene", "constructor", "missingPosition"));
            }

            Renderable.call(this);

            // Documented in defineProperties below.
            this._position = position;

            // Documented in defineProperties below.
            this._nodes = [];
            this._meshes = {};
            this._materials = {};
            this._images = {};
            this._upAxis = '';
            this._dirPath = '';

            // Documented in defineProperties below.
            this._xRotation = 0;
            this._yRotation = 0;
            this._zRotation = 0;

            // Documented in defineProperties below.
            this._xTranslation = 0;
            this._yTranslation = 0;
            this._zTranslation = 0;

            // Documented in defineProperties below.
            this._scale = 1;

            // Documented in defineProperties below.
            this._altitudeMode = WorldWind.ABSOLUTE;

            // Documented in defineProperties below.
            this._localTransforms = true;

            // Documented in defineProperties below.
            this._useTexturePaths = true;

            // Documented in defineProperties below.
            this._nodesToHide = [];
            this._hideNodes = false;

            this.setSceneData(sceneData);

            // Documented in defineProperties below.
            this._placePoint = new Vec3(0, 0, 0);

            // Documented in defineProperties below.
            this._transformationMatrix = Matrix.fromIdentity();

            // Documented in defineProperties below.
            this._normalMatrix = Matrix.fromIdentity();

            this._texCoordMatrix = Matrix.fromIdentity().setToUnitYFlip();

            this._activeTexture = null;

        };

        ColladaScene.prototype = Object.create(Renderable.prototype);
        ColladaScene.prototype.constructor = ColladaScene;

        Object.defineProperties(ColladaScene.prototype, {

            /**
             * The scene's geographic position.
             * @memberof ColladaScene.prototype
             * @type {Position}
             */
            position: {
                get: function () {
                    return this._position;
                },
                set: function (value) {
                    this._position = value;
                }
            },

            /**
             * An array of nodes extracted from the collada file.
             * @memberof ColladaScene.prototype
             * @type {ColladaNode[]}
             */
            nodes: {
                get: function () {
                    return this._nodes;
                },
                set: function (value) {
                    this._nodes = value;
                }
            },

            /**
             * An object with meshes extracted from the collada file.
             * @memberof ColladaScene.prototype
             * @type {{ColladaMesh}}
             */
            meshes: {
                get: function () {
                    return this._meshes;
                },
                set: function (value) {
                    this._meshes = value;
                }
            },

            /**
             * An object with materials and their effects extracted from the collada file.
             * @memberof ColladaScene.prototype
             * @type {ColladaMaterial}
             */
            materials: {
                get: function () {
                    return this._materials;
                },
                set: function (value) {
                    this._materials = value;
                }
            },

            /**
             * An object with images extracted from the collada file.
             * @memberof ColladaScene.prototype
             * @type {ColladaImage}
             */
            images: {
                get: function () {
                    return this._images;
                },
                set: function (value) {
                    this._images = value;
                }
            },

            /**
             * The up axis of the collada model extracted from the collada file.
             * @memberof ColladaScene.prototype
             * @type {String}
             */
            upAxis: {
                get: function () {
                    return this._upAxis;
                },
                set: function (value) {
                    this._upAxis = value;
                }
            },

            /**
             * The path to the directory of the collada file.
             * @memberof ColladaScene.prototype
             * @type {String}
             */
            dirPath: {
                get: function () {
                    return this._dirPath;
                },
                set: function (value) {
                    this._dirPath = value;
                }
            },

            /**
             * The scene's rotation angle in degrees for the x axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            xRotation: {
                get: function () {
                    return this._xRotation;
                },
                set: function (value) {
                    this._xRotation = value;
                }
            },

            /**
             * The scene's rotation angle in degrees for the x axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            yRotation: {
                get: function () {
                    return this._yRotation;
                },
                set: function (value) {
                    this._yRotation = value;
                }
            },

            /**
             * The scene's rotation angle in degrees for the x axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            zRotation: {
                get: function () {
                    return this._zRotation;
                },
                set: function (value) {
                    this._zRotation = value;
                }
            },

            /**
             * The scene's translation for the x axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            xTranslation: {
                get: function () {
                    return this._xTranslation;
                },
                set: function (value) {
                    this._xTranslation = value;
                }
            },

            /**
             * The scene's translation for the y axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            yTranslation: {
                get: function () {
                    return this._yTranslation;
                },
                set: function (value) {
                    this._yTranslation = value;
                }
            },

            /**
             * The scene's translation for the z axis.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            zTranslation: {
                get: function () {
                    return this._zTranslation;
                },
                set: function (value) {
                    this._zTranslation = value;
                }
            },

            /**
             * The scene's scale.
             * @memberof ColladaScene.prototype
             * @type {Number}
             */
            scale: {
                get: function () {
                    return this._scale;
                },
                set: function (value) {
                    this._scale = value;
                }
            },

            /**
             * The scene's Cartesian point on the globe for the specified position.
             * @memberof ColladaScene.prototype
             * @type {Vec3}
             */
            placePoint: {
                get: function () {
                    return this._placePoint;
                },
                set: function (value) {
                    this._placePoint = value;
                }
            },

            /**
             * The scene's altitude mode. May be one of
             * <ul>
             *  <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
             *  <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
             *  <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
             * </ul>
             * @default WorldWind.ABSOLUTE
             * @memberof ColladaScene.prototype
             * @type {String}
             */
            altitudeMode: {
                get: function () {
                    return this._altitudeMode;
                },
                set: function (value) {
                    this._altitudeMode = value;
                }
            },

            /**
             * The scene's transformation matrix containing the scale, rotations and translations
             * @memberof ColladaScene.prototype
             * @type {Matrix}
             */
            transformationMatrix: {
                get: function () {
                    return this._transformationMatrix;
                },
                set: function (value) {
                    this._transformationMatrix = value;
                }
            },

            /**
             * The scene's normal matrix
             * @memberof ColladaScene.prototype
             * @type {Matrix}
             */
            normalMatrix: {
                get: function () {
                    return this._normalMatrix;
                },
                set: function (value) {
                    this._normalMatrix = value;
                }
            },

            /**
             * Force the use of the nodes transformation info. Some 3d software may break the transformations when
             * importing/exporting models to collada format. Set to false to ignore the the nodes transformation.
             * Only use this option if the model does not render properly.
             * @memberof ColladaScene.prototype
             * @default true
             * @type {Boolean}
             */
            localTransforms: {
                get: function () {
                    return this._localTransforms;
                },
                set: function (value) {
                    this._localTransforms = value;
                }
            },

            /**
             * Force the use of the texture path specified in the collada file. Set to false to ignore the paths of the
             * textures in the collada file and instead get the textures from the same dir as the collada file.
             * @memberof ColladaScene.prototype
             * @default true
             * @type {Boolean}
             */
            useTexturePaths: {
                get: function () {
                    return this._useTexturePaths;
                },
                set: function (value) {
                    this._useTexturePaths = value;
                }
            },

            /**
             * An array of node id's to not render.
             * @memberof ColladaScene.prototype
             * @type {String[]}
             */
            nodesToHide: {
                get: function () {
                    return this._nodesToHide;
                },
                set: function (value) {
                    this._nodesToHide = value;
                }
            },

            /**
             * Set to true to force the renderer to not draw the nodes passed to the nodesToHide list.
             * @memberof ColladaScene.prototype
             * @default false
             * @type {Boolean}
             */
            hideNodes: {
                get: function () {
                    return this._hideNodes;
                },
                set: function (value) {
                    this._hideNodes = value;
                }
            }

        });

        // Internal. Intentionally not documented.
        ColladaScene.prototype.setSceneData = function (sceneData) {
            if (sceneData) {
                this.nodes = sceneData.root.children;
                this.meshes = sceneData.meshes;
                this.materials = sceneData.materials;
                this.images = sceneData.images;
                this.upAxis = sceneData.metadata.up_axis;
                this.dirPath = sceneData.dirPath;
            }
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.render = function (dc) {

            var orderedScene;

            if (!this.enabled) {
                return;
            }

            if (this.lastFrameTime !== dc.timestamp) {
                orderedScene = this.makeOrderedRenderable(dc);
            }

            if (!orderedScene) {
                return;
            }

            orderedScene.layer = dc.currentLayer;

            this.lastFrameTime = dc.timestamp;

            dc.addOrderedRenderable(orderedScene);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.makeOrderedRenderable = function (dc) {

            dc.surfacePointForMode(this.position.latitude, this.position.longitude, this.position.altitude,
                this.altitudeMode, this.placePoint);

            this.eyeDistance = dc.navigatorState.eyePoint.distanceTo(this.placePoint);

            return this;

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.renderOrdered = function (dc) {

            this.drawOrderedScene(dc);

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this,
                    this.position, this.layer, false);

                dc.resolvePick(po);
            }
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.drawOrderedScene = function (dc) {

            this.beginDrawing(dc);

            try {
                this.doDrawOrderedScene(dc);
            }
            finally {
                this.endDrawing(dc);
            }

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.beginDrawing = function (dc) {

            var gl = dc.currentGlContext;

            dc.findAndBindProgram(BasicTextureProgram);

            gl.enable(gl.CULL_FACE);
            gl.enable(gl.DEPTH_TEST);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.doDrawOrderedScene = function (dc) {

            if (dc.pickingMode) {
                this.pickColor = dc.uniquePickColor();
            }

            this.computeTransformationMatrix(dc.globe);

            for (var i = 0, nodesLen = this.nodes.length; i < nodesLen; i++) {
                this.traverseNodeTree(dc, this.nodes[i]);
            }
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.traverseNodeTree = function (dc, node) {

            var renderNode = this.mustRenderNode(node.id);

            if (renderNode) {

                if (node.mesh) {
                    var meshKey = node.mesh;
                    var buffers = this.meshes[meshKey].buffers;

                    for (var i = 0, bufLen = buffers.length; i < bufLen; i++) {

                        var materialBuf = buffers[i].material;

                        for (var j = 0; j < node.materials.length; j++) {
                            if (materialBuf === node.materials[j].symbol) {
                                var materialKey = node.materials[j].id;
                                break;
                            }
                        }

                        var material = this.materials[materialKey];

                        this.draw(dc, buffers[i], material, node.worldMatrix, node.normalMatrix);
                    }
                }

                for (var k = 0; k < node.children.length; k++) {
                    this.traverseNodeTree(dc, node.children[k]);
                }
            }

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.draw = function (dc, buffers, material, nodeWorldMatrix, nodeNormalMatrix) {

            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                vboId;

            this.applyVertices(dc, buffers);

            program.loadTextureEnabled(gl, false);

            this.applyColor(dc, material);

            var hasTexture = (material && material.textures != null && buffers.uvs && buffers.uvs.length > 0);
            if (hasTexture) {
                this.applyTexture(dc, buffers, material);
            }

            var hasLighting = (buffers.normals != null && buffers.normals.length > 0);
            if (hasLighting && !dc.pickingMode) {
                this.applyLighting(dc, buffers);
            }

            this.applyMatrix(dc, hasLighting, hasTexture , nodeWorldMatrix, nodeNormalMatrix);

            if (buffers.indexedRendering) {
                this.applyIndices(dc, buffers);
                gl.drawElements(gl.TRIANGLES, buffers.indices.length, gl.UNSIGNED_SHORT, 0);
            }
            else {
                gl.drawArrays(gl.TRIANGLES, 0, Math.floor(buffers.vertices.length / 3));
            }

            this.resetDraw(dc, hasLighting, hasTexture);

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyVertices = function (dc, buffers) {

            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                vboId;

            if (!buffers.verticesVboCacheKey) {
                buffers.verticesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(buffers.verticesVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(buffers.verticesVboCacheKey, vboId,
                    buffers.vertices.length);
                buffers.refreshVertexBuffer = true;
            }

            gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            if (buffers.refreshVertexBuffer) {
                gl.bufferData(gl.ARRAY_BUFFER, buffers.vertices, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                buffers.refreshVertexBuffer = false;
            }

            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyColor = function (dc, material) {

            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            if (material) {
                if (material.techniqueType === 'constant') {
                    var diffuse = material.reflective;
                }
                else {
                    diffuse = material.diffuse;
                }
            }

            var opacity;
            var r = 1, g = 1, b = 1, a = 1;

            if (diffuse) {
                r = diffuse[0];
                g = diffuse[1];
                b = diffuse[2];
                a = diffuse[3] != null ? diffuse[3] : 1;
            }

            var color = new Color(r, g, b, a);
            opacity = a * dc.currentLayer.opacity;
            gl.depthMask(opacity >= 1 || dc.pickingMode);
            program.loadColor(gl, dc.pickingMode ? this.pickColor : color);
            program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyTexture = function (dc, buffers, material) {

            var textureBound, vboId,
                gl = dc.currentGlContext,
                program = dc.currentProgram,
                wrapMode;

            if (material.textures.diffuse) {
                var imageKey = material.textures.diffuse.mapId;
            }
            else {
                imageKey = material.textures.reflective.mapId;
            }

            var image = this.useTexturePaths ? this.images[imageKey].path : this.images[imageKey].filename;

            this._activeTexture = dc.gpuResourceCache.resourceForKey(this.dirPath + image + "");
            if (!this._activeTexture) {
                wrapMode = buffers.isClamp ? gl.CLAMP_TO_EDGE : gl.REPEAT;
                this._activeTexture = dc.gpuResourceCache.retrieveTexture(gl, this.dirPath + image + "", wrapMode);
            }
            textureBound = this._activeTexture && this._activeTexture.bind(dc);

            if (textureBound) {
                if (!buffers.texCoordsVboCacheKey) {
                    buffers.texCoordsVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                }

                vboId = dc.gpuResourceCache.resourceForKey(buffers.texCoordsVboCacheKey);
                if (!vboId) {
                    vboId = gl.createBuffer();
                    dc.gpuResourceCache.putResource(buffers.texCoordsVboCacheKey, vboId, buffers.uvs.length);
                    buffers.refreshTexCoordBuffer = true;
                }

                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                if (buffers.refreshTexCoordBuffer) {
                    gl.bufferData(gl.ARRAY_BUFFER, buffers.uvs, gl.STATIC_DRAW);
                    dc.frameStatistics.incrementVboLoadCount(1);
                    buffers.refreshTexCoordBuffer = false;
                }

                program.loadTextureEnabled(gl, true);
                gl.enableVertexAttribArray(program.vertexTexCoordLocation);
                gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
                program.loadTextureUnit(gl, gl.TEXTURE0);
                program.loadModulateColor(gl, dc.pickingMode);
            }
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyLighting = function (dc, buffers) {

            var vboId,
                gl = dc.currentGlContext,
                program = dc.currentProgram;

            program.loadApplyLighting(gl, true);
            if (!buffers.normalsVboCacheKey) {
                buffers.normalsVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(buffers.normalsVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(buffers.normalsVboCacheKey, vboId, buffers.normals.length);
                buffers.refreshNormalBuffer = true;
            }

            gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            if (buffers.refreshNormalBuffer) {
                gl.bufferData(gl.ARRAY_BUFFER, buffers.normals, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                buffers.refreshNormalBuffer = false;
            }

            gl.enableVertexAttribArray(program.normalVectorLocation);
            gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, 0, 0);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyMatrix = function (dc, hasLighting, hasTexture, nodeWorldMatrix, nodeNormalMatrix) {

            var mvpMatrix = Matrix.fromIdentity();

            mvpMatrix.copy(dc.navigatorState.modelviewProjection);

            mvpMatrix.multiplyMatrix(this.transformationMatrix);

            if (nodeWorldMatrix && this.localTransforms) {
                mvpMatrix.multiplyMatrix(nodeWorldMatrix);
            }

            if (hasLighting && !dc.pickingMode) {

                var normalMatrix = Matrix.fromIdentity();

                normalMatrix.copy(dc.navigatorState.modelviewNormalTransform);

                normalMatrix.multiplyMatrix(this.normalMatrix);

                if (nodeNormalMatrix && this.localTransforms) {
                    normalMatrix.multiplyMatrix(nodeNormalMatrix);
                }

                dc.currentProgram.loadModelviewInverse(dc.currentGlContext, normalMatrix);
            }

            if (hasTexture && this._activeTexture){
                dc.currentProgram.loadTextureMatrix(dc.currentGlContext, this._texCoordMatrix);
                this._activeTexture = null;
            }

            dc.currentProgram.loadModelviewProjection(dc.currentGlContext, mvpMatrix);

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.applyIndices = function (dc, buffers) {

            var gl = dc.currentGlContext,
                vboId;

            if (!buffers.indicesVboCacheKey) {
                buffers.indicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(buffers.indicesVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(buffers.indicesVboCacheKey, vboId, buffers.indices.length);
                buffers.refreshIndicesBuffer = true;
            }

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
            if (buffers.refreshIndicesBuffer) {
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, buffers.indices, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                buffers.refreshIndicesBuffer = false;
            }

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.resetDraw = function (dc, hasLighting, hasTexture) {

            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            if (hasLighting && !dc.pickingMode) {
                program.loadApplyLighting(gl, false);
                gl.disableVertexAttribArray(program.normalVectorLocation);
            }

            if (hasTexture) {
                gl.disableVertexAttribArray(program.vertexTexCoordLocation);
            }

            gl.disableVertexAttribArray(program.vertexPointLocation);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.endDrawing = function (dc) {
            dc.bindProgram(null);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.computeTransformationMatrix = function (globe) {

            this.transformationMatrix = Matrix.fromIdentity();

            this.transformationMatrix.multiplyByLocalCoordinateTransform(this.placePoint, globe);

            this.transformationMatrix.multiplyByRotation(1, 0, 0, this.xRotation);
            this.transformationMatrix.multiplyByRotation(0, 1, 0, this.yRotation);
            this.transformationMatrix.multiplyByRotation(0, 0, 1, this.zRotation);

            this.transformationMatrix.multiplyByScale(this.scale, this.scale, this.scale);

            this.transformationMatrix.multiplyByTranslation(this.xTranslation, this.yTranslation, this.zTranslation);

            this.computeNormalMatrix();

        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.computeNormalMatrix = function () {

            var rotAngles = new Vec3(0, 0, 0);

            this.transformationMatrix.extractRotationAngles(rotAngles);

            this.normalMatrix = Matrix.fromIdentity();

            this.normalMatrix.multiplyByRotation(-1, 0, 0, rotAngles[0]);
            this.normalMatrix.multiplyByRotation(0, -1, 0, rotAngles[1]);
            this.normalMatrix.multiplyByRotation(0, 0, -1, rotAngles[2]);
        };

        // Internal. Intentionally not documented.
        ColladaScene.prototype.mustRenderNode = function (nodeId) {
            var draw = true;
            if (this.hideNodes) {
                var pos = this.nodesToHide.indexOf(nodeId);
                draw = (pos === -1);
            }
            return draw;
        };

        return ColladaScene;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ColladaLoader
 */

define('formats/collada/ColladaLoader',[
        '../../error/ArgumentError',
        './ColladaAsset',
        './ColladaImage',
        './ColladaMaterial',
        './ColladaMesh',
        './ColladaNode',
        './ColladaScene',
        './ColladaUtils',
        '../../util/Logger'
    ],
    function (ArgumentError,
              ColladaAsset,
              ColladaImage,
              ColladaMaterial,
              ColladaMesh,
              ColladaNode,
              ColladaScene,
              ColladaUtils,
              Logger) {
        "use strict";

        /**
         * Constructs a ColladaLoader
         * @alias ColladaLoader
         * @constructor
         * @classdesc Represents a Collada Loader. Fetches and parses a collada document and returns the
         * necessary information to render the collada model.
         * @param {Position} position The model's geographic position.
         * @param {Object} config Configuration options for the loader.
         * <ul>
         *  <li>dirPath - the path to the directory where the collada file is located</li>
         * </ul>
         */
        var ColladaLoader = function (position, config) {

            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ColladaLoader", "constructor", "missingPosition"));
            }

            this.position = position;

            this.dirPath = '/';

            this.init(config);
        };

        /**
         * Initialization of the ColladaLoader
         * @param {Object} config Configuration options for the loader.
         * <ul>
         *  <li>dirPath - the path to the directory where the collada file is located</li>
         * </ul>
         */
        ColladaLoader.prototype.init = function (config) {
            if (config) {
                this.dirPath = config.dirPath || '/';
            }

            this.scene = {
                type: "SceneTree",
                dirPath: this.dirPath,
                images: {},
                metadata: {},
                materials: {},
                meshes: {},
                root: {children: []}
            };

            this.xmlDoc = null;
        };

        /**
         * Fetches and parses a collada file
         * @param {String} url The url to the collada .dae file.
         * @param {Function} cb A callback function to call with the result when the parsing is done.
         * @returns {ColladaScene} A renderable shape.
         */
        ColladaLoader.prototype.load = function (url, cb) {

            if (url.indexOf("://") === -1) {
                url = this.dirPath + url;
            }

            ColladaUtils.fetchFile(url, function (data) {

                if (!data) {
                    var colladaScene = null;
                }
                else {

                    try {
                        colladaScene = this.parse(data);
                    }
                    catch (e) {
                        colladaScene = null;
                        Logger.log(Logger.LEVEL_SEVERE, "error parsing collada file: " + e);
                    }
                }

                cb(colladaScene);

            }.bind(this));
        };

        /**
         * Parses a collada file
         * @param {XML} data The raw XML data of the collada file.
         * @returns {ColladaScene} A renderable shape.
         */
        ColladaLoader.prototype.parse = function (data) {

            this.init();

            var parser = new DOMParser();
            this.xmlDoc = parser.parseFromString(data, "text/xml");

            var iNodes = this.xmlDoc.querySelectorAll('library_nodes node');
            var eNodes = this.xmlDoc.querySelectorAll("library_effects effect");

            this.scene.metadata = ( new ColladaAsset(this.xmlDoc) ).parse();
            this.parseLib('visual_scene', iNodes);
            this.parseLib('library_geometries');
            this.parseLib('library_materials', eNodes);
            this.parseLib('library_images');

            this.xmlDoc = null;

            return new ColladaScene(this.position, this.scene);
        };

        /**
         * Parses a collada library tag.
         * @param {String} libName The library tag name.
         * @param {NodeList} extraNodes Nodes from library_nodes or effects form library_effects
         */
        ColladaLoader.prototype.parseLib = function (libName, extraNodes) {

            var libs = this.xmlDoc.getElementsByTagName(libName);
            var libNodes = [];

            if (libs && libs.length) {
                libNodes = libs[0].childNodes;
            }

            for (var i = 0; i < libNodes.length; i++) {

                var libNode = libNodes[i];

                if (libNode.nodeType !== 1) {
                    continue;
                }

                switch (libNode.nodeName) {

                    case 'node':
                        var node = ( new ColladaNode() ).parse(libNode, extraNodes);
                        if (node) {
                            this.scene.root.children.push(node);
                        }
                        break;

                    case 'geometry':
                        var geometryId = libNode.getAttribute("id");
                        var xmlMesh = libNode.querySelector("mesh");
                        var mesh = ( new ColladaMesh(geometryId) ).parse(xmlMesh);
                        if (mesh) {
                            this.scene.meshes[geometryId] = mesh;
                        }
                        break;

                    case 'material':
                        var materialId = libNode.getAttribute("id");
                        var iEffect = libNode.querySelector("instance_effect");
                        var effectId = iEffect.getAttribute("url").substr(1);
                        var effect = ColladaUtils.querySelectorById(extraNodes, effectId);
                        var material = ( new ColladaMaterial(materialId) ).parse(effect);
                        if (material) {
                            this.scene.materials[materialId] = material;
                        }
                        break;

                    case 'image':
                        var imageId = libNode.getAttribute("id");
                        var imageName = libNode.getAttribute("name");
                        var image = ( new ColladaImage(imageId, imageName) ).parse(libNode);
                        if (image) {
                            this.scene.images[imageId] = image;
                        }
                        break;

                    default:
                        break;
                }
            }

        };

        return ColladaLoader;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ImageSource
 */
define('util/ImageSource',[
        '../error/ArgumentError',
        '../util/Color',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              Logger) {
        "use strict";

        /**
         * Constructs an image source.
         * @alias ImageSource
         * @constructor
         * @classdesc Holds an Image with an associated key that uniquely identifies that image. The key is
         * automatically generated but may be reassigned after construction. Instances of this class are used to
         * specify dynamically created image sources for {@link Placemark}, {@link SurfaceImage},
         * {@link Polygon} textures and other shapes that display imagery.
         * @param {Image} image The image for this image source.
         * @throws {ArgumentError} If the specified image is null or undefined.
         */
        var ImageSource = function (image) {
            if (!image) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ImageSource", "constructor",
                    "missingImage"));
            }

            /**
             * This image source's image
             * @type {Image}
             * @readonly
             */
            this.image = image;

            /**
             * This image source's key. A unique key is automatically generated and assigned during construction.
             * Applications may assign a different key after construction.
             * @type {String}
             * @default A unique string for this image source.
             */
            this.key = "ImageSource " + ++ImageSource.keyPool;
        };

        // Internal. Intentionally not documented.
        ImageSource.keyPool = 0; // source of unique ids

        return ImageSource;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ScreenImage
 */
define('shapes/ScreenImage',[
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/ImageSource',
        '../util/Logger',
        '../geom/Matrix',
        '../util/Offset',
        '../pick/PickedObject',
        '../render/Renderable',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              ImageSource,
              Logger,
              Matrix,
              Offset,
              PickedObject,
              Renderable,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a screen image.
         * @alias ScreenImage
         * @constructor
         * @augments Renderable
         * @classdesc Displays an image at a specified screen location in the WorldWindow.
         * The image location is specified by an offset, which causes the image to maintain its relative position
         * when the window size changes.
         * @param {Offset} screenOffset The offset indicating the image's placement on the screen.
         * Use [the image offset property]{@link ScreenImage#imageOffset} to position the image relative to the
         * specified screen offset.
         * @param {String|ImageSource} imageSource The source of the image to display.
         * May be either a string identifying the URL of the image, or an {@link ImageSource} object identifying a
         * dynamically created image.
         * @throws {ArgumentError} If the specified screen offset or image source is null or undefined.
         */
        var ScreenImage = function (screenOffset, imageSource) {
            if (!screenOffset) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenImage", "constructor", "missingOffset"));
            }

            if (!imageSource) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenImage", "constructor", "missingImage"));
            }

            Renderable.call(this);

            /**
             * The offset indicating this screen image's placement on the screen.
             * @type {Offset}
             */
            this.screenOffset = screenOffset;

            // Documented with its property accessor below.
            this._imageSource = imageSource;

            /**
             * The image color. When displayed, this shape's image is multiplied by this image color to achieve the
             * final image color. The color white, the default, causes the image to be drawn in its native colors.
             * @type {Color}
             * @default White (1, 1, 1, 1)
             */
            this.imageColor = Color.WHITE;

            /**
             * Indicates the location within the image at which to align with the specified screen location.
             * May be null, in which case the image's bottom-left corner is placed at the screen location.
             * @type {Offset}
             * @default 0.5, 0.5, both fractional (Centers the image on the screen location.)
             */
            this.imageOffset = new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5);

            /**
             * Indicates the amount to scale the image.
             * @type {Number}
             * @default 1
             */
            this.imageScale = 1;

            /**
             * The amount of rotation to apply to the image, measured in degrees clockwise from the top of the window.
             * @type {Number}
             * @default 0
             */
            this.imageRotation = 0;

            /**
             * The amount of tilt to apply to the image, measured in degrees.
             * @type {Number}
             * @default 0
             */
            this.imageTilt = 0;

            /**
             * Indicates whether to draw this screen image.
             * @type {Boolean}
             * @default true
             */
            this.enabled = true;

            /**
             * This image's opacity. When this screen image is drawn, the actual opacity is the product of
             * this opacity and the opacity of the layer containing this screen image.
             * @type {Number}
             */
            this.opacity = 1;

            /**
             * Indicates the object to return as the userObject of this shape when picked. If null,
             * then this shape is returned as the userObject.
             * @type {Object}
             * @default null
             * @see  [PickedObject.userObject]{@link PickedObject#userObject}
             */
            this.pickDelegate = null;

            // Internal use only. Intentionally not documented.
            this.activeTexture = null;

            // Internal use only. Intentionally not documented.
            this.imageTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.texCoordMatrix = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.imageBounds = null;

            // Internal use only. Intentionally not documented.
            this.layer = null;
        };

        // Internal use only. Intentionally not documented.
        ScreenImage.matrix = Matrix.fromIdentity(); // scratch variable

        ScreenImage.prototype = Object.create(Renderable.prototype);

        Object.defineProperties(ScreenImage.prototype, {
            /**
             * The source of the image to display.
             * May be either a string identifying the URL of the image, or an {@link ImageSource} object identifying a
             * dynamically created image.
             * @type {String|ImageSource}
             * @default null
             * @memberof ScreenImage.prototype
             */
            imageSource: {
                get: function () {
                    return this._imageSource;
                },
                set: function (imageSource) {
                    if (!imageSource) {
                        throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenImage", "imageSource",
                            "missingImage"));
                    }

                    this._imageSource = imageSource;
                    this.imageSourceWasUpdated = true;
                }
            }
        });

        /**
         * Renders this screen image. This method is typically not called by applications but is called by
         * {@link RenderableLayer} during rendering. For this shape this method creates and
         * enques an ordered renderable with the draw context and does not actually draw the image.
         * @param {DrawContext} dc The current draw context.
         */
        ScreenImage.prototype.render = function (dc) {
            if (!this.enabled) {
                return;
            }

            if (!dc.accumulateOrderedRenderables) {
                return;
            }

            // Create an ordered renderable, but don't create more than one per frame.
            var orderedScreenImage = null;
            if (this.lastFrameTime !== dc.timestamp) {
                orderedScreenImage = this.makeOrderedRenderable(dc);
            }

            if (!orderedScreenImage) {
                return;
            }

            if (!orderedScreenImage.isVisible(dc)) {
                return;
            }

            orderedScreenImage.layer = dc.currentLayer;

            this.lastFrameTime = dc.timestamp;
            dc.addOrderedRenderable(orderedScreenImage);
        };

        /**
         * Draws this shape as an ordered renderable. Applications do not call this function. It is called by
         * [WorldWindow]{@link WorldWindow} during rendering.
         * @param {DrawContext} dc The current draw context.
         */
        ScreenImage.prototype.renderOrdered = function (dc) {
            this.drawOrderedScreenImage(dc);

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this.pickDelegate ? this.pickDelegate : this,
                    null, this.layer, false);
                dc.resolvePick(po);
            }
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.makeOrderedRenderable = function (dc) {
            var w, h, s, ws, hs,
                iOffset, sOffset;

            this.activeTexture = this.getActiveTexture(dc);
            if (!this.activeTexture || this.imageSourceWasUpdated) {
                this.activeTexture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this._imageSource);
                if (!this.activeTexture) {
                    return null;
                }
            }

            this.eyeDistance = 0;

            // Compute the image's transform matrix and texture coordinate matrix according to its screen point, image size,
            // image offset and image scale. The image offset is defined with its origin at the image's bottom-left corner and
            // axes that extend up and to the right from the origin point.
            w = this.activeTexture.imageWidth;
            h = this.activeTexture.imageHeight;
            s = this.imageScale;
            iOffset = this.imageOffset.offsetForSize(w, h);
            ws = dc.navigatorState.viewport.width;
            hs = dc.navigatorState.viewport.height;
            sOffset = this.screenOffset.offsetForSize(ws, hs);

            this.imageTransform.setTranslation(
                sOffset[0] - iOffset[0] * s,
                sOffset[1] - iOffset[1] * s,
                0);

            this.imageTransform.setScale(w * s, h * s, 1);

            this.imageBounds = WWMath.boundingRectForUnitQuad(this.imageTransform);

            return this;
        };

        ScreenImage.prototype.getActiveTexture = function (dc) {
            return dc.gpuResourceCache.resourceForKey(this._imageSource);
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.isVisible = function (dc) {
            if (dc.pickingMode) {
                return dc.pickRectangle && (this.imageBounds.intersects(dc.pickRectangle));
            } else {
                return this.imageBounds.intersects(dc.navigatorState.viewport);
            }
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.drawOrderedScreenImage = function (dc) {
            this.beginDrawing(dc);
            try {
                this.doDrawOrderedScreenImage(dc);
            } finally {
                this.endDrawing(dc);
            }
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program;

            dc.findAndBindProgram(BasicTextureProgram);

            // Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
            // Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
            // that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
            // buffer for texture coords.
            program = dc.currentProgram;
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.enableVertexAttribArray(program.vertexTexCoordLocation);

            // Tell the program which texture unit to use.
            program.loadTextureUnit(gl, gl.TEXTURE0);
            program.loadModulateColor(gl, dc.pickingMode);

            // Turn off depth testing.
            gl.disable(gl.DEPTH_TEST);
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            // Clear the vertex attribute state.
            gl.disableVertexAttribArray(program.vertexPointLocation);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            // Clear GL bindings.
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindTexture(gl.TEXTURE_2D, null);

            // Re-enable depth testing.
            gl.enable(gl.DEPTH_TEST);
        };

        // Internal. Intentionally not documented.
        ScreenImage.prototype.doDrawOrderedScreenImage = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);

            // Compute and specify the MVP matrix.
            ScreenImage.matrix.copy(dc.screenProjection);
            ScreenImage.matrix.multiplyMatrix(this.imageTransform);

            ScreenImage.matrix.multiplyByTranslation(0.5, 0.5, 0.5); // shift Z to prevent image clipping
            ScreenImage.matrix.multiplyByRotation(1, 0, 0, this.imageTilt);
            ScreenImage.matrix.multiplyByRotation(0, 0, 1, this.imageRotation);
            ScreenImage.matrix.multiplyByTranslation(-0.5, -0.5, 0);

            program.loadModelviewProjection(gl, ScreenImage.matrix);

            // Enable texture for both normal display and for picking. If picking is enabled in the shader (set in
            // beginDrawing() above) then the texture's alpha component is still needed in order to modulate the
            // pick color to mask off transparent pixels.
            program.loadTextureEnabled(gl, true);

            // Set the pick color for picking or the color and opacity if not picking.
            if (dc.pickingMode) {
                this.pickColor = dc.uniquePickColor();
                program.loadColor(gl, this.pickColor);
            } else {
                program.loadColor(gl, this.imageColor);
                program.loadOpacity(gl, this.opacity * this.layer.opacity);
            }

            this.texCoordMatrix.setToIdentity();
            this.texCoordMatrix.multiplyByTextureTransform(this.activeTexture);
            program.loadTextureMatrix(gl, this.texCoordMatrix);

            if (this.activeTexture.bind(dc)) { // returns false if active texture cannot be bound
                // Draw the placemark's image quad.
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }
        };

        return ScreenImage;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Compass
 */
define('shapes/Compass',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/Offset',
        '../shapes/ScreenImage'
    ],
    function (ArgumentError,
              Logger,
              Offset,
              ScreenImage) {
        "use strict";

        /**
         * Constructs a compass.
         * @alias Compass
         * @constructor
         * @augments ScreenImage
         * @classdesc Displays a compass image at a specified location in the WorldWindow. The compass image rotates
         * and tilts to reflect the current navigator's heading and tilt.
         * @param {Offset} screenOffset The offset indicating the image's placement on the screen. If null or undefined
         * the compass is placed at the upper-right corner of the WorldWindow.
         * Use [the image offset property]{@link ScreenImage#imageOffset} to position the image relative to the
         * screen point.
         * @param {String} imagePath The URL of the image to display. If null or undefined, a default compass image is used.
         */
        var Compass = function (screenOffset, imagePath) {

            var sOffset = screenOffset ? screenOffset
                : new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 1), // upper-right placement
                iPath = imagePath ? imagePath : WorldWind.configuration.baseUrl + "images/notched-compass.png";

            ScreenImage.call(this, sOffset, iPath);

            // Must set the default image offset after calling the constructor above.

            if (!screenOffset) {
                // Align the upper right corner of the image with the screen point, and give the image some padding.
                this.imageOffset = new Offset(WorldWind.OFFSET_FRACTION, 1.1, WorldWind.OFFSET_FRACTION, 1.1);
            }

            /**
             * Specifies the size of the compass as a fraction of the WorldWindow width.
             * @type {number}
             * @default 0.15
             */
            this.size = 0.15;
        };

        Compass.prototype = Object.create(ScreenImage.prototype);

        /**
         * Capture the navigator's heading and tilt and apply it to the compass' screen image.
         * @param {DrawContext} dc The current draw context.
         */
        Compass.prototype.render = function (dc) {
            // Capture the navigator's heading and tilt and apply it to the compass' screen image.
            this.imageRotation = dc.navigatorState.heading;
            this.imageTilt = dc.navigatorState.tilt;

            var t = this.getActiveTexture(dc);
            if (t) {
                this.imageScale = this.size * dc.currentGlContext.drawingBufferWidth / t.imageWidth;
            }

            ScreenImage.prototype.render.call(this, dc);
        };

        return Compass;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports CompassLayer
 */
define('layer/CompassLayer',[
        '../shapes/Compass',
        '../layer/RenderableLayer'
    ],
    function (Compass,
              RenderableLayer) {
        "use strict";

        /**
         * Constructs a compass layer.
         * @alias CompassLayer
         * @constructor
         * @augments RenderableLayer
         * @classdesc Displays a compass. Compass layers cannot be shared among WorldWindows. Each WorldWindow if it
         * is to have a compass layer must have its own. See the MultiWindow example for guidance.
         */
        var CompassLayer = function () {
            RenderableLayer.call(this, "Compass");

            this._compass = new Compass(null, null);

            this.addRenderable(this._compass);
        };

        CompassLayer.prototype = Object.create(RenderableLayer.prototype);

        Object.defineProperties(CompassLayer.prototype, {
            /**
             * The compass to display.
             * @type {Compass}
             * @default {@link Compass}
             * @memberof CompassLayer.prototype
             */
            compass: {
                get: function () {
                    return this._compass;
                },
                set: function (compass) {
                    if (compass && compass instanceof Compass) {
                        this.removeAllRenderables();
                        this.addRenderable(compass);
                        this._compass = compass;
                    }
                }
            }
        });

        return CompassLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Text
 */
define('shapes/Text',[
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/Font',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../render/Renderable',
        '../shapes/TextAttributes',
        '../error/UnsupportedOperationError',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              Font,
              Logger,
              Matrix,
              PickedObject,
              Renderable,
              TextAttributes,
              UnsupportedOperationError,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a text shape. This constructor is intended to be called only by subclasses.
         * @alias Text
         * @constructor
         * @augments Renderable
         * @classdesc Represents a string of text displayed at a specified geographic or screen position.
         * This is an abstract class meant to be subclassed and not meant to be instantiated directly.
         * See {@link GeographicText} and {@link ScreenText} for concrete classes.
         *
         * @param {String} text The text to display.
         * @throws {ArgumentError} If the specified text is null or undefined.
         */
        var Text = function (text) {
            if (!text) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Text", "constructor", "missingText"));
            }

            Renderable.call(this);

            /**
             * The text's attributes. If null and this text is not highlighted, this text is not drawn.
             * @type {TextAttributes}
             * @default see [TextAttributes]{@link TextAttributes}
             */
            this.attributes = new TextAttributes(null);

            /**
             * The attributes used when this text's highlighted flag is true. If null and the
             * highlighted flag is true, this text's normal attributes are used. If they, too, are null, this
             * text is not drawn.
             * @type {TextAttributes}
             * @default null
             */
            this.highlightAttributes = null;

            /**
             * Indicates whether this text uses its highlight attributes rather than its normal attributes.
             * @type {boolean}
             * @default false
             */
            this.highlighted = false;

            /**
             * Indicates whether this text is drawn.
             * @type {boolean}
             * @default true
             */
            this.enabled = true;

            /**
             * This shape's text. If null or empty, no text is drawn.
             * @type {String}
             * @default null
             */
            this.text = text;

            /**
             * This text's altitude mode. May be one of
             * <ul>
             *  <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
             *  <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
             *  <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
             * </ul>
             * @default WorldWind.ABSOLUTE
             */
            this.altitudeMode = WorldWind.ABSOLUTE;

            /**
             * Indicates the object to return as the userObject of this text when picked. If null,
             * then this text object is returned as the userObject.
             * @type {Object}
             * @default null
             * @see  [PickedObject.userObject]{@link PickedObject#userObject}
             */
            this.pickDelegate = null;

            /**
             * Indicates whether this text has visual priority over other shapes in the scene.
             * @type {Boolean}
             * @default false
             */
            this.alwaysOnTop = false;

            /**
             * This shape's target visibility, a value between 0 and 1. During ordered rendering this shape modifies its
             * [current visibility]{@link Text#currentVisibility} towards its target visibility at the rate
             * specified by the draw context's [fadeVelocity]{@link DrawContext#fadeVelocity} property. The target
             * visibility and current visibility are used to control the fading in and out of this shape.
             * @type {Number}
             * @default 1
             */
            this.targetVisibility = 1;

            /**
             * This shape's current visibility, a value between 0 and 1. This property scales the shape's effective
             * opacity. It is incremented or decremented each frame according to the draw context's
             * [fade velocity]{@link DrawContext#fadeVelocity} property in order to achieve this shape's current
             * [target visibility]{@link Text#targetVisibility}. This current visibility and target visibility are
             * used to control the fading in and out of this shape.
             * @type {Number}
             * @default 1
             * @readonly
             */
            this.currentVisibility = 1;

            /**
             * Indicates the group ID of the declutter group to include this Text shape. If non-zer0, this shape
             * is decluttered relative to all other shapes within its group.
             * @type {Number}
             * @default 0
             */
            this.declutterGroup = 0;

            /**
             * The image to display when this text shape is eliminated from the scene due to decluttering.
             * @type {String}
             * @default A round dot drawn in this shape's text color.
             */
            this.markerImageSource = WorldWind.configuration.baseUrl + "images/white-dot.png";

            /**
             * The scale to apply to the [markerImageSource]{@link Text#markerImageSource}.
             * @type {Number}
             * @default 0.1
             */
            this.markerImageScale = 0.1;

            // Internal use only. Intentionally not documented.
            this.activeAttributes = null;

            // Internal use only. Intentionally not documented.
            this.activeTexture = null;

            // Internal use only. Intentionally not documented.
            this.imageTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.texCoordMatrix = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.imageBounds = null;

            // Internal use only. Intentionally not documented.
            this.layer = null;

            // Internal use only. Intentionally not documented.
            this.depthOffset = -0.003;

            // Internal use only. Intentionally not documented.
            this.screenPoint = new Vec3(0, 0, 0);
        };

        // Internal use only. Intentionally not documented.
        Text.matrix = Matrix.fromIdentity(); // scratch variable
        Text.glPickPoint = new Vec3(0, 0, 0); // scratch variable

        Text.prototype = Object.create(Renderable.prototype);

        /**
         * Copies the contents of a specified text object to this text object.
         * @param {Text} that The text object to copy.
         */
        Text.prototype.copy = function (that) {
            this.text = that.text;
            this.attributes = that.attributes;
            this.highlightAttributes = that.highlightAttributes;
            this.highlighted = that.highlighted;
            this.enabled = that.enabled;
            this.altitudeMode = that.altitudeMode;
            this.pickDelegate = that.pickDelegate;
            this.alwaysOnTop = that.alwaysOnTop;
            this.depthOffset = that.depthOffset;
            this.declutterGroup = that.declutterGroup;
            this.targetVisibility = that.targetVisibility;
            this.currentVisibility = that.currentVisibility;

            return this;
        };

        Object.defineProperties(Text.prototype, {
            /**
             * Indicates the screen coordinate bounds of this shape during ordered rendering.
             * @type {Rectangle}
             * @readonly
             * @memberof Text.prototype
             */
            screenBounds: {
                get: function () {
                    return this.imageBounds;
                }
            }
        });

        /**
         * Renders this text. This method is typically not called by applications but is called by
         * [RenderableLayer]{@link RenderableLayer} during rendering. For this shape this method creates and
         * enques an ordered renderable with the draw context and does not actually draw the text.
         * @param {DrawContext} dc The current draw context.
         */
        Text.prototype.render = function (dc) {
            if (!this.enabled || (!this.text) || this.text.length === 0) {
                return;
            }

            if (!dc.accumulateOrderedRenderables) {
                return;
            }

            // Create an ordered renderable for this text. If one has already been created this frame then we're
            // in 2D-continuous mode and another needs to be created for one of the alternate globe offsets.
            var orderedText;
            if (this.lastFrameTime != dc.timestamp) {
                orderedText = this.makeOrderedRenderable(dc);
            } else {
                var textCopy = this.clone();
                orderedText = textCopy.makeOrderedRenderable(dc);
            }

            if (!orderedText) {
                return;
            }

            if (!orderedText.isVisible(dc)) {
                return;
            }

            orderedText.layer = dc.currentLayer;

            this.lastFrameTime = dc.timestamp;
            dc.addOrderedRenderable(orderedText);
        };

        /**
         * Draws this shape as an ordered renderable. Applications do not call this function. It is called by
         * {@link WorldWindow} during rendering. Implements the {@link OrderedRenderable} interface.
         * @param {DrawContext} dc The current draw context.
         */
        Text.prototype.renderOrdered = function (dc) {
            // Optimize away the case of achieved target visibility of 0 and no marker image to display in that case.
            if (this.currentVisibility === 0 && this.targetVisibility === 0 && !this.markerImageSource) {
                return;
            }

            this.drawOrderedText(dc);

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this.pickDelegate ? this.pickDelegate : this,
                    this.position, this.layer, false);

                dc.resolvePick(po);
            }
        };

        // Intentionally not documented.
        Text.prototype.makeOrderedRenderable = function (dc) {
            var w, h, s,
                offset;

            this.determineActiveAttributes(dc);
            if (!this.activeAttributes) {
                return null;
            }

            //// Compute the text's screen point and distance to the eye point.
            if (!this.computeScreenPointAndEyeDistance(dc)) {
                return null;
            }

            var labelFont = this.activeAttributes.font,
                textureKey = this.text + labelFont.toString();

            this.activeTexture = dc.gpuResourceCache.resourceForKey(textureKey);
            if (!this.activeTexture) {
                this.activeTexture = dc.textSupport.createTexture(dc, this.text, labelFont, true);
                dc.gpuResourceCache.putResource(textureKey, this.activeTexture, this.activeTexture.size);
            }

            w = this.activeTexture.imageWidth;
            h = this.activeTexture.imageHeight;
            s = this.activeAttributes.scale;
            offset = this.activeAttributes.offset.offsetForSize(w, h);

            this.imageTransform.setTranslation(
                this.screenPoint[0] - offset[0] * s,
                this.screenPoint[1] - offset[1] * s,
                this.screenPoint[2]);

            this.imageTransform.setScale(w * s, h * s, 1);

            this.imageBounds = WWMath.boundingRectForUnitQuad(this.imageTransform);

            return this;
        };

        /**
         * Computes this shape's screen point and eye distance. Subclasses must override this method.
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if the screen point can be computed, otherwise false.
         * @protected
         */
        Text.prototype.computeScreenPointAndEyeDistance = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "Renderable", "render", "abstractInvocation"));
        };

        // Internal. Intentionally not documented.
        Text.prototype.determineActiveAttributes = function (dc) {
            if (this.highlighted && this.highlightAttributes) {
                this.activeAttributes = this.highlightAttributes;
            } else {
                this.activeAttributes = this.attributes;
            }
        };

        // Internal. Intentionally not documented.
        Text.prototype.isVisible = function (dc) {
            if (dc.pickingMode) {
                return dc.pickRectangle && this.imageBounds.intersects(dc.pickRectangle);
            } else {
                return this.imageBounds.intersects(dc.navigatorState.viewport);
            }
        };

        // Internal. Intentionally not documented.
        Text.prototype.drawOrderedText = function (dc) {
            this.beginDrawing(dc);

            try {
                this.doDrawOrderedText(dc);
                if (!dc.pickingMode) {
                    //this.drawBatchOrderedText(dc);
                }
            } finally {
                this.endDrawing(dc);
            }
        };

        // Internal. Intentionally not documented.
        Text.prototype.drawBatchOrderedText = function (dc) {
            // Draw any subsequent text in the ordered renderable queue, removing each from the queue as it's
            // processed. This avoids the overhead of setting up and tearing down OpenGL state for each text shape.

            var or;

            while ((or = dc.peekOrderedRenderable()) && or instanceof Text) {
                dc.popOrderedRenderable(); // remove it from the queue

                try {
                    or.doDrawOrderedText(dc)
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_WARNING, 'Text', 'drawBatchOrderedText',
                        "Error occurred while rendering text using batching: " + e.message);
                }
                // Keep going. Render the rest of the ordered renderables.
            }
        };

        // Internal. Intentionally not documented.
        Text.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program;

            dc.findAndBindProgram(BasicTextureProgram);

            // Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
            // Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
            // that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
            // buffer for texture coords.
            program = dc.currentProgram;
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.enableVertexAttribArray(program.vertexTexCoordLocation);

            // Tell the program which texture unit to use.
            program.loadTextureUnit(gl, gl.TEXTURE0);

            // Turn off color modulation since we want to pick against the text box and not just the text.
            program.loadModulateColor(gl, false);

            // Suppress depth-buffer writes.
            gl.depthMask(false);

            // The currentTexture field is used to avoid re-specifying textures unnecessarily. Clear it to start.
            Text.currentTexture = null;
        };

        // Internal. Intentionally not documented.
        Text.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            // Clear the vertex attribute state.
            gl.disableVertexAttribArray(program.vertexPointLocation);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            // Clear GL bindings.
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindTexture(gl.TEXTURE_2D, null);

            gl.depthMask(true);

            // Avoid keeping a dangling reference to the current texture.
            Text.currentTexture = null;
        };

        // Internal. Intentionally not documented.
        Text.prototype.doDrawOrderedText = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                textureBound;

            // Compute the text's current visibility, potentially requesting additional frames.
            if (!dc.pickingMode && this.currentVisibility !== this.targetVisibility) {
                var visibilityDelta = (dc.timestamp - dc.previousRedrawTimestamp) / dc.fadeTime;
                if (this.currentVisibility < this.targetVisibility) {
                    this.currentVisibility = Math.min(1, this.currentVisibility + visibilityDelta);
                } else {
                    this.currentVisibility = Math.max(0, this.currentVisibility - visibilityDelta);
                }
                dc.redrawRequested = true;
            }

            // Use the text color and opacity. When picking, use the pick color, 100% opacity and no texture.
            if (!dc.pickingMode) {
                program.loadColor(gl, this.activeAttributes.color);
                program.loadOpacity(gl, this.layer.opacity * this.currentVisibility);
            } else {
                this.pickColor = dc.uniquePickColor();
                program.loadColor(gl, this.pickColor);
                program.loadOpacity(gl, 1);
                program.loadTextureEnabled(gl, false);
            }

            // When the text is visible, draw the text label.
            if (this.currentVisibility > 0) {
                // Compute and specify the MVP matrix.
                Text.matrix.copy(dc.screenProjection);
                Text.matrix.multiplyMatrix(this.imageTransform);
                program.loadModelviewProjection(gl, Text.matrix);

                if (!dc.pickingMode) {
                    this.texCoordMatrix.setToIdentity();
                    if (this.activeTexture) {
                        this.texCoordMatrix.multiplyByTextureTransform(this.activeTexture);
                    }
                    program.loadTextureMatrix(gl, this.texCoordMatrix);

                    // Avoid unnecessary texture state changes
                    if (this.activeTexture && this.activeTexture != Text.currentTexture) {
                        textureBound = this.activeTexture.bind(dc); // returns false if texture is null or cannot be bound
                        program.loadTextureEnabled(gl, textureBound);
                        Text.currentTexture = this.activeTexture;
                    }
                }

                // Turn off depth testing for the label unless it's been requested.
                if (!this.activeAttributes.depthTest) {
                    gl.disable(gl.DEPTH_TEST, false);
                }
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
                if (!this.activeAttributes.depthTest) {
                    // Turn depth testing back on.
                    gl.disable(gl.DEPTH_TEST, true);
                }
            }

            // When the text is not visible, draw a marker to indicate that something is there.
            if (this.currentVisibility < 1 && this.markerImageSource) {
                var markerTexture = dc.gpuResourceCache.resourceForKey(this.markerImageSource);
                if (!markerTexture) {
                    dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.markerImageSource);
                    return;
                }

                var s = this.markerImageScale;
                var markerTransform = Matrix.fromIdentity();
                markerTransform.setTranslation(
                    this.screenPoint[0] - s * markerTexture.imageWidth / 2,
                    this.screenPoint[1] - s * markerTexture.imageWidth / 2,
                    this.screenPoint[2]);

                markerTransform.setScale(markerTexture.imageWidth * s, markerTexture.imageHeight * s, 1);

                Text.matrix.copy(dc.screenProjection);
                Text.matrix.multiplyMatrix(markerTransform);
                program.loadModelviewProjection(gl, Text.matrix);

                // Use the marker opacity and texture when not picking.
                if (!dc.pickingMode) {
                    program.loadOpacity(gl, this.layer.opacity * ( 1 - this.currentVisibility));

                    var tcMatrix = Matrix.fromIdentity();
                    tcMatrix.multiplyByTextureTransform(markerTexture);
                    program.loadTextureMatrix(gl, tcMatrix);

                    // Avoid unnecessary texture state changes
                    if (markerTexture != Text.currentTexture) {
                        textureBound = markerTexture.bind(dc); // returns false if texture is null or cannot be bound
                        program.loadTextureEnabled(gl, textureBound);
                        Text.currentTexture = markerTexture;
                    }
                }

                // Turn off depth testing unless it's been requested.
                if (!this.activeAttributes.depthTest) {
                    gl.disable(gl.DEPTH_TEST, false);
                }
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
                if (!this.activeAttributes.depthTest) {
                    // Turn depth testing back on.
                    gl.enable(gl.DEPTH_TEST, true);
                }
            }
        };

        return Text;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ScreenText
 */
define('shapes/ScreenText',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/Offset',
        '../shapes/Text'
    ],
    function (ArgumentError,
              Logger,
              Offset,
              Text) {
        "use strict";

        /**
         * Constructs a screen text shape at a specified screen location.
         * @alias ScreenText
         * @constructor
         * @augments Text
         * @classdesc Represents a string of text displayed at a screen location.
         * <p>
         * See also {@link GeographicText}.
         *
         * @param {Offset} screenOffset The offset indicating the text's placement on the screen.
         * Use [TextAttributes.offset]{@link TextAttributes#offset} to position the text relative to the specified
         * screen offset.
         * @param {String} text The text to display.
         * @throws {ArgumentError} If either the specified screen offset or text is null or undefined.
         */
        var ScreenText = function (screenOffset, text) {
            if (!screenOffset) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Text", "constructor", "missingOffset"));
            }

            Text.call(this, text);

            /**
             * The offset indicating this text's placement on the screen.
             * The [TextAttributes.offset]{@link TextAttributes#offset} property indicates the relationship of the text
             * string to this location.
             * @type {Offset}
             */
            this.screenOffset = screenOffset;

            /**
             * Inherited from [Text]{@link Text#altitudeMode} but not utilized by screen text.
             */
            this.altitudeMode = null;
        };

        ScreenText.prototype = Object.create(Text.prototype);

        // Documented in superclass.
        ScreenText.prototype.render = function (dc) {
            // Ensure that this text is drawn only once per frame.
            if (this.lastFrameTime != dc.timestamp) {
                Text.prototype.render.call(this, dc);
            }
        };

        // Documented in superclass.
        ScreenText.prototype.computeScreenPointAndEyeDistance = function (dc) {
            var gl = dc.currentGlContext,
                offset = this.screenOffset.offsetForSize(gl.drawingBufferWidth, gl.drawingBufferHeight);

            this.screenPoint[0] = offset[0];
            this.screenPoint[1] = offset[1];
            this.screenPoint[2] = 0;

            this.eyeDistance = 0;

            return true;
        };

        return ScreenText;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports CoordinatesDisplayLayer
 */
define('layer/CoordinatesDisplayLayer',[
        '../error/ArgumentError',
        '../util/Color',
        '../util/Font',
        '../layer/Layer',
        '../util/Logger',
        '../util/Offset',
        '../geom/Position',
        '../shapes/ScreenImage',
        '../shapes/ScreenText',
        '../shapes/TextAttributes',
        '../geom/Vec2'
    ],
    function (ArgumentError,
              Color,
              Font,
              Layer,
              Logger,
              Offset,
              Position,
              ScreenImage,
              ScreenText,
              TextAttributes,
              Vec2) {
        "use strict";

        /**
         * Constructs a layer that displays the current map coordinates.
         * @alias CoordinatesDisplayLayer
         * @constructor
         * @augments Layer
         * @classDesc Displays the current map coordinates. A coordinates display layer cannot be shared among World
         * Windows. Each WorldWindow if it is to have a coordinates display layer must have its own. See the
         * MultiWindow example for guidance.
         * @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
         * This layer may not be associated with more than one WorldWindow. Each WorldWindow must have it's own
         * instance of this layer if each window is to have a coordinates display.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var CoordinatesDisplayLayer = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ViewControlsLayer", "constructor", "missingWorldWindow"));
            }

            Layer.call(this, "Coordinates");

            /**
             * The WorldWindow associated with this layer.
             * @type {WorldWindow}
             * @readonly
             */
            this.wwd = worldWindow;

            // No picking of this layer's screen elements.
            this.pickEnabled = false;

            // Intentionally not documented.
            this.eventType = null;

            // Intentionally not documented.
            this.clientX = null;

            // Intentionally not documented.
            this.clientY = null;

            // Intentionally not documented.
            this.terrainPosition = null;

            // Intentionally not documented.
            this.latText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
            this.latText.attributes = new TextAttributes(null);
            this.latText.attributes.color = Color.YELLOW;

            // Intentionally not documented.
            this.lonText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
            this.lonText.attributes = new TextAttributes(null);
            this.lonText.attributes.color = Color.YELLOW;

            // Intentionally not documented.
            this.elevText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
            this.elevText.attributes = new TextAttributes(null);
            this.elevText.attributes.color = Color.YELLOW;

            // Intentionally not documented.
            this.eyeText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
            this.eyeText.attributes = new TextAttributes(null);
            this.eyeText.attributes.color = Color.YELLOW;

            // Intentionally not documented.
            var imageOffset = new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5),
                imagePath = WorldWind.configuration.baseUrl + "images/crosshair.png";
            this.crosshairImage = new ScreenImage(imageOffset, imagePath);

            // Register user input event listeners on the WorldWindow.
            var thisLayer = this;

            function eventListener(event) {
                thisLayer.handleUIEvent(event);
            }

            if (window.PointerEvent) {
                worldWindow.addEventListener("pointerdown", eventListener);
                worldWindow.addEventListener("pointermove", eventListener);
                worldWindow.addEventListener("pointerleave", eventListener);
            } else {
                worldWindow.addEventListener("mousedown", eventListener);
                worldWindow.addEventListener("mousemove", eventListener);
                worldWindow.addEventListener("mouseleave", eventListener);
                worldWindow.addEventListener("touchstart", eventListener);
                worldWindow.addEventListener("touchmove", eventListener);
            }

            // Register a redraw callback on the WorldWindow.
            function redrawCallback(worldWindow, stage) {
                thisLayer.handleRedraw(stage);
            }

            this.wwd.redrawCallbacks.push(redrawCallback);
        };

        CoordinatesDisplayLayer.prototype = Object.create(Layer.prototype);

        // Documented in superclass.
        CoordinatesDisplayLayer.prototype.doRender = function (dc) {
            var terrainPos = this.terrainPosition,
                eyePos = dc.eyePosition,
                canvasWidth = dc.currentGlContext.canvas.clientWidth,
                x, y, yUnitsScreen, yUnitsText, hideEyeAlt;

            if (canvasWidth > 650) { // large canvas, align the text with bottom center
                x = (canvasWidth / 2) - 50;
                y = 5;
                yUnitsScreen = WorldWind.OFFSET_PIXELS;
                yUnitsText = 0;
            } else if (canvasWidth > 400) { // medium canvas, align the text in the top left
                x = 60;
                y = 5;
                yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
                yUnitsText = 1;
            } else { // small canvas, suppress the eye altitude, align the text in the top left and suppress eye alt
                x = 60;
                y = 5;
                yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
                yUnitsText = 1;
                hideEyeAlt = true;
            }

            // TODO can we control terrain position visibility with Text's targetVisibility?
            this.latText.text = terrainPos ? this.formatLatitude(terrainPos.latitude) : null;
            this.latText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
            this.latText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
            this.latText.render(dc);

            x += 70;
            this.lonText.text = terrainPos ? this.formatLongitude(terrainPos.longitude) : null;
            this.lonText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
            this.lonText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
            this.lonText.render(dc);

            if (!dc.globe.is2D()) {
                x += 70;
                this.elevText.text = terrainPos ? this.formatAltitude(terrainPos.altitude, "m") : null;
                this.elevText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
                this.elevText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
                this.elevText.render(dc);
            }

            // TODO can we control eye altitude visibility with Text's targetVisibility?
            if (!hideEyeAlt) {
                x += 40;
                this.eyeText.text = "Eye  " + this.formatAltitude(eyePos.altitude, eyePos.altitude < 1000 ? "m" : "km");
                this.eyeText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
                this.eyeText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, yUnitsText);
                this.eyeText.render(dc);
            }

            // TODO can we control crosshair visibility by adding targetVisibility to ScreenImage?
            if (this.eventType == "touch") {
                this.crosshairImage.render(dc);
            }

            this.inCurrentFrame = true;
        };

        // Intentionally not documented.
        CoordinatesDisplayLayer.prototype.handleUIEvent = function (event) {
            if (event.type.indexOf("pointer") != -1) {
                this.eventType = event.pointerType; // possible values are "mouse", "pen" and "touch"
            } else if (event.type.indexOf("mouse") != -1) {
                this.eventType = "mouse";
            } else if (event.type.indexOf("touch") != -1) {
                this.eventType = "touch";
            }

            if (event.type.indexOf("leave") != -1) {
                this.clientX = null; // clear the event coordinates when a pointer leaves the canvas
                this.clientY = null;
            } else {
                this.clientX = event.clientX;
                this.clientY = event.clientY;
            }

            this.wwd.redraw();
        };

        // Intentionally not documented.
        CoordinatesDisplayLayer.prototype.handleRedraw = function (stage) {
            if (stage != WorldWind.BEFORE_REDRAW) {
                return; // ignore after redraw events
            }

            var pickPoint,
                terrainObject;

            if ((this.eventType == "mouse" || this.eventType == "pen") && this.clientX && this.clientY) {
                pickPoint = this.wwd.canvasCoordinates(this.clientX, this.clientY);
                if (pickPoint[0] >= 0 && pickPoint[0] < this.wwd.canvas.width &&
                    pickPoint[1] >= 0 && pickPoint[1] < this.wwd.canvas.height) {
                    terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
                }
            } else if (this.eventType == "touch") {
                pickPoint = new Vec2(this.wwd.canvas.width / 2, this.wwd.canvas.height / 2);
                terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
            }

            this.terrainPosition = terrainObject ? terrainObject.position : null;
        };

        // Intentionally not documented.
        CoordinatesDisplayLayer.prototype.formatLatitude = function (number) {
            var suffix = number < 0 ? "\u00b0S" : "\u00b0N";
            return Math.abs(number).toFixed(2) + suffix;
        };

        // Intentionally not documented.
        CoordinatesDisplayLayer.prototype.formatLongitude = function (number) {
            var suffix = number < 0 ? "\u00b0W" : "\u00b0E";
            return Math.abs(number).toFixed(2) + suffix;
        };

        // Intentionally not documented.
        CoordinatesDisplayLayer.prototype.formatAltitude = function (number, units) {
            // Convert from meters to the desired units format.
            if (units === "km") {
                number /= 1e3;
            }

            // Round to the nearest integer and place a comma every three digits. See the following Stack Overflow
            // thread for more information:
            // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
            return number.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " " + units;
        };

        return CoordinatesDisplayLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/Date',[], function () {
    "use strict";
    /**
     * Descendant of Date.
     * @param dateInFormat {String} Any format of date accepted by the Date constructor.
     * @constructor
     * @alias DateWW
     */
    var DateWW = function(dateInFormat) {
        if(dateInFormat) {
            this._date = new Date(dateInFormat);
        } else {
            this._date = new Date();
        }
    };

    DateWW.prototype = Object.create(Date.prototype);

    DateWW.prototype.isAfter = function(date) {
        return this.compare(date) == -1;
    };

    DateWW.prototype.isBefore = function(date) {
        return this.compare(date) == 1;
    };

    DateWW.prototype.valueOf = function() {
        return this._date.valueOf();
    };

    DateWW.prototype.getTime = function() {
        return this._date.getTime();
    };

    DateWW.prototype.compare = function(date) {
        var currentDate = this._date.valueOf();
        var comparedDate = date.valueOf();

        if(currentDate > comparedDate) {
            return -1;
        } else if(currentDate < comparedDate) {
            return 1;
        } else {
            return 0;
        }
    };

    return DateWW;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DigitalGlobeTiledImageLayer
 */
define('layer/DigitalGlobeTiledImageLayer',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../util/Color',
        '../geom/Location',
        '../util/Logger',
        '../geom/Sector',
        '../layer/MercatorTiledImageLayer'
    ],
    function (Angle,
              ArgumentError,
              Color,
              Location,
              Logger,
              Sector,
              MercatorTiledImageLayer) {
        "use strict";

        /**
         * Constructs Digital Globe tiled image layer for a specified dataset distributed by Digital Globe.
         * @alias DigitalGlobeTiledImageLayer
         * @constructor
         * @augments MercatorTiledImageLayer
         * @classdesc Provides a layer that shows Digital Globe imagery.
         *
         * @param {String} displayName This layer's display name. "Digital Globe" if this parameter is
         * null or undefined.
         * @param {String} mapId The map ID for the dataset to display.
         * @param {String} accessToken The access token to use when retrieving metadata and imagery. This code is
         * issued by Digital Globe.
         * @throws {ArgumentError} If the specified map ID or access token is null or undefined.
         */
        var DigitalGlobeTiledImageLayer = function (displayName, mapId, accessToken) {
            if (!mapId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DigitalGlobeTiledImageLayer", "constructor",
                        "The map ID is null or undefined."));
            }

            if (!accessToken) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DigitalGlobeTiledImageLayer", "constructor",
                        "The access token is null or undefined."));
            }

            this.imageSize = 256;
            displayName = displayName || "Digital Globe";

            MercatorTiledImageLayer.call(this,
                new Sector(-85.05, 85.05, -180, 180), new Location(85.05, 180), 19, "image/jpeg", displayName,
                this.imageSize, this.imageSize);

            /**
             * The map ID identifying the dataset displayed by this layer.
             * @type {String}
             * @readonly
             */
            this.mapId = mapId;

            /**
             * The access token used when requesting imagery from Digital Globe.
             * @type {String}
             */
            this.accessToken = accessToken;
            //"pk.eyJ1IjoiZGlnaXRhbGdsb2JlIiwiYSI6IjljZjQwNmEyMTNhOWUyMWM5NWUzYWIwOGNhYTY2ZDViIn0.Ju3tOUUUc0C_gcCSAVpFIA";

            this.displayName = displayName;
            this.pickEnabled = false;

            // Create a canvas we can use when unprojecting retrieved images.
            this.destCanvas = document.createElement("canvas");
            this.destContext = this.destCanvas.getContext("2d");

            this.requestMetadata();

            var self = this;
            this.urlBuilder = {
                urlForTile: function (tile, imageFormat) {
                    if (!self.metadataRetrievalInProcess) {
                        return self.urlTemplate.replace("{z}", (tile.level.levelNumber + 1)).
                            replace("{x}", tile.column).replace("{y}", tile.row);
                    } else {
                        return null;
                    }
                }
            };
        };

        DigitalGlobeTiledImageLayer.prototype = Object.create(MercatorTiledImageLayer.prototype);

        DigitalGlobeTiledImageLayer.prototype.requestMetadata = function () {
            if (!this.metadataRetrievalInProcess) {
                this.metadataRetrievalInProcess = true;

                var url = "https://api.mapbox.com/v4/" + this.mapId + ".json?secure&access_token=" + this.accessToken;

                var xhr = new XMLHttpRequest();
                var self = this;
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        var json = JSON.parse(xhr.responseText);
                        self.urlTemplate = json.tiles[0];

                        // Send an event to request a redraw.
                        var e = document.createEvent('Event');
                        e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                        window.dispatchEvent(e);

                        self.metadataRetrievalInProcess = false;
                    }
                };
                xhr.open("GET", url, true);
                xhr.send();
            }
        };

        DigitalGlobeTiledImageLayer.prototype.doRender = function (dc) {
            MercatorTiledImageLayer.prototype.doRender.call(this, dc);
            if (this.inCurrentFrame) {
                dc.screenCreditController.addStringCredit("\u00A9 Digital Globe", Color.DARK_GRAY);
            }
        };

        // Overridden from TiledImageLayer.
        DigitalGlobeTiledImageLayer.prototype.createTopLevelTiles = function (dc) {
            this.topLevelTiles = [];

            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 1));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 1));
        };

        // Determines the Bing map size for a specified level number.
        DigitalGlobeTiledImageLayer.prototype.mapSizeForLevel = function (levelNumber) {
            return 256 << (levelNumber + 1);
        };

        return DigitalGlobeTiledImageLayer;
    }
)
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DragRecognizer
 */
define('gesture/DragRecognizer',['../gesture/GestureRecognizer'],
    function (GestureRecognizer) {
        "use strict";

        /**
         * Constructs a mouse drag gesture recognizer.
         * @alias DragRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for mouse drag gestures.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var DragRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            /**
             *
             * @type {Number}
             */
            this.button = 0;

            // Intentionally not documented.
            this.interpretDistance = 5;
        };

        DragRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        // Documented in superclass.
        DragRecognizer.prototype.mouseMove = function (event) {
            if (this.state == WorldWind.POSSIBLE) {
                if (this.shouldInterpret()) {
                    if (this.shouldRecognize()) {
                        this.translationX = 0; // set translation to zero when the drag begins
                        this.translationY = 0;
                        this.state = WorldWind.BEGAN;
                    } else {
                        this.state = WorldWind.FAILED;
                    }
                }
            } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                this.state = WorldWind.CHANGED;
            }
        };

        // Documented in superclass.
        DragRecognizer.prototype.mouseUp = function (event) {
            if (this.mouseButtonMask == 0) { // last button up
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.ENDED;
                }
            }
        };

        // Documented in superclass.
        DragRecognizer.prototype.touchStart = function (touch) {
            if (this.state == WorldWind.POSSIBLE) {
                this.state = WorldWind.FAILED; // mouse gestures fail upon receiving a touch event
            }
        };

        /**
         *
         * @returns {Boolean}
         * @protected
         */
        DragRecognizer.prototype.shouldInterpret = function () {
            var dx = this.translationX,
                dy = this.translationY,
                distance = Math.sqrt(dx * dx + dy * dy);
            return distance > this.interpretDistance; // interpret mouse movement when the cursor moves far enough
        };

        /**
         *
         * @returns {Boolean}
         * @protected
         */
        DragRecognizer.prototype.shouldRecognize = function () {
            var buttonBit = (1 << this.button);
            return buttonBit == this.mouseButtonMask; // true when the specified button is the only button down
        };

        return DragRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports FrameStatistics
 */
define('util/FrameStatistics',[],
    function () {
        "use strict";

        /**
         * Constructs a performance statistics instance. This is performed internally by the {@link WorldWindow}.
         * Applications do not construct instances of this class.
         * @alias FrameStatistics
         * @constructor
         * @classdesc Captures performance statistics.
         */
        var FrameStatistics = function () {

            // Internal: intentionally not documented
            this.frameCount = 0;

            // Internal: intentionally not documented
            this.frameTimeCumulative = 0;

            // Internal: intentionally not documented
            this.frameTimeBase = 0;

            // Internal: intentionally not documented
            this.frameTimeExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];

            /**
             * The number of milliseconds required to render the most recent frame.
             * @type {Number}
             */
            this.frameTime = 0;

            /**
             * The number of milliseconds spent tessellating the terrain during the most recent frame.
             * @type {Number}
             */
            this.tessellationTime = 0;

            /**
             * The number of milliseconds spent rendering the active layers during the most recent frame.
             * @type {Number}
             */
            this.layerRenderingTime = 0;

            /**
             * The number of milliseconds spent rendering ordered renderables during the most recent frame.
             * @type {Number}
             */
            this.orderedRenderingTime = 0;

            /**
             * The number of terrain tiles in the most recent frame.
             * @type {Number}
             */
            this.terrainTileCount = 0;

            /**
             * The number of image tiles in the most recent frame.
             * @type {Number}
             */
            this.imageTileCount = 0;

            /**
             * The number of terrain tile renderings. Since terrain tiles are generally rendered more than once per
             * frame, this count will be greater than the number of terrain tiles created for the frame.
             * @type {Number}
             */
            this.renderedTileCount = 0;

            /**
             * The number of calls to [Tile.update()]{@link Tile#update} during the most recent frame.
             * @type {Number}
             */
            this.tileUpdateCount = 0;

            /**
             * The number of texture bind calls during the most recent frame.
             * @type {Number}
             */
            this.textureLoadCount = 0;

            /**
             * The number of WebGL VBO loads during the most recent frame.
             * @type {Number}
             */
            this.vboLoadCount = 0;

            /**
             * The average frame time over the most recent two seconds.
             * @type {Number}
             */
            this.frameTimeAverage = 0;

            /**
             * The average frame rate over the most recent two seconds.
             * @type {Number}
             */
            this.frameRateAverage = 0;

            /**
             * The minimum frame time over the most recent two seconds.
             * @type {Number}
             */
            this.frameTimeMin = 0;

            /**
             * The maximum frame time over the most recent two seconds.
             * @type {Number}
             */
            this.frameTimeMax = 0;
        };

        /**
         * Initializes this frame statistics with initial values.
         */
        FrameStatistics.prototype.beginFrame = function () {
            this.frameTime = Date.now();
            this.tessellationTime = 0;
            this.layerRenderingTime = 0;
            this.orderedRenderingTime = 0;
            this.terrainTileCount = 0;
            this.imageTileCount = 0;
            this.renderedTileCount = 0;
            this.tileUpdateCount = 0;
            this.textureLoadCount = 0;
            this.vboLoadCount = 0;

            ++this.frameCount;
        };

        /**
         * Computes the statistics for the most recent frame.
         */
        FrameStatistics.prototype.endFrame = function () {
            var now = Date.now();
            this.frameTime = now - this.frameTime;
            this.frameTimeCumulative += this.frameTime;
            this.frameTimeExtremes[0] = Math.min(this.frameTimeExtremes[0], this.frameTime);
            this.frameTimeExtremes[1] = Math.max(this.frameTimeExtremes[1], this.frameTime);

            // Compute averages every 2 seconds.
            if (now - this.frameTimeBase > 2000) {
                this.frameTimeAverage = this.frameTimeCumulative / this.frameCount;
                this.frameRateAverage = 1000 * this.frameCount / (now - this.frameTimeBase);
                this.frameTimeMin = this.frameTimeExtremes[0];
                this.frameTimeMax = this.frameTimeExtremes[1];
                this.frameCount = 0;
                this.frameTimeCumulative = 0;
                this.frameTimeBase = now;
                this.frameTimeExtremes = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
                //console.log(this.frameTimeAverage.toString() + ", " + this.frameRateAverage.toString());
            }
        };

        /**
         * Increments the rendered tile count.
         * @param {Number} tileCount The amount to increment the counter.
         */
        FrameStatistics.prototype.incrementRenderedTileCount = function (tileCount) {
            this.renderedTileCount += tileCount;
        };

        /**
         * Sets the terrain tile count.
         * @param {Number} tileCount The amount to set the counter to.
         */
        FrameStatistics.prototype.setTerrainTileCount = function (tileCount) {
            this.terrainTileCount = tileCount;
        };

        /**
         * Increments the image tile count.
         * @param {Number} tileCount The amount to increment the counter.
         */
        FrameStatistics.prototype.incrementImageTileCount = function (tileCount) {
            this.imageTileCount = tileCount;
        };

        /**
         * Increments the tile update count.
         * @param {Number} count The amount to increment the counter.
         */
        FrameStatistics.prototype.incrementTileUpdateCount = function (count) {
            this.tileUpdateCount += count;
        };

        /**
         * Increments the texture load count.
         * @param {Number} count The amount to increment the counter.
         */
        FrameStatistics.prototype.incrementTextureLoadCount = function (count) {
            this.textureLoadCount += count;
        };

        /**
         * Increments the VBO load count.
         * @param {Number} count The amount to increment the counter.
         */
        FrameStatistics.prototype.incrementVboLoadCount = function (count) {
            this.vboLoadCount += count;
        };

        return FrameStatistics;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports FramebufferTexture
 */
define('render/FramebufferTexture',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a framebuffer texture with the specified dimensions and an optional depth buffer. Use the
         * [DrawContext.bindFramebuffer]{@link DrawContext#bindFramebuffer} function to make the program current during rendering.
         *
         * @alias FramebufferTexture
         * @constructor
         * @classdesc Represents an off-screen WebGL framebuffer. The framebuffer has color buffer stored in a 32
         * bit RGBA texture, and has an optional depth buffer of at least 16 bits. Applications typically do not
         * interact with this class. WebGL framebuffers are created by instances of this class and made current when the
         * DrawContext.bindFramebuffer function is invoked.
         * @param {WebGLRenderingContext} gl The current WebGL rendering context.
         * @param {Number} width The width of the framebuffer, in pixels.
         * @param {Number} height The height of the framebuffer, in pixels.
         * @param {Boolean} depth true to configure the framebuffer with a depth buffer of at least 16 bits, false to
         * disable depth buffering.
         * @throws {ArgumentError} If the specified draw context is null or undefined, or if the width or height is less
         * than zero.
         */
        var FramebufferTexture = function (gl, width, height, depth) {
            if (!gl) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTexture", "constructor",
                    "missingGlContext"));
            }

            if (width < 0 || height < 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTexture", "constructor",
                    "The framebuffer width or height is less than zero."));
            }

            /**
             * The width of this framebuffer, in pixels.
             * @type {Number}
             * @readonly
             */
            this.width = width;

            /**
             * The height of this framebuffer, in pixels.
             * @type {Number}
             * @readonly
             */
            this.height = height;

            /**
             * Indicates whether or not this framebuffer has a depth buffer.
             * @type {Boolean}
             * @readonly
             */
            this.depth = depth;

            /**
             * Indicates the size of this framebuffer's WebGL resources, in bytes.
             * @type {Number}
             * @readonly
             */
            this.size = (width * height * 4) + (depth ? width * height * 2 : 0);

            /**
             * Indicates the WebGL framebuffer object object associated with this framebuffer texture.
             * @type {WebGLFramebuffer}
             * @readonly
             */
            this.framebufferId = gl.createFramebuffer();
            gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebufferId);

            // Internal. Intentionally not documented. Configure this framebuffer's color buffer.
            this.texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, this.texture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
                gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,
                gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
                gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
                gl.CLAMP_TO_EDGE);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
                gl.RGBA, gl.UNSIGNED_BYTE, null);
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
                gl.TEXTURE_2D, this.texture, 0);

            // Internal. Intentionally not documented. Configure this framebuffer's optional depth buffer.
            this.depthBuffer = null;
            if (depth) {
                this.depthBuffer = gl.createRenderbuffer();
                gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);
                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16,
                    width, height);
                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
                    gl.RENDERBUFFER, this.depthBuffer);
            }

            var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
            if (e != gl.FRAMEBUFFER_COMPLETE) {
                Logger.logMessage(Logger.LEVEL_WARNING, "FramebufferTexture", "constructor",
                    "Error creating framebuffer: " + e);
                this.framebufferId = null;
                this.texture = null;
                this.depthBuffer = null;
            }

            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.bindRenderbuffer(gl.RENDERBUFFER, null);
            gl.bindTexture(gl.TEXTURE_2D, null);
        };

        /**
         * Binds this off-screen framebuffer's texture in the current WebGL graphics context. This texture contains
         * color fragments resulting from WebGL operations executed when this framebuffer is bound by a call to
         * [FramebufferTexture.bindFramebuffer]{@link FramebufferTexture#bindFramebuffer}.
         *
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if this framebuffer's texture was bound successfully, otherwise false.
         */
        FramebufferTexture.prototype.bind = function (dc) {
            if (this.texture) {
                dc.currentGlContext.bindTexture(gl.TEXTURE_2D, this.texture);
            }

            return !!this.texture;
        };

        return FramebufferTexture;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports FramebufferTile
 */
define('render/FramebufferTile',[
        '../error/ArgumentError',
        '../render/FramebufferTexture',
        '../util/Logger',
        '../geom/Matrix',
        '../geom/Rectangle',
        '../render/TextureTile'
    ],
    function (ArgumentError,
              FramebufferTexture,
              Logger,
              Matrix,
              Rectangle,
              TextureTile) {
        "use strict";

        /**
         * Constructs a framebuffer tile.
         * @alias FramebufferTile
         * @constructor
         * @augments TextureTile
         * @classdesc Represents a WebGL framebuffer applied to a portion of a globe's terrain. The framebuffer's width
         * and height in pixels are equal to this tile's [tileWidth]{@link FramebufferTile#tileWidth} and
         * [tileHeight]{@link FramebufferTile#tileHeight}, respectively. The framebuffer can be made active by calling
         * [bindFramebuffer]{@link FramebufferTile#bindFramebuffer}. Color fragments written to this
         * tile's framebuffer can then be drawn on the terrain surface using a
         * [SurfaceTileRenderer]{@link SurfaceTileRenderer}.
         * <p>
         * This class is meant to be used internally. Applications typically do not interact with this class.
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {Number} row This tile's row in the associated level.
         * @param {Number} column This tile's column in the associated level.
         * @param {String} cacheKey A string uniquely identifying this tile relative to other tiles.
         * @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
         * are less than zero, or the cache name is null, undefined or empty.
         */
        var FramebufferTile = function (sector, level, row, column, cacheKey) {
            if (!cacheKey || (cacheKey.length < 1)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTile", "constructor",
                        "The specified cache name is null, undefined or zero length."));
            }

            TextureTile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            // Assign the cacheKey as the gpuCacheKey (inherited from TextureTile).
            this.gpuCacheKey = cacheKey;

            // Internal. Intentionally not documented.
            this.textureTransform = Matrix.fromIdentity().setToUnitYFlip();

            // Internal. Intentionally not documented.
            this.mustClear = true;
        };

        FramebufferTile.prototype = Object.create(TextureTile.prototype);

        /**
         * Causes this tile to clear any color fragments written to its off-screen framebuffer.
         * @param dc The current draw context.
         */
        FramebufferTile.prototype.clearFramebuffer = function (dc) {
            this.mustClear = true;
        };

        /**
         * Causes this tile's off-screen framebuffer as the current WebGL framebuffer. WebGL operations that affect the
         * framebuffer now affect this tile's framebuffer, rather than the default WebGL framebuffer.
         * Color fragments are written to this tile's WebGL texture, which can be made active by calling
         * [SurfaceTile.bind]{@link SurfaceTile#bind}.
         *
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if the framebuffer was bound successfully, otherwise false.
         */
        FramebufferTile.prototype.bindFramebuffer = function (dc) {
            var framebuffer = dc.gpuResourceCache.resourceForKey(this.gpuCacheKey);

            if (!framebuffer) {
                framebuffer = this.createFramebuffer(dc);
            }

            dc.bindFramebuffer(framebuffer);

            if (this.mustClear) {
                this.doClearFramebuffer(dc);
                this.mustClear = false;
            }

            return true;
        };

        // Internal. Intentionally not documented.
        FramebufferTile.prototype.createFramebuffer = function (dc) {
            var framebuffer = new FramebufferTexture(dc.currentGlContext, this.tileWidth, this.tileHeight, false);
            dc.gpuResourceCache.putResource(this.gpuCacheKey, framebuffer, framebuffer.size);

            return framebuffer;
        };

        // Internal. Intentionally not documented.
        FramebufferTile.prototype.doClearFramebuffer = function (dc) {
            var gl = dc.currentGlContext;
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);
        };

        /**
         * Applies the appropriate texture transform to display this tile's WebGL texture.
         * @param {DrawContext} dc The current draw context.
         * @param {Matrix} matrix The matrix to apply the transform to.
         */
        FramebufferTile.prototype.applyInternalTransform = function (dc, matrix) {
            matrix.multiplyMatrix(this.textureTransform);
        };

        return FramebufferTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports FramebufferTileController
 */
define('render/FramebufferTileController',[
        '../error/ArgumentError',
        '../render/FramebufferTile',
        '../util/LevelSet',
        '../geom/Location',
        '../util/Logger',
        '../cache/MemoryCache',
        '../geom/Sector',
        '../util/Tile'
    ],
    function (ArgumentError,
              FramebufferTile,
              LevelSet,
              Location,
              Logger,
              MemoryCache,
              Sector,
              Tile) {
        "use strict";

        /**
         * Constructs a framebuffer tile controller.
         * @alias FramebufferTileController
         * @constructor
         * @classdesc Provides access to a multi-resolution WebGL framebuffer arranged as adjacent tiles in a pyramid.
         * WorldWind shapes use this class internally to draw on the terrain surface. Applications typically do not
         * interact with this class.
         */
        var FramebufferTileController = function () {

            /**
             * The width in pixels of framebuffers associated with this controller's tiles.
             * @type {Number}
             * @readonly
             */
            this.tileWidth = 256;

            /**
             * The height in pixels of framebuffers associated with this controller's tiles.
             * @type {Number}
             * @readonly
             */
            this.tileHeight = 256;

            /**
             * Controls the level of detail switching for this controller. The next highest resolution level is
             * used when an image's texel size is greater than this number of pixels.
             * @type {Number}
             * @default 1.75
             */
            this.detailControl = 1.75;

            // Internal. Intentionally not documented.
            this.levels = new LevelSet(Sector.FULL_SPHERE, new Location(45, 45), 16, this.tileWidth, this.tileHeight);

            // Internal. Intentionally not documented.
            this.topLevelTiles = [];

            // Internal. Intentionally not documented.
            this.currentTiles = [];

            // Internal. Intentionally not documented.
            this.currentTimestamp = null;

            // Internal. Intentionally not documented.
            this.currentGlobeStateKey = null;

            // Internal. Intentionally not documented.
            this.tileCache = new MemoryCache(500000, 400000);

            // Internal. Intentionally not documented.
            this.key = "FramebufferTileController " + ++FramebufferTileController.keyPool;
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.keyPool = 0; // source of unique ids

        /**
         * Returns a set of multi-resolution [FramebufferTile]{@link FramebufferTile} instances appropriate for the
         * current draw context that overlap a specified sector.
         * @param {DrawContext} dc The current draw context.
         * @param {Sector} sector The geographic region of interest.
         * @returns {Array} The set of multi-resolution framebuffer tiles that overlap the sector.
         * @throws {ArgumentError} If the specified sector is null.
         */
        FramebufferTileController.prototype.selectTiles = function (dc, sector) {
            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FramebufferTileController",
                    "selectTiles", "missingSector"));
            }

            // Assemble a set of global tiles appropriate for the draw context.
            this.assembleTiles(dc);

            // Collect the tiles that overlap the specified sector and mark them as selected.
            var tiles = [];
            for (var i = 0, len = this.currentTiles.length; i < len; i++) {
                var tile = this.currentTiles[i];
                if (tile.sector.overlaps(sector)) {
                    tile.selected = true;
                    tiles.push(tile);
                }
            }

            return tiles;
        };

        /**
         * Draws this multi-resolution framebuffer on the terrain surface then clears the framebuffer. This has no
         * effect if the framebuffer is unchanged since the last call to render.
         * @param {DrawContext} dc The current draw context.
         */
        FramebufferTileController.prototype.render = function (dc) {
            // Exit immediately if there are no framebuffer tiles. This can happen when there ar eno surface shapes in
            // the scene, for example.
            if (this.currentTiles.length == 0) {
                return;
            }

            // Collect the tiles that have changed since the last call to render.
            var tiles = [];
            for (var i = 0, len = this.currentTiles.length; i < len; i++) {
                var tile = this.currentTiles[i];
                if (tile.selected) {
                    tiles.push(tile);
                }
            }

            // Draw the changed tiles on the terrain surface.
            dc.surfaceTileRenderer.renderTiles(dc, tiles, 1);

            // Clear the changed tile's WebGL framebuffers.
            var gl = dc.currentGlContext,
                framebuffer = dc.currentFramebuffer;
            try {
                gl.clearColor(0, 0, 0, 0);
                for (i = 0, len = tiles.length; i < len; i++) {
                    tile = tiles[i];
                    tile.selected = false;
                    tile.bindFramebuffer(dc);
                    gl.clear(gl.COLOR_BUFFER_BIT);
                }
            } finally {
                dc.bindFramebuffer(framebuffer);
            }
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.assembleTiles = function (dc) {
            var timestamp = dc.timestamp,
                globeStateKey = dc.globeStateKey;

            if (this.currentTimestamp != timestamp ||
                this.currentGlobeStateKey != globeStateKey) {

                this.doAssembleTiles(dc);

                this.currentTimestamp = timestamp;
                this.currentGlobeStateKey = globeStateKey;
            }
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.doAssembleTiles = function (dc) {
            this.currentTiles = [];

            if (!dc.terrain) {
                return;
            }

            if (this.topLevelTiles.length == 0) {
                this.createTopLevelTiles();
            }

            for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
                var tile = this.topLevelTiles[i];
                tile.update(dc);

                if (this.isTileVisible(dc, tile)) {
                    this.addTileOrDescendants(dc, tile);
                }
            }
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.createTile = function (sector, level, row, column) {
            var tileKey = this.key + " " + level.levelNumber + "." + row + "." + column;
            return new FramebufferTile(sector, level, row, column, tileKey);
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.createTopLevelTiles = function () {
            Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles);
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.addTileOrDescendants = function (dc, tile) {
            if (this.tileMeetsRenderingCriteria(dc, tile)) {
                this.addTile(tile);
                return;
            }

            var subTiles = tile.subdivideToCache(tile.level.nextLevel(), this, this.tileCache);
            for (var i = 0, len = subTiles.length; i < len; i++) {
                var child = subTiles[i];
                child.update(dc);

                if (this.isTileVisible(dc, child)) {
                    this.addTileOrDescendants(dc, child);
                }
            }
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.addTile = function (tile) {
            this.currentTiles.push(tile);
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.isTileVisible = function (dc, tile) {
            if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
                return false;
            }

            if (dc.pickingMode) {
                return tile.extent.intersectsFrustum(dc.pickFrustum);
            }

            return tile.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
        };

        // Internal. Intentionally not documented.
        FramebufferTileController.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
            var s = this.detailControl;
            if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
                s *= 1.2;
            }

            return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
        };

        return FramebufferTileController;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ElevationImage
 */
define('globe/ElevationImage',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Logger,
              WWMath) {
        "use strict";

        /**
         * Constructs an elevation image.
         * @alias ElevationImage
         * @constructor
         * @classdesc Holds elevation values for an elevation tile.
         * This class is typically not used directly by applications.
         * @param {String} imagePath A string uniquely identifying this elevation image relative to other elevation
         * images.
         * @param {Sector} sector The sector spanned by this elevation image.
         * @param {Number} imageWidth The number of longitudinal sample points in this elevation image.
         * @param {Number} imageHeight The number of latitudinal sample points in this elevation image.
         * @throws {ArgumentError} If the specified image path is null, undefined or empty, or the specified
         * sector is null or undefined.
         */
        var ElevationImage = function (imagePath, sector, imageWidth, imageHeight) {
            if (!imagePath || (imagePath.length < 1)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "constructor",
                        "The specified image path is null, undefined or zero length."));
            }

            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "constructor", "missingSector"));
            }

            /**
             * The sector spanned by this elevation image.
             * @type {Sector}
             * @readonly
             */
            this.sector = sector;

            /**
             * A string uniquely identifying this elevation image.
             * @type {String}
             * @readonly
             */
            this.imagePath = imagePath;

            /**
             * The number of longitudinal sample points in this elevation image.
             * @type {Number}
             * @readonly
             */
            this.imageWidth = imageWidth;

            /**
             * The number of latitudinal sample points in this elevation image.
             * @type {Number}
             * @readonly
             */
            this.imageHeight = imageHeight;

            /**
             * The size in bytes of this elevation image.
             * @type {number}
             * @readonly
             */
            this.size = this.imageWidth * this.imageHeight;
        };

        /**
         * Returns the pixel value at a specified coordinate in this elevation image. The coordinate origin is the
         * image's lower left corner, so (0, 0) indicates the lower left pixel and (imageWidth-1, imageHeight-1)
         * indicates the upper right pixel. This returns 0 if the coordinate indicates a pixel outside of this elevation
         * image.
         * @param x The pixel's X coordinate.
         * @param y The pixel's Y coordinate.
         * @returns {Number} The pixel value at the specified coordinate in this elevation image.
         * Returns 0 if the coordinate indicates a pixel outside of this elevation image.
         */
        ElevationImage.prototype.pixel = function (x, y) {
            if (x < 0 || x >= this.imageWidth) {
                return 0;
            }

            if (y < 0 || y >= this.imageHeight) {
                return 0;
            }

            y = this.imageHeight - y - 1; // flip the y coordinate origin to the lower left corner
            return this.imageData[x + y * this.imageWidth];
        };

        /**
         * Returns the elevation at a specified geographic location.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @returns {Number} The elevation at the specified location.
         */
        ElevationImage.prototype.elevationAtLocation = function (latitude, longitude) {
            var maxLat = this.sector.maxLatitude,
                minLon = this.sector.minLongitude,
                deltaLat = this.sector.deltaLatitude(),
                deltaLon = this.sector.deltaLongitude(),
                x = (this.imageWidth - 1) * (longitude - minLon) / deltaLon,
                y = (this.imageHeight - 1) * (maxLat - latitude) / deltaLat,
                x0 = Math.floor(WWMath.clamp(x, 0, this.imageWidth - 1)),
                x1 = Math.floor(WWMath.clamp(x0 + 1, 0, this.imageWidth - 1)),
                y0 = Math.floor(WWMath.clamp(y, 0, this.imageHeight - 1)),
                y1 = Math.floor(WWMath.clamp(y0 + 1, 0, this.imageHeight - 1)),
                pixels = this.imageData,
                x0y0 = pixels[x0 + y0 * this.imageWidth],
                x1y0 = pixels[x1 + y0 * this.imageWidth],
                x0y1 = pixels[x0 + y1 * this.imageWidth],
                x1y1 = pixels[x1 + y1 * this.imageWidth],
                xf = x - x0,
                yf = y - y0;

            return (1 - xf) * (1 - yf) * x0y0 +
                xf * (1 - yf) * x1y0 +
                (1 - xf) * yf * x0y1 +
                xf * yf * x1y1;
        };

        /**
         * Returns elevations for a specified sector.
         * @param {Sector} sector The sector for which to return the elevations.
         * @param {Number} numLat The number of sample points in the longitudinal direction.
         * @param {Number} numLon The number of sample points in the latitudinal direction.
         * @param {Number[]} result An array in which to return the computed elevations.
         * @throws {ArgumentError} If either the specified sector or result argument is null or undefined, or if the
         * specified number of sample points in either direction is less than 1.
         */
        ElevationImage.prototype.elevationsForGrid = function (sector, numLat, numLon, result) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid", "missingSector"));
            }

            if (numLat < 1 || numLon < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid",
                        "The specified number of sample points is less than 1."));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationImage", "elevationsForGrid", "missingResult"));
            }

            var minLatSelf = this.sector.minLatitude,
                maxLatSelf = this.sector.maxLatitude,
                minLonSelf = this.sector.minLongitude,
                maxLonSelf = this.sector.maxLongitude,
                deltaLatSelf = maxLatSelf - minLatSelf,
                deltaLonSelf = maxLonSelf - minLonSelf,
                minLat = sector.minLatitude,
                maxLat = sector.maxLatitude,
                minLon = sector.minLongitude,
                maxLon = sector.maxLongitude,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                lat, lon,
                i, j, index = 0,
                pixels = this.imageData;

            for (j = 0, lat = minLat; j < numLat; j += 1, lat += deltaLat) {
                if (j === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                if (lat >= minLatSelf && lat <= maxLatSelf) {
                    // Image y-coordinate of the specified location, given an image origin in the top-left corner.
                    var y = (this.imageHeight - 1) * (maxLatSelf - lat) / deltaLatSelf,
                        y0 = Math.floor(WWMath.clamp(y, 0, this.imageHeight - 1)),
                        y1 = Math.floor(WWMath.clamp(y0 + 1, 0, this.imageHeight - 1)),
                        yf = y - y0;

                    for (i = 0, lon = minLon; i < numLon; i += 1, lon += deltaLon) {
                        if (i === numLon - 1) {
                            lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                        }

                        if (lon >= minLonSelf && lon <= maxLonSelf) {
                            // Image x-coordinate of the specified location, given an image origin in the top-left corner.
                            var x = (this.imageWidth - 1) * (lon - minLonSelf) / deltaLonSelf,
                                x0 = Math.floor(WWMath.clamp(x, 0, this.imageWidth - 1)),
                                x1 = Math.floor(WWMath.clamp(x0 + 1, 0, this.imageWidth - 1)),
                                xf = x - x0;

                            var x0y0 = pixels[x0 + y0 * this.imageWidth],
                                x1y0 = pixels[x1 + y0 * this.imageWidth],
                                x0y1 = pixels[x0 + y1 * this.imageWidth],
                                x1y1 = pixels[x1 + y1 * this.imageWidth];

                            result[index] = (1 - xf) * (1 - yf) * x0y0 +
                            xf * (1 - yf) * x1y0 +
                            (1 - xf) * yf * x0y1 +
                            xf * yf * x1y1;
                        }

                        index++;
                    }
                } else {
                    index += numLon; // skip this row
                }
            }
        };

        /**
         * Returns the minimum and maximum elevations within a specified sector.
         * @param {Sector} sector The sector of interest. If null or undefined, the minimum and maximum elevations
         * for the sector associated with this tile are returned.
         * @returns {Number[]} An array containing the minimum and maximum elevations within the specified sector,
         * or null if the specified sector does not include this elevation image's coverage sector.
         */
        ElevationImage.prototype.minAndMaxElevationsForSector = function (sector) {
            var result = [];

            if (!sector) { // the sector is this sector
                result[0] = this.minElevation;
                result[1] = this.maxElevation;
            } else if (sector.contains(this.sector)) { // The specified sector completely contains this image; return the image min and max.
                if (result[0] > this.minElevation) {
                    result[0] = this.minElevation;
                }

                if (result[1] < this.maxElevation) {
                    result[1] = this.maxElevation;
                }
            } else { // The specified sector intersects a portion of this image; compute the min and max from intersecting pixels.
                var maxLatSelf = this.sector.maxLatitude,
                    minLonSelf = this.sector.minLongitude,
                    deltaLatSelf = this.sector.deltaLatitude(),
                    deltaLonSelf = this.sector.deltaLongitude(),
                    minLatOther = sector.minLatitude,
                    maxLatOther = sector.maxLatitude,
                    minLonOther = sector.minLongitude,
                    maxLonOther = sector.maxLongitude;

                // Image coordinates of the specified sector, given an image origin in the top-left corner. We take the floor and
                // ceiling of the min and max coordinates, respectively, in order to capture all pixels that would contribute to
                // elevations computed for the specified sector in a call to elevationsForSector.
                var minY = Math.floor((this.imageHeight - 1) * (maxLatSelf - maxLatOther) / deltaLatSelf),
                    maxY = Math.ceil((this.imageHeight - 1) * (maxLatSelf - minLatOther) / deltaLatSelf),
                    minX = Math.floor((this.imageWidth - 1) * (minLonOther - minLonSelf) / deltaLonSelf),
                    maxX = Math.ceil((this.imageWidth - 1) * (maxLonOther - minLonSelf) / deltaLonSelf);

                minY = WWMath.clamp(minY, 0, this.imageHeight - 1);
                maxY = WWMath.clamp(maxY, 0, this.imageHeight - 1);
                minX = WWMath.clamp(minX, 0, this.imageWidth - 1);
                maxX = WWMath.clamp(maxX, 0, this.imageWidth - 1);

                var pixels = this.imageData,
                    min = Number.MAX_VALUE,
                    max = -min;

                for (var y = minY; y <= maxY; y++) {
                    for (var x = minX; x <= maxX; x++) {
                        var p = pixels[Math.floor(x + y * this.imageWidth)];
                        if (min > p) {
                            min = p;
                        }

                        if (max < p) {
                            max = p;
                        }
                    }
                }

                if (result[0] > min) {
                    result[0] = min;
                }

                if (result[1] < max) {
                    result[1] = max;
                }
            }

            return result;
        };

        /**
         * Determines the minimum and maximum elevations within this elevation image and stores those values within
         * this object. See [minAndMaxElevationsForSector]{@link ElevationImage#minAndMaxElevationsForSector}
         */
        ElevationImage.prototype.findMinAndMaxElevation = function () {
            if (this.imageData && (this.imageData.length > 0)) {
                this.minElevation = Number.MAX_VALUE;
                this.maxElevation = -this.minElevation;

                var pixels = this.imageData,
                    pixelCount = this.imageWidth * this.imageHeight;

                for (var i = 0; i < pixelCount; i++) {
                    var p = pixels[i];

                    if (this.minElevation > p) {
                        this.minElevation = p;
                    }

                    if (this.maxElevation < p) {
                        this.maxElevation = p;
                    }
                }
            } else {
                this.minElevation = 0;
                this.maxElevation = 0;
            }
        };

        return ElevationImage;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ElevationTile
 */
define('globe/ElevationTile',[
        '../error/ArgumentError',
        '../util/Logger',
        '../util/Tile'
    ],
    function (ArgumentError,
              Logger,
              Tile) {
        "use strict";

        /**
         * Constructs an elevation tile.
         * @alias ElevationTile
         * @constructor
         * @augments Tile
         * @classdesc Represents a region of elevations. Applications typically do not interact directly with this class.
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {Number} row This tile's row in the associated level.
         * @param {Number} column This tile's column in the associated level.
         * @param {String} imagePath The full path to the image.
         * @param {MemoryCache} cache The cache to use for caching this elevation tile.
         * @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
         * are less than zero, or the specified image path is null, undefined or empty.
         *
         */
        var ElevationTile = function (sector, level, row, column, imagePath, cache) {
            if (!imagePath || (imagePath.length < 1)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationTile", "constructor",
                        "The specified image path is null, undefined or zero length."));
            }

            if (!cache) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationTile", "constructor",
                        "The specified cache is null or undefined."));
            }

            Tile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            /**
             * This tile's image path.
             * @type {String}
             */
            this.imagePath = imagePath;

            this.memoryCache = cache;
        };

        ElevationTile.prototype = Object.create(Tile.prototype);

        /**
         * Returns the size of the this tile in bytes.
         * @returns {Number} The size of this tile in bytes, not including the associated elevations image size.
         */
        ElevationTile.prototype.size = function () {
            return Tile.prototype.size.call(this) + this.imagePath.length + 8;
        };

        /**
         * Returns the {@link ElevationImage} associated with this tile.
         * @returns {ElevationImage} The elevation image associated with this tile, or null if that image is
         * currently not in the elevation image cache.
         */
        ElevationTile.prototype.image = function () {
            return this.memoryCache.entryForKey(this.imagePath);
        };

        return ElevationTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ElevationModel
 */
define('globe/ElevationModel',[
        '../util/AbsentResourceList',
        '../geom/Angle',
        '../error/ArgumentError',
        '../globe/ElevationImage',
        '../globe/ElevationTile',
        '../util/LevelSet',
        '../util/Logger',
        '../cache/MemoryCache',
        '../geom/Sector',
        '../util/Tile',
        '../util/WWMath'],
    function (AbsentResourceList,
              Angle,
              ArgumentError,
              ElevationImage,
              ElevationTile,
              LevelSet,
              Logger,
              MemoryCache,
              Sector,
              Tile,
              WWMath) {
        "use strict";

        /**
         * Constructs an elevation model.
         * @alias ElevationModel
         * @constructor
         * @classdesc Represents the elevations for an area, often but not necessarily the whole globe.
         * <p>
         *     While this class can be used as-is, it is intended to be a base class for more concrete elevation
         *     models, such as {@link EarthElevationModel}.
         * @param {Sector} coverageSector The sector this elevation model spans.
         * @param {Location} levelZeroDelta The size of top-level tiles, in degrees.
         * @param {Number} numLevels The number of levels used to represent this elevation model's resolution pyramid.
         * @param {String} retrievalImageFormat The mime type of the elevation data retrieved by this elevation model.
         * @param {String} cachePath A string unique to this elevation model relative to other elevation models used by
         * the application.
         * @param {Number} tileWidth The number of intervals (cells) in the longitudinal direction of this elevation
         * model's elevation tiles.
         * @param {Number} tileHeight The number of intervals (cells) in the latitudinal direction of this elevation
         * model's elevation tiles.
         * @throws {ArgumentError} If any argument is null or undefined, if the number of levels specified is less
         * than one, or if either the tile width or tile height are less than one.
         */
        var ElevationModel = function (coverageSector, levelZeroDelta, numLevels, retrievalImageFormat, cachePath,
                                       tileWidth, tileHeight) {
            if (!coverageSector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor", "missingSector"));
            }

            if (!levelZeroDelta) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified level-zero delta is null or undefined."));
            }

            if (!retrievalImageFormat) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified image format is null or undefined."));
            }

            if (!cachePath) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified cache path is null or undefined."));
            }

            if (!numLevels || numLevels < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified number of levels is not greater than zero."));
            }

            if (!tileWidth || !tileHeight || tileWidth < 1 || tileHeight < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified tile width or height is not greater than zero."));
            }

            /**
             * The sector this elevation model spans.
             * @type {Sector}
             * @readonly
             */
            this.coverageSector = coverageSector;

            /**
             * The mime type to use when retrieving elevations.
             * @type {String}
             * @readonly
             */
            this.retrievalImageFormat = retrievalImageFormat;

            /** A unique string identifying this elevation model relative to other elevation models in use.
             * @type {String}
             * @readonly
             */
            this.cachePath = cachePath;

            /**
             * Indicates this elevation model's display name.
             * @type {String}
             * @default "Elevations"
             */
            this.displayName = "Elevations";

            /**
             * Indicates the last time this elevation model changed, in milliseconds since midnight Jan 1, 1970.
             * @type {Number}
             * @readonly
             * @default Date.now() at construction
             */
            this.timestamp = Date.now();

            /**
             * This elevation model's minimum elevation in meters.
             * @type {Number}
             * @default 0
             */
            this.minElevation = 0;

            /**
             * This elevation model's maximum elevation in meters.
             * @type {Number}
             */
            this.maxElevation = 0;

            /**
             * Indicates whether the data associated with this elevation model is point data. A value of false
             * indicates that the data is area data (pixel is area).
             * @type {Boolean}
             * @default true
             */
            this.pixelIsPoint = true;

            /**
             * The {@link LevelSet} created during construction of this elevation model.
             * @type {LevelSet}
             * @readonly
             */
            this.levels = new LevelSet(this.coverageSector, levelZeroDelta, numLevels, tileWidth, tileHeight);

            // These are internal and intentionally not documented.
            this.currentTiles = []; // holds assembled tiles
            this.currentSector = new Sector(0, 0, 0, 0); // a scratch variable
            this.tileCache = new MemoryCache(1000000, 800000); // for elevation tiles
            this.imageCache = new MemoryCache(10000000, 8000000); // for the elevations, themselves
            this.currentRetrievals = []; // Identifies elevation retrievals in progress
            this.absentResourceList = new AbsentResourceList(3, 5e3);
            this.id = ++ElevationModel.idPool;

            /**
             * A string identifying this elevation model's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property. It is primarily used by shapes and terrain generators.
             * @memberof ElevationModel.prototype
             * @readonly
             * @type {String}
             */
            this.stateKey = "elevationModel " + this.id.toString() + " ";
        };

        ElevationModel.idPool = 0; // Used to assign unique IDs to elevation models for use in their state key.

        /**
         * Returns the minimum and maximum elevations within a specified sector.
         * @param {Sector} sector The sector for which to determine extreme elevations.
         * @returns {Number[]} An array containing the minimum and maximum elevations within the specified sector,
         * or null if the specified sector is outside this elevation model's coverage area.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        ElevationModel.prototype.minAndMaxElevationsForSector = function (sector) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "minAndMaxElevationsForSector", "missingSector"));
            }

            var level = this.levels.levelForTexelSize(sector.deltaLatitude() * Angle.DEGREES_TO_RADIANS / 64);
            this.assembleTiles(level, sector, false);

            if (this.currentTiles.length == 0) {
                return null; // Sector is outside the elevation model's coverage area. Do not modify the result array.
            }

            // Assign the output extreme elevations to the largest and smallest double values, respectively. This has the effect
            // of expanding the extremes with each subsequent tile as needed. If we initialized this array with zeros then the
            // output extreme elevations would always contain zero, even when the range of the image's extreme elevations in the
            // sector does not contain zero.
            var min = Number.MAX_VALUE,
                max = -min,
                image,
                imageMin,
                imageMax,
                result = [];

            for (var i = 0, len = this.currentTiles.length; i < len; i++) {
                image = this.currentTiles[i].image();
                if (image) {
                    imageMin = image.minElevation;
                    if (min > imageMin) {
                        min = imageMin;
                    }

                    imageMax = image.maxElevation;
                    if (max < imageMax) {
                        max = imageMax;
                    }
                } else {
                    result[0] = this.minElevation;
                    result[1] = this.maxElevation;
                    return result; // At least one tile image is not in memory; return the model's extreme elevations.
                }
            }

            result[0] = min;
            result[1] = max;

            return result;
        };

        /**
         * Returns the elevation at a specified location.
         * @param {Number} latitude The location's latitude in degrees.
         * @param {Number} longitude The location's longitude in degrees.
         * @returns {Number} The elevation at the specified location, in meters. Returns zero if the location is
         * outside the coverage area of this elevation model.
         */
        ElevationModel.prototype.elevationAtLocation = function (latitude, longitude) {
            if (!this.coverageSector.containsLocation(latitude, longitude)) {
                return 0; // location is outside the elevation model's coverage
            }

            return this.pointElevationForLocation(latitude, longitude);
        };

        /**
         * Returns the elevations at locations within a specified sector.
         * @param {Sector} sector The sector for which to determine the elevations.
         * @param {Number} numLat The number of latitudinal sample locations within the sector.
         * @param {Number} numLon The number of longitudinal sample locations within the sector.
         * @param {Number} targetResolution The desired elevation resolution, in radians. (To compute radians from
         * meters, divide the number of meters by the globe's radius.)
         * @param {Number[]} result An array in which to return the requested elevations.
         * @returns {Number} The resolution actually achieved, which may be greater than that requested if the
         * elevation data for the requested resolution is not currently available.
         * @throws {ArgumentError} If the specified sector or result array is null or undefined, or if either of the
         * specified numLat or numLon values is less than one.
         */
        ElevationModel.prototype.elevationsForGrid = function (sector, numLat, numLon, targetResolution, result) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForSector", "missingSector"));
            }

            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "elevationsForSector", "missingResult"));
            }

            if (!numLat || !numLon || numLat < 1 || numLon < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ElevationModel", "constructor",
                        "The specified number of latitudinal or longitudinal positions is less than one."));
            }

            var level = this.levels.levelForTexelSize(targetResolution);
            if (this.pixelIsPoint) {
                return this.pointElevationsForGrid(sector, numLat, numLon, level, result);
            } else {
                return this.areaElevationsForGrid(sector, numLat, numLon, level, result);
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.pointElevationForLocation = function (latitude, longitude) {
            var level = this.levels.lastLevel(),
                deltaLat = level.tileDelta.latitude,
                deltaLon = level.tileDelta.longitude,
                r = Tile.computeRow(deltaLat, latitude),
                c = Tile.computeColumn(deltaLon, longitude),
                tile,
                image = null;

            for (var i = level.levelNumber; i >= 0; i--) {
                tile = this.tileCache.entryForKey(i + "." + r + "." + c);
                if (tile) {
                    image = tile.image();
                    if (image) {
                        return image.elevationAtLocation(latitude, longitude);
                    }
                }

                r = Math.floor(r / 2);
                c = Math.floor(c / 2);
            }

            return 0; // did not find a tile with an image
        };

        // Intentionally not documented.
        ElevationModel.prototype.pointElevationsForGrid = function (sector, numLat, numLon, level, result) {
            var maxResolution = 0,
                resolution;

            this.assembleTiles(level, sector, true);
            if (this.currentTiles.length === 0) {
                return 0; // Sector is outside the elevation model's coverage area. Do not modify the results array.
            }

            // Sort from lowest resolution to highest so that higher resolutions override lower resolutions in the
            // loop below.
            this.currentTiles.sort(function (tileA, tileB) {
                return tileA.level.levelNumber - tileB.level.levelNumber;
            });

            for (var i = 0, len = this.currentTiles.length; i < len; i++) {
                var tile = this.currentTiles[i],
                    image = tile.image();

                if (image) {
                    image.elevationsForGrid(sector, numLat, numLon, result);
                    resolution = tile.level.texelSize;

                    if (maxResolution < resolution) {
                        maxResolution = resolution;
                    }
                } else {
                    maxResolution = Number.MAX_VALUE;
                }
            }

            return maxResolution;
        };

        // Internal. Returns elevations for a grid assuming pixel-is-area.
        ElevationModel.prototype.areaElevationsForGrid = function (sector, numLat, numLon, level, result) {
            var minLat = sector.minLatitude,
                maxLat = sector.maxLatitude,
                minLon = sector.minLongitude,
                maxLon = sector.maxLongitude,
                deltaLat = sector.deltaLatitude() / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = sector.deltaLongitude() / (numLon > 1 ? numLon - 1 : 1),
                lat, lon, s, t,
                latIndex, lonIndex, resultIndex = 0;

            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex += 1, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude ensure alignment
                }

                for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex += 1, lon += deltaLon) {
                    if (lonIndex === numLon - 1) {
                        lon = maxLon; // explicitly set the last lon to the max longitude ensure alignment
                    }

                    if (this.coverageSector.containsLocation(lat, lon)) { // ignore locations outside of the model
                        s = (lon + 180) / 360;
                        t = (lat + 90) / 180;
                        this.areaElevationForCoord(s, t, level.levelNumber, result, resultIndex);
                    }

                    resultIndex++;
                }
            }

            return level.texelSize; // TODO: return the actual achieved
        };

        // Internal. Returns an elevation for a location assuming pixel-is-area.
        ElevationModel.prototype.areaElevationForCoord = function (s, t, levelNumber, result, resultIndex) {
            var level, levelWidth, levelHeight,
                tMin, tMax,
                vMin, vMax,
                u, v,
                x0, x1, y0, y1,
                xf, yf,
                retrieveTiles,
                pixels = new Float64Array(4);

            for (var i = levelNumber; i >= 0; i--) {
                level = this.levels.level(i);
                levelWidth = Math.round(level.tileWidth * 360 / level.tileDelta.longitude);
                levelHeight = Math.round(level.tileHeight * 180 / level.tileDelta.latitude);
                tMin = 1 / (2 * levelHeight);
                tMax = 1 - tMin;
                vMin = 0;
                vMax = levelHeight - 1;
                u = levelWidth * WWMath.fract(s); // wrap the horizontal coordinate
                v = levelHeight * WWMath.clamp(t, tMin, tMax); // clamp the vertical coordinate to the level edge
                x0 = WWMath.mod(Math.floor(u - 0.5), levelWidth);
                x1 = WWMath.mod((x0 + 1), levelWidth);
                y0 = WWMath.clamp(Math.floor(v - 0.5), vMin, vMax);
                y1 = WWMath.clamp(y0 + 1, vMin, vMax);
                xf = WWMath.fract(u - 0.5);
                yf = WWMath.fract(v - 0.5);
                retrieveTiles = (i == levelNumber) || (i == 0);

                if (this.lookupPixels(x0, x1, y0, y1, level, retrieveTiles, pixels)) {
                    result[resultIndex] = (1 - xf) * (1 - yf) * pixels[0] +
                        xf * (1 - yf) * pixels[1] +
                        (1 - xf) * yf * pixels[2] +
                        xf * yf * pixels[3];
                    return;
                }
            }
        };

        // Internal. Bilinearly interpolates tile-image elevations.
        ElevationModel.prototype.lookupPixels = function (x0, x1, y0, y1, level, retrieveTiles, result) {
            var levelNumber = level.levelNumber,
                tileWidth = level.tileWidth,
                tileHeight = level.tileHeight,
                row0 = Math.floor(y0 / tileHeight),
                row1 = Math.floor(y1 / tileHeight),
                col0 = Math.floor(x0 / tileWidth),
                col1 = Math.floor(x1 / tileWidth),
                r0c0, r0c1, r1c0, r1c1;

            if (row0 == row1 && row0 == this.cachedRow && col0 == col1 && col0 == this.cachedCol) {
                r0c0 = r0c1 = r1c0 = r1c1 = this.cachedImage; // use results from previous lookup
            } else if (row0 == row1 && col0 == col1) {
                r0c0 = this.lookupImage(levelNumber, row0, col0, retrieveTiles); // only need to lookup one image
                r0c1 = r1c0 = r1c1 = r0c0; // re-use the single image
                this.cachedRow = row0;
                this.cachedCol = col0;
                this.cachedImage = r0c0; // note the results for subsequent lookups
            } else {
                r0c0 = this.lookupImage(levelNumber, row0, col0, retrieveTiles);
                r0c1 = this.lookupImage(levelNumber, row0, col1, retrieveTiles);
                r1c0 = this.lookupImage(levelNumber, row1, col0, retrieveTiles);
                r1c1 = this.lookupImage(levelNumber, row1, col1, retrieveTiles);
            }

            if (r0c0 && r0c1 && r1c0 && r1c1) {
                result[0] = r0c0.pixel(x0 % tileWidth, y0 % tileHeight);
                result[1] = r0c1.pixel(x1 % tileWidth, y0 % tileHeight);
                result[2] = r1c0.pixel(x0 % tileWidth, y1 % tileHeight);
                result[3] = r1c1.pixel(x1 % tileWidth, y1 % tileHeight);
                return true;
            }

            return false;
        };

        // Internal. Intentionally not documented.
        ElevationModel.prototype.lookupImage = function (levelNumber, row, column, retrieveTiles) {
            var tile = this.tileForLevel(levelNumber, row, column),
                image = tile.image();

            // If the tile's elevations have expired, cause it to be re-retrieved. Note that the current,
            // expired elevations are still used until the updated ones arrive.
            if (image == null && retrieveTiles) {
                this.retrieveTileImage(tile);
            }

            return image;
        };

        // Intentionally not documented.
        ElevationModel.prototype.createTile = function (sector, level, row, column) {
            var imagePath = this.cachePath + "/" + level.levelNumber + "/" + row + "/" + row + "_" + column + ".bil";

            return new ElevationTile(sector, level, row, column, imagePath, this.imageCache);
        };

        // Intentionally not documented.
        ElevationModel.prototype.assembleTiles = function (level, sector, retrieveTiles) {
            this.currentTiles = [];

            // Intersect the requested sector with the elevation model's coverage area. This avoids attempting to assemble tiles
            // that are outside the coverage area.
            this.currentSector.copy(sector);
            this.currentSector.intersection(this.coverageSector);

            if (this.currentSector.isEmpty())
                return; // sector is outside the elevation model's coverage area

            var deltaLat = level.tileDelta.latitude,
                deltaLon = level.tileDelta.longitude,
                firstRow = Tile.computeRow(deltaLat, this.currentSector.minLatitude),
                lastRow = Tile.computeLastRow(deltaLat, this.currentSector.maxLatitude),
                firstCol = Tile.computeColumn(deltaLon, this.currentSector.minLongitude),
                lastCol = Tile.computeLastColumn(deltaLon, this.currentSector.maxLongitude);

            for (var row = firstRow; row <= lastRow; row++) {
                for (var col = firstCol; col <= lastCol; col++) {
                    this.addTileOrAncestor(level, row, col, retrieveTiles);
                }
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.addTileOrAncestor = function (level, row, column, retrieveTiles) {
            var tile = this.tileForLevel(level.levelNumber, row, column);

            if (this.isTileImageInMemory(tile)) {
                this.addToCurrentTiles(tile);
            } else {
                if (retrieveTiles) {
                    this.retrieveTileImage(tile);
                }

                if (level.isFirstLevel()) {
                    this.currentTiles.push(tile); // no ancestor tile to add
                } else {
                    this.addAncestor(level, row, column, retrieveTiles);
                }
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.addAncestor = function (level, row, column, retrieveTiles) {
            var tile = null,
                r = Math.floor(row / 2),
                c = Math.floor(column / 2);

            for (var i = level.levelNumber - 1; i >= 0; i--) {
                tile = this.tileForLevel(i, r, c);
                if (this.isTileImageInMemory(tile)) {
                    this.addToCurrentTiles(tile);
                    return;
                }

                r = Math.floor(r / 2);
                c = Math.floor(c / 2);
            }

            // No ancestor tiles have an in-memory image. Retrieve the ancestor tile corresponding for the first level, and
            // add it. We add the necessary tiles to provide coverage over the requested sector in order to accurately return
            // whether or not this elevation model has data for the entire sector.
            this.addToCurrentTiles(tile);

            if (retrieveTiles) {
                this.retrieveTileImage(tile);
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.addToCurrentTiles = function (tile) {
            this.currentTiles.push(tile);
        };

        // Intentionally not documented.
        ElevationModel.prototype.tileForLevel = function (levelNumber, row, column) {
            var tileKey = levelNumber + "." + row + "." + column,
                tile = this.tileCache.entryForKey(tileKey);

            if (tile) {
                return tile;
            }

            var level = this.levels.level(levelNumber),
                sector = Tile.computeSector(level, row, column);

            tile = this.createTile(sector, level, row, column);
            this.tileCache.putEntry(tileKey, tile, tile.size());

            return tile;
        };

        // Intentionally not documented.
        ElevationModel.prototype.isTileImageInMemory = function (tile) {
            return this.imageCache.containsKey(tile.imagePath);
        };

        // Intentionally not documented.
        ElevationModel.prototype.resourceUrlForTile = function (tile) {
            return this.urlBuilder.urlForTile(tile, this.retrievalImageFormat);
        };

        // Intentionally not documented.
        ElevationModel.prototype.retrieveTileImage = function (tile) {
            if (this.currentRetrievals.indexOf(tile.imagePath) < 0) {
                var url = this.resourceUrlForTile(tile, this.retrievalImageFormat),
                    xhr = new XMLHttpRequest(),
                    elevationModel = this;

                if (!url)
                    return;

                xhr.open("GET", url, true);
                xhr.responseType = 'arraybuffer';
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        elevationModel.removeFromCurrentRetrievals(tile.imagePath);

                        var contentType = xhr.getResponseHeader("content-type");

                        if (xhr.status === 200) {
                            if (contentType === elevationModel.retrievalImageFormat
                                || contentType === "text/plain"
                                || contentType === "application/octet-stream") {
                                Logger.log(Logger.LEVEL_INFO, "Elevations retrieval succeeded: " + url);
                                elevationModel.loadElevationImage(tile, xhr);
                                elevationModel.absentResourceList.unmarkResourceAbsent(tile.imagePath);

                                // Send an event to request a redraw.
                                var e = document.createEvent('Event');
                                e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                                window.dispatchEvent(e);
                            } else if (contentType === "text/xml") {
                                elevationModel.absentResourceList.markResourceAbsent(tile.imagePath);
                                Logger.log(Logger.LEVEL_WARNING,
                                    "Elevations retrieval failed (" + xhr.statusText + "): " + url + ".\n "
                                    + String.fromCharCode.apply(null, new Uint8Array(xhr.response)));
                            } else {
                                elevationModel.absentResourceList.markResourceAbsent(tile.imagePath);
                                Logger.log(Logger.LEVEL_WARNING,
                                    "Elevations retrieval failed: " + url + ". " + "Unexpected content type "
                                    + contentType);
                            }
                        } else {
                            elevationModel.absentResourceList.markResourceAbsent(tile.imagePath);
                            Logger.log(Logger.LEVEL_WARNING,
                                "Elevations retrieval failed (" + xhr.statusText + "): " + url);
                        }
                    }
                };

                xhr.onerror = function () {
                    elevationModel.removeFromCurrentRetrievals(tile.imagePath);
                    elevationModel.absentResourceList.markResourceAbsent(tile.imagePath);
                    Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval failed: " + url);
                };

                xhr.ontimeout = function () {
                    elevationModel.removeFromCurrentRetrievals(tile.imagePath);
                    elevationModel.absentResourceList.markResourceAbsent(tile.imagePath);
                    Logger.log(Logger.LEVEL_WARNING, "Elevations retrieval timed out: " + url);
                };

                xhr.send(null);

                this.currentRetrievals.push(tile.imagePath);
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.removeFromCurrentRetrievals = function (imagePath) {
            var index = this.currentRetrievals.indexOf(imagePath);
            if (index > -1) {
                this.currentRetrievals.splice(index, 1);
            }
        };

        // Intentionally not documented.
        ElevationModel.prototype.loadElevationImage = function (tile, xhr) {
            var elevationImage = new ElevationImage(tile.imagePath, tile.sector, tile.tileWidth, tile.tileHeight);

            if (this.retrievalImageFormat == "application/bil16") {
                elevationImage.imageData = new Int16Array(xhr.response);
                elevationImage.size = elevationImage.imageData.length * 2;
            } else if (this.retrievalImageFormat == "application/bil32") {
                elevationImage.imageData = new Float32Array(xhr.response);
                elevationImage.size = elevationImage.imageData.length * 4;
            }

            if (elevationImage.imageData) {
                elevationImage.findMinAndMaxElevation();
                this.imageCache.putEntry(tile.imagePath, elevationImage, elevationImage.size);
                this.timestamp = Date.now();
            }
        };

        return ElevationModel;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeographicProjection
 */
define('projections/GeographicProjection',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Sector',
        '../error/UnsupportedOperationError'
    ],
    function (ArgumentError,
              Logger,
              Sector,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Constructs a base geographic projection.
         * @alias GeographicProjection
         * @constructor
         * @classdesc Represents a geographic projection.
         * This is an abstract class and is meant to be instantiated only by subclasses.
         * See the following projections:
         * <ul>
         *     <li>{@link ProjectionEquirectangular}</li>
         *     <li>{@link ProjectionMercator}</li>
         *     <li>{@link ProjectionPolarEquidistant}</li>
         *     <li>{@link ProjectionUPS}</li>
         </ul>
         * @param {String} displayName The projection's display name.
         * @param {boolean} continuous Indicates whether this projection is continuous.
         * @param {Sector} projectionLimits This projection's projection limits. May be null to indicate the full
         * range of latitude and longitude, +/- 90 degrees latitude, +/- 180 degrees longitude.
         */
        var GeographicProjection = function (displayName, continuous, projectionLimits) {

            /**
             * This projection's display name.
             * @type {string}
             */
            this.displayName = displayName || "Geographic Projection";

            /**
             * Indicates whether this projection should be treated as continuous with itself. If true, the 2D map
             * will appear to scroll continuously horizontally.
             * @type {boolean}
             * @readonly
             */
            this.continuous = continuous;

            /**
             * Indicates the geographic limits of this projection.
             * @type {Sector}
             * @readonly
             */
            this.projectionLimits = projectionLimits;

            /**
             * Indicates whether this projection is a 2D projection.
             * @type {boolean}
             * @readonly
             */
            this.is2D = true;
        };

        /**
         * Converts a geographic position to Cartesian coordinates.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} latitude The latitude of the position, in degrees.
         * @param {number} longitude The longitude of the position, in degrees.
         * @param {number} elevation The elevation of the position, in meters.
         * @param {Vec3} offset An offset to apply to the Cartesian output. Typically only projections that are
         * continuous (see [continuous]{@link GeographicProjection#continuous}) apply to this offset. Others ignore
         * it. May be null to indicate no offset is applied.
         * @param {Vec3} result A variable in which to store the computed Cartesian point.
         *
         * @returns {Vec3} The specified result argument containing the computed point.
         * @throws {ArgumentError} If the specified globe or result is null or undefined.
         */
        GeographicProjection.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                         offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesian", "abstractInvocation"));
        };

        /**
         * Computes a grid of Cartesian points within a specified sector and relative to a specified Cartesian
         * reference point.
         * <p>
         * This method is used to compute a collection of points within a sector. It is used by tessellators to
         * efficiently generate a tile's interior points. The number of points to generate is indicated by the tileWidth
         * and tileHeight parameters but is one more in each direction. Width refers to the longitudinal direction,
         * height to the latitudinal.
         * <p>
         * For each implied position within the sector, an elevation value is specified via an array of elevations. The
         * calculation at each position incorporates the associated elevation.
         * There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
         *
         * @param {Globe} globe The globe this projection applies to.
         * @param {Sector} sector The sector in which to compute the points.
         * @param {Number} numLat The number of latitudinal sections a tile is divided into.
         * @param {Number} numLon The number of longitudinal sections a tile is divided into.
         * @param {Number[]} elevations An array of elevations to incorporate in the point calculations. There must be
         * one elevation value in the array for each generated point. Elevations are in meters.
         * There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
         * @param {Vec3} referencePoint The X, Y and Z Cartesian coordinates to subtract from the computed coordinates.
         * This makes the computed coordinates relative to the specified point. May be null.
         * @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Float32Array} result A typed array to hold the computed coordinates. It must be at least of
         * size (tileWidth + 1) x (tileHeight + 1) * 3.
         * The points are returned in row major order, beginning with the row of minimum latitude.
         * @returns {Float32Array} The specified result argument, populated with the computed Cartesian coordinates.
         * @throws {ArgumentError} if any of the specified globe, sector, elevations array or results arrays is null or
         * undefined.
         */
        GeographicProjection.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
                                                                             referencePoint, offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesianGrid", "abstractInvocation"));
        };

        /**
         * Converts a Cartesian point to a geographic position.
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Position} result A variable in which to return the computed position.
         *
         * @returns {Position} The specified result argument containing the computed position.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "cartesianToGeographic", "abstractInvocation"));
        };

        /**
         * Computes a Cartesian vector that points north and is tangent to the meridian at a specified geographic
         * location.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} latitude The latitude of the location, in degrees.
         * @param {number} longitude The longitude of the location, in degrees.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "northTangentAtLocation", "missingResult"));
            }

            result[0] = 0;
            result[1] = 1;
            result[2] = 0;

            return result;
        };

        /**
         * Computes a Cartesian vector that points north and is tangent to the meridian at a specified Cartesian
         * point.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} offset An offset to apply to the Cartesian point. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "northTangentAtPoint", "missingResult"));
            }

            result[0] = 0;
            result[1] = 1;
            result[2] = 0;

            return result;
        };

        /**
         * Computes the Cartesian surface normal vector at a specified Cartesian point.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "surfaceNormalAtPoint",
                    "missingResult"));
            }

            result[0] = 0;
            result[1] = 0;
            result[2] = 1;

            return result;
        };

        return GeographicProjection;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionWgs84
 */
define('projections/ProjectionWgs84',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger',
        '../geom/Position',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger,
              Position,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a WGS84 ellipsoid
         * @alias ProjectionWgs84
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents a WGS84 ellipsoid.
         */
        var ProjectionWgs84 = function () {

            GeographicProjection.call(this, "WGS84", false, null);

            this.is2D = false;

            this.scratchPosition = new Position(0, 0, 0);
        };

        ProjectionWgs84.prototype = Object.create(GeographicProjection.prototype);

        Object.defineProperties(ProjectionWgs84.prototype, {
            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionEquirectangular.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return "projection wgs84 ";
                }
            }
        });

        // Documented in base class.
        ProjectionWgs84.prototype.geographicToCartesian = function (globe, latitude, longitude, altitude, offset,
                                                                    result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
                    "geographicToCartesian", "missingGlobe"));
            }

            var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
                sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
                cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
                sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS),
                rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);

            result[0] = (rpm + altitude) * cosLat * sinLon;
            result[1] = (rpm * (1.0 - globe.eccentricitySquared) + altitude) * sinLat;
            result[2] = (rpm + altitude) * cosLat * cosLon;

            return result;
        };

        // Documented in base class.
        ProjectionWgs84.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
                                                                        referencePoint, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            var minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                lat, lon, rpm, elev,
                cosLat, sinLat,
                cosLon = new Float64Array(numLon), sinLon = new Float64Array(numLon);

            // Compute and save values that are a function of each unique longitude value in the specified sector. This
            // eliminates the need to re-compute these values for each column of constant longitude.
            for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                if (lonIndex === numLon - 1) {
                    lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                }

                cosLon[lonIndex] = Math.cos(lon);
                sinLon[lonIndex] = Math.sin(lon);
            }

            // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian
            // point corresponding to each latitude and longitude.
            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max longitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                cosLat = Math.cos(lat);
                sinLat = Math.sin(lat);
                rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);

                for (lonIndex = 0; lonIndex < numLon; lonIndex++) {
                    elev = elevations[elevIndex++];
                    result[resultIndex++] = (rpm + elev) * cosLat * sinLon[lonIndex] - refCenter[0];
                    result[resultIndex++] = (rpm * (1.0 - globe.eccentricitySquared) + elev) * sinLat - refCenter[1];
                    result[resultIndex++] = (rpm + elev) * cosLat * cosLon[lonIndex] - refCenter[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionWgs84.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
                    "cartesianToGeographic", "missingGlobe"));
            }

            // According to H. Vermeille, "An analytical method to transform geocentric into geodetic coordinates"
            // http://www.springerlink.com/content/3t6837t27t351227/fulltext.pdf
            // Journal of Geodesy, accepted 10/2010, not yet published
            var X = z,
                Y = x,
                Z = y,
                XXpYY = X * X + Y * Y,
                sqrtXXpYY = Math.sqrt(XXpYY),
                a = globe.equatorialRadius,
                ra2 = 1 / (a * a),
                e2 = globe.eccentricitySquared,
                e4 = e2 * e2,
                p = XXpYY * ra2,
                q = Z * Z * (1 - e2) * ra2,
                r = (p + q - e4) / 6,
                h,
                phi,
                u,
                evoluteBorderTest = 8 * r * r * r + e4 * p * q,
                rad1,
                rad2,
                rad3,
                atan,
                v,
                w,
                k,
                D,
                sqrtDDpZZ,
                e,
                lambda,
                s2;

            if (evoluteBorderTest > 0 || q != 0) {
                if (evoluteBorderTest > 0) {
                    // Step 2: general case
                    rad1 = Math.sqrt(evoluteBorderTest);
                    rad2 = Math.sqrt(e4 * p * q);

                    // 10*e2 is my arbitrary decision of what Vermeille means by "near... the cusps of the evolute".
                    if (evoluteBorderTest > 10 * e2) {
                        rad3 = WWMath.cbrt((rad1 + rad2) * (rad1 + rad2));
                        u = r + 0.5 * rad3 + 2 * r * r / rad3;
                    }
                    else {
                        u = r + 0.5 * WWMath.cbrt((rad1 + rad2) * (rad1 + rad2))
                            + 0.5 * WWMath.cbrt((rad1 - rad2) * (rad1 - rad2));
                    }
                }
                else {
                    // Step 3: near evolute
                    rad1 = Math.sqrt(-evoluteBorderTest);
                    rad2 = Math.sqrt(-8 * r * r * r);
                    rad3 = Math.sqrt(e4 * p * q);
                    atan = 2 * Math.atan2(rad3, rad1 + rad2) / 3;

                    u = -4 * r * Math.sin(atan) * Math.cos(Math.PI / 6 + atan);
                }

                v = Math.sqrt(u * u + e4 * q);
                w = e2 * (u + v - q) / (2 * v);
                k = (u + v) / (Math.sqrt(w * w + u + v) + w);
                D = k * sqrtXXpYY / (k + e2);
                sqrtDDpZZ = Math.sqrt(D * D + Z * Z);

                h = (k + e2 - 1) * sqrtDDpZZ / k;
                phi = 2 * Math.atan2(Z, sqrtDDpZZ + D);
            }
            else {
                // Step 4: singular disk
                rad1 = Math.sqrt(1 - e2);
                rad2 = Math.sqrt(e2 - p);
                e = Math.sqrt(e2);

                h = -a * rad1 * rad2 / e;
                phi = rad2 / (e * rad2 + rad1 * Math.sqrt(p));
            }

            // Compute lambda
            s2 = Math.sqrt(2);
            if ((s2 - 1) * Y < sqrtXXpYY + X) {
                // case 1 - -135deg < lambda < 135deg
                lambda = 2 * Math.atan2(Y, sqrtXXpYY + X);
            }
            else if (sqrtXXpYY + Y < (s2 + 1) * X) {
                // case 2 - -225deg < lambda < 45deg
                lambda = -Math.PI * 0.5 + 2 * Math.atan2(X, sqrtXXpYY - Y);
            }
            else {
                // if (sqrtXXpYY-Y<(s2=1)*X) {  // is the test, if needed, but it's not
                // case 3: - -45deg < lambda < 225deg
                lambda = Math.PI * 0.5 - 2 * Math.atan2(X, sqrtXXpYY + Y);
            }

            result.latitude = Angle.RADIANS_TO_DEGREES * phi;
            result.longitude = Angle.RADIANS_TO_DEGREES * lambda;
            result.altitude = h;

            return result;
        };

        ProjectionWgs84.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            // The north-pointing tangent is derived by rotating the vector (0, 1, 0) about the Y-axis by longitude degrees,
            // then rotating it about the X-axis by -latitude degrees. The latitude angle must be inverted because latitude
            // is a clockwise rotation about the X-axis, and standard rotation matrices assume counter-clockwise rotation.
            // The combined rotation can be represented by a combining two rotation matrices Rlat, and Rlon, then
            // transforming the vector (0, 1, 0) by the combined transform:
            //
            // NorthTangent = (Rlon * Rlat) * (0, 1, 0)
            //
            // This computation can be simplified and encoded inline by making two observations:
            // - The vector's X and Z coordinates are always 0, and its Y coordinate is always 1.
            // - Inverting the latitude rotation angle is equivalent to inverting sinLat. We know this by the
            //  trigonometric identities cos(-x) = cos(x), and sin(-x) = -sin(x).

            var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
                cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
                sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
                sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);

            result[0] = -sinLat * sinLon;
            result[1] = cosLat;
            result[2] = -sinLat * cosLon;

            return result;
        };

        ProjectionWgs84.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            this.cartesianToGeographic(globe, x, y, z, Vec3.ZERO, this.scratchPosition);

            return this.northTangentAtLocation(globe, this.scratchPosition.latitude, this.scratchPosition.longitude, result);
        };

        ProjectionWgs84.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
                    "surfaceNormalAtPoint", "missingGlobe"));
            }

            var eSquared = globe.equatorialRadius * globe.equatorialRadius,
                polSquared = globe.polarRadius * globe.polarRadius;

            result[0] = x / eSquared;
            result[1] = y / polSquared;
            result[2] = z / eSquared;

            return result.normalize();
        };

        return ProjectionWgs84;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports NavigatorState
 */
define('navigate/NavigatorState',[
        '../error/ArgumentError',
        '../geom/Frustum',
        '../geom/Line',
        '../util/Logger',
        '../geom/Matrix',
        '../geom/Rectangle',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Frustum,
              Line,
              Logger,
              Matrix,
              Rectangle,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a navigator state. This constructor is meant to be called by navigators when their current state
         * is requested.
         * @alias NavigatorState
         * @constructor
         * @classdesc Represents the state of a navigator.
         * <p>
         * Properties of NavigatorState objects are
         * read-only because they are values captured from a {@link Navigator}. Setting the properties on
         * a NavigatorState instance has no effect on the Navigator from which they came.
         * @param {Matrix} modelViewMatrix The navigator's model-view matrix.
         * @param {Matrix} projectionMatrix The navigator's projection matrix.
         * @param {Rectangle} viewport The navigator's viewport.
         * @param {Number} heading The navigator's heading.
         * @param {Number} tilt The navigator's tilt.
         */
        var NavigatorState = function (modelViewMatrix, projectionMatrix, viewport, heading, tilt) {

            /**
             * The navigator's model-view matrix. The model-view matrix transforms points from model coordinates to eye
             * coordinates.
             * @type {Matrix}
             * @readonly
             */
            this.modelview = modelViewMatrix;

            /**
             * The navigator's projection matrix. The projection matrix transforms points from eye coordinates to clip
             * coordinates.
             * @type {Matrix}
             * @readonly
             */
            this.projection = projectionMatrix;

            /**
             * The concatenation of the navigator's model-view and projection matrices. This matrix transforms points
             * from model coordinates to clip coordinates.
             * @type {Matrix}
             * @readonly
             */
            this.modelviewProjection = Matrix.fromIdentity();
            this.modelviewProjection.setToMultiply(projectionMatrix, modelViewMatrix);

            /**
             * The navigator's viewport, in WebGL screen coordinates. The viewport places the origin in the bottom-left
             * corner and has axes that extend up and to the right from the origin.
             * @type {Rectangle}
             * @readonly
             */
            this.viewport = viewport;

            /**
             * Indicates the number of degrees clockwise from north to which the view is directed.
             * @type {Number}
             * @readonly
             */
            this.heading = heading;

            /**
             * The number of degrees the globe is tilted relative to its surface being parallel to the screen. Values are
             * typically in the range 0 to 90 but may vary from that depending on the navigator in use.
             * @type {Number}
             * @readonly
             */
            this.tilt = tilt;

            /**
             * The navigator's eye point in model coordinates, relative to the globe's center.
             * @type {Vec3}
             * @readonly
             */
            this.eyePoint = this.modelview.extractEyePoint(new Vec3(0, 0, 0));

            /**
             * The navigator's viewing frustum in model coordinates. The frustum originates at the eyePoint and extends
             * outward along the forward vector. The navigator's near distance and far distance identify the minimum and
             * maximum distance, respectively, at which an object in the scene is visible.
             * @type {Frustum}
             * @readonly
             */
            this.frustumInModelCoordinates = null;
            // Compute the frustum in model coordinates. Start by computing the frustum in eye coordinates from the
            // projection matrix, then transform this frustum to model coordinates by multiplying its planes by the
            // transpose of the modelview matrix. We use the transpose of the modelview matrix because planes are
            // transformed by the inverse transpose of a matrix, and we want to transform from eye coordinates to model
            // coordinates.
            var modelviewTranspose = Matrix.fromIdentity();
            modelviewTranspose.setToTransposeOfMatrix(this.modelview);
            this.frustumInModelCoordinates = Frustum.fromProjectionMatrix(this.projection);
            this.frustumInModelCoordinates.transformByMatrix(modelviewTranspose);
            this.frustumInModelCoordinates.normalize();

            // Compute the inverse of the modelview, projection, and modelview-projection matrices. The inverse matrices
            // are used to support operations on navigator state, such as project, unProject, and pixelSizeAtDistance.
            this.modelviewInv = Matrix.fromIdentity();
            this.modelviewInv.invertOrthonormalMatrix(this.modelview);
            this.projectionInv = Matrix.fromIdentity();
            this.projectionInv.invertMatrix(this.projection);
            this.modelviewProjectionInv = Matrix.fromIdentity();
            this.modelviewProjectionInv.invertMatrix(this.modelviewProjection);

            /**
             * The matrix that transforms normal vectors in model coordinates to normal vectors in eye coordinates.
             * Typically used to transform a shape's normal vectors during lighting calculations.
             * @type {Matrix}
             * @readonly
             */
            this.modelviewNormalTransform = Matrix.fromIdentity().setToTransposeOfMatrix(this.modelviewInv.upper3By3());

            // Compute the eye coordinate rectangles carved out of the frustum by the near and far clipping planes, and
            // the distance between those planes and the eye point along the -Z axis. The rectangles are determined by
            // transforming the bottom-left and top-right points of the frustum from clip coordinates to eye
            // coordinates.
            var nbl = new Vec3(-1, -1, -1),
                ntr = new Vec3(+1, +1, -1),
                fbl = new Vec3(-1, -1, +1),
                ftr = new Vec3(+1, +1, +1);
            // Convert each frustum corner from clip coordinates to eye coordinates by multiplying by the inverse
            // projection matrix.
            nbl.multiplyByMatrix(this.projectionInv);
            ntr.multiplyByMatrix(this.projectionInv);
            fbl.multiplyByMatrix(this.projectionInv);
            ftr.multiplyByMatrix(this.projectionInv);

            var nrRectWidth = WWMath.fabs(ntr[0] - nbl[0]),
                frRectWidth = WWMath.fabs(ftr[0] - fbl[0]),
                nrDistance = -nbl[2],
                frDistance = -fbl[2];

            // Compute the scale and offset used to determine the width of a pixel on a rectangle carved out of the
            // frustum at a distance along the -Z axis in eye coordinates. These values are found by computing the scale
            // and offset of a frustum rectangle at a given distance, then dividing each by the viewport width.
            var frustumWidthScale = (frRectWidth - nrRectWidth) / (frDistance - nrDistance),
                frustumWidthOffset = nrRectWidth - frustumWidthScale * nrDistance;
            this.pixelSizeScale = frustumWidthScale / viewport.width;
            this.pixelSizeOffset = frustumWidthOffset / viewport.height;
        };

        /**
         * Transforms the specified model point from model coordinates to WebGL screen coordinates.
         * <p>
         * The resultant screen point is in WebGL screen coordinates, with the origin in the bottom-left corner and
         * axes that extend up and to the right from the origin.
         * <p>
         * This function stores the transformed point in the result argument, and returns true or false to indicate
         * whether or not the transformation is successful. It returns false if this navigator state's modelview or
         * projection matrices are malformed, or if the specified model point is clipped by the near clipping plane or
         * the far clipping plane.
         *
         * @param {Vec3} modelPoint The model coordinate point to project.
         * @param {Vec3} result A pre-allocated vector in which to return the projected point.
         * @returns {boolean} true if the transformation is successful, otherwise false.
         * @throws {ArgumentError} If either the specified point or result argument is null or undefined.
         */
        NavigatorState.prototype.project = function (modelPoint, result) {
            if (!modelPoint) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "project",
                    "missingPoint"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "project",
                    "missingResult"));
            }

            // Transform the model point from model coordinates to eye coordinates then to clip coordinates. This
            // inverts the Z axis and stores the negative of the eye coordinate Z value in the W coordinate.
            var mx = modelPoint[0],
                my = modelPoint[1],
                mz = modelPoint[2],
                m = this.modelviewProjection,
                x = m[0] * mx + m[1] * my + m[2] * mz + m[3],
                y = m[4] * mx + m[5] * my + m[6] * mz + m[7],
                z = m[8] * mx + m[9] * my + m[10] * mz + m[11],
                w = m[12] * mx + m[13] * my + m[14] * mz + m[15],
                viewport = this.viewport;

            if (w == 0) {
                return false;
            }

            // Complete the conversion from model coordinates to clip coordinates by dividing by W. The resultant X, Y
            // and Z coordinates are in the range [-1,1].
            x /= w;
            y /= w;
            z /= w;

            // Clip the point against the near and far clip planes.
            if (z < -1 || z > 1) {
                return false;
            }

            // Convert the point from clip coordinate to the range [0,1]. This enables the X and Y coordinates to be
            // converted to screen coordinates, and the Z coordinate to represent a depth value in the range[0,1].
            x = x * 0.5 + 0.5;
            y = y * 0.5 + 0.5;
            z = z * 0.5 + 0.5;

            // Convert the X and Y coordinates from the range [0,1] to screen coordinates.
            x = x * viewport.width + viewport.x;
            y = y * viewport.height + viewport.y;

            result[0] = x;
            result[1] = y;
            result[2] = z;

            return true;
        };
        /**
         * Transforms the specified model point from model coordinates to WebGL screen coordinates, applying an offset
         * to the modelPoint's projected depth value.
         * <p>
         * The resultant screen point is in WebGL screen coordinates, with the origin in the bottom-left corner and axes
         * that extend up and to the right from the origin.
         * <p>
         * This function stores the transformed point in the result argument, and returns true or false to indicate whether or
         * not the transformation is successful. It returns false if this navigator state's modelview or projection
         * matrices are malformed, or if the modelPoint is clipped by the near clipping plane or the far clipping plane,
         * ignoring the depth offset.
         * <p>
         * The depth offset may be any real number and is typically used to move the screenPoint slightly closer to the
         * user's eye in order to give it visual priority over nearby objects or terrain. An offset of zero has no effect.
         * An offset less than zero brings the screenPoint closer to the eye, while an offset greater than zero pushes the
         * projected screen point away from the eye.
         * <p>
         * Applying a non-zero depth offset has no effect on whether the model point is clipped by this method or by
         * WebGL. Clipping is performed on the original model point, ignoring the depth offset. The final depth value
         * after applying the offset is clamped to the range [0,1].
         *
         * @param {Vec3} modelPoint The model coordinate point to project.
         * @param {Number} depthOffset The amount of offset to apply.
         * @param {Vec3} result A pre-allocated vector in which to return the projected point.
         * @returns {boolean} true if the transformation is successful, otherwise false.
         * @throws {ArgumentError} If either the specified point or result argument is null or undefined.
         */
        NavigatorState.prototype.projectWithDepth = function (modelPoint, depthOffset, result) {
            if (!modelPoint) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "projectWithDepth",
                    "missingPoint"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "projectWithDepth",
                    "missingResult"));
            }

            // Transform the model point from model coordinates to eye coordinates. The eye coordinate and the clip
            // coordinate are transformed separately in order to reuse the eye coordinate below.
            var mx = modelPoint[0],
                my = modelPoint[1],
                mz = modelPoint[2],
                m = this.modelview,
                ex = m[0] * mx + m[1] * my + m[2] * mz + m[3],
                ey = m[4] * mx + m[5] * my + m[6] * mz + m[7],
                ez = m[8] * mx + m[9] * my + m[10] * mz + m[11],
                ew = m[12] * mx + m[13] * my + m[14] * mz + m[15];

            // Transform the point from eye coordinates to clip coordinates.
            var p = this.projection,
                x = p[0] * ex + p[1] * ey + p[2] * ez + p[3] * ew,
                y = p[4] * ex + p[5] * ey + p[6] * ez + p[7] * ew,
                z = p[8] * ex + p[9] * ey + p[10] * ez + p[11] * ew,
                w = p[12] * ex + p[13] * ey + p[14] * ez + p[15] * ew,
                viewport = this.viewport;

            if (w === 0) {
                return false;
            }

            // Complete the conversion from model coordinates to clip coordinates by dividing by W. The resultant X, Y
            // and Z coordinates are in the range [-1,1].
            x /= w;
            y /= w;
            z /= w;

            // Clip the point against the near and far clip planes.
            if (z < -1 || z > 1) {
                return false;
            }

            // Transform the Z eye coordinate to clip coordinates again, this time applying a depth offset. The depth
            // offset is applied only to the matrix element affecting the projected Z coordinate, so we inline the
            // computation here instead of re-computing X, Y, Z and W in order to improve performance. See
            // Matrix.offsetProjectionDepth for more information on the effect of this offset.
            z = p[8] * ex + p[9] * ey + p[10] * ez * (1 + depthOffset) + p[11] * ew;
            z /= w;

            // Clamp the point to the near and far clip planes. We know the point's original Z value is contained within
            // the clip planes, so we limit its offset z value to the range [-1, 1] in order to ensure it is not clipped
            // by WebGL. In clip coordinates the near and far clip planes are perpendicular to the Z axis and are
            // located at -1 and 1, respectively.
            z = WWMath.clamp(z, -1, 1);

            // Convert the point from clip coordinates to the range [0, 1]. This enables the XY coordinates to be
            // converted to screen coordinates, and the Z coordinate to represent a depth value in the range [0, 1].
            x = x * 0.5 + 0.5;
            y = y * 0.5 + 0.5;
            z = z * 0.5 + 0.5;

            // Convert the X and Y coordinates from the range [0,1] to screen coordinates.
            x = x * viewport.width + viewport.x;
            y = y * viewport.height + viewport.y;

            result[0] = x;
            result[1] = y;
            result[2] = z;

            return true;
        };

        /**
         * Transforms the specified screen point from WebGL screen coordinates to model coordinates.
         * <p>
         * The screen point is understood to be in WebGL screen coordinates, with the origin in the bottom-left corner
         * and axes that extend up and to the right from the origin.
         * <p>
         * This function stores the transformed point in the result argument, and returns true or false to indicate whether the
         * transformation is successful. It returns false if this navigator state's modelview or projection matrices
         * are malformed, or if the screenPoint is clipped by the near clipping plane or the far clipping plane.
         *
         * @param {Vec3} screenPoint The screen coordinate point to un-project.
         * @param {Vec3} result A pre-allocated vector in which to return the unprojected point.
         * @returns {boolean} true if the transformation is successful, otherwise false.
         * @throws {ArgumentError} If either the specified point or result argument is null or undefined.
         */
        NavigatorState.prototype.unProject = function (screenPoint, result) {
            if (!screenPoint) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "unProject",
                    "missingPoint"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "unProject",
                    "missingResult"));
            }

            var sx = screenPoint[0],
                sy = screenPoint[1],
                sz = screenPoint[2],
                viewport = this.viewport;

            // Convert the XY screen coordinates to coordinates in the range [0, 1]. This enables the XY coordinates to
            // be converted to clip coordinates.
            sx = (sx - viewport.x) / viewport.width;
            sy = (sy - viewport.y) / viewport.height;

            // Convert from coordinates in the range [0, 1] to clip coordinates in the range [-1, 1].
            sx = sx * 2 - 1;
            sy = sy * 2 - 1;
            sz = sz * 2 - 1;

            // Clip the point against the near and far clip planes. In clip coordinates the near and far clip planes are
            // perpendicular to the Z axis and are located at -1 and 1, respectively.
            if (sz < -1 || sz > 1) {
                return false;
            }

            // Transform the screen point from clip coordinates to model coordinates. This inverts the Z axis and stores
            // the negative of the eye coordinate Z value in the W coordinate.
            var m = this.modelviewProjectionInv,
                x = m[0] * sx + m[1] * sy + m[2] * sz + m[3],
                y = m[4] * sx + m[5] * sy + m[6] * sz + m[7],
                z = m[8] * sx + m[9] * sy + m[10] * sz + m[11],
                w = m[12] * sx + m[13] * sy + m[14] * sz + m[15];

            if (w === 0) {
                return false;
            }

            // Complete the conversion from model coordinates to clip coordinates by dividing by W.
            result[0] = x / w;
            result[1] = y / w;
            result[2] = z / w;

            return true;
        };

        /**
         * Converts a WebGL screen point to window coordinates.
         * <p>
         * The specified point is understood to be in WebGL screen coordinates, with the origin in the bottom-left
         * corner and axes that extend up and to the right from the origin point.
         * <p>
         * The returned point is in the window coordinate system of the WorldWindow, with the origin in the top-left
         * corner and axes that extend down and to the right from the origin point.
         *
         * @param {Vec2} screenPoint The screen point to convert.
         * @param {Vec2} result A pre-allocated {@link Vec2} in which to return the computed point.
         * @returns {Vec2} The specified result argument set to the computed point.
         * @throws {ArgumentError} If either argument is null or undefined.
         */
        NavigatorState.prototype.convertPointToWindow = function (screenPoint, result) {
            if (!screenPoint) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "convertPointToWindow",
                    "missingPoint"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "convertPointToWindow",
                    "missingResult"));
            }

            result[0] = screenPoint[0];
            result[1] = this.viewport.height - screenPoint[1];

            return result;
        };

        /**
         * Converts a window-coordinate point to WebGL screen coordinates.
         * <p>
         * The specified point is understood to be in the window coordinate system of the WorldWindow, with the origin
         * in the top-left corner and axes that extend down and to the right from the origin point.
         * <p>
         * The returned point is in WebGL screen coordinates, with the origin in the bottom-left corner and axes that
         * extend up and to the right from the origin point.
         *
         * @param {Vec2} point The window-coordinate point to convert.
         * @param {Vec2} result A pre-allocated {@link Vec2} in which to return the computed point.
         * @returns {Vec2} The specified result argument set to the computed point.
         * @throws {ArgumentError} If either argument is null or undefined.
         */
        NavigatorState.prototype.convertPointToViewport = function (point, result) {
            if (!point) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "convertPointToViewport",
                    "missingPoint"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "convertPointToViewport",
                    "missingResult"));
            }

            result[0] = point[0];
            result[1] = this.viewport.height - point[1];

            return result;
        };

        /**
         * Computes a ray originating at the navigator's eyePoint and extending through the specified point in window
         * coordinates.
         * <p>
         * The specified point is understood to be in the window coordinate system of the WorldWindow, with the origin
         * in the top-left corner and axes that extend down and to the right from the origin point.
         * <p>
         * The results of this method are undefined if the specified point is outside of the WorldWindow's
         * bounds.
         *
         * @param {Vec2} point The window coordinates point to compute a ray for.
         * @returns {Line} A new Line initialized to the origin and direction of the computed ray, or null if the
         * ray could not be computed.
         */
        NavigatorState.prototype.rayFromScreenPoint = function (point) {
            if (!point) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "NavigatorState", "rayFromScreenPoint",
                    "missingPoint"));
            }

            // Convert the point's xy coordinates from window coordinates to WebGL screen coordinates.
            var screenPoint = this.convertPointToViewport(point, new Vec3(0, 0, 0)),
                nearPoint = new Vec3(0, 0, 0),
                farPoint = new Vec3(0, 0, 0);

            // Compute the model coordinate point on the near clip plane with the xy coordinates and depth 0.
            if (!this.unProject(screenPoint, nearPoint)) {
                return null;
            }

            // Compute the model coordinate point on the far clip plane with the xy coordinates and depth 1.
            screenPoint[2] = 1;
            if (!this.unProject(screenPoint, farPoint)) {
                return null;
            }

            // Compute a ray originating at the eye point and with direction pointing from the xy coordinate on the near
            // plane to the same xy coordinate on the far plane.
            var origin = new Vec3(this.eyePoint[0], this.eyePoint[1], this.eyePoint[2]),
                direction = new Vec3(farPoint[0], farPoint[1], farPoint[2]);

            direction.subtract(nearPoint);
            direction.normalize();

            return new Line(origin, direction);
        };

        /**
         * Computes the approximate size of a pixel at a specified distance from the navigator's eye point.
         * <p>
         * This method assumes rectangular pixels, where pixel coordinates denote
         * infinitely thin spaces between pixels. The units of the returned size are in model coordinates per pixel
         * (usually meters per pixel). This returns 0 if the specified distance is zero. The returned size is undefined
         * if the distance is less than zero.
         *
         * @param {Number} distance The distance from the eye point at which to determine pixel size, in model
         * coordinates.
         * @returns {Number} The approximate pixel size at the specified distance from the eye point, in model
         * coordinates per pixel.
         */
        NavigatorState.prototype.pixelSizeAtDistance = function (distance) {
            // Compute the pixel size from the width of a rectangle carved out of the frustum in model coordinates at
            // the specified distance along the -Z axis and the viewport width in screen coordinates. The pixel size is
            // expressed in model coordinates per screen coordinate (e.g. meters per pixel).
            //
            // The frustum width is determined by noticing that the frustum size is a linear function of distance from
            // the eye point. The linear equation constants are determined during initialization, then solved for
            // distance here.
            //
            // This considers only the frustum width by assuming that the frustum and viewport share the same aspect
            // ratio, so that using either the frustum width or height results in the same pixel size.

            return this.pixelSizeScale * distance + this.pixelSizeOffset;
        };

        return NavigatorState;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Terrain
 */
define('globe/Terrain',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Vec3'
    ],
    function (ArgumentError,
              Logger,
              Vec3) {
        "use strict";

        /**
         * Constructs a Terrain object.
         * @alias Terrain
         * @constructor
         * @classdesc Represents terrain and provides functions for computing points on or relative to the terrain.
         * Applications do not typically interact directly with this class.
         */
        var Terrain = function (globe, tessellator, terrainTiles, verticalExaggeration) {

            /**
             * The globe associated with this terrain.
             * @type {Globe}
             */
            this.globe = globe;

            /**
             * The vertical exaggeration of this terrain.
             * @type {Number}
             */
            this.verticalExaggeration = verticalExaggeration;

            /**
             * The sector spanned by this terrain.
             * @type {Sector}
             */
            this.sector = terrainTiles.sector;

            /**
             * The tessellator used to generate this terrain.
             * @type {Tessellator}
             */
            this.tessellator = tessellator;

            /**
             * The surface geometry for this terrain
             * @type {TerrainTile[]}
             */
            this.surfaceGeometry = terrainTiles.tileArray;

            /**
             * A string identifying this terrain's current state. Used to compare states during rendering to
             * determine whether state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @readonly
             * @type {String}
             */
            this.stateKey = globe.stateKey + " ve " + verticalExaggeration.toString();
        };

        Terrain.scratchPoint = new Vec3(0, 0, 0);

        /**
         * Computes a Cartesian point at a location on the surface of this terrain.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Number} offset Distance above the terrain, in meters, at which to compute the point.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
         * @returns {Vec3} The specified result parameter, set to the coordinates of the computed point. If the
         * specfied location is not within this terrain, the associated globe is used to compute the point.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Terrain.prototype.surfacePoint = function (latitude, longitude, offset, result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "surfacePoint", "missingResult"));
            }

            for (var i = 0, len = this.surfaceGeometry.length; i < len; i++) {
                if (this.surfaceGeometry[i].sector.containsLocation(latitude, longitude)) {
                    this.surfaceGeometry[i].surfacePoint(latitude, longitude, result);

                    if (offset) {
                        var normal = this.globe.surfaceNormalAtPoint(result[0], result[1], result[2], Terrain.scratchPoint);
                        result[0] += normal[0] * offset;
                        result[1] += normal[1] * offset;
                        result[2] += normal[2] * offset;
                    }

                    return result;
                }
            }

            // No tile was found that contains the location, so approximate one using the globe.
            var h = offset + this.globe.elevationAtLocation(latitude, longitude) * this.verticalExaggeration;
            this.globe.computePointFromPosition(latitude, longitude, h, result);

            return result;
        };

        /**
         * Computes a Cartesian point at a location on the surface of this terrain according to a specified
         * altitude mode.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Number} offset Distance above the terrain, in meters relative to the specified altitude mode, at
         * which to compute the point.
         * @param {String} altitudeMode The altitude mode to use to compute the point. Recognized values are
         * WorldWind.ABSOLUTE, WorldWind.CLAMP_TO_GROUND and
         * WorldWind.RELATIVE_TO_GROUND. The mode WorldWind.ABSOLUTE is used if the
         * specified mode is null, undefined or unrecognized, or if the specified location is outside this terrain.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
         * @returns {Vec3} The specified result parameter, set to the coordinates of the computed point.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Terrain.prototype.surfacePointForMode = function (latitude, longitude, offset, altitudeMode, result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "surfacePointForMode", "missingResult"));
            }

            if (!altitudeMode)
                altitudeMode = WorldWind.ABSOLUTE;

            if (altitudeMode === WorldWind.CLAMP_TO_GROUND) {
                return this.surfacePoint(latitude, longitude, 0, result);
            } else if (altitudeMode === WorldWind.RELATIVE_TO_GROUND) {
                return this.surfacePoint(latitude, longitude, offset, result);
            } else {
                var height = offset * this.verticalExaggeration;
                this.globe.computePointFromPosition(latitude, longitude, height, result);
                return result;
            }
        };

        /**
         * Initializes rendering state to draw a succession of terrain tiles.
         * @param {DrawContext} dc The current draw context.
         */
        Terrain.prototype.beginRendering = function (dc) {
            if (this.globe && this.globe.tessellator) {
                this.globe.tessellator.beginRendering(dc);
            }
        };

        /**
         * Restores rendering state after drawing a succession of terrain tiles.
         * @param {DrawContext} dc The current draw context.
         */
        Terrain.prototype.endRendering = function (dc) {
            if (this.globe && this.globe.tessellator) {
                this.globe.tessellator.endRendering(dc);
            }
        };

        /**
         * Initializes rendering state for drawing a specified terrain tile.
         * @param {DrawContext} dc The current draw context.
         * @param {TerrainTile} terrainTile The terrain tile subsequently drawn via this tessellator's render function.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Terrain.prototype.beginRenderingTile = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "beginRenderingTile", "missingTile"));
            }

            if (this.globe && this.globe.tessellator) {
                this.globe.tessellator.beginRenderingTile(dc, terrainTile);
            }
        };

        /**
         * Restores rendering state after drawing the most recent tile specified to
         * [beginRenderingTile]{@link Terrain#beginRenderingTile}.
         * @param {DrawContext} dc The current draw context.
         * @param {TerrainTile} terrainTile The terrain tile most recently rendered.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Terrain.prototype.endRenderingTile = function (dc, terrainTile) {
            // Intentionally empty.
        };

        /**
         * Renders a specified terrain tile.
         * @param {DrawContext} dc The current draw context.
         * @param {TerrainTile} terrainTile The terrain tile to render.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Terrain.prototype.renderTile = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Terrain", "renderTile", "missingTile"));
            }

            if (this.globe && this.globe.tessellator) {
                this.globe.tessellator.renderTile(dc, terrainTile);
            }
        };

        /**
         * Causes this terrain to perform the picking operations appropriate for the draw context's pick settings.
         * Normally, this draws the terrain in a unique pick color and computes the picked terrain position. When the
         * draw context is set to region picking mode this omits the computation of a picked terrain position.
         * @param {DrawContext} dc The current draw context.
         */
        Terrain.prototype.pick = function (dc) {
            if (this.globe && this.globe.tessellator) {
                this.globe.tessellator.pick(dc, this.surfaceGeometry, this); // use this terrain as the userObject
            }
        };

        return Terrain;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TerrainTile
 */
define('globe/TerrainTile',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Matrix',
        '../util/Tile'
    ],
    function (ArgumentError,
              Logger,
              Matrix,
              Tile) {
        "use strict";

        /**
         * Constructs a terrain tile.
         * @alias TerrainTile
         * @constructor
         * @augments Tile
         * @classdesc Represents a portion of a globe's terrain. Applications typically do not interact directly with
         * this class.
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {Number} row This tile's row in the associated level.
         * @param {Number} column This tile's column in the associated level.
         * @throws {ArgumentError} If the specified sector or level is null or undefined or the row or column arguments
         * are less than zero.
         */
        var TerrainTile = function (sector, level, row, column) {
            Tile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            /**
             * The transformation matrix that maps tile local coordinates to model coordinates.
             * @type {Matrix}
             */
            this.transformationMatrix = Matrix.fromIdentity();

            /**
             * The tile's model coordinate points.
             * @type {Float32Array}
             */
            this.points = null;

            /**
             * Indicates the state of this tile when the model coordinate points were last updated. This is used to
             * invalidate the points when this tile's state changes.
             * @type {String}
             */
            this.pointsStateKey = null;

            /**
             * Indicates the state of this tile when the model coordinate VBO was last uploaded to GL. This is used to
             * invalidate the VBO when the tile's state changes.
             * @type {String}
             */
            this.pointsVboStateKey = null;

            // Internal use. Intentionally not documented.
            this.neighborMap = {};
            this.neighborMap[WorldWind.NORTH] = null;
            this.neighborMap[WorldWind.SOUTH] = null;
            this.neighborMap[WorldWind.EAST] = null;
            this.neighborMap[WorldWind.WEST] = null;

            // Internal use. Intentionally not documented.
            this._stateKey = null;

            // Internal use. Intentionally not documented.
            this._elevationTimestamp = null;

            // Internal use. Intentionally not documented.
            this.scratchArray = [];
        };

        TerrainTile.prototype = Object.create(Tile.prototype);

        Object.defineProperties(TerrainTile.prototype, {
            /**
             * A string identifying the state of this tile as a function of the elevation model's timestamp and this
             * tile's neighbors. Used to compare states during rendering to determine whether cached values must be
             * updated. Applications typically do not interact with this property.
             * @type {String}
             * @memberof TerrainTile.prototype
             * @readonly
             */
            stateKey: {
                get: function () {
                    if (!this._stateKey) {
                        this._stateKey = this.computeStateKey();
                    }

                    return this._stateKey;
                }
            }
        });

        /**
         * Indicates the level of the tile adjacent to this tile in a specified direction. This returns null when this
         * tile has no neighbor in that direction.
         * @param {String} direction The cardinal direction. Must be one of WorldWind.NORTH, WorldWind.SOUTH,
         * WorldWind.EAST or WorldWind.WEST.
         * @returns {Level} The neighbor tile's level in the specified direction, or null if there is no neighbor.
         */
        TerrainTile.prototype.neighborLevel = function (direction) {
            return this.neighborMap[direction];
        };

        /**
         * Specifies the level of the tile adjacent to this tile in a specified direction.
         * @param {String} direction The cardinal direction. Must be one of WorldWind.NORTH, WorldWind.SOUTH,
         * WorldWind.EAST or WorldWind.WEST.
         * @param {Level} level The neighbor tile's level in the specified direction, or null to indicate that there is
         * no neighbor in that direction.
         */
        TerrainTile.prototype.setNeighborLevel = function (direction, level) {
            this.neighborMap[direction] = level;
            this._stateKey = null; // cause updates to any neighbor-dependent cached state
        };

        /**
         * Computes a point on the terrain at a specified location.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
         * @returns {Vec3} The result argument set to the computed point.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        TerrainTile.prototype.surfacePoint = function (latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTile", "surfacePoint", "missingResult"));
            }

            var tileSector = this.sector,
                minLat = tileSector.minLatitude,
                maxLat = tileSector.maxLatitude,
                minLon = tileSector.minLongitude,
                maxLon = tileSector.maxLongitude,
                tileWidth = this.tileWidth,
                tileHeight = this.tileHeight,
                s, t, si, ti, rowStride, vertices, points, k, sf, tf, x, y, z;

            // Compute the location's horizontal (s) and vertical (t) parameterized coordinates within the tiles 2D grid of
            // points as a floating-point value in the range [0, tileWidth] and [0, tileHeight]. These coordinates indicate
            // which cell contains the location, as well as the location's placement within the cell. Note that this method
            // assumes that the caller has tested whether the location is contained within the tile's sector.
            s = (longitude - minLon) / (maxLon - minLon) * tileWidth;
            t = (latitude - minLat) / (maxLat - minLat) * tileHeight;

            // Get the coordinates for the four vertices defining the cell this point is in. Tile vertices start in the lower
            // left corner and proceed in row major order across the tile. The tile contains one more vertex per row or
            // column than the tile width or height. Vertices in the points array are organized in the
            // following order: lower-left, lower-right, upper-left, upper-right. The cell's diagonal starts at the
            // lower-left vertex and ends at the upper-right vertex.
            si = s < tileWidth ? Math.floor(s) : tileWidth - 1;
            ti = t < tileHeight ? Math.floor(t) : tileHeight - 1;
            rowStride = tileWidth + 1;

            vertices = this.points;
            points = this.scratchArray; // temporary working buffer
            k = 3 * (si + ti * rowStride); // lower-left and lower-right vertices
            for (var i = 0; i < 6; i++) {
                points[i] = vertices[k + i];
            }

            k = 3 * (si + (ti + 1) * rowStride); // upper-left and upper-right vertices
            for (var j = 6; j < 12; j++) {
                points[j] = vertices[k + (j - 6)];
            }

            // Compute the location's corresponding point on the cell in tile local coordinates,
            // given the fractional portion of the parameterized s and t coordinates. These values indicate the location's
            // relative placement within the cell. The cell's vertices are defined in the following order: lower-left,
            // lower-right, upper-left, upper-right. The cell's diagonal starts at the lower-right vertex and ends at the
            // upper-left vertex.
            sf = (s < tileWidth ? s - Math.floor(s) : 1);
            tf = (t < tileHeight ? t - Math.floor(t) : 1);

            if (sf > tf) {
                result[0] = points[0] + sf * (points[3] - points[0]) + tf * (points[6] - points[0]);
                result[1] = points[1] + sf * (points[4] - points[1]) + tf * (points[7] - points[1]);
                result[2] = points[2] + sf * (points[5] - points[2]) + tf * (points[8] - points[2]);
            }
            else {
                result[0] = points[9] + (1 - sf) * (points[6] - points[9]) + (1 - tf) * (points[3] - points[9]);
                result[1] = points[10] + (1 - sf) * (points[7] - points[10]) + (1 - tf) * (points[4] - points[10]);
                result[2] = points[11] + (1 - sf) * (points[8] - points[11]) + (1 - tf) * (points[5] - points[11]);
            }

            result[0] += this.referencePoint[0];
            result[1] += this.referencePoint[1];
            result[2] += this.referencePoint[2];

            return result;
        };

        TerrainTile.prototype.update = function (dc) {
            Tile.prototype.update.call(this, dc);

            var elevationTimestamp = dc.globe.elevationTimestamp();
            if (this._elevationTimestamp != elevationTimestamp) {
                this._elevationTimestamp = elevationTimestamp;
                this._stateKey = null; // cause updates to any elevation-dependent cached state
            }
        };

        // Intentionally not documented.
        TerrainTile.prototype.computeStateKey = function () {
            var array = [];
            array.push(this._elevationTimestamp);
            array.push(this.neighborMap[WorldWind.NORTH] ? this.neighborMap[WorldWind.NORTH].compare(this.level) : 0);
            array.push(this.neighborMap[WorldWind.SOUTH] ? this.neighborMap[WorldWind.SOUTH].compare(this.level) : 0);
            array.push(this.neighborMap[WorldWind.EAST] ? this.neighborMap[WorldWind.EAST].compare(this.level) : 0);
            array.push(this.neighborMap[WorldWind.WEST] ? this.neighborMap[WorldWind.WEST].compare(this.level) : 0);

            return array.join(".");
        };

        return TerrainTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TerrainTileList
 */
define('globe/TerrainTileList',['../error/ArgumentError',
        '../util/Logger',
        '../geom/Sector'
    ],
    function (ArgumentError,
              Logger,
              Sector) {
        "use strict";

        /**
         * Constructs a terrain tile list, a container for terrain tiles that also has a tessellator and a sector
         * associated with it.
         * @alias TerrainTileList
         * @constructor
         * @classdesc Represents a portion of a globe's terrain.
         * @param {Tessellator} tessellator The tessellator that created this terrain tile list.
         *
         */
        var TerrainTileList = function TerrainTileList(tessellator) {
            if (!tessellator) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTileList", "TerrainTileList", "missingTessellator"));
            }
            this.tessellator = tessellator;
            this.sector = null;
            this.tileArray = [];
        };

        Object.defineProperties(TerrainTileList.prototype, {
            /**
             * The number of terrain tiles in this terrain tile list.
             * @memberof TerrainTileList.prototype
             * @readonly
             * @type {Number}
             */
            length: {
                get: function () {
                    return this.tileArray.length
                }
            }
        });

        TerrainTileList.prototype.addTile = function (tile) {
            if (!tile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TerrainTileList", "addTile", "missingTile"));
            }

            if (this.tileArray.indexOf(tile) == -1) {
                this.tileArray.push(tile);

                if (!this.sector) {
                    this.sector = new Sector(0, 0, 0, 0);
                    this.sector.copy(tile.sector);
                } else {
                    this.sector.union(tile.sector);
                }
            }
        };

        TerrainTileList.prototype.removeAllTiles = function () {
            this.tileArray = [];
            this.sector = null;
        };

        return TerrainTileList;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Tessellator
 */
define('globe/Tessellator',[
        '../error/ArgumentError',
        '../shaders/BasicProgram',
        '../globe/Globe',
        '../shaders/GpuProgram',
        '../util/Level',
        '../util/LevelSet',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../cache/MemoryCache',
        '../navigate/NavigatorState',
        '../error/NotYetImplementedError',
        '../pick/PickedObject',
        '../geom/Position',
        '../geom/Rectangle',
        '../geom/Sector',
        '../globe/Terrain',
        '../globe/TerrainTile',
        '../globe/TerrainTileList',
        '../util/Tile',
        '../util/WWMath',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              BasicProgram,
              Globe,
              GpuProgram,
              Level,
              LevelSet,
              Location,
              Logger,
              Matrix,
              MemoryCache,
              NavigatorState,
              NotYetImplementedError,
              PickedObject,
              Position,
              Rectangle,
              Sector,
              Terrain,
              TerrainTile,
              TerrainTileList,
              Tile,
              WWMath,
              WWUtil) {
        "use strict";

        /**
         * Constructs a Tessellator.
         * @alias Tessellator
         * @constructor
         * @classdesc Provides terrain tessellation for a globe.
         */
        var Tessellator = function () {
            // Parameterize top level subdivision in one place.

            // TilesInTopLevel describes the most coarse tile structure.
            this.numRowsTilesInTopLevel = 4; // baseline: 4
            this.numColumnsTilesInTopLevel = 8; // baseline: 8

            // The maximum number of levels that will ever be tessellated.
            this.maximumSubdivisionDepth = 15; // baseline: 15

            // tileWidth, tileHeight - the number of subdivisions a single tile has; this determines the sampling grid.
            this.tileWidth = 32; // baseline: 32
            this.tileHeight = 32; // baseline: 32

            /**
             * Controls the level of detail switching for this layer. The next highest resolution level is
             * used when an elevation tile's cell size is greater than this number of pixels, up to the maximum
             * resolution of the elevation model.
             * @type {Number}
             * @default 1.75
             */
            this.detailControl = 40;

            this.levels = new LevelSet(
                Sector.FULL_SPHERE,
                new Location(
                    180 / this.numRowsTilesInTopLevel,
                    360 / this.numColumnsTilesInTopLevel),
                this.maximumSubdivisionDepth,
                this.tileWidth,
                this.tileHeight);

            this.topLevelTiles = {};
            this.currentTiles = new TerrainTileList(this);

            this.tileCache = new MemoryCache(5000000, 4000000); // Holds 316 32x32 tiles.

            this.elevationTimestamp = undefined;
            this.lastModelViewProjection = undefined;

            this.vertexPointLocation = -1;
            this.vertexTexCoordLocation = -1;

            this.texCoords = null;
            this.texCoordVboCacheKey = 'global_tex_coords';

            this.indices = null;
            this.indicesVboCacheKey = 'global_indices';

            this.baseIndices = null;
            this.baseIndicesOffset = null;
            this.numBaseIndices = null;

            this.indicesNorth = null;
            this.indicesNorthOffset = null;
            this.numIndicesNorth = null;

            this.indicesSouth = null;
            this.indicesSouthOffset = null;
            this.numIndicesSouth = null;

            this.indicesWest = null;
            this.indicesWestOffset = null;
            this.numIndicesWest = null;

            this.indicesEast = null;
            this.indicesEastOffset = null;
            this.numIndicesEast = null;

            this.indicesLoresNorth = null;
            this.indicesLoresNorthOffset = null;
            this.numIndicesLoresNorth = null;

            this.indicesLoresSouth = null;
            this.indicesLoresSouthOffset = null;
            this.numIndicesLoresSouth = null;

            this.indicesLoresWest = null;
            this.indicesLoresWestOffset = null;
            this.numIndicesLoresWest = null;

            this.indicesLoresEast = null;
            this.indicesLoresEastOffset = null;
            this.numIndicesLoresEast = null;

            this.outlineIndicesOffset = null;
            this.numOutlineIndices = null;

            this.wireframeIndicesOffset = null;
            this.numWireframeIndices = null;

            this.scratchMatrix = Matrix.fromIdentity();
            this.scratchElevations = null;
            this.scratchPrevElevations = null;

            this.corners = {};
            this.tiles = [];
        };

        /**
         * Creates the visible terrain of the globe associated with the current draw context.
         * @param {DrawContext} dc The draw context.
         * @returns {Terrain} The computed terrain, or null if terrain could not be computed.
         * @throws {ArgumentError} If the dc is null or undefined.
         */
        Tessellator.prototype.tessellate = function (dc) {
            if (!dc) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "tessellate", "missingDC"));
            }

            var lastElevationsChange = dc.globe.elevationTimestamp();
            if (this.lastGlobeStateKey === dc.globeStateKey
                && this.lastVerticalExaggeration === dc.verticalExaggeration
                && this.elevationTimestamp === lastElevationsChange
                && this.lastModelViewProjection
                && dc.navigatorState.modelviewProjection.equals(this.lastModelViewProjection)) {

                return this.lastTerrain;
            }

            var navigatorState = dc.navigatorState;

            this.lastModelViewProjection = navigatorState.modelviewProjection;
            this.lastGlobeStateKey = dc.globeStateKey;
            this.elevationTimestamp = lastElevationsChange;
            this.lastVerticalExaggeration = dc.verticalExaggeration;

            this.currentTiles.removeAllTiles();

            if (!this.topLevelTiles[dc.globeStateKey] || this.topLevelTiles[dc.globeStateKey].length == 0) {
                this.createTopLevelTiles(dc);
            }

            this.corners = {};
            this.tiles = [];

            for (var index = 0, len = this.topLevelTiles[dc.globeStateKey].length; index < len; index += 1) {
                var tile = this.topLevelTiles[dc.globeStateKey][index];

                tile.update(dc);

                if (this.isTileVisible(dc, tile)) {
                    this.addTileOrDescendants(dc, tile);
                }
            }

            this.refineNeighbors(dc);
            this.finishTessellating(dc);

            this.lastTerrain = this.currentTiles.length === 0 ? null
                : new Terrain(dc.globe, this, this.currentTiles, dc.verticalExaggeration);

            return this.lastTerrain;
        };

        Tessellator.prototype.createTile = function (tileSector, level, row, column) {
            if (!tileSector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor", "missingSector"));
            }

            if (!level) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
                        "The specified level is null or undefined."));
            }

            if (row < 0 || column < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
                        "The specified row or column is less than zero."));
            }

            return new TerrainTile(tileSector, level, row, column);
        };

        /**
         * Initializes rendering state to draw a succession of terrain tiles.
         * @param {DrawContext} dc The draw context.
         */
        Tessellator.prototype.beginRendering = function (dc) {
            var program = dc.currentProgram; // use the current program; the caller configures other program state
            if (!program) {
                Logger.logMessage(Logger.LEVEL_INFO, "Tessellator", "beginRendering", "Current Program is empty");
                return;
            }

            this.buildSharedGeometry();
            this.cacheSharedGeometryVBOs(dc);

            var gl = dc.currentGlContext,
                gpuResourceCache = dc.gpuResourceCache;

            // Keep track of the program's attribute locations. The tessellator does not know which program the caller has
            // bound, and therefore must look up the location of attributes by name.
            this.vertexPointLocation = program.attributeLocation(gl, "vertexPoint");
            this.vertexTexCoordLocation = program.attributeLocation(gl, "vertexTexCoord");
            gl.enableVertexAttribArray(this.vertexPointLocation);

            if (this.vertexTexCoordLocation >= 0) { // location of vertexTexCoord attribute is -1 when the basic program is bound
                var texCoordVbo = gpuResourceCache.resourceForKey(this.texCoordVboCacheKey);
                gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVbo);
                gl.vertexAttribPointer(this.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(this.vertexTexCoordLocation);
            }

            var indicesVbo = gpuResourceCache.resourceForKey(this.indicesVboCacheKey);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesVbo);

        };

        /**
         * Restores rendering state after drawing a succession of terrain tiles.
         * @param {DrawContext} dc The draw context.
         */
        Tessellator.prototype.endRendering = function (dc) {
            var gl = dc.currentGlContext;

            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

            // Restore the global OpenGL vertex attribute array state.
            if (this.vertexPointLocation >= 0) {
                gl.disableVertexAttribArray(this.vertexPointLocation);
            }

            if (this.vertexTexCoordLocation >= 0) { // location of vertexTexCoord attribute is -1 when the basic program is bound
                gl.disableVertexAttribArray(this.vertexTexCoordLocation);
            }
        };

        /**
         * Initializes rendering state for drawing a specified terrain tile.
         * @param {DrawContext} dc The draw context.
         * @param {TerrainTile} terrainTile The terrain tile subsequently drawn via this tessellator's render function.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Tessellator.prototype.beginRenderingTile = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "beginRenderingTile", "missingTile"));
            }

            var gl = dc.currentGlContext,
                gpuResourceCache = dc.gpuResourceCache;

            this.scratchMatrix.setToMultiply(dc.navigatorState.modelviewProjection, terrainTile.transformationMatrix);
            dc.currentProgram.loadModelviewProjection(gl, this.scratchMatrix);

            var vboCacheKey = dc.globeStateKey + terrainTile.tileKey,
                vbo = gpuResourceCache.resourceForKey(vboCacheKey);
            if (!vbo) {
                vbo = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
                gl.bufferData(gl.ARRAY_BUFFER, terrainTile.points, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                gpuResourceCache.putResource(vboCacheKey, vbo, terrainTile.points.length * 4);
                terrainTile.pointsVboStateKey = terrainTile.pointsStateKey;
            }
            else if (terrainTile.pointsVboStateKey != terrainTile.pointsStateKey) {
                gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
                gl.bufferSubData(gl.ARRAY_BUFFER, 0, terrainTile.points);
                terrainTile.pointsVboStateKey = terrainTile.pointsStateKey;
            }
            else {
                dc.currentGlContext.bindBuffer(gl.ARRAY_BUFFER, vbo);
            }

            gl.vertexAttribPointer(this.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
        };

        /**
         * Restores rendering state after drawing the most recent tile specified to
         * [beginRenderingTile]{@link Tessellator#beginRenderingTile}.
         * @param {DrawContext} dc The draw context.
         * @param {TerrainTile} terrainTile The terrain tile most recently rendered.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Tessellator.prototype.endRenderingTile = function (dc, terrainTile) {
            // Intentionally empty until there's some reason to add code here.
        };

        /**
         * Renders a specified terrain tile.
         * @param {DrawContext} dc The draw context.
         * @param {TerrainTile} terrainTile The terrain tile to render.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Tessellator.prototype.renderTile = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderTile", "missingTile"));
            }

            var gl = dc.currentGlContext,
                prim = gl.TRIANGLE_STRIP; // replace TRIANGLE_STRIP with LINE_STRIP to debug borders

            /*
             * Indices order in the buffer:
             *
             * base indices
             *
             * north border
             * south border
             * west border
             * east border
             *
             * north lores
             * south lores
             * west lores
             * east lores
             *
             * wireframe
             * outline
             */

            gl.drawElements(
                prim,
                this.numBaseIndices,
                gl.UNSIGNED_SHORT,
                this.baseIndicesOffset * 2);

            var level = terrainTile.level,
                neighborLevel;

            neighborLevel = terrainTile.neighborLevel(WorldWind.NORTH);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                gl.drawElements(
                    prim,
                    this.numIndicesLoresNorth,
                    gl.UNSIGNED_SHORT,
                    this.indicesLoresNorthOffset * 2);
            }
            else {
                gl.drawElements(
                    prim,
                    this.numIndicesNorth,
                    gl.UNSIGNED_SHORT,
                    this.indicesNorthOffset * 2);
            }

            neighborLevel = terrainTile.neighborLevel(WorldWind.SOUTH);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                gl.drawElements(
                    prim,
                    this.numIndicesLoresSouth,
                    gl.UNSIGNED_SHORT,
                    this.indicesLoresSouthOffset * 2);
            }
            else {
                gl.drawElements(
                    prim,
                    this.numIndicesSouth,
                    gl.UNSIGNED_SHORT,
                    this.indicesSouthOffset * 2);
            }

            neighborLevel = terrainTile.neighborLevel(WorldWind.WEST);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                gl.drawElements(
                    prim,
                    this.numIndicesLoresWest,
                    gl.UNSIGNED_SHORT,
                    this.indicesLoresWestOffset * 2);
            }
            else {
                gl.drawElements(
                    prim,
                    this.numIndicesWest,
                    gl.UNSIGNED_SHORT,
                    this.indicesWestOffset * 2);
            }

            neighborLevel = terrainTile.neighborLevel(WorldWind.EAST);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                gl.drawElements(
                    prim,
                    this.numIndicesLoresEast,
                    gl.UNSIGNED_SHORT,
                    this.indicesLoresEastOffset * 2);
            }
            else {
                gl.drawElements(
                    prim,
                    this.numIndicesEast,
                    gl.UNSIGNED_SHORT,
                    this.indicesEastOffset * 2);
            }
        };

        /**
         * Draws outlines of the triangles composing the tile.
         * @param {DrawContext} dc The current draw context.
         * @param {TerrainTile} terrainTile The tile to draw.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Tessellator.prototype.renderWireframeTile = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderWireframeTile", "missingTile"));
            }

            var gl = dc.currentGlContext;

            // Must turn off texture coordinates, which were turned on in beginRendering.
            if (this.vertexTexCoordLocation >= 0) {
                gl.disableVertexAttribArray(this.vertexTexCoordLocation);
            }

            gl.drawElements(
                gl.LINES,
                this.numWireframeIndices,
                gl.UNSIGNED_SHORT,
                this.wireframeIndicesOffset * 2);
        };

        /**
         * Draws the outer boundary of a specified terrain tile.
         * @param {DrawContext} dc The current draw context.
         * @param {TerrainTile} terrainTile The tile whose outer boundary to draw.
         * @throws {ArgumentError} If the specified tile is null or undefined.
         */
        Tessellator.prototype.renderTileOutline = function (dc, terrainTile) {
            if (!terrainTile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderTileOutline", "missingTile"));
            }

            var gl = dc.currentGlContext;

            // Must turn off texture coordinates, which were turned on in beginRendering.
            if (this.vertexTexCoordLocation >= 0) {
                gl.disableVertexAttribArray(this.vertexTexCoordLocation);
            }

            gl.drawElements(
                gl.LINE_LOOP,
                this.numOutlineIndices,
                gl.UNSIGNED_SHORT,
                this.outlineIndicesOffset * 2);
        };

        /**
         * Causes this terrain to perform the picking operations on the specified tiles, as appropriate for the draw
         * context's pick settings. Normally, this draws the terrain in a unique pick color and computes the picked
         * terrain position. When the draw context is set to region picking mode, this omits the computation of a picked
         * terrain position.
         * @param {DrawContext} dc The current draw context.
         * @param {Array} tileList The list of tiles to pick.
         * @param {Object} pickDelegate Indicates the object to use as the picked object's <code>userObject</code>.
         * If null, then this tessellator is used as the <code>userObject</code>.
         * @throws {ArgumentError} If either the draw context or the tile list are null or undefined.
         */
        Tessellator.prototype.pick = function (dc, tileList, pickDelegate) {
            if (!dc) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "pick", "missingDc"));
            }

            if (!tileList) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "pick", "missingList"));
            }

            var color = null,
                userObject = pickDelegate || this,
                position = new Position(0, 0, 0),
                pickableTiles = [];

            // Assemble a list of tiles that intersect the pick frustum. This eliminates unnecessary work for tiles that
            // do not contribute to the pick result.
            for (var i = 0, len = tileList.length; i < len; i++) {
                var tile = tileList[i];
                if (tile.extent.intersectsFrustum(dc.pickFrustum)) {
                    pickableTiles.push(tile);
                }
            }

            // Draw the pickable tiles in a unique pick color. Suppress this step when picking the terrain only. In this
            // case drawing to the pick framebuffer is unnecessary.
            if (!dc.pickTerrainOnly) {
                color = dc.uniquePickColor();
                this.drawPickTiles(dc, pickableTiles, color);
            }

            // Determine the terrain position at the pick point. If the terrain is picked, add a corresponding picked
            // object to the draw context. Suppress this step in region picking mode.
            if (!dc.regionPicking) {
                var ray = dc.navigatorState.rayFromScreenPoint(dc.pickPoint),
                    point = this.computeNearestIntersection(ray, pickableTiles);
                if (point) {
                    dc.globe.computePositionFromPoint(point[0], point[1], point[2], position);
                    position.altitude = dc.globe.elevationAtLocation(position.latitude, position.longitude);
                    dc.addPickedObject(new PickedObject(color, userObject, position, null, true));
                }
            }
        };

        // Internal function. Intentionally not documented.
        Tessellator.prototype.drawPickTiles = function (dc, tileList, color) {
            var gl = dc.currentGlContext;

            try {
                dc.findAndBindProgram(BasicProgram);
                dc.currentProgram.loadColor(gl, color);
                this.beginRendering(dc);

                for (var i = 0, len = tileList.length; i < len; i++) {
                    var tile = tileList[i];
                    this.beginRenderingTile(dc, tile);
                    this.renderTile(dc, tile);
                    this.endRenderingTile(dc, tile);
                }
            } finally {
                this.endRendering(dc);
            }
        };

        // Internal function. Intentionally not documented.
        Tessellator.prototype.computeNearestIntersection = function (line, tileList) {
            // Compute all intersections between the specified line and tile list.
            var results = [];
            for (var i = 0, len = tileList.length; i < len; i++) {
                this.computeIntersections(line, tileList[i], results);
            }

            if (results.length == 0) {
                return null; // no intersection
            } else {
                // Find and return the intersection nearest to the line's origin.
                var minDistance = Number.POSITIVE_INFINITY,
                    minIndex;
                for (i = 0, len = results.length; i < len; i++) {
                    var distance = line.origin.distanceToSquared(results[i]);
                    if (minDistance > distance) {
                        minDistance = distance;
                        minIndex = i;
                    }
                }

                return results[minIndex];
            }
        };

        // Internal function. Intentionally not documented.
        Tessellator.prototype.computeIntersections = function (line, tile, results) {
            var level = tile.level,
                neighborLevel,
                points = tile.points,
                elements,
                firstResult = results.length;

            // Translate the line from model coordinates to tile local coordinates.
            line.origin.subtract(tile.referencePoint);

            // Assemble the shared tile index geometry. This initializes the index properties used below.
            this.buildSharedGeometry(tile);

            // Compute any intersections with the tile's interior triangles..
            elements = this.baseIndices;
            WWMath.computeTriStripIntersections(line, points, elements, results);

            // Compute any intersections with the tile's south border triangles.
            neighborLevel = tile.neighborLevel(WorldWind.SOUTH);
            elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresSouth : this.indicesSouth;
            WWMath.computeTriStripIntersections(line, points, elements, results);

            // Compute any intersections with the tile's west border triangles.
            neighborLevel = tile.neighborLevel(WorldWind.WEST);
            elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresWest : this.indicesWest;
            WWMath.computeTriStripIntersections(line, points, elements, results);

            // Compute any intersections with the tile's east border triangles.
            neighborLevel = tile.neighborLevel(WorldWind.EAST);
            elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresEast : this.indicesEast;
            WWMath.computeTriStripIntersections(line, points, elements, results);

            // Compute any intersections with the tile's north border triangles.
            neighborLevel = tile.neighborLevel(WorldWind.NORTH);
            elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresNorth : this.indicesNorth;
            WWMath.computeTriStripIntersections(line, points, elements, results);

            // Translate the line and the intersection results from tile local coordinates to model coordinates.
            line.origin.add(tile.referencePoint);
            for (var i = firstResult, len = results.length; i < len; i++) {
                results[i].add(tile.referencePoint);
            }
        };

        /***********************************************************************
         * Internal methods - assume that arguments have been validated already.
         ***********************************************************************/

        Tessellator.prototype.createTopLevelTiles = function (dc) {
            this.topLevelTiles[dc.globeStateKey] = [];
            Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles[dc.globeStateKey]);
        };

        Tessellator.prototype.addTileOrDescendants = function (dc, tile) {
            if (this.tileMeetsRenderCriteria(dc, tile)) {
                this.addTile(dc, tile);
                return;
            }

            this.addTileDescendants(dc, tile);
        };

        Tessellator.prototype.addTileDescendants = function (dc, tile) {
            var nextLevel = tile.level.nextLevel();
            var subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);
            for (var index = 0; index < subTiles.length; index += 1) {
                var child = subTiles[index];

                child.update(dc);

                if (this.levels.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
                    this.addTileOrDescendants(dc, child);
                }
            }
        };

        Tessellator.prototype.addTile = function (dc, tile) {
            // Insert tile at index idx.
            var idx = this.tiles.length;
            this.tiles.push(tile);

            // Insert tile into corner data collection for later LOD neighbor analysis.
            var sector = tile.sector;

            // Corners of the tile.
            var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
                seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
                nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
                swTileCorner = [sector.minLatitude, sector.minLongitude].toString(),
                corner;

            corner = this.corners[swTileCorner];
            if (!corner) {
                this.corners[swTileCorner] = {'sw': idx}; //corner;
            }
            else {
                // assert(!corner.sw, "sw already defined");
                corner.sw = idx;
            }

            corner = this.corners[nwTileCorner];
            if (!corner) {
                this.corners[nwTileCorner] = {'nw': idx};
            }
            else {
                // assert(!corner.nw, "nw already defined");
                corner.nw = idx;
            }

            corner = this.corners[seTileCorner];
            if (!corner) {
                this.corners[seTileCorner] = {'se': idx};
            }
            else {
                // assert(!corver.se, "se already defined");
                corner.se = idx;
            }

            corner = this.corners[neTileCorner];
            if (!corner) {
                this.corners[neTileCorner] = {'ne': idx};
            }
            else {
                //assert(!corner.ne, "ne already defined");
                corner.ne = idx;
            }
        };

        Tessellator.prototype.refineNeighbors = function (dc) {
            var tileRefinementSet = {};

            for (var idx = 0, len = this.tiles.length; idx < len; idx += 1) {
                var tile = this.tiles[idx],
                    levelNumber = tile.level.levelNumber,
                    sector = tile.sector,
                    corner,
                    neighbor,
                    idx,
                    len;

                // Corners of the tile.
                var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
                    seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
                    nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
                    swTileCorner = [sector.minLatitude, sector.minLongitude].toString();

                corner = this.corners[neTileCorner];
                // assert(corner, "northeast corner not found");
                if (corner.hasOwnProperty('se')) {
                    neighbor = corner.se;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }
                if (corner.hasOwnProperty('nw')) {
                    neighbor = corner.nw;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }

                corner = this.corners[seTileCorner];
                // assert(corner, "southeast corner not found");
                if (corner.hasOwnProperty('ne')) {
                    neighbor = corner.ne;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }
                if (corner.hasOwnProperty('sw')) {
                    neighbor = corner.sw;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }

                corner = this.corners[nwTileCorner];
                // assert(corner, "northwest corner not found");
                if (corner.hasOwnProperty('ne')) {
                    neighbor = corner.ne;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }
                if (corner.hasOwnProperty('sw')) {
                    neighbor = corner.sw;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }

                corner = this.corners[swTileCorner];
                // assert(corner, "southwest corner not found");
                if (corner.hasOwnProperty('se')) {
                    neighbor = corner.se;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }
                if (corner.hasOwnProperty('nw')) {
                    neighbor = corner.nw;
                    if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
                        if (!tileRefinementSet[neighbor]) {
                            tileRefinementSet[neighbor] = true;
                        }
                    }
                }
            }

            // Partition tiles into those requiring refinement and those that don't need refinement.
            var tilesNeedingRefinement = [],
                tilesNotNeedingRefinement = [];
            for (idx = 0, len = this.tiles.length; idx < len; idx += 1) {
                tile = this.tiles[idx];
                if (tileRefinementSet[idx]) {
                    tilesNeedingRefinement.push(tile);
                }
                else {
                    tilesNotNeedingRefinement.push(tile);
                }
            }

            // When tiles need refinement, recur.
            if (tilesNeedingRefinement.length > 0) {
                // Reset refinement state.
                this.tiles = [];
                this.corners = {};

                // For tiles that don't need refinement, simply add the tile.
                for (idx = 0, len = tilesNotNeedingRefinement.length; idx < len; idx += 1) {
                    tile = tilesNotNeedingRefinement[idx];

                    this.addTile(dc, tile);
                }

                // For tiles that do need refinement, subdivide the tile and add its descendants.
                for (idx = 0, len = tilesNeedingRefinement.length; idx < len; idx += 1) {
                    var tile = tilesNeedingRefinement[idx];

                    this.addTileDescendants(dc, tile);
                }

                // Recur.
                this.refineNeighbors(dc);
            }
        };

        Tessellator.prototype.finishTessellating = function (dc) {
            for (var idx = 0, len = this.tiles.length; idx < len; idx += 1) {
                var tile = this.tiles[idx];
                this.setNeighbors(tile);
                this.regenerateTileGeometryIfNeeded(dc, tile);
                this.currentTiles.addTile(tile);
            }
        };

        Tessellator.prototype.setNeighbors = function (tile) {
            var sector = tile.sector;

            // Corners of the tile.
            var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
                seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
                nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
                swTileCorner = [sector.minLatitude, sector.minLongitude].toString();

            var neCorner = this.corners[neTileCorner],
                seCorner = this.corners[seTileCorner],
                nwCorner = this.corners[nwTileCorner],
                swCorner = this.corners[swTileCorner];

            var northIdx = -1, // neCorner.hasOwnProperty('se') ? neCorner.se : nwCorner.hasOwnProperty('sw') ? nwCorner.sw : -1,
                southIdx = -1, // seCorner.hasOwnProperty('ne') ? seCorner.ne : swCorner.hasOwnProperty('nw') ? swCorner.nw : -1,
                eastIdx = -1, // neCorner.hasOwnProperty('nw') ? neCorner.nw : seCorner.hasOwnProperty('sw') ? seCorner.sw : -1,
                westIdx = -1; //nwCorner.hasOwnProperty('ne') ? nwCorner.ne : swCorner.hasOwnProperty('se') ? swCorner.se : -1;

            if (neCorner.hasOwnProperty('se')) {
                northIdx = neCorner.se;
            }
            else if (nwCorner.hasOwnProperty('sw')) {
                northIdx = nwCorner.sw;
            }

            if (seCorner.hasOwnProperty('ne')) {
                southIdx = seCorner.ne;
            }
            else if (swCorner.hasOwnProperty('nw')) {
                southIdx = swCorner.nw;
            }

            if (neCorner.hasOwnProperty('nw')) {
                eastIdx = neCorner.nw;
            }
            else if (seCorner.hasOwnProperty('sw')) {
                eastIdx = seCorner.sw;
            }

            if (nwCorner.hasOwnProperty('ne')) {
                westIdx = nwCorner.ne;
            }
            else if (swCorner.hasOwnProperty('se')) {
                westIdx = swCorner.se;
            }

            tile.setNeighborLevel(WorldWind.NORTH, (northIdx >= 0) ? this.tiles[northIdx].level : null);
            tile.setNeighborLevel(WorldWind.SOUTH, (southIdx >= 0) ? this.tiles[southIdx].level : null);
            tile.setNeighborLevel(WorldWind.EAST, (eastIdx >= 0) ? this.tiles[eastIdx].level : null);
            tile.setNeighborLevel(WorldWind.WEST, (westIdx >= 0) ? this.tiles[westIdx].level : null);
        };

        Tessellator.prototype.isTileVisible = function (dc, tile) {
            if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
                return false;
            }

            return tile.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
        };

        Tessellator.prototype.tileMeetsRenderCriteria = function (dc, tile) {
            var s = this.detailControl;
            if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
                s *= 2;
            }
            return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
        };

        Tessellator.prototype.regenerateTileGeometryIfNeeded = function (dc, tile) {
            var stateKey = dc.globeStateKey + tile.stateKey + dc.verticalExaggeration;

            if (!tile.points || tile.pointsStateKey != stateKey) {
                this.regenerateTileGeometry(dc, tile);
                tile.pointsStateKey = stateKey;
            }
        };

        Tessellator.prototype.regenerateTileGeometry = function (dc, tile) {
            var numLat = tile.tileHeight + 1, // num points in each dimension is 1 more than the number of tile cells
                numLon = tile.tileWidth + 1,
                refPoint = tile.referencePoint,
                elevations = this.scratchElevations;

            // Allocate space for the tile's elevations.
            if (!elevations) {
                elevations = new Float64Array(numLat * numLon);
                this.scratchElevations = elevations;
            }

            // Allocate space for the tile's Cartesian coordinates.
            if (!tile.points) {
                tile.points = new Float32Array(numLat * numLon * 3);
            }

            // Retrieve the elevations for all points in the tile.
            WWUtil.fillArray(elevations, 0);
            dc.globe.elevationsForGrid(tile.sector, numLat, numLon, tile.texelSize, elevations);

            // Modify the elevations around the tile's border to match neighbors of lower resolution, if any.
            if (this.mustAlignNeighborElevations(dc, tile)) {
                this.alignNeighborElevations(dc, tile, elevations);
            }

            // Compute the tile's Cartesian coordinates relative to a local origin, called the reference point.
            WWUtil.multiplyArray(elevations, dc.verticalExaggeration);
            dc.globe.computePointsForGrid(tile.sector, numLat, numLon, elevations, refPoint, tile.points);

            // Establish a transform that is used later to move the tile coordinates into place relative to the globe.
            tile.transformationMatrix.setTranslation(refPoint[0], refPoint[1], refPoint[2]);
        };

        Tessellator.prototype.mustAlignNeighborElevations = function (dc, tile) {
            var level = tile.level,
                northLevel = tile.neighborLevel(WorldWind.NORTH),
                southLevel = tile.neighborLevel(WorldWind.SOUTH),
                eastLevel = tile.neighborLevel(WorldWind.EAST),
                westLevel = tile.neighborLevel(WorldWind.WEST);

            return (northLevel && northLevel.compare(level) < 0) ||
                (southLevel && southLevel.compare(level) < 0) ||
                (eastLevel && eastLevel.compare(level) < 0) ||
                (westLevel && westLevel.compare(level) < 0);
        };

        Tessellator.prototype.alignNeighborElevations = function (dc, tile, elevations) {
            var numLat = tile.tileHeight + 1, // num points in each dimension is 1 more than the number of tile cells
                numLon = tile.tileWidth + 1,
                level = tile.level,
                prevNumLat = Math.floor(numLat / 2) + 1, // num prev level points is 1 more than 1/2 the number of cells
                prevNumLon = Math.floor(numLon / 2) + 1,
                prevLevel = level.previousLevel(),
                prevElevations = this.scratchPrevElevations,
                neighborLevel,
                i, index, prevIndex;

            // Allocate space for the previous level elevations.
            if (!prevElevations) {
                prevElevations = new Float64Array(prevNumLat * prevNumLon);
                this.scratchPrevElevations = prevElevations;
            }

            // Retrieve the previous level elevations, using 1/2 the number of tile cells.
            WWUtil.fillArray(prevElevations, 0);
            dc.globe.elevationsForGrid(tile.sector, prevNumLat, prevNumLon, prevLevel.texelSize, prevElevations);

            // Use previous level elevations along the north edge when the northern neighbor is lower resolution.
            neighborLevel = tile.neighborLevel(WorldWind.NORTH);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                index = (numLat - 1) * numLon;
                prevIndex = (prevNumLat - 1) * prevNumLon;
                for (i = 0; i < prevNumLon; i++, index += 2, prevIndex += 1) {
                    elevations[index] = prevElevations[prevIndex];
                    if (i < prevNumLon - 1) {
                        elevations[index + 1] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + 1]);
                    }
                }
            }

            // Use previous level elevations along the south edge when the southern neighbor is lower resolution.
            neighborLevel = tile.neighborLevel(WorldWind.SOUTH);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                index = 0;
                prevIndex = 0;
                for (i = 0; i < prevNumLon; i++, index += 2, prevIndex += 1) {
                    elevations[index] = prevElevations[prevIndex];
                    if (i < prevNumLon - 1) {
                        elevations[index + 1] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + 1]);
                    }
                }
            }

            // Use previous level elevations along the east edge when the eastern neighbor is lower resolution.
            neighborLevel = tile.neighborLevel(WorldWind.EAST);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                index = numLon - 1;
                prevIndex = prevNumLon - 1;
                for (i = 0; i < prevNumLat; i++, index += 2 * numLon, prevIndex += prevNumLon) {
                    elevations[index] = prevElevations[prevIndex];
                    if (i < prevNumLat - 1) {
                        elevations[index + numLon] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + prevNumLon]);
                    }
                }
            }

            // Use previous level elevations along the west edge when the western neighbor is lower resolution.
            neighborLevel = tile.neighborLevel(WorldWind.WEST);
            if (neighborLevel && neighborLevel.compare(level) < 0) {
                index = 0;
                prevIndex = 0;
                for (i = 0; i < prevNumLat; i++, index += 2 * numLon, prevIndex += prevNumLon) {
                    elevations[index] = prevElevations[prevIndex];
                    if (i < prevNumLat - 1) {
                        elevations[index + numLon] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + prevNumLon]);
                    }
                }
            }
        };

        Tessellator.prototype.buildSharedGeometry = function () {
            // TODO: put all indices into a single buffer
            var tileWidth = this.levels.tileWidth,
                tileHeight = this.levels.tileHeight;

            if (!this.texCoords) {
                this.buildTexCoords(tileWidth, tileHeight);
            }

            if (!this.indices) {
                this.buildIndices(tileWidth, tileHeight);
            }
        };

        Tessellator.prototype.buildTexCoords = function (tileWidth, tileHeight) {
            var numCols = tileWidth + 1,
                numRows = tileHeight + 1,
                colDelta = 1 / tileWidth,
                rowDelta = 1 / tileHeight,
                buffer = new Float32Array(numCols * numRows * 2),
                index = 0;

            for (var row = 0, t = 0; row < numRows; row++, t += rowDelta) {
                if (row == numRows - 1) {
                    t = 1; // explicitly set the last row coordinate to ensure alignment
                }

                for (var col = 0, s = 0; col < numCols; col++, s += colDelta) {
                    if (col == numCols - 1) {
                        s = 1; // explicitly set the last column coordinate to ensure alignment
                    }

                    buffer[index++] = s;
                    buffer[index++] = t;
                }
            }

            this.texCoords = buffer;
        };

        Tessellator.prototype.buildIndices = function (tileWidth, tileHeight) {
            var vertexIndex; // The index of the vertex in the sample grid.

            // The number of vertices in each dimension is 1 more than the number of cells.
            var numLatVertices = tileHeight + 1,
                numLonVertices = tileWidth + 1,
                latIndexMid = tileHeight / 2,   // Assumption: tileHeight is even, so that there is a midpoint!
                lonIndexMid = tileWidth / 2;    // Assumption: tileWidth is even, so that there is a midpoint!

            // Each vertex has two indices associated with it: the current vertex index and the index of the row.
            // There are tileHeight rows.
            // There are tileHeight + 2 columns
            var numIndices = 2 * (numLatVertices - 3) * (numLonVertices - 2) + 2 * (numLatVertices - 3);
            var indices = [];

            // Inset core by one round of sub-tiles. Full grid is numLatVertices x numLonVertices. This must be used
            // to address vertices in the core as well.
            var index = 0;
            for (var lonIndex = 1; lonIndex < numLonVertices - 2; lonIndex += 1) {
                for (var latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
                    vertexIndex = lonIndex + latIndex * numLonVertices;

                    // Create a triangle strip joining each adjacent column of vertices, starting in the top left corner and
                    // proceeding to the right. The first vertex starts with the left row of vertices and moves right to create a
                    // counterclockwise winding order.
                    indices[index++] = vertexIndex;
                    indices[index++] = vertexIndex + 1;
                }

                // Insert indices to create 2 degenerate triangles:
                //      one for the end of the current row, and
                //      one for the beginning of the next row.
                indices[index++] = vertexIndex + 1;
                vertexIndex = (lonIndex + 1) + 1 * numLonVertices;
                indices[index++] = vertexIndex;
            }

            this.baseIndicesOffset = indices.length - numIndices;
            this.baseIndices = new Uint16Array(indices.slice(this.baseIndicesOffset));
            this.numBaseIndices = numIndices;

            // TODO: parameterize and refactor!!!!!
            // Software engineering notes: There are patterns being used in the following code that should be abstracted.
            // However, I suspect that the process of abstracting the patterns will result in as much code created
            // as gets removed. YMMV. If JavaScript had a meta-programming (a.k.a., macro) facility, that code would be
            // processed at "compile" time rather than "runtime". But it doesn't have such a facility that I know of.
            //
            // Patterns used:
            //  0) Each tile has four borders: north, south, east, and west.
            //  1) Counter-clockwise traversal around the outside results in clockwise meshes amendable to back-face elimination.
            //  2) For each vertex on the exterior, there corresponds a vertex on the interior that creates a diagonal.
            //  3) Each border construction is broken into three phases:
            //      a) The starting phase to generate the first half of the border,
            //      b) The middle phase, where a single vertex reference gets created, and
            //      c) The ending phase to complete the generation of the border.
            //  4) Each border is generated in two variants:
            //      a) one variant that mates with a tile at the same level of detail, and
            //      b) another variant that mates with a tile at the next lower level of detail.
            //  5) Borders that mate with the next lower level of detail are constrained to lie on even indices.
            //  6) Evenness is generated by ANDing the index with a mask that has 1's in all bits except for the LSB,
            //      which results in clearing the LSB os the index, making it even.
            //  7) The section that generates lower level LOD borders gives up any attempt to be optimal because of the
            //      complexity. Instead, correctness was preferred. That said, any performance lost is in the noise,
            //      since this code only gets run once.

            /*
             *  The following section of code generates full resolution boundary meshes. These are used to mate
             *  with neighboring tiles that are at the same level of detail.
             */
            // North border.
            numIndices = 2 * numLonVertices - 2;
            latIndex = numLatVertices - 1;

            // Corner vertex.
            lonIndex = numLonVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (lonIndex = numLonVertices - 2; lonIndex > 0; lonIndex -= 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
                indices[index++] = vertexIndex - numLonVertices;
            }

            // Corner vertex.
            lonIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesNorthOffset = indices.length - numIndices;
            this.indicesNorth = new Uint16Array(indices.slice(this.indicesNorthOffset));
            this.numIndicesNorth = numIndices;

            // South border.
            numIndices = 2 * numLonVertices - 2;
            latIndex = 0;

            // Corner vertex.
            lonIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (lonIndex = 1; lonIndex < numLonVertices - 1; lonIndex += 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
                indices[index++] = vertexIndex + numLonVertices;
            }

            // Corner vertex.
            lonIndex = numLonVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesSouthOffset = indices.length - numIndices;
            this.indicesSouth = new Uint16Array(indices.slice(this.indicesSouthOffset));
            this.numIndicesSouth = numIndices;

            // West border.
            numIndices = 2 * numLatVertices - 2;
            lonIndex = 0;

            // Corner vertex.
            latIndex = numLatVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (latIndex = numLatVertices - 2; latIndex > 0; latIndex -= 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
                indices[index++] = vertexIndex + 1;
            }

            // Corner vertex.
            latIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesWestOffset = indices.length - numIndices;
            this.indicesWest = new Uint16Array(indices.slice(this.indicesWestOffset));
            this.numIndicesWest = numIndices;

            // East border.
            numIndices = 2 * numLatVertices - 2;
            lonIndex = numLonVertices - 1;

            // Corner vertex.
            latIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
                indices[index++] = vertexIndex - 1;
            }

            // Corner vertex.
            latIndex = numLatVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesEastOffset = indices.length - numIndices;
            this.indicesEast = new Uint16Array(indices.slice(this.indicesEastOffset));
            this.numIndicesEast = numIndices;

            /*
             *  The following section of code generates "lores" low resolution boundary meshes. These are used to mate
             *  with neighboring tiles that are at a lower level of detail. The property of these lower level meshes is that
             *  they have half the number of vertices.
             *
             *  To generate the boundary meshes, force the use of only even boundary vertex indices.
             */
            // North border.
            numIndices = 2 * numLonVertices - 2;
            latIndex = numLatVertices - 1;

            // Corner vertex.
            lonIndex = numLonVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (lonIndex = numLonVertices - 2; lonIndex > 0; lonIndex -= 1) {
                // Exterior vertex rounded up to even index.
                vertexIndex = ((lonIndex + 1) & ~1) + latIndex * numLonVertices;
                indices[index++] = vertexIndex;

                // Interior vertex.
                vertexIndex = lonIndex + (latIndex - 1) * numLonVertices;
                indices[index++] = vertexIndex;
            }

            // Corner vertex.
            lonIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesLoresNorthOffset = indices.length - numIndices;
            this.indicesLoresNorth = new Uint16Array(indices.slice(this.indicesLoresNorthOffset));
            this.numIndicesLoresNorth = numIndices;

            // South border.
            numIndices = 2 * numLonVertices - 2;
            latIndex = 0;

            // Corner vertex.
            lonIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (lonIndex = 1; lonIndex < numLonVertices - 1; lonIndex += 1) {
                // Exterior Vertex rounded down to even index.
                vertexIndex = (lonIndex & ~1) + latIndex * numLonVertices;
                indices[index++] = vertexIndex;

                // Interior vertex.
                vertexIndex = lonIndex + (latIndex + 1) * numLonVertices;
                indices[index++] = vertexIndex;
            }

            // Corner vertex.
            lonIndex = numLonVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesLoresSouthOffset = indices.length - numIndices;
            this.indicesLoresSouth = new Uint16Array(indices.slice(this.indicesLoresSouthOffset));
            this.numIndicesLoresSouth = numIndices;

            // West border.
            numIndices = 2 * numLatVertices - 2;
            lonIndex = 0;

            // Corner vertex.
            latIndex = numLatVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (latIndex = numLatVertices - 2; latIndex > 0; latIndex -= 1) {
                // Exterior Vertex rounded up to even index.
                vertexIndex = lonIndex + ((latIndex + 1) & ~1) * numLonVertices;
                indices[index++] = vertexIndex;

                // Interior vertex.
                vertexIndex = (lonIndex + 1) + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
            }

            // Corner vertex.
            latIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesLoresWestOffset = indices.length - numIndices;
            this.indicesLoresWest = new Uint16Array(indices.slice(this.indicesLoresWestOffset));
            this.numIndicesLoresWest = numIndices;

            // East border.
            numIndices = 2 * numLatVertices - 2;
            lonIndex = numLonVertices - 1;

            // Corner vertex.
            latIndex = 0;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            for (latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
                // Exterior vertex rounded down to even index.
                vertexIndex = lonIndex + (latIndex & ~1) * numLonVertices;
                indices[index++] = vertexIndex;

                // Interior vertex.
                vertexIndex = (lonIndex - 1) + latIndex * numLonVertices;
                indices[index++] = vertexIndex;
            }

            // Corner vertex.
            latIndex = numLatVertices - 1;
            vertexIndex = lonIndex + latIndex * numLonVertices;
            indices[index++] = vertexIndex;

            this.indicesLoresEastOffset = indices.length - numIndices;
            this.indicesLoresEast = new Uint16Array(indices.slice(this.indicesLoresEastOffset));
            this.numIndicesLoresEast = numIndices;

            var wireframeIndices = this.buildWireframeIndices(tileWidth, tileHeight);
            var outlineIndices = this.buildOutlineIndices(tileWidth, tileHeight);

            indices = indices.concat(wireframeIndices);
            this.wireframeIndicesOffset = indices.length - this.numWireframeIndices;

            indices = indices.concat(outlineIndices);
            this.outlineIndicesOffset = indices.length - this.numOutlineIndices;

            this.indices = new Uint16Array(indices);
        };

        Tessellator.prototype.buildWireframeIndices = function (tileWidth, tileHeight) {
            // The wireframe representation draws the vertices that appear on the surface.

            // The number of vertices in each dimension is 1 more than the number of cells.
            var numLatVertices = tileHeight + 1;
            var numLonVertices = tileWidth + 1;

            // Allocate an array to hold the computed indices.
            var numIndices = 2 * tileWidth * numLatVertices + 2 * tileHeight * numLonVertices;
            var indices = [];

            var rowStride = numLonVertices;

            var index = 0,
                lonIndex,
                latIndex,
                vertexIndex;

            // Add a line between each row to define the horizontal cell outlines.
            for (latIndex = 0; latIndex < numLatVertices; latIndex += 1) {
                for (lonIndex = 0; lonIndex < tileWidth; lonIndex += 1) {
                    vertexIndex = lonIndex + latIndex * rowStride;
                    indices[index] = vertexIndex;
                    indices[index + 1] = (vertexIndex + 1);
                    index += 2
                }
            }

            // Add a line between each column to define the vertical cell outlines.
            for (lonIndex = 0; lonIndex < numLonVertices; lonIndex += 1) {
                for (latIndex = 0; latIndex < tileHeight; latIndex += 1) {
                    vertexIndex = lonIndex + latIndex * rowStride;
                    indices[index] = vertexIndex;
                    indices[index + 1] = (vertexIndex + rowStride);
                    index += 2;
                }
            }

            this.numWireframeIndices = numIndices;
            return indices;
        };

        Tessellator.prototype.buildOutlineIndices = function (tileWidth, tileHeight) {
            // The outline representation traces the tile's outer edge on the surface.

            // The number of vertices in each dimension is 1 more than the number of cells.
            var numLatVertices = tileHeight + 1;
            var numLonVertices = tileWidth + 1;

            // Allocate an array to hold the computed indices.
            var numIndices = 2 * (numLatVertices - 2) + 2 * numLonVertices + 1;
            var indices = [];

            var rowStride = numLatVertices;

            var index = 0,
                lonIndex,
                latIndex,
                vertexIndex;

            // Bottom row, starting at the left and going right.
            latIndex = 0;
            for (lonIndex = 0; lonIndex < numLonVertices; lonIndex += 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index] = vertexIndex;
                index += 1;
            }

            // Right column, starting at the bottom and going up.
            lonIndex = numLonVertices - 1;
            for (latIndex = 1; latIndex < numLatVertices; latIndex += 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index] = vertexIndex;
                index += 1
            }

            // Top row, starting on the right and going to the left.
            latIndex = numLatVertices - 1;
            for (lonIndex = numLonVertices - 1; lonIndex >= 0; lonIndex -= 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index] = vertexIndex;
                index += 1
            }

            // Leftmost column, starting at the top and going down.
            lonIndex = 0;
            for (latIndex = numLatVertices - 1; latIndex >= 0; latIndex -= 1) {
                vertexIndex = lonIndex + latIndex * numLonVertices;
                indices[index] = vertexIndex;
                index += 1
            }

            this.numOutlineIndices = numIndices;
            return indices;
        };

        Tessellator.prototype.cacheSharedGeometryVBOs = function (dc) {
            var gl = dc.currentGlContext,
                gpuResourceCache = dc.gpuResourceCache;

            var texCoordVbo = gpuResourceCache.resourceForKey(this.texCoordVboCacheKey);
            if (!texCoordVbo) {
                texCoordVbo = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVbo);
                gl.bufferData(gl.ARRAY_BUFFER, this.texCoords, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                gpuResourceCache.putResource(this.texCoordVboCacheKey, texCoordVbo, this.texCoords.length * 4 / 2);
            }

            var indicesVbo = gpuResourceCache.resourceForKey(this.indicesVboCacheKey);
            if (!indicesVbo) {
                indicesVbo = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesVbo);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                gpuResourceCache.putResource(this.indicesVboCacheKey, indicesVbo, this.indices.length * 2);
            }
        };

        return Tessellator;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Globe
 */
define('globe/Globe',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/BoundingBox',
        '../globe/ElevationModel',
        '../geom/Line',
        '../geom/Location',
        '../util/Logger',
        '../geom/Position',
        '../projections/ProjectionWgs84',
        '../geom/Sector',
        '../globe/Tessellator',
        '../geom/Vec3',
        '../util/WWMath'],
    function (Angle,
              ArgumentError,
              BoundingBox,
              ElevationModel,
              Line,
              Location,
              Logger,
              Position,
              ProjectionWgs84,
              Sector,
              Tessellator,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs an ellipsoidal Globe with default radii for Earth (WGS84).
         * @alias Globe
         * @constructor
         * @classdesc Represents an ellipsoidal globe. The default configuration represents Earth but may be changed.
         * To configure for another planet, set the globe's equatorial and polar radii properties and its
         * eccentricity-squared property.
         * <p>
         * A globe uses a Cartesian coordinate system whose origin is at the globe's center. It's Y axis points to the
         * north pole, the Z axis points to the intersection of the prime meridian and the equator,
         * and the X axis completes a right-handed coordinate system, is in the equatorial plane and 90 degrees east
         * of the Z axis.
         * <p>
         *     All Cartesian coordinates and elevations are in meters.

         * @param {ElevationModel} elevationModel The elevation model to use for this globe.
         * @param {GeographicProjection} projection The projection to apply to the globe. May be null or undefined,
         * in which case no projection is applied and the globe is a WGS84 ellipsoid.
         * @throws {ArgumentError} If the specified elevation model is null or undefined.
         */
        var Globe = function (elevationModel, projection) {
            if (!elevationModel) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
                    "constructor", "Elevation model is null or undefined."));
            }
            /**
             * This globe's elevation model.
             * @type {ElevationModel}
             */
            this.elevationModel = elevationModel;

            /**
             * This globe's equatorial radius.
             * @type {Number}
             * @default 6378137.0 meters
             */
            this.equatorialRadius = 6378137.0;

            /**
             * This globe's polar radius.
             * @type {Number}
             * @default 6356752.3 meters
             */
            this.polarRadius = 6356752.3;

            /**
             * This globe's eccentricity squared.
             * @type {Number}
             * @default 0.00669437999013
             */
            this.eccentricitySquared = 0.00669437999013;

            /**
             * The tessellator used to create this globe's terrain.
             * @type {Tessellator}
             */
            this.tessellator = new Tessellator();

            // Internal. Intentionally not documented.
            this._projection = projection || new ProjectionWgs84();

            // Internal. Intentionally not documented.
            this._offset = 0;

            // Internal. Intentionally not documented.
            this.offsetVector = new Vec3(0, 0, 0);

            // A unique ID for this globe. Intentionally not documented.
            this.id = ++Globe.idPool;

            this._stateKey = "globe " + this.id.toString() + " ";
        };

        Globe.idPool = 0; // Used to assign unique IDs to globes for use in their state keys.

        Object.defineProperties(Globe.prototype, {
            /**
             * A string identifying this globe's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof Globe.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return this._stateKey + this.elevationModel.stateKey + "offset " + this.offset.toString() + " "
                        + this.projection.stateKey;
                }
            },

            /**
             * Indicates whether this globe is 2D and continuous with itself -- that it should scroll continuously
             * horizontally.
             * @memberof Globe.prototype
             * @readonly
             * @type {Boolean}
             */
            continuous: {
                get: function () {
                    return this.projection.continuous;
                }
            },

            /**
             * The projection used by this globe.
             * @memberof Globe.prototype
             * @default {@link ProjectionWgs84}
             * @type {GeographicProjection}
             */
            projection: {
                get: function () {
                    return this._projection;
                },
                set: function (projection) {
                    if (!projection) {
                        throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
                            "projection", "missingProjection"));
                    }

                    if (this.projection != projection) {
                        this.tessellator = new Tessellator();
                    }
                    this._projection = projection;
                }
            },

            /**
             * The projection limits of the associated projection.
             * @memberof Globe.prototype
             * @type {Sector}
             */
            projectionLimits: {
                get: function () {
                    return this._projection.projectionLimits;
                }
            },

            /**
             * An offset to apply to this globe when translating between Geographic positions and Cartesian points.
             * Used during scrolling to position points appropriately.
             * Applications typically do not access this property. It is used by the associated globe.
             * @memberof Globe.prototype
             * @type {Number}
             */
            offset: {
                get: function () {
                    return this._offset;
                },
                set: function (offset) {
                    this._offset = offset;
                    this.offsetVector[0] = offset * 2 * Math.PI * this.equatorialRadius;
                }
            }
        });

        /**
         * Indicates whether this is a 2D globe.
         * @returns {Boolean} true if this is a 2D globe, otherwise false.
         */
        Globe.prototype.is2D = function () {
            return this.projection.is2D;
        };

        /**
         * Computes a Cartesian point from a specified position.
         * See this class' Overview section for a description of the Cartesian coordinate system used.
         * @param {Number} latitude The position's latitude.
         * @param {Number} longitude The position's longitude.
         * @param {Number} altitude The position's altitude.
         * @param {Vec3} result A reference to a pre-allocated {@link Vec3} in which to return the computed X,
         * Y and Z Cartesian coordinates.
         * @returns {Vec3} The result argument.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.computePointFromPosition = function (latitude, longitude, altitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointFromPosition",
                    "missingResult"));
            }

            return this.projection.geographicToCartesian(this, latitude, longitude, altitude, this.offsetVector, result);
        };

        /**
         * Computes a Cartesian point from a specified location.
         * See this class' Overview section for a description of the Cartesian coordinate system used.
         * @param {Number} latitude The position's latitude.
         * @param {Number} longitude The position's longitude.
         * @param {Vec3} result A reference to a pre-allocated {@link Vec3} in which to return the computed X,
         * Y and Z Cartesian coordinates.
         * @returns {Vec3} The result argument.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.computePointFromLocation = function (latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointFromLocation",
                    "missingResult"));
            }

            return this.computePointFromPosition(latitude, longitude, 0, result);
        };

        /**
         * Computes a grid of Cartesian points within a specified sector and relative to a specified Cartesian
         * reference point.
         * <p>
         * This method is used to compute a collection of points within a sector. It is used by tessellators to
         * efficiently generate a tile's interior points. The number of points to generate is indicated by the numLon
         * and numLat parameters.
         * <p>
         * For each implied position within the sector, an elevation value is specified via an array of elevations. The
         * calculation at each position incorporates the associated elevation. There must be numLat x numLon elevations
         * in the array.
         *
         * @param {Sector} sector The sector for which to compute the points.
         * @param {Number} numLat The number of latitudinal points in the grid.
         * @param {Number} numLon The number of longitudinal points in the grid.
         * @param {Number[]} elevations An array of elevations to incorporate in the point calculations. There must be
         * one elevation value in the array for each generated point. Elevations are in meters. There must be
         * numLat x numLon elevations in the array.
         * @param {Vec3} referencePoint The X, Y and Z Cartesian coordinates to subtract from the computed coordinates.
         * This makes the computed coordinates relative to the specified point.
         * @param {Float32Array} result A typed array to hold the computed coordinates. It must be at least of
         * size numLat x numLon. The points are returned in row major order, beginning with the row of minimum latitude.
         * @returns {Float32Array} The specified result argument.
         * @throws {ArgumentError} if the specified sector, elevations array or results arrays are null or undefined, or
         * if the lengths of any of the arrays are insufficient.
         */
        Globe.prototype.computePointsForGrid = function (sector, numLat, numLon, elevations, referencePoint, result) {
            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
                    "computePointsFromPositions", "missingSector"));
            }

            if (numLat < 1 || numLon < 1) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
                    "Number of latitude or longitude locations is less than one."));
            }

            var numPoints = numLat * numLon;
            if (!elevations || elevations.length < numPoints) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
                    "Elevations array is null, undefined or insufficient length."));
            }

            if (!result || result.length < numPoints) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePointsFromPositions",
                    "Result array is null, undefined or insufficient length."));
            }

            return this.projection.geographicToCartesianGrid(this, sector, numLat, numLon, elevations, referencePoint,
                this.offsetVector, result);
        };

        /**
         * Computes a geographic position from a specified Cartesian point.
         *
         * See this class' Overview section for a description of the Cartesian coordinate system used.
         *
         * @param {Number} x The X coordinate.
         * @param {Number} y The Y coordinate.
         * @param {Number} z The Z coordinate.
         * @param {Position} result A pre-allocated {@link Position} instance in which to return the computed position.
         * @returns {Position} The specified result position.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.computePositionFromPoint = function (x, y, z, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "computePositionFromPoint",
                    "missingResult"));
            }

            this.projection.cartesianToGeographic(this, x, y, z, this.offsetVector, result);

            // Wrap if the globe is continuous.
            if (this.continuous) {
                if (result.longitude < -180) {
                    result.longitude += 360;
                } else if (result.longitude > 180) {
                    result.longitude -= 360;
                }
            }

            return result;
        };

        /**
         * Computes the radius of this globe at a specified location.
         * @param {Number} latitude The locations' latitude.
         * @param {Number} longitude The locations' longitude.
         * @returns {Number} The radius at the specified location.
         */
        Globe.prototype.radiusAt = function (latitude, longitude) {
            var sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
                rpm = this.equatorialRadius / Math.sqrt(1.0 - this.eccentricitySquared * sinLat * sinLat);

            return rpm * Math.sqrt(1.0 + (this.eccentricitySquared * this.eccentricitySquared - 2.0 * this.eccentricitySquared) * sinLat * sinLat);
        };

        /**
         * Computes the normal vector to this globe's surface at a specified location.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
         * normal vector is unit length.
         * @returns {Vec3} The specified result vector.  The returned normal vector is unit length.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.surfaceNormalAtLocation = function (latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "surfaceNormalAtLocation",
                    "missingResult"));
            }

            if (this.is2D()) {
                result[0] = 0;
                result[1] = 0;
                result[2] = 1;

                return result;
            }

            var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
                cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
                sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
                sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS),
                eqSquared = this.equatorialRadius * this.equatorialRadius,
                polSquared = this.polarRadius * this.polarRadius;

            result[0] = cosLat * sinLon / eqSquared;
            result[1] = (1 - this.eccentricitySquared) * sinLat / polSquared;
            result[2] = cosLat * cosLon / eqSquared;

            return result.normalize();
        };

        /**
         * Computes the normal vector to this globe's surface at a specified Cartesian point.
         * @param {Number} x The point's X coordinate.
         * @param {Number} y The point's Y coordinate.
         * @param {Number} z The point's Z coordinate.
         * @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
         * normal vector is unit length.
         * @returns {Vec3} The specified result vector.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.surfaceNormalAtPoint = function (x, y, z, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "surfaceNormalAtPoint",
                    "missingResult"));
            }

            // For backwards compatibility, check whether the projection defines a surfaceNormalAtPoint function
            // before calling it. If it's not available, use the old code to compute the normal.
            if (this.projection.surfaceNormalAtPoint) {
                return this.projection.surfaceNormalAtPoint(this, x, y, z, result);
            }

            if (this.is2D()) {
                result[0] = 0;
                result[1] = 0;
                result[2] = 1;

                return result;
            }

            var eSquared = this.equatorialRadius * this.equatorialRadius,
                polSquared = this.polarRadius * this.polarRadius;

            result[0] = x / eSquared;
            result[1] = y / polSquared;
            result[2] = z / eSquared;

            return result.normalize();
        };

        /**
         * Computes the north-pointing tangent vector to this globe's surface at a specified location.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
         * tangent vector is unit length.
         * @returns {Vec3} The specified result vector.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.northTangentAtLocation = function (latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "northTangentAtLocation",
                    "missingResult"));
            }

            return this.projection.northTangentAtLocation(this, latitude, longitude, result);
        };

        /**
         * Computes the north-pointing tangent vector to this globe's surface at a specified Cartesian point.
         * @param {Number} x The point's X coordinate.
         * @param {Number} y The point's Y coordinate.
         * @param {Number} z The point's Z coordinate.
         * @param {Vec3} result A pre-allocated {@Link Vec3} instance in which to return the computed vector. The returned
         * tangent vector is unit length.
         * @returns {Vec3} The specified result vector.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        Globe.prototype.northTangentAtPoint = function (x, y, z, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "northTangentAtPoint",
                    "missingResult"));
            }

            return this.projection.northTangentAtPoint(this, x, y, z, this.offsetVector, result);
        };

        /**
         * Indicates whether this globe intersects a specified frustum.
         * @param {Frustum} frustum The frustum to test.
         * @returns {Boolean} true if this globe intersects the frustum, otherwise false.
         * @throws {ArgumentError} If the specified frustum is null or undefined.
         */
        Globe.prototype.intersectsFrustum = function (frustum) {
            if (!frustum) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsFrustum", "missingFrustum"));
            }

            if (this.is2D()) {
                var bbox = new BoundingBox();
                bbox.setToSector(Sector.FULL_SPHERE, this, this.elevationModel.minElevation,
                    this.elevationModel.maxElevation);

                return bbox.intersectsFrustum(frustum);
            }

            if (frustum.far.distance <= this.equatorialRadius)
                return false;
            if (frustum.left.distance <= this.equatorialRadius)
                return false;
            if (frustum.right.distance <= this.equatorialRadius)
                return false;
            if (frustum.top.distance <= this.equatorialRadius)
                return false;
            if (frustum.bottom.distance <= this.equatorialRadius)
                return false;
            if (frustum.near.distance <= this.equatorialRadius)
                return false;

            return true;
        };

        /**
         * Computes the first intersection of this globe with a specified line. The line is interpreted as a ray;
         * intersection points behind the line's origin are ignored.
         * @param {Line} line The line to intersect with this globe.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
         * @returns {boolean} true If the ray intersects the globe, otherwise false.
         * @throws {ArgumentError} If the specified line or result argument is null or undefined.
         */
        Globe.prototype.intersectsLine = function (line, result) {
            if (!line) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectWithRay", "missingLine"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsLine", "missingResult"));
            }

            // Taken from "Mathematics for 3D Game Programming and Computer Graphics, Third Edition", Section 6.2.3.
            //
            // Note that the parameter n from equations 6.70 and 6.71 is omitted here. For an ellipsoidal globe this
            // parameter is always 1, so its square and its product with any other value simplifies to the identity.

            var vx = line.direction[0],
                vy = line.direction[1],
                vz = line.direction[2],
                sx = line.origin[0],
                sy = line.origin[1],
                sz = line.origin[2],
                t;

            if (this.is2D()) {
                if (vz == 0 && sz != 0) { // ray is parallel to and not coincident with the XY plane
                    return false;
                }

                t = -sz / vz; // intersection distance, simplified for the XY plane
                if (t < 0) { // intersection is behind the ray's origin
                    return false;
                }

                result[0] = sx + vx * t;
                result[1] = sy + vy * t;
                result[2] = sz + vz * t;

                return true;
            } else {
                var eqr = this.equatorialRadius, eqr2 = eqr * eqr, m = eqr / this.polarRadius, m2 = m * m, a, b, c, d;

                a = vx * vx + m2 * vy * vy + vz * vz;
                b = 2 * (sx * vx + m2 * sy * vy + sz * vz);
                c = sx * sx + m2 * sy * sy + sz * sz - eqr2;
                d = b * b - 4 * a * c; // discriminant

                if (d < 0) {
                    return false;
                }

                t = (-b - Math.sqrt(d)) / (2 * a);
                // check if the nearest intersection point is in front of the origin of the ray
                if (t > 0) {
                    result[0] = sx + vx * t;
                    result[1] = sy + vy * t;
                    result[2] = sz + vz * t;
                    return true;
                }

                t = (-b + Math.sqrt(d)) / (2 * a);
                // check if the second intersection point is in the front of the origin of the ray
                if (t > 0) {
                    result[0] = sx + vx * t;
                    result[1] = sy + vy * t;
                    result[2] = sz + vz * t;
                    return true;
                }

                // the intersection points were behind the origin of the provided line
                return false;
            }
        };

        /**
         * Returns the time at which any elevations associated with this globe last changed.
         * @returns {Number} The time in milliseconds relative to the Epoch of the most recent elevation change.
         */
        Globe.prototype.elevationTimestamp = function () {
            return this.elevationModel.timestamp;
        };

        /**
         * Returns this globe's minimum elevation.
         * @returns {Number} This globe's minimum elevation.
         */
        Globe.prototype.minElevation = function () {
            return this.elevationModel.minElevation
        };

        /**
         * Returns this globe's maximum elevation.
         * @returns {Number} This globe's maximum elevation.
         */
        Globe.prototype.maxElevation = function () {
            return this.elevationModel.maxElevation
        };

        /**
         * Returns the minimum and maximum elevations within a specified sector of this globe.
         * @param {Sector} sector The sector for which to determine extreme elevations.
         * @returns {Number[]} The An array containing the minimum and maximum elevations.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        Globe.prototype.minAndMaxElevationsForSector = function (sector) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "minAndMaxElevationsForSector",
                        "missingSector"));
            }

            return this.elevationModel.minAndMaxElevationsForSector(sector);
        };

        /**
         * Returns the elevation at a specified location.
         * @param {Number} latitude The location's latitude in degrees.
         * @param {Number} longitude The location's longitude in degrees.
         * @returns {Number} The elevation at the specified location, in meters. Returns zero if the location is
         * outside the coverage area of this elevation model.
         */
        Globe.prototype.elevationAtLocation = function (latitude, longitude) {
            return this.elevationModel.elevationAtLocation(latitude, longitude);
        };

        /**
         * Returns the elevations at locations within a specified sector.
         * @param {Sector} sector The sector for which to determine the elevations.
         * @param {Number} numLat The number of latitudinal sample locations within the sector.
         * @param {Number} numLon The number of longitudinal sample locations within the sector.
         * @param {Number} targetResolution The desired elevation resolution, in radians. (To compute radians from
         * meters, divide the number of meters by the globe's radius.)
         * @param {Number[]} result An array in which to return the requested elevations.
         * @returns {Number} The resolution actually achieved, which may be greater than that requested if the
         * elevation data for the requested resolution is not currently available.
         * @throws {ArgumentError} If the specified sector or result array is null or undefined, or if either of the
         * specified numLat or numLon values is less than one.
         */
        Globe.prototype.elevationsForGrid = function (sector, numLat, numLon, targetResolution, result) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "elevationsForSector", "missingSector"));
            }

            if (numLat <= 0 || numLon <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
                    "elevationsForSector", "numLat or numLon is less than 1"));
            }

            if (!result || result.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
                    "elevationsForSector", "missingArray"));
            }

            return this.elevationModel.elevationsForGrid(sector, numLat, numLon, targetResolution, result);
        };

        return Globe;
    }
)
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GpuResourceCache
 */
define('cache/GpuResourceCache',[
        '../util/AbsentResourceList',
        '../error/ArgumentError',
        '../util/ImageSource',
        '../util/Logger',
        '../cache/MemoryCache',
        '../render/Texture'
    ],
    function (AbsentResourceList,
              ArgumentError,
              ImageSource,
              Logger,
              MemoryCache,
              Texture) {
        "use strict";

        /**
         * Constructs a GPU resource cache for a specified size and low-water value.
         * @alias GpuResourceCache
         * @constructor
         * @classdesc Maintains a cache of GPU resources such as textures and GLSL programs.
         * Applications typically do not interact with this class unless they create their own shapes.
         * @param {Number} capacity The cache capacity, in bytes.
         * @param {Number} lowWater The number of bytes to clear the cache to when it exceeds its capacity.
         * @throws {ArgumentError} If the specified capacity is undefined, 0 or negative or the low-water value is
         * undefined, negative or not less than the capacity.
         */
        var GpuResourceCache = function (capacity, lowWater) {
            if (!capacity || capacity < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "constructor",
                        "Specified cache capacity is undefined, 0 or negative."));
            }

            if (!lowWater || lowWater < 0 || lowWater >= capacity) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "constructor",
                        "Specified cache low-water value is undefined, negative or not less than the capacity."));
            }

            // Private. Holds the actual cache entries.
            this.entries = new MemoryCache(capacity, lowWater);

            // Private. Counter for generating cache keys.
            this.cacheKeyPool = 0;

            // Private. List of retrievals currently in progress.
            this.currentRetrievals = {};

            // Private. Identifies requested resources that whose retrieval failed.
            this.absentResourceList = new AbsentResourceList(3, 60e3);
        };

        Object.defineProperties(GpuResourceCache.prototype, {
            /**
             * Indicates the capacity of this cache in bytes.
             * @type {Number}
             * @readonly
             * @memberof GpuResourceCache.prototype
             */
            capacity: {
                get: function () {
                    return this.entries.capacity;
                }
            },

            /**
             * Indicates the low-water value for this cache in bytes, the size this cache is cleared to when it
             * exceeds its capacity.
             * @type {Number}
             * @readonly
             * @memberof GpuResourceCache.prototype
             */
            lowWater: {
                get: function () {
                    return this.entries.lowWater;
                }
            },

            /**
             * Indicates the number of bytes currently used by this cache.
             * @type {Number}
             * @readonly
             * @memberof GpuResourceCache.prototype
             */
            usedCapacity: {
                get: function () {
                    return this.entries.usedCapacity;
                }
            },

            /**
             * Indicates the number of free bytes in this cache.
             * @type {Number}
             * @readonly
             * @memberof GpuResourceCache.prototype
             */
            freeCapacity: {
                get: function () {
                    return this.entries.freeCapacity;
                }
            }
        });

        /**
         * Creates a cache key unique to this cache, typically for a resource about to be added to this cache.
         * @returns {String} The generated cache key.
         */
        GpuResourceCache.prototype.generateCacheKey = function () {
            return "GpuResourceCache " + ++this.cacheKeyPool;
        };

        /**
         * Adds a specified resource to this cache. Replaces the existing resource for the specified key if the
         * cache currently contains a resource for that key.
         * @param {String|ImageSource} key The key or image source of the resource to add.
         * @param {Object} resource The resource to add to the cache.
         * @param {Number} size The resource's size in bytes. Must be greater than 0.
         * @throws {ArgumentError} If either the key or resource arguments is null or undefined
         * or if the specified size is less than 1.
         */
        GpuResourceCache.prototype.putResource = function (key, resource, size) {
            if (!key) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource", "missingKey."));
            }

            if (!resource) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource", "missingResource."));
            }

            if (!size || size < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GpuResourceCache", "putResource",
                        "The specified resource size is undefined or less than 1."));
            }

            var entry = {
                resource: resource
            };

            this.entries.putEntry(key instanceof ImageSource ? key.key : key, entry, size);
        };

        /**
         * Returns the resource associated with a specified key.
         * @param {String|ImageSource} key The key or image source of the resource to find.
         * @returns {Object} The resource associated with the specified key, or null if the resource is not in
         * this cache or the specified key is null or undefined.
         */
        GpuResourceCache.prototype.resourceForKey = function (key) {
            var entry = (key instanceof ImageSource)
                ? this.entries.entryForKey(key.key) : this.entries.entryForKey(key);

            return entry ? entry.resource : null;
        };

        /**
         * Indicates whether a specified resource is in this cache.
         * @param {String|ImageSource} key The key or image source of the resource to find.
         * @returns {Boolean} true If the resource is in this cache, false if the resource
         * is not in this cache or the specified key is null or undefined.
         */
        GpuResourceCache.prototype.containsResource = function (key) {
            return this.entries.containsKey(key instanceof ImageSource ? key.key : key);
        };

        /**
         * Removes the specified resource from this cache. The cache is not modified if the specified key is null or
         * undefined or does not correspond to an entry in the cache.
         * @param {String|ImageSource} key The key or image source of the resource to remove.
         */
        GpuResourceCache.prototype.removeResource = function (key) {
            this.entries.removeEntry(key instanceof ImageSource ? key.key : key);
        };

        /**
         * Removes all resources from this cache.
         */
        GpuResourceCache.prototype.clear = function () {
            this.entries.clear(false);
        };

        /**
         * Retrieves an image and adds it to this cache when it arrives. If the specified image source is a URL, a
         * retrieval request for the image is made and this method returns immediately with a value of null. A redraw
         * event is generated when the image subsequently arrives and is added to this cache. If the image source is an
         * {@link ImageSource}, the image is used immediately and this method returns the {@link Texture} created and
         * cached for the image. No redraw event is generated in this case.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {String|ImageSource} imageSource The image source, either a {@link ImageSource} or a String
         * giving the URL of the image.
         * @param {GL.enum} wrapMode Optional. Specifies the wrap mode of the texture. Defaults to gl.CLAMP_TO_EDGE
         * @returns {Texture} The {@link Texture} created for the image if the specified image source is an
         * {@link ImageSource}, otherwise null.
         */
        GpuResourceCache.prototype.retrieveTexture = function (gl, imageSource, wrapMode) {
            if (!imageSource) {
                return null;
            }

            if (imageSource instanceof ImageSource) {
                var t = new Texture(gl, imageSource.image, wrapMode);
                this.putResource(imageSource.key, t, t.size);
                return t;
            }

            if (this.currentRetrievals[imageSource] || this.absentResourceList.isResourceAbsent(imageSource)) {
                return null;
            }

            var cache = this,
                image = new Image();

            image.onload = function () {
                Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + imageSource);

                var texture = new Texture(gl, image, wrapMode);

                cache.putResource(imageSource, texture, texture.size);

                delete cache.currentRetrievals[imageSource];
                cache.absentResourceList.unmarkResourceAbsent(imageSource);

                // Send an event to request a redraw.
                var e = document.createEvent('Event');
                e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                window.dispatchEvent(e);
            };

            image.onerror = function () {
                delete cache.currentRetrievals[imageSource];
                cache.absentResourceList.markResourceAbsent(imageSource);
                Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + imageSource);
            };

            this.currentRetrievals[imageSource] = imageSource;
            image.crossOrigin = 'anonymous';
            image.src = imageSource;

            return null;
        };

        return GpuResourceCache;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PickedObjectList
 */
define('pick/PickedObjectList',[],
    function () {
        "use strict";

        /**
         * Constructs a picked-object list.
         * @alias PickedObjectList
         * @constructor
         * @classdesc Holds a collection of picked objects.
         */
        var PickedObjectList = function () {
            /**
             * The picked objects.
             * @type {Array}
             */
            this.objects = [];
        };

        /**
         * Indicates whether this list contains picked objects that are not terrain.
         * @returns {Boolean} true if this list contains objects that are not terrain,
         * otherwise false.
         */
        PickedObjectList.prototype.hasNonTerrainObjects = function () {
            return this.objects.length > 1 || (this.objects.length === 1 && this.terrainObject() == null);
        };

        /**
         * Returns the terrain object within this list, if this list contains a terrain object.
         * @returns {PickedObject} The terrain object, or null if this list does not contain a terrain object.
         */
        PickedObjectList.prototype.terrainObject = function () {
            for (var i = 0, len = this.objects.length; i < len; i++) {
                if (this.objects[i].isTerrain) {
                    return this.objects[i];
                }
            }

            return null;
        };

        /**
         * Adds a picked object to this list.
         * If the picked object is a terrain object and the list already contains a terrain object, the terrain
         * object in the list is replaced by the specified one.
         * @param {PickedObject} pickedObject The picked object to add. If null, this list remains unchanged.
         */
        PickedObjectList.prototype.add = function (pickedObject) {
            if (pickedObject) {
                if (pickedObject.isTerrain) {
                    var terrainObjectIndex = this.objects.length;

                    for (var i = 0, len = this.objects.length; i < len; i++) {
                        if (this.objects[i].isTerrain) {
                            terrainObjectIndex = i;
                            break;
                        }
                    }

                    this.objects[terrainObjectIndex] = pickedObject;
                } else {
                    this.objects.push(pickedObject);
                }
            }
        };

        /**
         * Removes all items from this list.
         */
        PickedObjectList.prototype.clear = function () {
            this.objects = [];
        };

        /**
         * Returns the top-most picked object in this list.
         * @returns {PickedObject} The top-most picked object in this list, or null if this list is empty.
         */
        PickedObjectList.prototype.topPickedObject = function () {
            var size = this.objects.length;

            if (size > 1) {
                for (var i = 0; i < size; i++) {
                    if (this.objects[[i].isOnTop]) {
                        return this.objects[i];
                    }
                }
            }

            if (size > 0) {
                return this.objects[0];
            }

            return null;
        };

        return PickedObjectList;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ScreenCreditController
 */
define('render/ScreenCreditController',[
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/Font',
        '../util/Logger',
        '../geom/Matrix',
        '../util/Offset',
        '../pick/PickedObject',
        '../render/Renderable',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              Font,
              Logger,
              Matrix,
              Offset,
              PickedObject,
              Renderable,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a screen credit controller.
         * @alias ScreenCreditController
         * @constructor
         * @classdesc Collects and displays screen credits.
         */
        var ScreenCreditController = function () {
            // Internal. Intentionally not documented.
            this.imageUrls = [];

            // Internal. Intentionally not documented.
            this.stringCredits = [];

            // Internal. Intentionally not documented.
            this.imageCreditSize = 64;

            // Internal. Intentionally not documented.
            this.margin = 5;

            // Internal. Intentionally not documented.
            this.opacity = 0.5;

            // Internal. Intentionally not documented.
            this.creditFont = new Font(14);
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.scratchMatrix = Matrix.fromIdentity(); // scratch variable
        ScreenCreditController.imageTransform = Matrix.fromIdentity(); // scratch variable
        ScreenCreditController.texCoordMatrix = Matrix.fromIdentity(); // scratch variable

        /**
         * Clears all credits from this controller.
         */
        ScreenCreditController.prototype.clear = function () {
            this.imageUrls = [];
            this.stringCredits = [];
        };

        /**
         * Adds an image credit to this controller.
         * @param {String} imageUrl The URL of the image to display in the credits area.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        ScreenCreditController.prototype.addImageCredit = function (imageUrl) {
            if (!imageUrl) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenCreditController", "addImageCredit", "missingUrl"));
            }

            if (this.imageUrls.indexOf(imageUrl) === -1) {
                this.imageUrls.push(imageUrl);
            }
        };

        /**
         * Adds a string credit to this controller.
         * @param {String} stringCredit The string to display in the credits area.
         * @param (Color} color The color with which to draw the string.
         * @throws {ArgumentError} If either the specified string or color is null or undefined.
         */
        ScreenCreditController.prototype.addStringCredit = function (stringCredit, color) {
            if (!stringCredit) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenCreditController", "addStringCredit", "missingText"));
            }

            if (!color) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ScreenCreditController", "addStringCredit", "missingColor"));
            }

            if (this.stringCredits.indexOf(stringCredit) === -1) {
                this.stringCredits.push({
                    text: stringCredit,
                    color: color || Color.WHITE
                });
            }
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.prototype.drawCredits = function (dc) {
            // Check to see if there's anything to draw.
            if ((this.imageUrls.length === 0 && this.stringCredits.length === 0)) {
                return;
            }

            // Picking not provided.
            if (dc.pickingMode) {
                return;
            }

            // Want to draw only once per frame.
            if (dc.timestamp == this.lastFrameTimestamp) {
                return;
            }
            this.lastFrameTimestamp = dc.timestamp;

            this.beginDrawingCredits(dc);

            // Draw the image credits in a row along the bottom of the window from right to left.
            var imageX = dc.navigatorState.viewport.width - (this.margin + this.imageCreditSize),
                imageHeight, maxImageHeight = 0;

            for (var i = 0; i < this.imageUrls.length; i++) {
                imageHeight = this.drawImageCredit(dc, this.imageUrls[i], imageX, this.margin);
                if (imageHeight > 0) {
                    imageX -= (this.margin + this.imageCreditSize);
                    maxImageHeight = WWMath.max(imageHeight, maxImageHeight);
                }
            }

            // Draw the string credits above the image credits and progressing from bottom to top.
            var stringY = maxImageHeight + this.margin;
            for (var j = 0; j < this.stringCredits.length; j++) {
                this.drawStringCredit(dc, this.stringCredits[j], stringY);
                stringY += this.margin + 15; // margin + string height
            }

            this.endDrawingCredits(dc);
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.prototype.beginDrawingCredits = function (dc) {
            var gl = dc.currentGlContext,
                program;

            dc.findAndBindProgram(BasicTextureProgram);

            // Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
            // Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
            // that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
            // buffer for texture coords.
            program = dc.currentProgram;
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.enableVertexAttribArray(program.vertexTexCoordLocation);

            // Tell the program which texture unit to use.
            program.loadTextureUnit(gl, gl.TEXTURE0);
            program.loadModulateColor(gl, false);

            // Turn off depth testing.
            // tag, 6/17/15: It's not clear why this call was here. It was carried over from WWJ.
            //gl.disable(WebGLRenderingContext.DEPTH_TEST);
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.prototype.endDrawingCredits = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            // Clear the vertex attribute state.
            gl.disableVertexAttribArray(program.vertexPointLocation);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            // Clear GL bindings.
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindTexture(gl.TEXTURE_2D, null);

            // Re-enable depth testing.
            gl.enable(gl.DEPTH_TEST);
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.prototype.drawImageCredit = function (dc, creditUrl, x, y) {
            var imageWidth, imageHeight, scale, activeTexture, gl, program;

            activeTexture = dc.gpuResourceCache.resourceForKey(creditUrl);
            if (!activeTexture) {
                dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, creditUrl);
                return 0;
            }

            // Scale the image to fit within a constrained size.
            imageWidth = activeTexture.imageWidth;
            imageHeight = activeTexture.imageHeight;
            if (imageWidth <= this.imageCreditSize && this.imageHeight <= this.imageCreditSize) {
                scale = 1;
            } else if (imageWidth >= imageHeight) {
                scale = this.imageCreditSize / imageWidth;
            } else {
                scale = this.imageCreditSize / imageHeight;
            }

            ScreenCreditController.imageTransform.setTranslation(x, y, 0);
            ScreenCreditController.imageTransform.setScale(scale * imageWidth, scale * imageHeight, 1);

            gl = dc.currentGlContext;
            program = dc.currentProgram;

            // Compute and specify the MVP matrix.
            ScreenCreditController.scratchMatrix.copy(dc.screenProjection);
            ScreenCreditController.scratchMatrix.multiplyMatrix(ScreenCreditController.imageTransform);
            program.loadModelviewProjection(gl, ScreenCreditController.scratchMatrix);

            program.loadTextureEnabled(gl, true);
            program.loadColor(gl, Color.WHITE);
            program.loadOpacity(gl, this.opacity);

            ScreenCreditController.texCoordMatrix.setToIdentity();
            ScreenCreditController.texCoordMatrix.multiplyByTextureTransform(activeTexture);
            program.loadTextureMatrix(gl, ScreenCreditController.texCoordMatrix);

            if (activeTexture.bind(dc)) { // returns false if active texture cannot be bound
                // Draw the image quad.
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }

            return imageHeight;
        };

        // Internal use only. Intentionally not documented.
        ScreenCreditController.prototype.drawStringCredit = function (dc, credit, y) {
            var imageWidth, imageHeight, activeTexture, textureKey, gl, program, x;

            textureKey = credit.text + this.creditFont.toString();
            activeTexture = dc.gpuResourceCache.resourceForKey(textureKey);
            if (!activeTexture) {
                activeTexture = dc.textSupport.createTexture(dc, credit.text, this.creditFont, false);
                dc.gpuResourceCache.putResource(textureKey, activeTexture, activeTexture.size);
            }

            imageWidth = activeTexture.imageWidth;
            imageHeight = activeTexture.imageHeight;

            x = dc.navigatorState.viewport.width - (imageWidth + this.margin);
            ScreenCreditController.imageTransform.setTranslation(x, y, 0);
            ScreenCreditController.imageTransform.setScale(imageWidth, imageHeight, 1);

            gl = dc.currentGlContext;
            program = dc.currentProgram;

            // Compute and specify the MVP matrix.
            ScreenCreditController.scratchMatrix.copy(dc.screenProjection);
            ScreenCreditController.scratchMatrix.multiplyMatrix(ScreenCreditController.imageTransform);
            program.loadModelviewProjection(gl, ScreenCreditController.scratchMatrix);

            program.loadTextureEnabled(gl, true);
            program.loadColor(gl, credit.color);
            program.loadOpacity(gl, this.opacity);

            ScreenCreditController.texCoordMatrix.setToIdentity();
            ScreenCreditController.texCoordMatrix.multiplyByTextureTransform(activeTexture);
            program.loadTextureMatrix(gl, ScreenCreditController.texCoordMatrix);

            if (activeTexture.bind(dc)) { // returns false if active texture cannot be bound
                // Draw the image quad.
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }

            return true;
        };

        return ScreenCreditController;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports HashMap
 */
define('util/HashMap',[], function () {
    'use strict';

    /**
     * Constructs a hash map.
     * @alias HashMap
     * @constructor
     */
    var HashMap = function () {
        this._entries = Object.create(null);
    };

    /**
     * Returns the stored value for this key or undefined
     * @param{String | Number} key
     * @returns the value for the specified key or undefined
     */
    HashMap.prototype.get = function (key) {
        return this._entries[key];
    };

    /**
     * Stores a value for a specified key
     * @param{String | Number} key
     * @param value a value to store for the specified key
     */
    HashMap.prototype.set = function (key, value) {
        this._entries[key] = value;
    };

    /**
     * Removes the value and key for a specified key
     * @param{String | Number} key
     */
    HashMap.prototype.remove = function (key) {
        delete this._entries[key];
    };

    /**
     * Indicates if the has map contains a key
     * @param{String | Number} key
     * @returns {Boolean}
     */
    HashMap.prototype.contains = function (key) {
        return key in this._entries;
    };

    /**
     * Internal. Applications should call this function
     * Creates a new HashMap with the same values as the original but increased indexes.
     * The keys are used as indexes and are assumed to be natural numbers.
     * Used by the PolygonSplitter.
     * @param{HashMap} hashMap the hash map to re-index
     * @param{Number} fromIndex the index from with to start reindexing
     * @param{Number} amount the amount by which to increase the index
     * @returns {HashMap} a new has map with re-indexed keys
     */
    HashMap.reIndex = function (hashMap, fromIndex, amount) {
        var newHashMap = new HashMap();
        for (var key in hashMap._entries) {
            var index = parseInt(key);
            if (index >= fromIndex) {
                index += amount;
            }
            var entry = hashMap.get(key);
            entry.index = index;
            newHashMap.set(index, entry);
        }
        return newHashMap;
    };

    return HashMap;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */

define('util/PolygonSplitter',[
        './HashMap',
        '../geom/Location',
        '../geom/Position',
        './WWMath'
    ],
    function (HashMap,
              Location,
              Position,
              WWMath) {
        'use strict';

        /**
         * Splits polygons that cross the anti-meridian and/or contain a pole.
         * @exports PolygonSplitter
         */

        var PolygonSplitter = {

            //Internal
            //Keeps track of the index of added points so that no point is duplicated
            addedIndex: -1,

            //Internal
            //The index where pole insertion began
            poleIndexOffset: -1,

            /**
             * Splits an array of polygons that cross the anti-meridian or contain a pole.
             *
             * @param {Array} contours an array of arrays of Locations or Positions
             * Each array entry defines one of this polygon's boundaries.
             * @param {Array} resultContours an empty array to put the result of the split. Each element will have the
             * shape of PolygonSplitter.formatContourOutput
             * @returns {Boolean} true if one of the boundaries crosses the anti-meridian otherwise false
             * */
            splitContours: function (contours, resultContours) {
                var doesCross = false;

                for (var i = 0, len = contours.length; i < len; i++) {
                    var contourInfo = this.splitContour(contours[i]);
                    if (contourInfo.polygons.length > 1) {
                        doesCross = true;
                    }
                    resultContours.push(contourInfo);
                }

                return doesCross;
            },

            /**
             * Splits a polygon that cross the anti-meridian or contain a pole.
             *
             * @param {Location[] | Position[]} points an array of Locations or Positions that define a polygon
             * @returns {Object} @see PolygonSplitter.formatContourOutput
             * */
            splitContour: function (points) {
                var iMap = new HashMap();
                var newPoints = [];
                var intersections = [];
                var polygons = [];
                var iMaps = [];
                var poleIndex = -1;

                var pole = this.findIntersectionAndPole(points, newPoints, intersections, iMap);

                if (intersections.length === 0) {
                    polygons.push(newPoints);
                    iMaps.push(iMap);
                    return this.formatContourOutput(polygons, pole, poleIndex, iMaps);
                }

                if (intersections.length > 2) {
                    intersections.sort(function (a, b) {
                        return b.latitude - a.latitude;
                    });
                }

                if (pole !== Location.poles.NONE) {
                    newPoints = this.handleOnePole(newPoints, intersections, iMap, pole);
                    iMap = this.reindexIntersections(intersections, iMap, this.poleIndexOffset);
                }
                if (intersections.length === 0) {
                    polygons.push(newPoints);
                    iMaps.push(iMap);
                    poleIndex = 0;
                    return this.formatContourOutput(polygons, pole, poleIndex, iMaps);
                }

                this.linkIntersections(intersections, iMap);

                poleIndex = this.makePolygons(newPoints, intersections, iMap, polygons, iMaps);

                return this.formatContourOutput(polygons, pole, poleIndex, iMaps);
            },

            /**
             * Internal. Applications should not call this method.
             * Finds the intersections with the anti-meridian and if the polygon contains one of the poles.
             * A new polygon is constructed with the intersections and pole points and stored in newPoints
             *
             * @param {Location[] | Position[]} points An array of Locations or Positions that define a polygon
             * @param {Location[] | Position[]} newPoints An empty array where to store the resulting polygon with intersections
             * @param {Array} intersections An empty array where to store the intersection latitude and index
             * @param {HashMap} iMap A hashMap to store intersection data
             * The key is the index in the newPoints array and value is PolygonSplitter.makeIntersectionEntry
             * @returns {Number} The pole number @see Location.poles
             * */
            findIntersectionAndPole: function (points, newPoints, intersections, iMap) {
                var containsPole = false;
                var minLatitude = 90.0;
                var maxLatitude = -90.0;
                this.addedIndex = -1;

                for (var i = 0, lenC = points.length; i < lenC; i++) {
                    var pt1 = points[i];
                    var pt2 = points[(i + 1) % lenC];

                    minLatitude = Math.min(minLatitude, pt1.latitude);
                    maxLatitude = Math.max(maxLatitude, pt1.latitude);

                    var doesCross = Location.locationsCrossDateLine([pt1, pt2]);
                    if (doesCross) {
                        containsPole = !containsPole;

                        var iLatitude = Location.meridianIntersection(pt1, pt2, 180);
                        if (iLatitude === null) {
                            iLatitude = (pt1.latitude + pt2.latitude) / 2;
                        }
                        var iLongitude = WWMath.signum(pt1.longitude) * 180 || 180;

                        var iLoc1 = this.createPoint(iLatitude, iLongitude, pt1.altitude);
                        var iLoc2 = this.createPoint(iLatitude, -iLongitude, pt2.altitude);

                        this.safeAdd(newPoints, pt1, i, lenC);

                        var index = newPoints.length;
                        iMap.set(index, this.makeIntersectionEntry(index));
                        iMap.set(index + 1, this.makeIntersectionEntry(index + 1));
                        intersections.push({
                            indexEnd: index,
                            indexStart: index + 1,
                            latitude: iLatitude
                        });

                        newPoints.push(iLoc1);
                        newPoints.push(iLoc2);

                        this.safeAdd(newPoints, pt2, i + 1, lenC);
                    }
                    else {
                        this.safeAdd(newPoints, pt1, i, lenC);
                        this.safeAdd(newPoints, pt2, i + 1, lenC);
                    }
                }

                var pole = Location.poles.NONE;
                if (containsPole) {
                    pole = this.determinePole(minLatitude, maxLatitude);
                }

                return pole;
            },

            /**
             * Internal. Applications should not call this method.
             * Determine which pole is enclosed. If the shape is entirely in one hemisphere, then assume that it encloses
             * the pole in that hemisphere. Otherwise, assume that it encloses the pole that is closest to the shape's
             * extreme latitude.
             * @param {Number} minLatitude The minimum latitude of a polygon that contains a pole
             * @param {Number} maxLatitude The maximum latitude of a polygon that contains a pole
             * @returns {Number} The pole number @see Location.poles
             * */
            determinePole: function (minLatitude, maxLatitude) {
                var pole;
                if (minLatitude > 0) {
                    pole = Location.poles.NORTH; // Entirely in Northern Hemisphere.
                }
                else if (maxLatitude < 0) {
                    pole = Location.poles.SOUTH; // Entirely in Southern Hemisphere.
                }
                else if (Math.abs(maxLatitude) >= Math.abs(minLatitude)) {
                    pole = Location.poles.NORTH; // Spans equator, but more north than south.
                }
                else {
                    pole = Location.poles.SOUTH; // Spans equator, but more south than north.
                }
                return pole;
            },

            /**
             * Internal. Applications should not call this method.
             * Creates a new array of points containing the two pole locations on both sides of the anti-meridian
             *
             * @param {Location[] | Position[]} points
             * @param {Array} intersections
             * @param {HashMap} iMap
             * @param {Number} pole
             * @return {Object} an object containing the new points and a new reIndexed iMap
             * */
            handleOnePole: function (points, intersections, iMap, pole) {
                var pointsClone;

                if (pole === Location.poles.NORTH) {
                    var intersection = intersections.shift();
                    var poleLat = 90;
                }
                else if (pole === Location.poles.SOUTH) {
                    intersection = intersections.pop();
                    poleLat = -90;
                }

                var iEnd = iMap.get(intersection.indexEnd);
                var iStart = iMap.get(intersection.indexStart);
                iEnd.forPole = true;
                iStart.forPole = true;

                this.poleIndexOffset = intersection.indexStart;

                pointsClone = points.slice(0, intersection.indexEnd + 1);
                var polePoint1 = this.createPoint(poleLat, points[iEnd.index].longitude, points[iEnd.index].altitude);
                var polePoint2 = this.createPoint(poleLat, points[iStart.index].longitude, points[iStart.index].altitude);
                pointsClone.push(polePoint1, polePoint2);
                pointsClone = pointsClone.concat(points.slice(this.poleIndexOffset));

                return pointsClone;
            },

            /**
             * Internal. Applications should not call this method.
             * Links adjacents pairs of intersection by index
             * @param {Array} intersections
             * @param {HashMap} iMap
             * */
            linkIntersections: function (intersections, iMap) {
                for (var i = 0; i < intersections.length - 1; i += 2) {
                    var i0 = intersections[i];
                    var i1 = intersections[i + 1];

                    var iEnd0 = iMap.get(i0.indexEnd);
                    var iStart0 = iMap.get(i0.indexStart);
                    var iEnd1 = iMap.get(i1.indexEnd);
                    var iStart1 = iMap.get(i1.indexStart);

                    iEnd0.linkTo = i1.indexStart;
                    iStart0.linkTo = i1.indexEnd;
                    iEnd1.linkTo = i0.indexStart;
                    iStart1.linkTo = i0.indexEnd;
                }
            },

            /**
             * Internal. Applications should not call this method.
             * ReIndexes the intersections due to the poles being added to the array of points
             * @param {Array} intersections
             * @param {HashMap} iMap
             * @param {Number} indexOffset the index from which to start reIndexing
             * @returns {HashMap} a new hash map with the correct indices
             * */
            reindexIntersections: function (intersections, iMap, indexOffset) {
                iMap = HashMap.reIndex(iMap, indexOffset, 2);

                for (var i = 0, len = intersections.length; i < len; i++) {
                    if (intersections[i].indexEnd >= indexOffset) {
                        intersections[i].indexEnd += 2;
                    }
                    if (intersections[i].indexStart >= indexOffset) {
                        intersections[i].indexStart += 2;
                    }
                }

                return iMap;
            },

            /**
             * Internal. Applications should not call this method.
             * @param {Location[] | Position[]} points
             * @param {Array} intersections
             * @param {HashMap} iMap
             * @param {Array} polygons an empty array to store the resulting polygons
             * @param {HashMap[]} iMaps an empty array to store the resulting hasp maps for each polygon
             * @returns {Number} the pole number @see Location.poles
             * */
            makePolygons: function (points, intersections, iMap, polygons, iMaps) {
                var poleIndex = -1;
                for (var i = 0; i < intersections.length - 1; i += 2) {
                    var i0 = intersections[i];
                    var i1 = intersections[i + 1];

                    var start = i0.indexStart;
                    var end = i1.indexEnd;
                    var polygon = [];
                    var polygonHashMap = new HashMap();
                    var containsPole = this.makePolygon(start, end, points, iMap, polygon, polygonHashMap);
                    if (polygon.length) {
                        polygons.push(polygon);
                        iMaps.push(polygonHashMap);
                        if (containsPole) {
                            poleIndex = polygons.length - 1;
                        }
                    }

                    start = i1.indexStart;
                    end = i0.indexEnd;
                    polygon = [];
                    polygonHashMap = new HashMap();
                    containsPole = this.makePolygon(start, end, points, iMap, polygon, polygonHashMap);
                    if (polygon.length) {
                        polygons.push(polygon);
                        iMaps.push(polygonHashMap);
                        if (containsPole) {
                            poleIndex = polygons.length - 1;
                        }
                    }
                }

                return poleIndex;
            },

            /**
             * Internal. Applications should not call this method.
             * Paths from a start intersection index to an end intersection index and makes a polygon and a hashMap
             * with the intersection indices
             * @param {Number} start the index of a start type intersection
             * @param {Number} end the index of an end type intersection
             * @param {Location[] | Position[]} points
             * @param {HashMap} iMap
             * @param {Location[] | Position[]} resultPolygon an empty array to store the resulting polygon
             * @param {HashMap} polygonHashMap a hash map to record the indices of the intersections in the polygon
             * @returns {Boolean} true if the polygon contains a pole
             * */
            makePolygon: function (start, end, points, iMap, resultPolygon, polygonHashMap) {
                var pass = false;
                var len = points.length;
                var containsPole = false;

                if (end < start) {
                    end += len;
                }

                for (var i = start; i <= end; i++) {
                    var idx = i % len;
                    var pt = points[idx];
                    var intersection = iMap.get(idx);

                    if (intersection) {
                        if (intersection.visited) {
                            break;
                        }

                        resultPolygon.push(pt);
                        polygonHashMap.set(resultPolygon.length - 1, intersection);

                        if (intersection.forPole) {
                            containsPole = true;
                        }
                        else {
                            if (pass) {
                                i = intersection.linkTo - 1;
                                if (i + 1 === start) {
                                    break;
                                }
                            }
                            pass = !pass;
                            intersection.visited = true;
                        }
                    }
                    else {
                        resultPolygon.push(pt);
                    }
                }

                return containsPole;
            },

            /**
             * Internal. Applications should not call this method.
             * Adds an element to an array preventing duplication
             * @param {Location[] | Position[]} points
             * @param {Location | Position} point
             * @param {Number} index The index of the Point from the source array
             * @param {Number} len The length of the source array
             * */
            safeAdd: function (points, point, index, len) {
                if (this.addedIndex < index && this.addedIndex < len - 1) {
                    points.push(point);
                    this.addedIndex = index;
                }
            },

            /**
             * Internal. Applications should not call this method.
             * Creates a Location or a Position
             * @param {Number} latitude
             * @param {Number} longitude
             * @param {Number} altitude
             * @returns Location | Position
             * */
            createPoint: function (latitude, longitude, altitude) {
                if (altitude == null) {
                    return new Location(latitude, longitude);
                }
                return new Position(latitude, longitude, altitude);
            },

            /**
             * Internal. Applications should not call this method.
             * @param {Array} polygons an array of arrays of Locations or Positions
             * @param {Number} pole the pole number @see Location.poles
             * @param {Number} poleIndex the index of the polygon containing the pole
             * @param {HashMap[]} iMaps an array of hash maps for each polygon
             * */
            formatContourOutput: function (polygons, pole, poleIndex, iMaps) {
                return {
                    polygons: polygons,
                    pole: pole,
                    poleIndex: poleIndex,
                    iMap: iMaps
                };
            },

            /**
             * Internal. Applications should not call this method.
             * @param {Number} index the index of the intersection in the array of points
             * */
            makeIntersectionEntry: function (index) {
                if (index == null) {
                    index = -1;
                }
                return {
                    visited: false,
                    forPole: false,
                    index: index,
                    linkTo: -1
                }
            }
        };

        return PolygonSplitter;

    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapeAttributes
 */
define('shapes/ShapeAttributes',[
        '../util/Color',
        '../util/ImageSource'
    ],
    function (Color,
              ImageSource) {
        "use strict";

        /**
         * Constructs a shape attributes bundle, optionally specifying a prototype set of attributes. Not all shapes
         * use all the properties in the bundle. See the documentation of a specific shape to determine the properties
         * it does use.
         * @alias ShapeAttributes
         * @constructor
         * @classdesc Holds attributes applied to WorldWind shapes.
         * @param {ShapeAttributes} attributes An attribute bundle whose properties are used to initially populate
         * the constructed attributes bundle. May be null, in which case the constructed attributes bundle is populated
         * with default attributes.
         */
        var ShapeAttributes = function (attributes) {

            // All these are documented with their property accessors below.
            this._drawInterior = attributes ? attributes._drawInterior : true;
            this._drawOutline = attributes ? attributes._drawOutline : true;
            this._enableLighting = attributes ? attributes._enableLighting : false;
            this._interiorColor = attributes ? attributes._interiorColor : Color.WHITE;
            this._outlineColor = attributes ? attributes._outlineColor : Color.RED;
            this._outlineWidth = attributes ? attributes._outlineWidth : 1.0;
            this._outlineStippleFactor = attributes ? attributes._outlineStippleFactor : 0;
            this._outlineStipplePattern = attributes ? attributes._outlineStipplePattern : 0xF0F0;
            this._imageSource = attributes ? attributes._imageSource : null;
            this._depthTest = attributes ? attributes._depthTest : true;
            this._drawVerticals = attributes ? attributes._drawVerticals : false;
            this._applyLighting = attributes ? attributes._applyLighting : false;

            /**
             * Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
             * attributes change. The state key will be automatically computed the next time it's requested. This flag
             * will be set to false when that occurs.
             * @type {Boolean}
             * @protected
             */
            this.stateKeyInvalid = true;
        };

        /**
         * Computes the state key for this attributes object. Subclasses that define additional attributes must
         * override this method, call it from that method, and append the state of their attributes to its
         * return value.
         * @returns {String} The state key for this object.
         * @protected
         */
        ShapeAttributes.prototype.computeStateKey = function () {
            return "di " + this._drawInterior +
                  " do " + this._drawOutline +
                  " el " + this._enableLighting +
                  " ic " + this._interiorColor.toHexString(true) +
                  " oc " + this._outlineColor.toHexString(true) +
                  " ow " + this._outlineWidth +
                  " osf " + this._outlineStippleFactor +
                  " osp " + this._outlineStipplePattern +
                  " is " + (this._imageSource ?
                        (this.imageSource instanceof ImageSource ? this.imageSource.key : this.imageSource) : "null") +
                  " dt " + this._depthTest +
                  " dv " + this._drawVerticals +
                  " li " + this._applyLighting;
        };

        Object.defineProperties(ShapeAttributes.prototype, {
            /**
             * A string identifying the state of this attributes object. The string encodes the current values of all
             * this object's properties. It's typically used to validate cached representations of shapes associated
             * with this attributes object.
             * @type {String}
             * @readonly
             * @memberof ShapeAttributes.prototype
             */
            stateKey: {
                get: function () {
                    if (this.stateKeyInvalid) {
                        this._stateKey = this.computeStateKey();
                        this.stateKeyInvalid = false;
                    }
                    return this._stateKey;
                }
            },

            /**
             * Indicates whether the interior of the associated shape is drawn.
             * @type {Boolean}
             * @default true
             * @memberof ShapeAttributes.prototype
             */
            drawInterior: {
                get: function () {
                    return this._drawInterior;
                },
                set: function (value) {
                    this._drawInterior = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether the outline of the associated shape is drawn
             * @type {Boolean}
             * @default true
             * @memberof ShapeAttributes.prototype
             */
            drawOutline: {
                get: function () {
                    return this._drawOutline;
                },
                set: function (value) {
                    this._drawOutline = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether lighting is applied to the associated shape.
             * @type {Boolean}
             * @default false
             * @memberof ShapeAttributes.prototype
             */
            enableLighting: {
                get: function () {
                    return this._enableLighting;
                },
                set: function (value) {
                    this._enableLighting = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's interior color and opacity.
             * @type {Color}
             * @default Opaque white (red = 1, green = 1, blue = 1, alpha = 1)
             * @memberof ShapeAttributes.prototype
             */
            interiorColor: {
                get: function () {
                    return this._interiorColor;
                },
                set: function (value) {
                    this._interiorColor = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's outline color and opacity.
             * @type {Color}
             * @default Opaque red (red = 1, green = 0, blue = 0, alpha = 1)
             * @memberof ShapeAttributes.prototype
             */
            outlineColor: {
                get: function () {
                    return this._outlineColor;
                },
                set: function (value) {
                    this._outlineColor = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's outline width.
             * @type {Number}
             * @default 1.0
             * @memberof ShapeAttributes.prototype
             */
            outlineWidth: {
                get: function () {
                    return this._outlineWidth;
                },
                set: function (value) {
                    this._outlineWidth = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's outline stipple pattern. Specifies a number whose lower 16 bits
             * define a pattern of which pixels in the outline are rendered and which are suppressed. Each bit
             * corresponds to a pixel in the shape's outline, and the pattern repeats after every n*16 pixels, where
             * n is the [stipple factor]{@link ShapeAttributes#outlineStippleFactor}. For example, if the outline
             * stipple factor is 3, each bit in the stipple pattern is repeated three times before using the next bit.
             * <p>
             * To disable outline stippling, either specify a stipple factor of 0 or specify a stipple pattern of
             * all 1 bits, i.e., 0xFFFF.
             * @type {Number}
             * @default 0xF0F0
             * @memberof ShapeAttributes.prototype
             */
            outlineStipplePattern: {
                get: function () {
                    return this._outlineStipplePattern;
                },
                set: function (value) {
                    this._outlineStipplePattern = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's outline stipple factor. Specifies the number of times each bit in the
             * outline stipple pattern is repeated before the next bit is used. For example, if the outline stipple
             * factor is 3, each bit is repeated three times before using the next bit. The specified factor must be
             * either 0 or an integer greater than 0. A stipple factor of 0 indicates no stippling.
             * @type {Number}
             * @default 0
             * @memberof ShapeAttributes.prototype
             */
            outlineStippleFactor: {
                get: function () {
                    return this._outlineStippleFactor;
                },
                set: function (value) {
                    this._outlineStippleFactor = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the associated shape's image source. May be null, in which case no image is
             * applied to the shape.
             * @type {String|ImageSource}
             * @memberof ShapeAttributes.prototype
             * @default null
             */
            imageSource: {
                get: function () {
                    return this._imageSource;
                },
                set: function (value) {
                    this._imageSource = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether the shape should be depth-tested against other objects in the scene. If true,
             * the shape may be occluded by terrain and other objects in certain viewing situations. If false,
             * the shape will not be occluded by terrain and other objects.
             * @type {Boolean}
             * @default true
             * @memberof ShapeAttributes.prototype
             */
            depthTest: {
                get: function () {
                    return this._depthTest;
                },
                set: function (value) {
                    this._depthTest = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether this shape should draw vertical lines extending from its specified positions to the
             * ground.
             * @type {Boolean}
             * @default false
             * @memberof ShapeAttributes.prototype
             */
            drawVerticals: {
                get: function () {
                    return this._drawVerticals;
                },
                set: function (value) {
                    this._drawVerticals = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether lighting is applied to the shape.
             * @type {Boolean}
             * @default false
             * @memberof ShapeAttributes.prototype
             */
            applyLighting: {
                get: function () {
                    return this._applyLighting;
                },
                set: function (value) {
                    this._applyLighting = value;
                    this.stateKeyInvalid = true;
                }
            }
        });

        return ShapeAttributes;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceShape
 */
define('shapes/SurfaceShape',[
        '../error/AbstractError',
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/BoundingBox',
        '../geom/Location',
        '../util/Logger',
        '../cache/MemoryCache',
        '../error/NotYetImplementedError',
        '../pick/PickedObject',
        '../util/PolygonSplitter',
        '../render/Renderable',
        '../geom/Sector',
        '../shapes/ShapeAttributes',
        '../error/UnsupportedOperationError',
        '../util/WWMath'
    ],
    function (AbstractError,
              Angle,
              ArgumentError,
              BoundingBox,
              Location,
              Logger,
              MemoryCache,
              NotYetImplementedError,
              PickedObject,
              PolygonSplitter,
              Renderable,
              Sector,
              ShapeAttributes,
              UnsupportedOperationError,
              WWMath) {
        "use strict";

        /**
         * Constructs a surface shape with an optionally specified bundle of default attributes.
         * @alias SurfaceShape
         * @constructor
         * @augments Renderable
         * @abstract
         * @classdesc Represents a surface shape. This is an abstract base class and is meant to be instantiated
         * only by subclasses.
         * <p>
         * Surface shapes other than SurfacePolyline {@link SurfacePolyline} have an interior and an outline and utilize
         * the corresponding attributes in their associated ShapeAttributes {@link ShapeAttributes}. They do not
         * utilize image-related attributes.
         *
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         */
        var SurfaceShape = function (attributes) {

            Renderable.call(this);

            // All these are documented with their property accessors below.
            this._displayName = "Surface Shape";
            this._attributes = attributes ? attributes : new ShapeAttributes(null);
            this._highlightAttributes = null;
            this._highlighted = false;
            this._enabled = true;
            this._pathType = WorldWind.GREAT_CIRCLE;
            this._maximumNumEdgeIntervals = SurfaceShape.DEFAULT_NUM_EDGE_INTERVALS;
            this._polarThrottle = SurfaceShape.DEFAULT_POLAR_THROTTLE;
            this._sector = null;

            /**
             * Indicates the object to return as the owner of this shape when picked.
             * @type {Object}
             * @default null
             */
            this.pickDelegate = null;

            /*
             * The bounding sectors for this tile, which may be needed for crossing the dateline.
             * @type {Sector[]}
             * @protected
             */
            this._sectors = [];

            /*
             * The raw collection of locations defining this shape and are explicitly specified by the client of this class.
             * @type {Location[]}
             * @protected
             */
            this._locations = null;

            /*
             * Boundaries that are either the user specified locations or locations that are algorithmically generated.
             * @type {Location[]}
             * @protected
             */
            this._boundaries = null;

            /*
             * The collection of locations that describes a closed curve which can be filled.
             * @type {Location[][]}
             * @protected
             */
            this._interiorGeometry = null;

            /*
             * The collection of locations that describe the outline of the shape.
             * @type {Location[][]}
             * @protected
             */
            this._outlineGeometry = null;

            /*
             * Internal use only.
             * Inhibit the filling of the interior. This is to be used ONLY by polylines.
             * @type {Boolean}
             * @protected
             */
            this._isInteriorInhibited = false;

            /*
             * Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
             * attributes change. The state key will be automatically computed the next time it's requested. This flag
             * will be set to false when that occurs.
             * @type {Boolean}
             * @protected
             */
            this.stateKeyInvalid = true;

            // Internal use only. Intentionally not documented.
            this._attributesStateKey = null;

            // Internal use only. Intentionally not documented.
            this.boundariesArePrepared = false;

            // Internal use only. Intentionally not documented.
            this.layer = null;

            // Internal use only. Intentionally not documented.
            this.pickColor = null;

            //the split contours returned from polygon splitter
            this.contours = [];
            this.containsPole = false;
            this.crossesAntiMeridian = false;

            /**
             * Indicates how long to use terrain-specific shape data before regenerating it, in milliseconds. A value
             * of zero specifies that shape data should be regenerated every frame. While this causes the shape to
             * adapt more frequently to the terrain, it decreases performance.
             * @type {Number}
             * @default 2000 (milliseconds)
             */
            this.expirationInterval = 2000;

            // Internal use only. Intentionally not documented.
            // Holds the per-globe data
            this.shapeDataCache = new MemoryCache(3, 2);

            // Internal use only. Intentionally not documented.
            // The shape-data-cache data that is for the currently active globe.
            this.currentData = null;
        };

        SurfaceShape.prototype = Object.create(Renderable.prototype);

        Object.defineProperties(SurfaceShape.prototype, {
            stateKey: {
                /**
                 * A hash key of the total visible external state of the surface shape.
                 * @memberof SurfaceShape.prototype
                 * @type {String}
                 */
                get: function () {
                    // If we don't have a state key for the shape attributes, consider this state key to be invalid.
                    if (!this._attributesStateKey) {
                        // Update the state key for the appropriate attributes for future
                        if (this._highlighted) {
                            if (!!this._highlightAttributes) {
                                this._attributesStateKey = this._highlightAttributes.stateKey;
                            }
                        } else {
                            if (!!this._attributes) {
                                this._attributesStateKey = this._attributes.stateKey;
                            }
                        }

                        // If we now actually have a state key for the attributes, it was previously invalid.
                        if (!!this._attributesStateKey) {
                            this.stateKeyInvalid = true;
                        }
                    } else {
                        // Detect a change in the appropriate attributes.
                        var currentAttributesStateKey = null;

                        if (this._highlighted) {
                            // If there are highlight attributes associated with this shape, ...
                            if (!!this._highlightAttributes) {
                                currentAttributesStateKey = this._highlightAttributes.stateKey;
                            }
                        } else {
                            if (!!this._attributes) {
                                currentAttributesStateKey = this._attributes.stateKey;
                            }
                        }

                        // If the attributes state key changed, ...
                        if (currentAttributesStateKey != this._attributesStateKey) {
                            this._attributesStateKey = currentAttributesStateKey;
                            this.stateKeyInvalid = true;
                        }
                    }

                    if (this.stateKeyInvalid) {
                        this._stateKey = this.computeStateKey();
                    }

                    return this._stateKey;
                }
            },

            /**
             * The shape's display name and label text.
             * @memberof SurfaceShape.prototype
             * @type {String}
             * @default Surface Shape
             */
            displayName: {
                get: function () {
                    return this._displayName;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this._displayName = value;
                }
            },

            /**
             * The shape's attributes. If null and this shape is not highlighted, this shape is not drawn.
             * @memberof SurfaceShape.prototype
             * @type {ShapeAttributes}
             * @default see [ShapeAttributes]{@link ShapeAttributes}
             */
            attributes: {
                get: function () {
                    return this._attributes;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this._attributes = value;
                    this._attributesStateKey = value.stateKey;
                }
            },

            /**
             * The attributes used when this shape's highlighted flag is true. If null and the
             * highlighted flag is true, this shape's normal attributes are used. If they, too, are null, this
             * shape is not drawn.
             * @memberof SurfaceShape.prototype
             * @type {ShapeAttributes}
             * @default null
             */
            highlightAttributes: {
                get: function () {
                    return this._highlightAttributes;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this._highlightAttributes = value;
                }
            },

            /**
             * Indicates whether this shape displays with its highlight attributes rather than its normal attributes.
             * @memberof SurfaceShape.prototype
             * @type {Boolean}
             * @default false
             */
            highlighted: {
                get: function () {
                    return this._highlighted;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this._highlighted = value;
                }
            },

            /**
             * Indicates whether this shape is drawn.
             * @memberof SurfaceShape.prototype
             * @type {Boolean}
             * @default true
             */
            enabled: {
                get: function () {
                    return this._enabled;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this._enabled = value;
                }
            },

            /**
             * The path type to used to interpolate between locations on this shape. Recognized values are:
             * <ul>
             * <li>WorldWind.GREAT_CIRCLE</li>
             * <li>WorldWind.RHUMB_LINE</li>
             * <li>WorldWind.LINEAR</li>
             * </ul>
             * @memberof SurfaceShape.prototype
             * @type {String}
             * @default WorldWind.GREAT_CIRCLE
             */
            pathType: {
                get: function () {
                    return this._pathType;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._pathType = value;
                }
            },

            /**
             * The maximum number of intervals an edge will be broken into. This is the number of intervals that an
             * edge that spans to opposite side of the globe would be broken into. This is strictly an upper bound
             * and the number of edge intervals may be lower if this resolution is not needed.
             * @memberof SurfaceShape.prototype
             * @type {Number}
             * @default SurfaceShape.DEFAULT_NUM_EDGE_INTERVALS
             */
            maximumNumEdgeIntervals: {
                get: function () {
                    return this._maximumNumEdgeIntervals;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._maximumNumEdgeIntervals = value;
                }
            },

            /**
             * A dimensionless number that controls throttling of edge traversal near the poles where edges need to be
             * sampled more closely together.
             * A value of 0 indicates that no polar throttling is to be performed.
             * @memberof SurfaceShape.prototype
             * @type {Number}
             * @default SurfaceShape.DEFAULT_POLAR_THROTTLE
             */
            polarThrottle: {
                get: function () {
                    return this._polarThrottle;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._polarThrottle = value;
                }
            },

            /**
             * Defines the extent of the shape in latitude and longitude.
             * This sector only has valid data once the boundary is defined. Prior to this, it is null.
             * @memberof SurfaceShape.prototype
             * @type {Sector}
             */
            sector: {
                get: function () {
                    return this._sector;
                }
            }
        });

        SurfaceShape.staticStateKey = function (shape) {
            shape.stateKeyInvalid = false;

            if (shape.highlighted) {
                if (!shape._highlightAttributes) {
                    if (!shape._attributes) {
                        shape._attributesStateKey = null;
                    } else {
                        shape._attributesStateKey = shape._attributes.stateKey;
                    }
                } else {
                    shape._attributesStateKey = shape._highlightAttributes.stateKey;
                }
            } else {
                if (!shape._attributes) {
                    shape._attributesStateKey = null;
                } else {
                    shape._attributesStateKey = shape._attributes.stateKey;
                }
            }

            return "dn " + shape.displayName +
                " at " + (!shape._attributesStateKey ? "null" : shape._attributesStateKey) +
                " hi " + shape.highlighted +
                " en " + shape.enabled +
                " pt " + shape.pathType +
                " ne " + shape.maximumNumEdgeIntervals +
                " po " + shape.polarThrottle +
                " se " + "[" +
                shape.sector.minLatitude + "," +
                shape.sector.maxLatitude + "," +
                shape.sector.minLongitude + "," +
                shape.sector.maxLongitude +
                "]";
        };

        SurfaceShape.prototype.computeStateKey = function () {
            return SurfaceShape.staticStateKey(this);
        };

        /**
         * Returns this shape's area in square meters.
         * @param {Globe} globe The globe on which to compute the area.
         * @param {Boolean} terrainConformant If true, the returned area is that of the terrain,
         * including its hillsides and other undulations. If false, the returned area is the shape's
         * projected area.
         */
        SurfaceShape.prototype.area = function (globe, terrainConformant) {
            throw new NotYetImplementedError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceShape", "area", "notYetImplemented"));
        };

        // Internal function. Intentionally not documented.
        SurfaceShape.prototype.computeBoundaries = function (globe) {
            // This method is in the base class and should be overridden if the boundaries are generated.

            throw new AbstractError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceShape", "computeBoundaries", "abstractInvocation"));
        };

        // Internal. Intentionally not documented.
        SurfaceShape.prototype.intersectsFrustum = function (dc) {
            if (this.currentData && this.currentData.extent) {
                if (dc.pickingMode) {
                    return this.currentData.extent.intersectsFrustum(dc.pickFrustum);
                } else {
                    return this.currentData.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
                }
            } else {
                return true;
            }
        };

        /**
         * Indicates whether a specified shape data object is current. Subclasses may override this method to add
         * criteria indicating whether the shape data object is current, but must also call this method on this base
         * class. Applications do not call this method.
         * @param {DrawContext} dc The current draw context.
         * @param {Object} shapeData The object to validate.
         * @returns {Boolean} true if the object is current, otherwise false.
         * @protected
         */
        SurfaceShape.prototype.isShapeDataCurrent = function (dc, shapeData) {
            return shapeData.verticalExaggeration === dc.verticalExaggeration
                && shapeData.expiryTime > Date.now();
        };

        /**
         * Creates a new shape data object for the current globe state. Subclasses may override this method to
         * modify the shape data object that this method creates, but must also call this method on this base class.
         * Applications do not call this method.
         * @returns {Object} The shape data object.
         * @protected
         */
        SurfaceShape.prototype.createShapeDataObject = function () {
            return {};
        };

        // Intentionally not documented.
        SurfaceShape.prototype.resetExpiration = function (shapeData) {
            // The random addition in the line below prevents all shapes from regenerating during the same frame.
            shapeData.expiryTime = Date.now() + this.expirationInterval + 1e3 * Math.random();
        };

        // Internal. Intentionally not documented.
        SurfaceShape.prototype.establishCurrentData = function (dc) {
            this.currentData = this.shapeDataCache.entryForKey(dc.globeStateKey);
            if (!this.currentData) {
                this.currentData = this.createShapeDataObject();
                this.resetExpiration(this.currentData);
                this.shapeDataCache.putEntry(dc.globeStateKey, this.currentData, 1);
            }

            this.currentData.isExpired = !this.isShapeDataCurrent(dc, this.currentData);
        };

        // Internal function. Intentionally not documented.
        SurfaceShape.prototype.render = function (dc) {
            if (!this.enabled) {
                return;
            }

            this.layer = dc.currentLayer;

            this.prepareBoundaries(dc);

            this.establishCurrentData(dc);

            if (this.currentData.isExpired || !this.currentData.extent) {
                this.computeExtent(dc);
                this.currentData.verticalExaggeration = dc.verticalExaggeration;
                this.resetExpiration(this.currentData);
            }

            // Use the last computed extent to see if this shape is out of view.
            if (this.currentData && this.currentData.extent && !this.intersectsFrustum(dc)) {
                return;
            }

            dc.surfaceShapeTileBuilder.insertSurfaceShape(this);
        };

        // Internal function. Intentionally not documented.
        SurfaceShape.prototype.interpolateLocations = function (locations) {
            var first = locations[0],
                next = first,
                prev,
                isNextFirst = true,
                isPrevFirst = true,// Don't care initially, this will get set in first iteration.
                countFirst = 0,
                isInterpolated = true,
                idx, len;

            this._locations = [first];

            for (idx = 1, len = locations.length; idx < len; idx += 1) {
                // Advance to next location, retaining previous location.
                prev = next;
                isPrevFirst = isNextFirst;

                next = locations[idx];

                // Detect whether the next location and the first location are the same.
                isNextFirst = next.latitude == first.latitude && next.longitude == first.longitude;

                // Inhibit interpolation if either endpoint if the first location,
                // except for the first segement which will be the actual first location or that location
                // as the polygon closes the first time.
                // All subsequent encounters of the first location are used to connected secondary domains with the
                // primary domain in multiply-connected geometry (an outer ring with multiple inner rings).
                isInterpolated = true;
                if (isNextFirst || isPrevFirst) {
                    countFirst += 1;

                    if (countFirst > 2) {
                        isInterpolated = false;
                    }
                }

                if (isInterpolated) {
                    this.interpolateEdge(prev, next, this._locations);
                }

                this._locations.push(next);

                prev = next;
            }

            // Force the closing of the border.
            if (!this._isInteriorInhibited) {
                // Avoid duplication if the first endpoint was already emitted.
                if (prev.latitude != first.latitude || prev.longitude != first.longitude) {
                    this.interpolateEdge(prev, first, this._locations);
                    this._locations.push(first);
                }
            }
        };

        // Internal function. Intentionally not documented.
        SurfaceShape.prototype.interpolateEdge = function (start, end, locations) {
            var distanceRadians = Location.greatCircleDistance(start, end),
                steps = Math.round(this._maximumNumEdgeIntervals * distanceRadians / Math.PI),
                dt,
                location;

            if (steps > 0) {
                dt = 1 / steps;
                location = start;

                for (var t = this.throttledStep(dt, location); t < 1; t += this.throttledStep(dt, location)) {
                    location = new Location(0, 0);
                    Location.interpolateAlongPath(this._pathType, t, start, end, location);

                    //florin: ensure correct longitude sign and decimal error for anti-meridian
                    if (start.longitude === 180 && end.longitude === 180) {
                        location.longitude = 180;
                    }
                    else if (start.longitude === -180 && end.longitude === -180) {
                        location.longitude = -180;
                    }

                    locations.push(location);
                }
            }
        };

        // Internal function. Intentionally not documented.
        // Return a throttled step size when near the poles.
        SurfaceShape.prototype.throttledStep = function (dt, location) {
            var cosLat = Math.cos(location.latitude * Angle.DEGREES_TO_RADIANS);
            cosLat *= cosLat; // Square cos to emphasize poles and de-emphasize equator.

            // Remap polarThrottle:
            //  0 .. INF => 0 .. 1
            // This acts as a weight between no throttle and fill throttle.
            var weight = this._polarThrottle / (1 + this._polarThrottle);

            return dt * ((1 - weight) + weight * cosLat);
        };

        // Internal function. Intentionally not documented.
        SurfaceShape.prototype.prepareBoundaries = function (dc) {
            if (this.boundariesArePrepared) {
                return;
            }

            this.computeBoundaries(dc);

            var newBoundaries = this.formatBoundaries();
            this.normalizeAngles(newBoundaries);
            newBoundaries = this.interpolateBoundaries(newBoundaries);

            var contoursInfo = [];
            var doesCross = PolygonSplitter.splitContours(newBoundaries, contoursInfo);
            this.contours = contoursInfo;
            this.crossesAntiMeridian = doesCross;

            this.prepareGeometry(dc, contoursInfo);

            this.prepareSectors();

            this.boundariesArePrepared = true;
        };

        //Internal. Formats the boundaries of a surface shape to be a multi dimensional array
        SurfaceShape.prototype.formatBoundaries = function () {
            var boundaries = [];
            if (!this._boundaries.length) {
                return boundaries;
            }
            if (this._boundaries[0].latitude != null) {
                //not multi dim array
                boundaries.push(this._boundaries);
            }
            else {
                boundaries = this._boundaries;
            }
            return boundaries;
        };

        // Internal. Resets boundaries for SurfaceShape recomputing.
        SurfaceShape.prototype.resetBoundaries = function () {
            this.boundariesArePrepared = false;
            this.shapeDataCache.clear(false);
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.normalizeAngles = function (boundaries) {
            for (var i = 0, len = boundaries.length; i < len; i++) {
                var polygon = boundaries[i];
                for (var j = 0, lenP = polygon.length; j < lenP; j++) {
                    var point = polygon[j];
                    if (point.longitude < -180 || point.longitude > 180) {
                        point.longitude = Angle.normalizedDegreesLongitude(point.longitude);
                    }
                    if (point.latitude < -90 || point.latitude > 90) {
                        point.latitude = Angle.normalizedDegreesLatitude(point.latitude);
                    }
                }
            }
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.interpolateBoundaries = function (boundaries) {
            var newBoundaries = [];
            for (var i = 0, len = boundaries.length; i < len; i++) {
                var contour = boundaries[i];
                this.interpolateLocations(contour);
                newBoundaries.push(this._locations.slice());
                this._locations.length = 0;
            }
            return newBoundaries;
        };

        /**
         * Computes the bounding sectors for the shape. There will be more than one if the shape crosses the date line,
         * but does not enclose a pole.
         *
         * @param {DrawContext} dc The drawing context containing a globe.
         *
         * @return {Sector[]}  Bounding sectors for the shape.
         */
        SurfaceShape.prototype.computeSectors = function (dc) {
            // Return a previously computed value if it already exists.
            if (this._sectors && this._sectors.length > 0) {
                return this._sectors;
            }

            this.prepareBoundaries(dc);

            return this._sectors;
        };

        /**
         * Computes the extent for the shape based on its sectors.
         *
         * @param {DrawContext} dc The drawing context containing a globe.
         *
         * @return {BoundingBox} The extent for the shape.
         */
        SurfaceShape.prototype.computeExtent = function (dc) {

            if (!this._sectors || this._sectors.length === 0) {
                return null;
            }

            if (!this.currentData) {
                return null;
            }

            if (!this.currentData.extent) {
                this.currentData.extent = new BoundingBox();
            }


            var boxPoints;
            // This surface shape does not cross the international dateline, and therefore has a single bounding sector.
            // Return the box which contains that sector.
            if (this._sectors.length === 1) {
                boxPoints = this._sectors[0].computeBoundingPoints(dc.globe, dc.verticalExaggeration);
                this.currentData.extent.setToVec3Points(boxPoints);
            }
            // This surface crosses the international dateline, and its bounding sectors are split along the dateline.
            // Return a box which contains the corners of the boxes bounding each sector.
            else {
                var boxCorners = [];

                for (var i = 0; i < this._sectors.length; i++) {
                    boxPoints = this._sectors[i].computeBoundingPoints(dc.globe, dc.verticalExaggeration);
                    var box = new BoundingBox();
                    box.setToVec3Points(boxPoints);
                    var corners = box.getCorners();
                    for (var j = 0; j < corners.length; j++) {
                        boxCorners.push(corners[j]);
                    }
                }
                this.currentData.extent.setToVec3Points(boxCorners);
            }

            return this.currentData.extent;

        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.prepareSectors = function () {
            this.determineSectors();
            if (this.crossesAntiMeridian) {
                this.sectorsOverAntiMeridian();
            }
            else {
                this.sectorsNotOverAntiMeridian();
            }
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.determineSectors = function () {
            for (var i = 0, len = this.contours.length; i < len; i++) {
                var contour = this.contours[i];
                var polygons = contour.polygons;
                contour.sectors = [];
                for (var j = 0, lenP = polygons.length; j < lenP; j++) {
                    var polygon = polygons[j];
                    var sector = new Sector(0, 0, 0, 0);
                    sector.setToBoundingSector(polygon);
                    if (this._pathType === WorldWind.GREAT_CIRCLE) {
                        var extremes = Location.greatCircleArcExtremeLocations(polygon);
                        var minLatitude = Math.min(sector.minLatitude, extremes[0].latitude);
                        var maxLatitude = Math.max(sector.maxLatitude, extremes[1].latitude);
                        sector.minLatitude = minLatitude;
                        sector.maxLatitude = maxLatitude;
                    }
                    contour.sectors.push(sector);
                }
            }
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.sectorsOverAntiMeridian = function () {
            var eastSector = new Sector(90, -90, 180, -180); //positive
            var westSector = new Sector(90, -90, 180, -180); //negative
            for (var i = 0, len = this.contours.length; i < len; i++) {
                var sectors = this.contours[i].sectors;
                for (var j = 0, lenS = sectors.length; j < lenS; j++) {
                    var sector = sectors[j];
                    if (sector.minLongitude < 0 && sector.maxLongitude > 0) {
                        westSector.union(sector);
                        eastSector.union(sector);
                    }
                    else if (sector.minLongitude < 0) {
                        westSector.union(sector);
                    }
                    else {
                        eastSector.union(sector);
                    }
                }
            }
            var minLatitude = Math.min(eastSector.minLatitude, westSector.minLatitude);
            var maxLatitude = Math.max(eastSector.maxLatitude, eastSector.maxLatitude);
            this._sector = new Sector(minLatitude, maxLatitude, -180, 180);
            this._sectors = [eastSector, westSector];
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.sectorsNotOverAntiMeridian = function () {
            this._sector = new Sector(90, -90, 180, -180);
            for (var i = 0, len = this.contours.length; i < len; i++) {
                var sectors = this.contours[i].sectors;
                for (var j = 0, lenS = sectors.length; j < lenS; j++) {
                    this._sector.union(sectors[j]);
                }
            }
            this._sectors = [this._sector];
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.prepareGeometry = function (dc, contours) {
            var interiorPolygons = [];
            var outlinePolygons = [];

            for (var i = 0, len = contours.length; i < len; i++) {
                var contour = contours[i];
                var poleIndex = contour.poleIndex;

                for (var j = 0, lenC = contour.polygons.length; j < lenC; j++) {
                    var polygon = contour.polygons[j];
                    var iMap = contour.iMap[j];
                    interiorPolygons.push(polygon);

                    if (contour.pole !== Location.poles.NONE && lenC > 1) {
                        //split with pole
                        if (j === poleIndex) {
                            this.outlineForPole(polygon, iMap, outlinePolygons);
                        }
                        else {
                            this.outlineForSplit(polygon, iMap, outlinePolygons);
                        }
                    }
                    else if (contour.pole !== Location.poles.NONE && lenC === 1) {
                        //only pole
                        this.outlineForPole(polygon, iMap, outlinePolygons);
                    }
                    else if (contour.pole === Location.poles.NONE && lenC > 1) {
                        //only split
                        this.outlineForSplit(polygon, iMap, outlinePolygons);
                    }
                    else if (contour.pole === Location.poles.NONE && lenC === 1) {
                        //no pole, no split
                        outlinePolygons.push(polygon);
                    }
                }
            }

            this._interiorGeometry = interiorPolygons;
            this._outlineGeometry = outlinePolygons;
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.outlineForPole = function (polygon, iMap, outlinePolygons) {
            this.containsPole = true;
            var outlinePolygon = [];
            var pCount = 0;
            for (var k = 0, lenP = polygon.length; k < lenP; k++) {
                var point = polygon[k];
                var intersection = iMap.get(k);
                if (intersection && intersection.forPole) {
                    pCount++;
                    if (pCount % 2 === 1) {
                        outlinePolygon.push(point);
                        outlinePolygons.push(outlinePolygon);
                        outlinePolygon = [];
                    }
                }
                if (pCount % 2 === 0) {
                    outlinePolygon.push(point);
                }
            }
            if (outlinePolygon.length) {
                outlinePolygons.push(outlinePolygon);
            }
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.outlineForSplit = function (polygon, iMap, outlinePolygons) {
            var outlinePolygon = [];
            var iCount = 0;
            for (var k = 0, lenP = polygon.length; k < lenP; k++) {
                var point = polygon[k];
                var intersection = iMap.get(k);
                if (intersection && !intersection.forPole) {
                    iCount++;
                    if (iCount % 2 === 0) {
                        outlinePolygon.push(point);
                        outlinePolygons.push(outlinePolygon);
                        outlinePolygon = [];
                    }
                }
                if (iCount % 2 === 1) {
                    outlinePolygon.push(point);
                }
            }
        };

        // Internal use only. Intentionally not documented.
        SurfaceShape.prototype.resetPickColor = function () {
            this.pickColor = null;
        };

        /**
         * Internal use only.
         * Render the shape onto the texture map of the tile.
         * @param {DrawContext} dc The draw context to render onto.
         * @param {CanvasRenderingContext2D} ctx2D The rendering context for SVG.
         * @param {Number} xScale The multiplicative scale factor in the horizontal direction.
         * @param {Number} yScale The multiplicative scale factor in the vertical direction.
         * @param {Number} dx The additive offset in the horizontal direction.
         * @param {Number} dy The additive offset in the vertical direction.
         */
        SurfaceShape.prototype.renderToTexture = function (dc, ctx2D, xScale, yScale, dx, dy) {
            var attributes = (this._highlighted ? (this._highlightAttributes || this._attributes) : this._attributes);
            var drawInterior = (!this._isInteriorInhibited && attributes.drawInterior);
            var drawOutline = (attributes.drawOutline && attributes.outlineWidth > 0);

            if (!drawInterior && !drawOutline) {
                return;
            }

            if (dc.pickingMode && !this.pickColor) {
                this.pickColor = dc.uniquePickColor();
            }

            if (dc.pickingMode) {
                var pickColor = this.pickColor.toHexString();
            }

            if (this.crossesAntiMeridian || this.containsPole) {
                if (drawInterior) {
                    this.draw(this._interiorGeometry, ctx2D, xScale, yScale, dx, dy);
                    ctx2D.fillStyle = dc.pickingMode ? pickColor : attributes.interiorColor.toRGBAString();
                    ctx2D.fill();
                }
                if (drawOutline) {
                    this.draw(this._outlineGeometry, ctx2D, xScale, yScale, dx, dy);
                    ctx2D.lineWidth = attributes.outlineWidth;
                    ctx2D.strokeStyle = dc.pickingMode ? pickColor : attributes.outlineColor.toRGBAString();
                    ctx2D.stroke();
                }
            }
            else {
                this.draw(this._interiorGeometry, ctx2D, xScale, yScale, dx, dy);
                if (drawInterior) {
                    ctx2D.fillStyle = dc.pickingMode ? pickColor : attributes.interiorColor.toRGBAString();
                    ctx2D.fill();
                }
                if (drawOutline) {
                    ctx2D.lineWidth = attributes.outlineWidth;
                    ctx2D.strokeStyle = dc.pickingMode ? pickColor : attributes.outlineColor.toRGBAString();
                    ctx2D.stroke();
                }
            }

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this.pickDelegate ? this.pickDelegate : this,
                    null, this.layer, false);
                dc.resolvePick(po);
            }
        };

        SurfaceShape.prototype.draw = function (contours, ctx2D, xScale, yScale, dx, dy) {
            ctx2D.beginPath();
            for (var i = 0, len = contours.length; i < len; i++) {
                var contour = contours[i];
                var point = contour[0];
                var x = point.longitude * xScale + dx;
                var y = point.latitude * yScale + dy;
                ctx2D.moveTo(x, y);
                for (var j = 1, lenC = contour.length; j < lenC; j++) {
                    point = contour[j];
                    x = point.longitude * xScale + dx;
                    y = point.latitude * yScale + dy;
                    ctx2D.lineTo(x, y);
                }
            }
        };

        /**
         * Default value for the maximum number of edge intervals. This results in a maximum error of 480 m for an arc
         * that spans the entire globe.
         *
         * Other values for this parameter have the associated errors below:
         * Intervals        Maximum error (meters)
         *      2           1280253.5
         *      4           448124.5
         *      8           120837.6
         *      16          30628.3
         *      32          7677.9
         *      64          1920.6
         *      128         480.2
         *      256         120.0
         *      512         30.0
         *      1024        7.5
         *      2048        1.8
         * The errors cited above are upper bounds and the actual error may be lower.
         * @type {Number}
         */
        SurfaceShape.DEFAULT_NUM_EDGE_INTERVALS = 128;

        /**
         * The defualt value for the polar throttle, which slows edge traversal near the poles.
         * @type {Number}
         */
        SurfaceShape.DEFAULT_POLAR_THROTTLE = 10;

        return SurfaceShape;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceShapeTile
 */
define('shapes/SurfaceShapeTile',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../util/Level',
        '../util/Logger',
        '../geom/Sector',
        '../render/Texture',
        '../render/TextureTile'
    ],
    function (Angle,
              ArgumentError,
              Level,
              Logger,
              Sector,
              Texture,
              TextureTile) {
        "use strict";

        /**
         * Constructs a surface shape tile.
         * @alias SurfaceShapeTile
         * @constructor
         * @classdesc Represents a texture map containing renditions of surface shapes applied to a portion of a globe's terrain.
         * @param {Sector} sector The sector this tile covers.
         * @param {Level} level The level this tile is associated with.
         * @param {number} row This tile's row in the associated level.
         * @param {number} column This tile's column in the associated level.
         * @throws {ArgumentError} If the specified sector or level is null or undefined, the row or column arguments
         * are less than zero, or the specified image path is null, undefined or empty.
         *
         */
        var SurfaceShapeTile = function (sector, level, row, column) {
            TextureTile.call(this, sector, level, row, column); // args are checked in the superclass' constructor

            /**
             * The surface shapes that affect this tile.
             * @type {SurfaceShape[]}
             */
            this.surfaceShapes = [];

            // Internal use only. Intentionally not documented.
            this.surfaceShapeStateKeys = [];

            // Internal use only. Intentionally not documented.
            this.asRenderedSurfaceShapeStateKeys = [];

            /**
             * The sector that bounds this tile.
             * @type {Sector}
             */
            this.sector = sector;

            /**
             * A string to use as a cache key.
             * @type {string}
             */
            this.cacheKey = null;

            // Internal use only. Intentionally not documented.
            this.pickSequence = 0;

            this.createCtx2D();
        };

        SurfaceShapeTile.prototype = Object.create(TextureTile.prototype);

        /**
         * Clear all collected surface shapes.
         */
        SurfaceShapeTile.prototype.clearShapes = function () {
            // Clear out next surface shape.
            this.surfaceShapes = [];
            this.surfaceShapeStateKeys = [];
        };

        /**
         * Query whether any surface shapes have been collected.
         * @returns {boolean} Returns true if there are collected surface shapes.
         */
        SurfaceShapeTile.prototype.hasShapes = function () {
            return this.surfaceShapes.length > 0;
        };

        /**
         * Get all shapes that this tile references.
         * @returns {SurfaceShape[]} The collection of surface shapes referenced by this tile.
         */
        SurfaceShapeTile.prototype.getShapes = function () {
            return this.surfaceShapes;
        };

        /**
         * Set the shapes this tile should reference.
         * @param {SurfaceShape[]} surfaceShapes The collection of surface shapes to be referenced by this tile.
         */
        SurfaceShapeTile.prototype.setShapes = function (surfaceShapes) {
            this.surfaceShapes = surfaceShapes;
        };

        /**
         * The sector that bounds this tile.
         * @returns {Sector}
         */
        SurfaceShapeTile.prototype.getSector = function () {
            return this.sector;
        };

        /**
         * Add a surface shape to this tile's collection of surface shapes.
         * @param {SurfaceShape} surfaceShape The surface shape to add.
         */
        SurfaceShapeTile.prototype.addSurfaceShape = function (surfaceShape) {
            this.surfaceShapes.push(surfaceShape);
            this.surfaceShapeStateKeys.push(surfaceShape.stateKey);
        };

        // Internal use only. Intentionally not documented.
        SurfaceShapeTile.prototype.needsUpdate = function (dc) {
            var idx, len, surfaceShape, surfaceShapeStateKey;

            // If the number of surface shapes does not match the number of surface shapes already in the texture
            if (this.surfaceShapes.length != this.asRenderedSurfaceShapeStateKeys.length) {
                return true;
            }

            // If the state key of the shape is different from the saved state key (in order or configuration)
            for (idx = 0, len = this.surfaceShapes.length; idx < len; idx += 1) {
                if (this.surfaceShapeStateKeys[idx] !== this.asRenderedSurfaceShapeStateKeys[idx]) {
                    return true;
                }
            }

            // If a texture does not already exist, ...
            if (!this.hasTexture(dc)) {
                return true;
            }

            // If you get here, the texture can be reused.
            return false;
        };

        /**
         * Determine whether the surface shape tile has a valid texture.
         * @param {DrawContext} dc The draw context.
         * @returns {boolean} True if the surface shape tile has a valid texture, else false.
         */
        SurfaceShapeTile.prototype.hasTexture = function (dc) {
            if (dc.pickingMode) {
                return false;
            }

            if (!this.gpuCacheKey) {
                this.gpuCacheKey = this.getCacheKey();
            }

            var gpuResourceCache = dc.gpuResourceCache;
            var texture = gpuResourceCache.resourceForKey(this.gpuCacheKey);

            return !!texture;
        };

        /**
         * Redraw all of the surface shapes onto the texture for this tile.
         * @param {DrawContext} dc
         * @returns {Texture}
         */
        SurfaceShapeTile.prototype.updateTexture = function (dc) {
            var gl = dc.currentGlContext,
                canvas = SurfaceShapeTile.canvas,
                ctx2D = SurfaceShapeTile.ctx2D;

            canvas.width = this.tileWidth;
            canvas.height = this.tileHeight;

            // Mapping from lat/lon to x/y:
            //  lon = minlon => x = 0
            //  lon = maxLon => x = 256
            //  lat = minLat => y = 256
            //  lat = maxLat => y = 0
            //  (assuming texture size is 256)
            // So:
            //  x = 256 / sector.dlon * (lon - minLon)
            //  y = -256 / sector.dlat * (lat - maxLat)
            var xScale = this.tileWidth / this.sector.deltaLongitude(),
                yScale = -this.tileHeight / this.sector.deltaLatitude(),
                xOffset = -this.sector.minLongitude * xScale,
                yOffset = -this.sector.maxLatitude * yScale;

            // Reset the surface shape state keys
            this.asRenderedSurfaceShapeStateKeys = [];

            for (var idx = 0, len = this.surfaceShapes.length; idx < len; idx += 1) {
                var shape = this.surfaceShapes[idx];
                this.asRenderedSurfaceShapeStateKeys.push(this.surfaceShapeStateKeys[idx]);

                shape.renderToTexture(dc, ctx2D, xScale, yScale, xOffset, yOffset);
            }

            this.gpuCacheKey = this.getCacheKey();

            var gpuResourceCache = dc.gpuResourceCache;
            var texture = new Texture(gl, canvas);
            gpuResourceCache.putResource(this.gpuCacheKey, texture, texture.size);

            return texture;
        };

        /**
         * Get a key suitable for cache look-ups.
         * @returns {string}
         */
        SurfaceShapeTile.prototype.getCacheKey = function () {
            if (!this.cacheKey) {
                this.cacheKey = "SurfaceShapeTile:" +
                    this.tileKey + "," +
                    this.pickSequence.toString();
            }

            return this.cacheKey;
        };

        /**
         * Create a new canvas and its 2D context on demand.
         */
        SurfaceShapeTile.prototype.createCtx2D = function () {
            // If the context was previously created, ...
            if (!SurfaceShapeTile.ctx2D) {
                SurfaceShapeTile.canvas = document.createElement("canvas");
                SurfaceShapeTile.ctx2D = SurfaceShapeTile.canvas.getContext("2d");
            }
        };

        /*
         * For internal use only.
         * 2D canvas and context, which is created lazily on demand.
         */
        SurfaceShapeTile.canvas = null;
        SurfaceShapeTile.ctx2D = null;

        return SurfaceShapeTile;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceShapeTileBuilder
 */
define('shapes/SurfaceShapeTileBuilder',[
        '../error/ArgumentError',
        '../render/DrawContext',
        '../globe/Globe',
        '../shaders/GpuProgram',
        '../util/Level',
        '../util/LevelSet',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../cache/MemoryCache',
        '../navigate/NavigatorState',
        '../error/NotYetImplementedError',
        '../pick/PickedObject',
        '../geom/Rectangle',
        '../geom/Sector',
        '../shapes/SurfaceShape',
        '../shapes/SurfaceShapeTile',
        '../globe/Terrain',
        '../globe/TerrainTile',
        '../globe/TerrainTileList',
        '../render/TextureTile',
        '../util/Tile'
    ],
    function (ArgumentError,
              DrawContext,
              Globe,
              GpuProgram,
              Level,
              LevelSet,
              Location,
              Logger,
              Matrix,
              MemoryCache,
              NavigatorState,
              NotYetImplementedError,
              PickedObject,
              Rectangle,
              Sector,
              SurfaceShape,
              SurfaceShapeTile,
              Terrain,
              TerrainTile,
              TerrainTileList,
              TextureTile,
              Tile) {
        "use strict";

        var SurfaceShapeTileBuilder = function() {
            // Parameterize top level subdivision in one place.

            // TilesInTopLevel describes the most coarse tile structure.
            this.numRowsTilesInTopLevel = 4;
            this.numColumnsTilesInTopLevel = 8;

            // The maximum number of levels that will ever be tessellated.
            this.maximumSubdivisionDepth = 15;

            // tileWidth, tileHeight - the number of subdivisions a single tile has; this determines the sampling grid.
            this.tileWidth = 256;
            this.tileHeight = 256;

            /**
             * The collection of levels.
             * @type {LevelSet}
             */
            this.levels = new LevelSet(
                Sector.FULL_SPHERE,
                new Location(
                    180 / this.numRowsTilesInTopLevel,
                    360 / this.numColumnsTilesInTopLevel),
                this.maximumSubdivisionDepth,
                this.tileWidth,
                this.tileHeight);

            /**
             * The collection of surface shapes processed by this class.
             * @type {SurfaceShape[]}
             */
            this.surfaceShapes = [];

            /**
             * The collection of surface shape tiles that actually contain surface shapes.
             * @type {SurfaceShapeTile[]}
             */
            this.surfaceShapeTiles = [];

            /**
             * The collection of top level surface shape tiles, from which actual tiles are derived.
             * @type {SurfaceShapeTile[]}
             */
            this.topLevelTiles = [];

            /**
             * Accumulator of all sectors for surface shapes
             * @type {Sector}
             */
            this.sector = new Sector(-90, 90, -180, 180);

            /**
             * The default split scale. The split scale 2.9 has been empirically determined to render sharp lines and edges with
             * the SurfaceShapes such as SurfacePolyline and SurfacePolygon.
             *
             * @type {Number}
             */
            this.detailControl = 1.25;

            // Internal use only. Intentionally not documented.
            this.tileCache = new MemoryCache(500000, 400000);
        };

        /**
         * Clear all transient state from the surface shape tile builder.
         */
        SurfaceShapeTileBuilder.prototype.clear = function() {
            this.surfaceShapeTiles.splice(0, this.surfaceShapeTiles.length);
            this.surfaceShapes.splice(0, this.surfaceShapes.length);
        };

        /**
         * Insert a surface shape to be rendered into the surface shape tile builder.
         *
         * @param {SurfaceShape} surfaceShape A surfave shape to be processed.
         */
        SurfaceShapeTileBuilder.prototype.insertSurfaceShape = function(surfaceShape) {
            this.surfaceShapes.push(surfaceShape);
        };

        /**
         * Perform the rendering of any accumulated surface shapes by building the surface shape tiles that contain these
         * shapes and then rendering those tiles.
         *
         * @param {DrawContext} dc The drawing context.
         */
        SurfaceShapeTileBuilder.prototype.doRender = function(dc) {
            if (dc.pickingMode) {
                // Picking rendering strategy:
                //  1) save all tiles created prior to picking,
                //  2) construct and render new tiles with pick-based contents (colored with pick IDs),
                //  3) restore all prior tiles.
                // This has a big potential win for normal rendering, since there is a lot of coherence
                // from frame to frame if no picking is occurring.
                for (var idx = 0, len = this.surfaceShapes.length; idx < len; idx += 1) {
                    this.surfaceShapes[idx].resetPickColor();
                }

                SurfaceShapeTileBuilder.pickSequence += 1;

                var savedTiles = this.surfaceShapeTiles;
                var savedTopLevelTiles = this.topLevelTiles;

                this.surfaceShapeTiles = [];
                this.topLevelTiles = [];

                this.buildTiles(dc);

                if (dc.deepPicking) {
                    // Normally, we render all shapes together in one tile (or a small number, but this detail
                    // doesn't matter). For deep picking, we need to render each shape individually.
                    this.doDeepPickingRender(dc);

                } else {
                    dc.surfaceTileRenderer.renderTiles(dc, this.surfaceShapeTiles, 1);
                }

                this.surfaceShapeTiles = savedTiles;
                this.topLevelTiles = savedTopLevelTiles;
            } else {
                this.buildTiles(dc);

                dc.surfaceTileRenderer.renderTiles(dc, this.surfaceShapeTiles, 1);
            }
        };

        SurfaceShapeTileBuilder.prototype.doDeepPickingRender = function (dc) {
            var idxTile, lenTiles, idxShape, lenShapes, idxPick, lenPicks, po, shape, tile;

            // Determine the shapes that were drawn during buildTiles. These shapes may not actually be
            // at the pick point, but they are candidates for deep picking.
            var deepPickShapes = [];
            for (idxPick = 0, lenPicks = dc.objectsAtPickPoint.objects.length; idxPick < lenPicks; idxPick += 1) {
                po = dc.objectsAtPickPoint.objects[idxPick];
                if (po.userObject instanceof SurfaceShape) {
                    shape = po.userObject;

                    // If the shape was not already in the collection of deep picked shapes, ...
                    if (deepPickShapes.indexOf(shape) < 0) {
                        deepPickShapes.push(shape);

                        // Delete the shape that was drawn during buildTiles from the pick list.
                        dc.objectsAtPickPoint.objects.splice(idxPick, 1);

                        // Update the index and length to reflect the deletion.
                        idxPick -= 1;
                        lenPicks -= 1;
                    }
                }
            }

            if (deepPickShapes.length <= 0) {
                return;
            }

            // For all shapes,
            //  1) force that shape to be the only shape in a tile,
            //  2) re-render the tile, and
            //  3) use the surfaceTileRenderer to render the tile on the terrain,
            //  4) read the color to see if it is attributable to the current shape.
            var resolvablePickObjects = [];
            for (idxShape = 0, lenShapes = deepPickShapes.length; idxShape < lenShapes; idxShape += 1) {
                shape = deepPickShapes[idxShape];
                for (idxTile = 0, lenTiles = this.surfaceShapeTiles.length; idxTile < lenTiles; idxTile += 1) {
                    tile = this.surfaceShapeTiles[idxTile];
                    tile.setShapes([shape]);
                    tile.updateTexture(dc);
                }

                dc.surfaceTileRenderer.renderTiles(dc, this.surfaceShapeTiles, 1);

                var pickColor = dc.readPickColor(dc.pickPoint);
                if (!!pickColor && shape.pickColor.equals(pickColor)) {
                    po = new PickedObject(shape.pickColor.clone(),
                        shape.pickDelegate ? shape.pickDelegate : shape, null, shape.layer, false);
                    resolvablePickObjects.push(po);
                }
            }

            // Flush surface shapes that have accumulated in the updateTexture pass just completed on all shapes.
            for (idxPick = 0, lenPicks = dc.objectsAtPickPoint.objects.length; idxPick < lenPicks; idxPick += 1) {
                po = dc.objectsAtPickPoint.objects[idxPick];
                if (po.userObject instanceof SurfaceShape) {
                    // Delete the shape that was picked in the most recent pass.
                    dc.objectsAtPickPoint.objects.splice(idxPick, 1);

                    // Update the index and length to reflect the deletion.
                    idxPick -= 1;
                    lenPicks -= 1;
                }
            }

            // Add the resolvable pick objects for surface shapes that were actually visible at the pick point
            // to the pick list.
            for (idxPick = 0, lenPicks = resolvablePickObjects.length; idxPick < lenPicks; idxPick += 1) {
                po = resolvablePickObjects[idxPick];
                dc.objectsAtPickPoint.objects.push(po);
            }
        };

        /**
         * Assembles the surface tiles and draws any surface shapes that have been accumulated into those offscreen tiles. The
         * surface tiles are assembled to meet the necessary resolution of to the draw context's.
         * <p/>
         * This does nothing if there are no surface shapes associated with this builder.
         *
         * @param {DrawContext} dc The draw context to build tiles for.
         *
         * @throws {ArgumentError} If the draw context is null.
         */
        SurfaceShapeTileBuilder.prototype.buildTiles = function(dc) {
            if (!dc) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceShapeTileBuilder", "buildTiles", "missingDc"));
            }

            if (!this.surfaceShapes || this.surfaceShapes.length < 1) {
                return;
            }

            // Assemble the current visible tiles and update their associated textures if necessary.
            this.assembleTiles(dc);

            // Clean up references to all surface shapes to avoid dangling references. The surface shape list is no
            // longer needed, now that the shapes are held by each tile.
            this.surfaceShapes.splice(0, this.surfaceShapes.length);
            for (var idx = 0, len = this.surfaceShapeTiles.length; idx < len; idx += 1) {
                var tile = this.surfaceShapeTiles[idx];
                tile.clearShapes();
            }
        };

        /**
         * Assembles a set of surface tiles that are visible in the specified DrawContext and meet the tile builder's
         * resolution criteria. Tiles are culled against the current surface shape list, against the DrawContext's view
         * frustum during rendering mode, and against the DrawContext's pick frustums during picking mode. If a tile does
         * not meet the tile builder's resolution criteria, it's split into four sub-tiles and the process recursively
         * repeated on the sub-tiles.
         * <p/>
         * During assembly, each surface shape in {@link #surfaceShapes} is sorted into the tiles they
         * intersect. The top level tiles are used as an index to quickly determine which tiles each shape intersects.
         * Surface shapes are sorted into sub-tiles by simple intersection tests, and are added to each tile's surface
         * renderable list at most once. See {@link SurfaceShapeTileBuilder.SurfaceShapeTile#addSurfaceShape(SurfaceShape,
         * gov.nasa.worldwind.geom.Sector)}. Tiles that don't intersect any surface shapes are discarded.
         *
         * @param {DrawContext} dc The DrawContext to assemble tiles for.
         */
        SurfaceShapeTileBuilder.prototype.assembleTiles = function(dc) {
            var tile, idxShape, lenShapes, idxTile, lenTiles, idxSector, lenSectors;

            // Create a set of top level tiles only if that set doesn't exist yet.
            if (this.topLevelTiles.length < 1) {
                this.createTopLevelTiles();
            }

            // Store the top level tiles in a set to ensure that each top level tile is added only once. Store the tiles
            // that intersect each surface shape in a set to ensure that each object is added to a tile at most once.
            var intersectingTiles = {};

            // Iterate over the current surface shapes, adding each surface shape to the top level tiles that it
            // intersects. This produces a set of top level tiles containing the surface shapes that intersect each
            // tile. We use the tile structure as an index to quickly determine the tiles a surface shape intersects,
            // and add object to those tiles. This has the effect of quickly sorting the objects into the top level tiles.
            // We collect the top level tiles in a HashSet to ensure there are no duplicates when multiple objects intersect
            // the same top level tiles.
            for (idxShape = 0, lenShapes = this.surfaceShapes.length; idxShape < lenShapes; idxShape += 1) {
                var surfaceShape = this.surfaceShapes[idxShape];

                var sectors = surfaceShape.computeSectors(dc);
                if (!sectors) {
                    continue;
                }

                for (idxSector = 0, lenSectors = sectors.length; idxSector < lenSectors; idxSector += 1) {
                    var sector = sectors[idxSector];

                    for (idxTile = 0, lenTiles = this.topLevelTiles.length; idxTile < lenTiles; idxTile += 1) {
                        tile = this.topLevelTiles[idxTile];

                        if (tile.sector.intersects(sector)) {
                            var cacheKey = tile.tileKey;
                            intersectingTiles[cacheKey] = tile;
                            tile.addSurfaceShape(surfaceShape);
                        }
                    }
                }
            }

            // Add each top level tile or its descendants to the current tile list.
            //for (var idxTile = 0, lenTiles = this.topLevelTiles.length; idxTile < lenTiles; idxTile += 1) {
            for (var key in intersectingTiles) {
                if (intersectingTiles.hasOwnProperty(key)) {
                    tile = intersectingTiles[key];

                    this.addTileOrDescendants(dc, this.levels, null, tile);
                }
            }
        };

        /**
         * Potentially adds the specified tile or its descendants to the tile builder's surface shape tile collection.
         * The tile and its descendants are discarded if the tile is not visible or does not intersect any surface shapes in the
         * parent's surface shape list.
         * <p/>
         * If the tile meet the tile builder's resolution criteria it's added to the tile builder's
         * <code>currentTiles</code> list. Otherwise, it's split into four sub-tiles and each tile is recursively processed.
         *
         * @param {DrawContext} dc              The current DrawContext.
         * @param {LevelSet} levels             The tile's LevelSet.
         * @param {SurfaceShapeTile} parentTile The tile's parent, or null if the tile is a top level tile.
         * @param {SurfaceShapeTile} tile       The tile to add.
         */
        SurfaceShapeTileBuilder.prototype.addTileOrDescendants = function (dc, levels, parentTile, tile) {
            // Ignore this tile if it falls completely outside the frustum. This may be the viewing frustum or the pick
            // frustum, depending on the implementation.
            if (!this.intersectsFrustum(dc, tile)) {
                // This tile is not added to the current tile list, so we clear it's object list to prepare it for use
                // during the next frame.
                tile.clearShapes();
                return;
            }

            // If the parent tile is not null, add any parent surface shapes that intersect this tile.
            if (parentTile != null) {
                this.addIntersectingShapes(dc, parentTile, tile);
            }

            // Ignore tiles that do not intersect any surface shapes.
            if (!tile.hasShapes()) {
                return;
            }

            // If this tile meets the current rendering criteria, add it to the current tile list. This tile's object list
            // is cleared after the tile update operation.
            if (this.meetsRenderCriteria(dc, levels, tile)) {
                this.addTile(dc, tile);
                return;
            }

            var nextLevel = levels.level(tile.level.levelNumber + 1);
            var subTiles = dc.pickingMode ?
                tile.subdivide(nextLevel, this) :
                tile.subdivideToCache(nextLevel, this, this.tileCache);
            for (var idxTile = 0, lenTiles = subTiles.length; idxTile < lenTiles; idxTile += 1) {
                var subTile = subTiles[idxTile];
                this.addTileOrDescendants(dc, levels, tile, subTile);
            }

            // This tile is not added to the current tile list, so we clear it's object list to prepare it for use during
            // the next frame.
            tile.clearShapes();
        };

        /**
         * Adds surface shapes from the parent's object list to the specified tile's object list. Adds any of the
         * parent's surface shapes that intersect the tile's sector to the tile's object list.
         *
         * @param {DrawContext} dc              The current DrawContext.
         * @param {SurfaceShapeTile} parentTile The tile's parent.
         * @param {SurfaceShapeTile} tile       The tile to add intersecting surface shapes to.
         */
        SurfaceShapeTileBuilder.prototype.addIntersectingShapes = function (dc, parentTile, tile) {
            var shapes = parentTile.getShapes();
            for (var idxShape = 0, lenShapes = shapes.length; idxShape < lenShapes; idxShape += 1) {
                var shape = shapes[idxShape];

                var sectors = shape.computeSectors(dc);
                if (!sectors) {
                    continue;
                }

                // Test intersection against each of the surface shape's sectors. We break after finding an
                // intersection to avoid adding the same object to the tile more than once.
                for (var idxSector = 0, lenSectors = sectors.length; idxSector < lenSectors; idxSector += 1) {
                    var sector = sectors[idxSector];

                    if (tile.getSector().intersects(sector)) {
                        tile.addSurfaceShape(shape);
                        break;
                    }
                }
            }
        };

        /**
         * Adds the specified tile to this tile builder's surface tile collection.
         *
         * @param {DrawContext} dc The draw context.
         * @param {SurfaceShapeTile} tile The tile to add.
         */
        SurfaceShapeTileBuilder.prototype.addTile = function(dc, tile) {
            if (dc.pickingMode) {
                tile.pickSequence = SurfaceShapeTileBuilder.pickSequence;
            }

            if (tile.needsUpdate(dc)) {
                tile.updateTexture(dc);
            }

            this.surfaceShapeTiles.push(tile);
        };

        /**
         * Internal use only.
         *
         * Returns a new SurfaceObjectTile corresponding to the specified {@code sector}, {@code level}, {@code row},
         * and {@code column}.
         *
         * CAUTION: it is assumed that there exists a single SurfaceShapeTileBuilder. This algorithm might be invalid if there
         * are more of them (or it might actually work, although it hasn't been tested in that context).
         *
         * @param {Sector} sector       The tile's Sector.
         * @param {Level} level         The tile's Level in a {@link LevelSet}.
         * @param {Number} row          The tile's row in the Level, starting from 0 and increasing to the right.
         * @param {Number} column       The tile's column in the Level, starting from 0 and increasing upward.
         *
         * @return {SurfaceShapeTile} a new SurfaceShapeTile.
         */
        SurfaceShapeTileBuilder.prototype.createTile = function(sector, level, row, column) {
            return new SurfaceShapeTile(sector, level, row, column);
        };

        SurfaceShapeTileBuilder.prototype.createTopLevelTiles = function() {
            Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles);
        };

        /**
         * Test if the tile intersects the specified draw context's frustum. During picking mode, this tests intersection
         * against all of the draw context's pick frustums. During rendering mode, this tests intersection against the draw
         * context's viewing frustum.
         *
         * @param {DrawContext} dc   The draw context the surface shape is related to.
         * @param {SurfaceShapeTile} tile The tile to test for intersection.
         *
         * @return {Boolean} true if the tile intersects the draw context's frustum; false otherwise.
         */
        SurfaceShapeTileBuilder.prototype.intersectsFrustum = function(dc, tile) {
            if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
                return false;
            }

            tile.update(dc);

            return tile.extent.intersectsFrustum(dc.pickingMode ? dc.pickFrustum : dc.navigatorState.frustumInModelCoordinates);
        };

        /**
         * Tests if the specified tile meets the rendering criteria on the specified draw context. This returns true if the
         * tile is from the level set's final level, or if the tile achieves the desired resolution on the draw context.
         *
         * @param {DrawContext} dc          The current draw context.
         * @param {LevelSet} levels         The level set the tile belongs to.
         * @param {SurfaceShapeTile} tile   The tile to test.
         *
         * @return {Boolean} true if the tile meets the rendering criteria; false otherwise.
         */
        SurfaceShapeTileBuilder.prototype.meetsRenderCriteria = function(dc, levels, tile) {
            return tile.level.levelNumber == levels.lastLevel().levelNumber || !tile.mustSubdivide(dc, this.detailControl);
        };

        /**
         * Internal use only.
         * Count of pick operations. This is used to give a surface shape tile a unique pick sequence number if it is
         * participating in picking.
         * @type {Number}
         */
        SurfaceShapeTileBuilder.pickSequence = 0;

        return SurfaceShapeTileBuilder;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceTileRendererProgram
 */
define('shaders/SurfaceTileRendererProgram',[
        '../error/ArgumentError',
        '../util/Color',
        '../shaders/GpuProgram',
        '../util/Logger'
    ],
    function (ArgumentError,
              Color,
              GpuProgram,
              Logger) {
        "use strict";

        /**
         * Constructs a new surface-tile-renderer program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program. This
         * method then compiles the shaders and links the program if compilation is successful. Use the bind method to make the
         * program current during rendering.
         *
         * @alias SurfaceTileRendererProgram
         * @constructor
         * @augments GpuProgram
         * @classdesc A GLSL program that draws textured geometry on the globe's terrain.
         * Application's typically do not interact with this class.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         */
        var SurfaceTileRendererProgram = function (gl) {
            var vertexShaderSource =
                    'attribute vec4 vertexPoint;\n' +
                    'attribute vec4 vertexTexCoord;\n' +
                    'uniform mat4 mvpMatrix;\n' +
                    'uniform mat4 texSamplerMatrix;\n' +
                    'uniform mat4 texMaskMatrix;\n' +
                    'varying vec2 texSamplerCoord;\n' +
                    'varying vec2 texMaskCoord;\n' +
                    'void main() {\n' +
                    'gl_Position = mvpMatrix * vertexPoint;\n' +
                        /* Transform the vertex texture coordinate into sampler texture coordinates. */
                    'texSamplerCoord = (texSamplerMatrix * vertexTexCoord).st;\n' +
                        /* Transform the vertex texture coordinate into mask texture coordinates. */
                    'texMaskCoord = (texMaskMatrix * vertexTexCoord).st;\n' +
                    '}',
                fragmentShaderSource =
                    'precision mediump float;\n' +
                        /* Uniform sampler indicating the texture 2D unit (0, 1, 2, etc.) to use when sampling texture color. */
                    'uniform sampler2D texSampler;\n' +
                    'uniform float opacity;\n' +
                    'uniform vec4 color;\n' +
                    'uniform bool modulateColor;\n' +
                    'varying vec2 texSamplerCoord;\n' +
                    'varying vec2 texMaskCoord;\n' +
                        /*
                         * Returns true when the texture coordinate samples texels outside the texture image.
                         */
                    'bool isInsideTextureImage(const vec2 coord) {\n' +
                    '    return coord.x >= 0.0 && coord.x <= 1.0 && coord.y >= 0.0 && coord.y <= 1.0;\n' +
                    '}\n' +
                        /*
                         * OpenGL ES Shading Language v1.00 fragment shader for SurfaceTileRendererProgram. Writes the value of the texture 2D
                         * object bound to texSampler at the current transformed texture coordinate, multiplied by the uniform opacity. Writes
                         * transparent black (0, 0, 0, 0) if the transformed texture coordinate indicates a texel outside of the texture data's
                         * standard range of [0,1].
                         */
                    'void main(void) {\n' +
                        'float mask = float(isInsideTextureImage(texMaskCoord));' +
                    'if (modulateColor) {\n' +
                    '    gl_FragColor = color * mask * floor(texture2D(texSampler, texSamplerCoord).a + 0.5);\n' +
                    '} else {\n' +
                        /* Return either the sampled texture2D color multiplied by opacity or transparent black. */
                    '    gl_FragColor = texture2D(texSampler, texSamplerCoord) * mask * opacity;\n' +
                    '}\n' +
                    '}';

            // Call to the superclass, which performs shader program compiling and linking.
            GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource);

            // Capture the attribute and uniform locations.

            /**
             * This program's vertex point location.
             * @type {Number}
             * @readonly
             */
            this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");

            /**
             * This program's texture coordinate location.
             * @type {Number}
             * @readonly
             */
            this.vertexTexCoordLocation = this.attributeLocation(gl, "vertexTexCoord");

            /**
             * This program's modelview-projection matrix location.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");

            /**
             * The WebGL location for this program's 'color' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.colorLocation = this.uniformLocation(gl, "color");

            /**
             * The WebGL location for this program's 'modulateColor' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.modulateColorLocation = this.uniformLocation(gl, "modulateColor");

            // The rest of these are strictly internal and intentionally not documented.
            this.texSamplerMatrixLocation = this.uniformLocation(gl, "texSamplerMatrix");
            this.texMaskMatrixLocation = this.uniformLocation(gl, "texMaskMatrix");
            this.texSamplerLocation = this.uniformLocation(gl, "texSampler");
            this.opacityLocation = this.uniformLocation(gl, "opacity");

            /**
             * The WebGL location for this program's 'vertexTexCoord' attribute.
             * @type {Number}
             * @readonly
             */
            this.vertexPointLocation = -1;
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        SurfaceTileRendererProgram.key = "WorldWindGpuSurfaceTileRenderingProgram";

        SurfaceTileRendererProgram.prototype = Object.create(GpuProgram.prototype);

        /**
         * Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        SurfaceTileRendererProgram.prototype.loadModelviewProjection = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadModelviewProjection",
                        "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
        };

        /**
         * Loads the specified matrix as the value of this program's 'texSamplerMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        SurfaceTileRendererProgram.prototype.loadTexSamplerMatrix = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadTexSamplerMatrix",
                        "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.texSamplerMatrixLocation);
        };

        /**
         * Loads the specified matrix as the value of this program's 'texMaskMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        SurfaceTileRendererProgram.prototype.loadTexMaskMatrix = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadTexMaskMatrix",
                        "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.texMaskMatrixLocation);
        };

        /**
         * Loads the specified texture unit ID as the value of this program's 'texSampler' uniform variable.
         * The specified unit ID must be one of the GL_TEXTUREi WebGL enumerations, where i ranges from 0 to
         * GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} unit The unit ID to load.
         */
        SurfaceTileRendererProgram.prototype.loadTexSampler = function (gl, unit) {
            gl.uniform1i(this.texSamplerLocation, unit - WebGLRenderingContext.TEXTURE0);
        };

        /**
         * Loads the specified value as the value of this program's 'opacity' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} opacity The opacity to load.
         */
        SurfaceTileRendererProgram.prototype.loadOpacity = function (gl, opacity) {
            gl.uniform1f(this.opacityLocation, opacity);
        };

        /**
         * Loads the specified color as the value of this program's 'color' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Color} color The color to load.
         * @throws {ArgumentError} If the specified color is null or undefined.
         */
        SurfaceTileRendererProgram.prototype.loadColor = function (gl, color) {
            if (!color) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRendererProgram", "loadColor", "missingColor"));
            }

            this.loadUniformColor(gl, color, this.colorLocation);
        };

        /**
         * Loads the specified boolean as the value of this program's 'modulateColor' uniform variable. When this
         * value is true the color uniform of this shader is
         * multiplied by the rounded alpha component of the texture color at each fragment. This causes the color
         * to be either fully opaque or fully transparent depending on the value of the texture color's alpha value.
         * This is used during picking to replace opaque or mostly opaque texture colors with the pick color, and
         * to make all other texture colors transparent.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Boolean} enable <code>true</code> to enable modulation, <code>false</code> to disable modulation.
         */
        SurfaceTileRendererProgram.prototype.loadModulateColor = function (gl, enable) {
            gl.uniform1i(this.modulateColorLocation, enable ? 1 : 0);
        };

        return SurfaceTileRendererProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceTileRenderer
 */
define('render/SurfaceTileRenderer',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Matrix',
        '../shapes/SurfaceShapeTile',
        '../shaders/SurfaceTileRendererProgram'
    ],
    function (ArgumentError,
              Logger,
              Matrix,
              SurfaceShapeTile,
              SurfaceTileRendererProgram) {
        "use strict";

        /**
         * Constructs a new surface tile renderer.
         * @alias SurfaceTileRenderer
         * @constructor
         * @classdesc This class is responsible for rendering imagery onto the terrain.
         * It is meant to be used internally. Applications typically do not interact with this class.
         */
        var SurfaceTileRenderer = function () {

            // Scratch values to avoid constantly recreating these matrices.
            this.texMaskMatrix = Matrix.fromIdentity();
            this.texSamplerMatrix = Matrix.fromIdentity();

            // Internal. Intentionally not documented.
            this.isSurfaceShapeTileRendering = false;
        };

        /**
         * Render a specified collection of surface tiles.
         * @param {DrawContext} dc The current draw context.
         * @param {SurfaceTile[]} surfaceTiles The surface tiles to render.
         * @param {Number} opacity The opacity at which to draw the surface tiles.
         * @param {Boolean} tilesHaveOpacity If true, incoming tiles each have their own opacity property and
         * it's value is applied when the tile is drawn.
         * @throws {ArgumentError} If the specified surface tiles array is null or undefined.
         */
        SurfaceTileRenderer.prototype.renderTiles = function (dc, surfaceTiles, opacity, tilesHaveOpacity) {
            if (!surfaceTiles) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceTileRenderer", "renderTiles",
                        "Specified surface tiles array is null or undefined."));
            }

            if (surfaceTiles.length < 1)
                return;

            var terrain = dc.terrain,
                gl = dc.currentGlContext,
                tileCount = 0,// for frame statistics,
                program,
                terrainTile,
                terrainTileSector,
                surfaceTile,
                currentTileOpacity = 1;

            if (!terrain)
                return;

            this.isSurfaceShapeTileRendering = surfaceTiles[0] instanceof SurfaceShapeTile;

            opacity *= dc.surfaceOpacity;

            // For each terrain tile, render it for each overlapping surface tile.
            program = this.beginRendering(dc, opacity);
            terrain.beginRendering(dc);
            try {
                for (var i = 0, ttLen = terrain.surfaceGeometry.length; i < ttLen; i++) {
                    terrainTile = terrain.surfaceGeometry[i];
                    terrainTileSector = terrainTile.sector;

                    terrain.beginRenderingTile(dc, terrainTile);
                    try {
                        // Render the terrain tile for each overlapping surface tile.
                        for (var j = 0, stLen = surfaceTiles.length; j < stLen; j++) {
                            surfaceTile = surfaceTiles[j];
                            if (surfaceTile.sector.overlaps(terrainTileSector)) {
                                if (surfaceTile.bind(dc)) {
                                    if (dc.pickingMode) {
                                        if (surfaceTile.pickColor) {
                                            program.loadColor(gl, surfaceTile.pickColor);
                                        } else {
                                            // Surface shape tiles don't use a pick color. Pick colors are encoded into
                                            // the colors of the individual shapes drawn into the tile.
                                        }
                                    } else {
                                        if (tilesHaveOpacity && surfaceTile.opacity != currentTileOpacity) {
                                            program.loadOpacity(gl, opacity * surfaceTile.opacity);
                                            currentTileOpacity = surfaceTile.opacity;
                                        }
                                    }

                                    this.applyTileState(dc, terrainTile, surfaceTile);
                                    terrain.renderTile(dc, terrainTile);
                                    ++tileCount;
                                }
                            }
                        }
                    }
                    catch (e) {
                        console.log(e);
                    }
                    finally {
                        terrain.endRenderingTile(dc, terrainTile);
                    }
                }
            }
            catch (e) {
                console.log(e);
            }
            finally {
                terrain.endRendering(dc);
                this.endRendering(dc);
                dc.frameStatistics.incrementRenderedTileCount(tileCount);
            }
        };

        // Intentionally not documented.
        SurfaceTileRenderer.prototype.beginRendering = function (dc, opacity) {
            var gl = dc.currentGlContext,
                program = dc.findAndBindProgram(SurfaceTileRendererProgram);
            program.loadTexSampler(gl, gl.TEXTURE0);

            if (dc.pickingMode && !this.isSurfaceShapeTileRendering) {
                program.loadModulateColor(gl, true);
            } else {
                program.loadModulateColor(gl, false);
                program.loadOpacity(gl, opacity);
            }

            return program;
        };

        // Intentionally not documented.
        SurfaceTileRenderer.prototype.endRendering = function (dc) {
            var gl = dc.currentGlContext;
            gl.bindTexture(gl.TEXTURE_2D, null);
        };

        // Intentionally not documented.
        SurfaceTileRenderer.prototype.applyTileState = function (dc, terrainTile, surfaceTile) {
            // Sets up the texture transform and mask that applies the texture tile to the terrain tile.
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                terrainSector = terrainTile.sector,
                terrainDeltaLat = terrainSector.deltaLatitude(),
                terrainDeltaLon = terrainSector.deltaLongitude(),
                surfaceSector = surfaceTile.sector,
                rawSurfaceDeltaLat = surfaceSector.deltaLatitude(),
                rawSurfaceDeltaLon = surfaceSector.deltaLongitude(),
                surfaceDeltaLat = rawSurfaceDeltaLat > 0 ? rawSurfaceDeltaLat : 1,
                surfaceDeltaLon = rawSurfaceDeltaLon > 0 ? rawSurfaceDeltaLon : 1,
                sScale = terrainDeltaLon / surfaceDeltaLon,
                tScale = terrainDeltaLat / surfaceDeltaLat,
                sTrans = -(surfaceSector.minLongitude - terrainSector.minLongitude) / surfaceDeltaLon,
                tTrans = -(surfaceSector.minLatitude - terrainSector.minLatitude) / surfaceDeltaLat;

            this.texMaskMatrix.set(
                sScale, 0, 0, sTrans,
                0, tScale, 0, tTrans,
                0, 0, 1, 0,
                0, 0, 0, 1
            );

            this.texSamplerMatrix.setToUnitYFlip();
            surfaceTile.applyInternalTransform(dc, this.texSamplerMatrix);
            this.texSamplerMatrix.multiplyMatrix(this.texMaskMatrix);

            program.loadTexSamplerMatrix(gl, this.texSamplerMatrix);
            program.loadTexMaskMatrix(gl, this.texMaskMatrix);
        };

        return SurfaceTileRenderer;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TextSupport
 */
define('render/TextSupport',[
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/Logger',
        '../geom/Matrix',
        '../render/Texture',
        '../geom/Vec2'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              Logger,
              Matrix,
              Texture,
              Vec2) {
        "use strict";

        /**
         * Constructs a TextSupport instance.
         * @alias TextSupport
         * @constructor
         * @classdesc Provides methods useful for displaying text. An instance of this class is attached to the
         * WorldWindow {@link DrawContext} and is not intended to be used independently of that. Applications typically do
         * not create instances of this class.
         */
        var TextSupport = function () {

            // Internal use only. Intentionally not documented.
            this.canvas2D = document.createElement("canvas");

            // Internal use only. Intentionally not documented.
            this.ctx2D = this.canvas2D.getContext("2d");

            // Internal use only. Intentionally not documented.
            this.lineSpacing = 0.15; // fraction of font size

            // Internal use only. Intentionally not documented.
            this.strokeStyle = "rgba(0, 0, 0, " + 0.5 + ")";

            // Internal use only. Intentionally not documented.
            this.strokeWidth = 4;
        };

        /**
         * Returns the width and height of a specified text string upon applying a specified font and optional outline.
         * @param {string} text The text string.
         * @param {Font} font The font to apply when drawing the text.
         * @param {Boolean} outline Indicates whether the text includes an outline, which increases its width and height.
         * @returns {Vec2} A vector indicating the text's width and height, respectively, in pixels.
         */
        TextSupport.prototype.textSize = function (text, font, outline) {
            if (text.length === 0) {
                return new Vec2(0, 0);
            }

            this.ctx2D.font = font.fontString;

            var lines = text.split("\n"),
                height = lines.length * (font.size * (1 + this.lineSpacing)),
                maxWidth = 0;

            for (var i = 0; i < lines.length; i++) {
                maxWidth = Math.max(maxWidth, this.ctx2D.measureText(lines[i]).width);
            }

            if (outline) {
                maxWidth += this.strokeWidth;
                height += this.strokeWidth;
            }

            return new Vec2(maxWidth, height);
        };

        /**
         * Creates a texture for a specified text string, a specified font and an optional outline.
         * @param {DrawContext} dc The current draw context.
         * @param {String} text The text string.
         * @param {Font} font The font to use.
         * @param {Boolean} outline Indicates whether the text is drawn with a thin black outline.
         * @returns {Texture} A texture for the specified text string and font.
         */
        TextSupport.prototype.createTexture = function (dc, text, font, outline) {
            var gl = dc.currentGlContext,
                ctx2D = this.ctx2D,
                canvas2D = this.canvas2D,
                textSize = this.textSize(text, font, outline),
                lines = text.split("\n"),
                strokeOffset = outline ? this.strokeWidth / 2 : 0,
                pixelScale = dc.pixelScale,
                x, y;

            canvas2D.width = Math.ceil(textSize[0]) * pixelScale;
            canvas2D.height = Math.ceil(textSize[1]) * pixelScale;

            ctx2D.scale(pixelScale, pixelScale);
            ctx2D.font = font.fontString;
            ctx2D.textBaseline = "top";
            ctx2D.textAlign = font.horizontalAlignment;
            ctx2D.fillStyle = Color.WHITE.toHexString(false);
            ctx2D.strokeStyle = this.strokeStyle;
            ctx2D.lineWidth = this.strokeWidth;
            ctx2D.lineCap = "round";
            ctx2D.lineJoin = "round";

            if (font.horizontalAlignment === "left") {
                ctx2D.translate(strokeOffset, 0);
            } else if (font.horizontalAlignment === "right") {
                ctx2D.translate(textSize[0] - strokeOffset, 0);
            } else {
                ctx2D.translate(textSize[0] / 2, 0);
            }

            for (var i = 0; i < lines.length; i++) {
                if (outline) {
                    ctx2D.strokeText(lines[i], 0, 0);
                }
                ctx2D.fillText(lines[i], 0, 0);
                ctx2D.translate(0, font.size * (1 + this.lineSpacing) + strokeOffset);
            }

            return new Texture(gl, canvas2D);
        };

        /**
         * Calculates maximum line height based on a font
         * @param {Font} font The font to use.
         * @returns {Vec2} A vector indicating the text's width and height, respectively, in pixels based on the passed font.
         */
        TextSupport.prototype.getMaxLineHeight = function(font)
        {
            // Check underscore + capital E with acute accent
            return this.textSize("_\u00c9", font, 0)[1];
        };

        /**
         * Wraps the text based on width and height using new linew delimiter
         * @param {String} text The text to wrap.
         * @param {Number} width The width in pixels.
         * @param {Number} height The height in pixels.
         * @param {Font} font The font to use.
         * @returns {String} The wrapped text.
         */
        TextSupport.prototype.wrap = function(text, width, height, font)
        {
            if (!text) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.WARNING, "TextSupport", "wrap", "missing text"));
            }

            var i;

            var lines = text.split("\n");
            var wrappedText = "";

            // Wrap each line
            for (i = 0; i < lines.length; i++)
            {
                lines[i] = this.wrapLine(lines[i], width, font);
            }
            // Concatenate all lines in one string with new line separators
            // between lines - not at the end
            // Checks for height limit.
            var currentHeight = 0;
            var heightExceeded = false;
            var maxLineHeight = this.getMaxLineHeight(font);
            for (i = 0; i < lines.length && !heightExceeded; i++)
            {
                var subLines = lines[i].split("\n");
                for (var j = 0; j < subLines.length && !heightExceeded; j++)
                {
                    if (height <= 0 || currentHeight + maxLineHeight <= height)
                    {
                        wrappedText += subLines[j];
                        currentHeight += maxLineHeight + this.lineSpacing;
                        if (j < subLines.length - 1) {
                            wrappedText += '\n';
                        }
                    }
                    else
                    {
                        heightExceeded = true;
                    }
                }

                if (i < lines.length - 1 && !heightExceeded) {
                    wrappedText += '\n';
                }
            }
            // Add continuation string if text truncated
            if (heightExceeded)
            {
                if (wrappedText.length > 0) {
                    wrappedText = wrappedText.substring(0, wrappedText.length - 1);
                }

                wrappedText += "...";
            }

            return wrappedText;
        };

        /**
         * Wraps a line of text based on width and height
         * @param {String} text The text to wrap.
         * @param {Number} width The width in pixels.
         * @param {Font} font The font to use.
         * @returns {String} The wrapped text.
         */
        TextSupport.prototype.wrapLine = function(text, width, font)
        {
            var wrappedText = "";

            // Single line - trim leading and trailing spaces
            var source = text.trim();
            var lineBounds = this.textSize(source, font, 0);
            if (lineBounds[0] > width)
            {
                // Split single line to fit preferred width
                var line = "";
                var start = 0;
                var end = source.indexOf(' ', start + 1);
                while (start < source.length)
                {
                    if (end == -1) {
                        end = source.length;   // last word
                    }

                    // Extract a 'word' which is in fact a space and a word
                    var word = source.substring(start, end);
                    var linePlusWord = line + word;
                    if (this.textSize(linePlusWord, font, 0)[0] <= width)
                    {
                        // Keep adding to the current line
                        line += word;
                    }
                    else
                    {
                        // Width exceeded
                        if (line.length != 0)
                        {
                            // Finish current line and start new one
                            wrappedText += line;
                            wrappedText += '\n';
                            line = "";
                            line += word.trim();  // get read of leading space(s)
                        }
                        else
                        {
                            // Line is empty, force at least one word
                            line += word.trim();
                        }
                    }
                    // Move forward in source string
                    start = end;
                    if (start < source.length - 1)
                    {
                        end = source.indexOf(' ', start + 1);
                    }
                }
                // Gather last line
                wrappedText += line;
            }
            else
            {
                // Line doesn't need to be wrapped
                wrappedText += source;
            }

            return wrappedText;
        };

        return TextSupport;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DrawContext
 */
define('render/DrawContext',[
        '../error/ArgumentError',
        '../util/Color',
        '../util/FrameStatistics',
        '../render/FramebufferTexture',
        '../render/FramebufferTileController',
        '../geom/Frustum',
        '../globe/Globe',
        '../shaders/GpuProgram',
        '../cache/GpuResourceCache',
        '../layer/Layer',
        '../util/Logger',
        '../geom/Matrix',
        '../navigate/NavigatorState',
        '../pick/PickedObjectList',
        '../geom/Plane',
        '../geom/Position',
        '../geom/Rectangle',
        '../render/ScreenCreditController',
        '../geom/Sector',
        '../shapes/SurfaceShape',
        '../shapes/SurfaceShapeTileBuilder',
        '../render/SurfaceTileRenderer',
        '../render/TextSupport',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Color,
              FrameStatistics,
              FramebufferTexture,
              FramebufferTileController,
              Frustum,
              Globe,
              GpuProgram,
              GpuResourceCache,
              Layer,
              Logger,
              Matrix,
              NavigatorState,
              PickedObjectList,
              Plane,
              Position,
              Rectangle,
              ScreenCreditController,
              Sector,
              SurfaceShape,
              SurfaceShapeTileBuilder,
              SurfaceTileRenderer,
              TextSupport,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a DrawContext. Applications do not call this constructor. A draw context is created by a
         * {@link WorldWindow} during its construction.
         * @alias DrawContext
         * @constructor
         * @classdesc Provides current state during rendering. The current draw context is passed to most rendering
         * methods in order to make those methods aware of current state.
         * @param {WebGLRenderingContext} gl The WebGL rendering context this draw context is associated with.
         * @throws {ArgumentError} If the specified WebGL rendering context is null or undefined.
         */
        var DrawContext = function (gl) {
            if (!gl) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
                    "missingGlContext"));
            }

            /**
             * The current WebGL rendering context.
             * @type {WebGLRenderingContext}
             */
            this.currentGlContext = gl;

            /**
             * A 2D canvas for creating texture maps.
             * @type {HTMLElement}
             */
            this.canvas2D = document.createElement("canvas");

            /**
             * A 2D context for this draw context's [canvas property]{@link DrawContext#canvas}.
             */
            this.ctx2D = this.canvas2D.getContext("2d");

            /**
             * The current clear color.
             * @type {Color}
             * @default Color.TRANSPARENT (red = 0, green = 0, blue = 0, alpha = 0)
             */
            this.clearColor = Color.TRANSPARENT;

            /**
             * The GPU resource cache, which tracks WebGL resources.
             * @type {GpuResourceCache}
             */
            this.gpuResourceCache = new GpuResourceCache(WorldWind.configuration.gpuCacheSize,
                0.8 * WorldWind.configuration.gpuCacheSize);

            /**
             * The surface-tile-renderer to use for drawing surface tiles.
             * @type {SurfaceTileRenderer}
             */
            this.surfaceTileRenderer = new SurfaceTileRenderer();

            /**
             * The surface shape tile builder used to create and draw surface shapes.
             * @type {SurfaceShapeTileBuilder}
             */
            this.surfaceShapeTileBuilder = new SurfaceShapeTileBuilder();

            /**
             * Provides access to a multi-resolution WebGL framebuffer arranged as adjacent tiles in a pyramid. Surface
             * shapes use these tiles internally to draw on the terrain surface.
             * @type {FramebufferTileController}
             */
            this.surfaceShapeTileController = new FramebufferTileController();

            /**
             * The screen credit controller responsible for collecting and drawing screen credits.
             * @type {ScreenCreditController}
             */
            this.screenCreditController = new ScreenCreditController();

            /**
             * A shared TextSupport instance.
             * @type {TextSupport}
             */
            this.textSupport = new TextSupport();

            /**
             * The current WebGL framebuffer. Null indicates that the default WebGL framebuffer is active.
             * @type {FramebufferTexture}
             */
            this.currentFramebuffer = null;

            /**
             * The current WebGL program. Null indicates that no WebGL program is active.
             * @type {GpuProgram}
             */
            this.currentProgram = null;

            /**
             * The list of surface renderables.
             * @type {Array}
             */
            this.surfaceRenderables = [];

            /**
             * Indicates whether this draw context is in ordered rendering mode.
             * @type {Boolean}
             */
            this.orderedRenderingMode = false;

            /**
             * The list of ordered renderables.
             * @type {Array}
             */
            this.orderedRenderables = [];

            /**
             * The list of screen renderables.
             * @type {Array}
             */
            this.screeRenderables = [];

            // Internal. Intentionally not documented. Provides ordinal IDs to ordered renderables.
            this.orderedRenderablesCounter = 0; // Number

            /**
             * The starting time of the current frame, in milliseconds. The frame timestamp is updated immediately
             * before the WorldWindow associated with this draw context is rendered, either as a result of redrawing or
             * as a result of a picking operation.
             * @type {Number}
             * @readonly
             */
            this.timestamp = Date.now();

            /**
             * The [time stamp]{@link DrawContext#timestamp} of the last visible frame, in milliseconds. This indicates
             * the time stamp that was current during the WorldWindow's last frame, ignoring frames associated with a
             * picking operation. The difference between the previous redraw time stamp and the current time stamp
             * indicates the duration between visible frames, e.g. <code style='white-space:nowrap'>timeStamp - previousRedrawTimestamp</code>.
             * @type {Number}
             * @readonly
             */
            this.previousRedrawTimestamp = this.timestamp;

            /**
             * Indicates whether a redraw has been requested during the current frame. When true, this causes the World
             * Window associated with this draw context to redraw after the current frame.
             * @type {Boolean}
             */
            this.redrawRequested = false;

            /**
             * The globe being rendered.
             * @type {Globe}
             */
            this.globe = null;

            /**
             * A copy of the current globe's state key. Provided here to avoid having to recompute it every time
             * it's needed.
             * @type {String}
             */
            this.globeStateKey = null;

            /**
             * The layers being rendered.
             * @type {Layer[]}
             */
            this.layers = null;

            /**
             * The layer being rendered.
             * @type {Layer}
             */
            this.currentLayer = null;

            /**
             * The current state of the associated navigator.
             * @type {NavigatorState}
             */
            this.navigatorState = null;

            /**
             * The current eye position.
             * @type {Position}
             */
            this.eyePosition = new Position(0, 0, 0);

            /**
             * The current screen projection matrix.
             * @type {Matrix}
             */
            this.screenProjection = Matrix.fromIdentity();

            /**
             * The terrain for the current frame.
             * @type {Terrain}
             */
            this.terrain = null;

            /**
             * The current vertical exaggeration.
             * @type {Number}
             */
            this.verticalExaggeration = 1;

            /**
             * The number of milliseconds over which to fade shapes that support fading. Fading is most typically
             * used during decluttering.
             * @type {Number}
             * @default 500
             */
            this.fadeTime = 500;

            /**
             * The opacity to apply to terrain and surface shapes. Should be a number between 0 and 1.
             * @type {Number}
             * @default 1
             */
            this.surfaceOpacity = 1;

            /**
             * Frame statistics.
             * @type {FrameStatistics}
             */
            this.frameStatistics = null;

            /**
             * Indicates whether the frame is being drawn for picking.
             * @type {Boolean}
             */
            this.pickingMode = false;

            /**
             * Indicates that picking will return only the terrain object, if the pick point is over the terrain.
             * @type {Boolean}
             * @default false
             */
            this.pickTerrainOnly = false;

            /**
             * Indicates that picking will return all objects at the pick point, if any. The top-most object will have
             * its isOnTop flag set to true. If [deep picking]{@link WorldWindow#deepPicking} is false, the default,
             * only the top-most object is returned, plus the picked-terrain object if the pick point is over the
             * terrain.
             * @type {Boolean}
             * @default false
             */
            this.deepPicking = false;

            /**
             * Indicates that picking will return all objects that intersect the pick region, if any. Visible objects
             * will have the isOnTop flag set to true.
             * @type {Boolean}
             * @default false
             */
            this.regionPicking = false;

            /**
             * The current pick point, in screen coordinates.
             * @type {Vec2}
             */
            this.pickPoint = null;

            /**
             * The current pick rectangle, in WebGL (lower-left origin) screen coordinates.
             * @type {Rectangle}
             */
            this.pickRectangle = null;

            /**
             * The off-screen WebGL framebuffer used during picking.
             * @type {FramebufferTexture}
             * @readonly
             */
            this.pickFramebuffer = null;

            /**
             * The current pick frustum, created anew each picking frame.
             * @type {Frustum}
             * @readonly
             */
            this.pickFrustum = null;

            // Internal. Keeps track of the current pick color.
            this.pickColor = new Color(0, 0, 0, 1);

            /**
             * The objects at the current pick point.
             * @type {PickedObjectList}
             * @readonly
             */
            this.objectsAtPickPoint = new PickedObjectList();

            // Intentionally not documented.
            this.pixelScale = 1;
        };

        // Internal use. Intentionally not documented.
        DrawContext.unitCubeKey = "DrawContextUnitCubeKey";
        DrawContext.unitCubeElementsKey = "DrawContextUnitCubeElementsKey";
        DrawContext.unitQuadKey = "DrawContextUnitQuadKey";
        DrawContext.unitQuadKey3 = "DrawContextUnitQuadKey3";

        /**
         * Prepare this draw context for the drawing of a new frame.
         */
        DrawContext.prototype.reset = function () {
            // Reset the draw context's internal properties.
            this.screenCreditController.clear();
            this.surfaceRenderables = []; // clears the surface renderables array
            this.orderedRenderingMode = false;
            this.orderedRenderables = []; // clears the ordered renderables array
            this.screenRenderables = [];
            this.orderedRenderablesCounter = 0;

            // Advance the per-frame timestamp.
            var previousTimestamp = this.timestamp;
            this.timestamp = Date.now();
            if (this.timestamp === previousTimestamp)
                ++this.timestamp;

            // Reset properties set by the WorldWindow every frame.
            this.redrawRequested = false;
            this.globe = null;
            this.globeStateKey = null;
            this.layers = null;
            this.currentLayer = null;
            this.navigatorState = null;
            this.terrain = null;
            this.verticalExaggeration = 1;
            this.frameStatistics = null;
            this.accumulateOrderedRenderables = true;

            // Reset picking properties that may be set by the WorldWindow.
            this.pickingMode = false;
            this.pickTerrainOnly = false;
            this.deepPicking = false;
            this.regionPicking = false;
            this.pickPoint = null;
            this.pickRectangle = null;
            this.pickFrustum = null;
            this.pickColor = new Color(0, 0, 0, 1);
            this.objectsAtPickPoint.clear();
        };

        /**
         * Computes any values necessary to render the upcoming frame. Called after all draw context state for the
         * frame has been set.
         */
        DrawContext.prototype.update = function () {
            var gl = this.currentGlContext,
                eyePoint = this.navigatorState.eyePoint;

            this.globeStateKey = this.globe.stateKey;
            this.globe.computePositionFromPoint(eyePoint[0], eyePoint[1], eyePoint[2], this.eyePosition);
            this.screenProjection.setToScreenProjection(gl.drawingBufferWidth, gl.drawingBufferHeight);
        };

        /**
         * Notifies this draw context that the current WebGL rendering context has been lost. This function removes all
         * cached WebGL resources and resets all properties tracking the current WebGL state.
         */
        DrawContext.prototype.contextLost = function () {
            // Remove all cached WebGL resources, which are now invalid.
            this.gpuResourceCache.clear();
            this.pickFramebuffer = null;
            // Reset properties tracking the current WebGL state, which are now invalid.
            this.currentFramebuffer = null;
            this.currentProgram = null;
        };

        /**
         * Notifies this draw context that the current WebGL rendering context has been restored. This function prepares
         * this draw context to resume rendering.
         */
        DrawContext.prototype.contextRestored = function () {
            // Remove all cached WebGL resources. This cache is already cleared when the context is lost, but
            // asynchronous load operations that complete between context lost and context restored populate the cache
            // with invalid entries.
            this.gpuResourceCache.clear();
        };

        /**
         * Binds a specified WebGL framebuffer. This function also makes the framebuffer the active framebuffer.
         * @param {FramebufferTexture} framebuffer The framebuffer to bind. May be null or undefined, in which case the
         * default WebGL framebuffer is made active.
         */
        DrawContext.prototype.bindFramebuffer = function (framebuffer) {
            if (this.currentFramebuffer != framebuffer) {
                this.currentGlContext.bindFramebuffer(this.currentGlContext.FRAMEBUFFER,
                    framebuffer ? framebuffer.framebufferId : null);
                this.currentFramebuffer = framebuffer;
            }
        };

        /**
         * Binds a specified WebGL program. This function also makes the program the current program.
         * @param {GpuProgram} program The program to bind. May be null or undefined, in which case the currently
         * bound program is unbound.
         */
        DrawContext.prototype.bindProgram = function (program) {
            if (this.currentProgram != program) {
                this.currentGlContext.useProgram(program ? program.programId : null);
                this.currentProgram = program;
            }
        };

        /**
         * Binds a potentially cached WebGL program, creating and caching it if it isn't already cached.
         * This function also makes the program the current program.
         * @param {function} programConstructor The constructor to use to create the program.
         * @returns {GpuProgram} The bound program.
         * @throws {ArgumentError} If the specified constructor is null or undefined.
         */
        DrawContext.prototype.findAndBindProgram = function (programConstructor) {
            if (!programConstructor) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DrawContext", "findAndBindProgram",
                        "The specified program constructor is null or undefined."));
            }

            var program = this.gpuResourceCache.resourceForKey(programConstructor.key);
            if (program) {
                this.bindProgram(program);
            } else {
                try {
                    program = new programConstructor(this.currentGlContext);
                    this.bindProgram(program);
                    this.gpuResourceCache.putResource(programConstructor.key, program, program.size);
                } catch (e) {
                    Logger.log(Logger.LEVEL_SEVERE, "Error attempting to create GPU program.")
                }
            }

            return program;
        };

        /**
         * Adds a surface renderable to this draw context's surface renderable list.
         * @param {SurfaceRenderable} surfaceRenderable The surface renderable to add. May be null, in which case the
         * current surface renderable list remains unchanged.
         */
        DrawContext.prototype.addSurfaceRenderable = function (surfaceRenderable) {
            if (surfaceRenderable) {
                this.surfaceRenderables.push(surfaceRenderable);
            }
        };

        /**
         * Returns the surface renderable at the head of the surface renderable list without removing it from the list.
         * @returns {SurfaceRenderable} The first surface renderable in this draw context's surface renderable list, or
         * null if the surface renderable list is empty.
         */
        DrawContext.prototype.peekSurfaceRenderable = function () {
            if (this.surfaceRenderables.length > 0) {
                return this.surfaceRenderables[this.surfaceRenderables.length - 1];
            } else {
                return null;
            }
        };

        /**
         * Returns the surface renderable at the head of the surface renderable list and removes it from the list.
         * @returns {SurfaceRenderable} The first surface renderable in this draw context's surface renderable list, or
         * null if the surface renderable list is empty.
         */
        DrawContext.prototype.popSurfaceRenderable = function () {
            if (this.surfaceRenderables.length > 0) {
                return this.surfaceRenderables.pop();
            } else {
                return null;
            }
        };

        /**
         * Reverses the surface renderable list in place. After this function completes, the functions
         * peekSurfaceRenderable and popSurfaceRenderable return renderables in the order in which they were added to
         * the surface renderable list.
         */
        DrawContext.prototype.reverseSurfaceRenderables = function () {
            this.surfaceRenderables.reverse();
        };

        /**
         * Adds an ordered renderable to this draw context's ordered renderable list.
         * @param {OrderedRenderable} orderedRenderable The ordered renderable to add. May be null, in which case the
         * current ordered renderable list remains unchanged.
         * @param {Number} eyeDistance An optional argument indicating the ordered renderable's eye distance.
         * If this parameter is not specified then the ordered renderable must have an eyeDistance property.
         */
        DrawContext.prototype.addOrderedRenderable = function (orderedRenderable, eyeDistance) {
            if (orderedRenderable) {
                var ore = {
                    orderedRenderable: orderedRenderable,
                    insertionOrder: this.orderedRenderablesCounter++,
                    eyeDistance: eyeDistance || orderedRenderable.eyeDistance,
                    globeStateKey: this.globeStateKey
                };

                if (this.globe.continuous) {
                    ore.globeOffset = this.globe.offset;
                }

                if (ore.eyeDistance === 0) {
                    this.screenRenderables.push(ore);
                } else {
                    this.orderedRenderables.push(ore);
                }
            }
        };

        /**
         * Adds an ordered renderable to the end of this draw context's ordered renderable list, denoting it as the
         * most distant from the eye point.
         * @param {OrderedRenderable} orderedRenderable The ordered renderable to add. May be null, in which case the
         * current ordered renderable list remains unchanged.
         */
        DrawContext.prototype.addOrderedRenderableToBack = function (orderedRenderable) {
            if (orderedRenderable) {
                var ore = {
                    orderedRenderable: orderedRenderable,
                    insertionOrder: this.orderedRenderablesCounter++,
                    eyeDistance: Number.MAX_VALUE,
                    globeStateKey: this.globeStateKey
                };

                if (this.globe.continuous) {
                    ore.globeOffset = this.globe.offset;
                }

                this.orderedRenderables.push(ore);
            }
        };

        /**
         * Returns the ordered renderable at the head of the ordered renderable list without removing it from the list.
         * @returns {OrderedRenderable} The first ordered renderable in this draw context's ordered renderable list, or
         * null if the ordered renderable list is empty.
         */
        DrawContext.prototype.peekOrderedRenderable = function () {
            if (this.orderedRenderables.length > 0) {
                return this.orderedRenderables[this.orderedRenderables.length - 1].orderedRenderable;
            } else {
                return null;
            }
        };

        /**
         * Returns the ordered renderable at the head of the ordered renderable list and removes it from the list.
         * @returns {OrderedRenderable} The first ordered renderable in this draw context's ordered renderable list, or
         * null if the ordered renderable list is empty.
         */
        DrawContext.prototype.popOrderedRenderable = function () {
            if (this.orderedRenderables.length > 0) {
                var ore = this.orderedRenderables.pop();
                this.globeStateKey = ore.globeStateKey;

                if (this.globe.continuous) {
                    // Restore the globe state to that when the ordered renderable was created.
                    this.globe.offset = ore.globeOffset;
                }

                return ore.orderedRenderable;
            } else {
                return null;
            }
        };

        /**
         * Returns the ordered renderable at the head of the ordered renderable list and removes it from the list.
         * @returns {OrderedRenderable} The first ordered renderable in this draw context's ordered renderable list, or
         * null if the ordered renderable list is empty.
         */
        DrawContext.prototype.nextScreenRenderable = function () {
            if (this.screenRenderables.length > 0) {
                var ore = this.screenRenderables.shift();
                this.globeStateKey = ore.globeStateKey;

                if (this.globe.continuous) {
                    // Restore the globe state to that when the ordered renderable was created.
                    this.globe.offset = ore.globeOffset;
                }

                return ore.orderedRenderable;
            } else {
                return null;
            }
        };

        /**
         * Sorts the ordered renderable list from nearest to the eye point to farthest from the eye point.
         */
        DrawContext.prototype.sortOrderedRenderables = function () {
            // Sort the ordered renderables by eye distance from front to back and then by insertion time. The ordered
            // renderable peek and pop access the back of the ordered renderable list, thereby causing ordered renderables to
            // be processed from back to front.

            this.orderedRenderables.sort(function (oreA, oreB) {
                var eA = oreA.eyeDistance,
                    eB = oreB.eyeDistance;

                if (eA < eB) { // orA is closer to the eye than orB; sort orA before orB
                    return -1;
                } else if (eA > eB) { // orA is farther from the eye than orB; sort orB before orA
                    return 1;
                } else { // orA and orB are the same distance from the eye; sort them based on insertion time
                    var tA = oreA.insertionOrder,
                        tB = oreB.insertionOrder;

                    if (tA > tB) {
                        return -1;
                    } else if (tA < tB) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            });
        };

        /**
         * Reads the color from the current render buffer at a specified point. Used during picking to identify the item most
         * recently affecting the pixel at the specified point.
         * @param {Vec2} pickPoint The current pick point.
         * @returns {Color} The color at the pick point.
         */
        DrawContext.prototype.readPickColor = function (pickPoint) {
            var glPickPoint = this.navigatorState.convertPointToViewport(pickPoint, new Vec2(0, 0)),
                colorBytes = new Uint8Array(4);

            this.currentGlContext.readPixels(glPickPoint[0], glPickPoint[1], 1, 1, this.currentGlContext.RGBA,
                this.currentGlContext.UNSIGNED_BYTE, colorBytes);

            if (this.clearColor.equalsBytes(colorBytes)) {
                return null;
            }

            return Color.colorFromByteArray(colorBytes);
        };

        /**
         * Reads the current pick buffer colors in a specified rectangle. Used during region picking to identify
         * the items not occluded.
         * @param {Rectangle} pickRectangle The rectangle for which to read the colors.
         * @returns {{}} An object containing the unique colors in the specified rectangle, excluding the current
         * clear color. The colors are referenced by their byte string
         * (see [Color.toByteString]{@link Color#toByteString}.
         */
        DrawContext.prototype.readPickColors = function (pickRectangle) {
            var gl = this.currentGlContext,
                colorBytes = new Uint8Array(pickRectangle.width * pickRectangle.height * 4),
                uniqueColors = {},
                color,
                blankColor = new Color(0, 0, 0, 0),
                packAlignment = gl.getParameter(gl.PACK_ALIGNMENT);

            gl.pixelStorei(gl.PACK_ALIGNMENT, 1); // read byte aligned
            this.currentGlContext.readPixels(pickRectangle.x, pickRectangle.y,
                pickRectangle.width, pickRectangle.height,
                gl.RGBA, gl.UNSIGNED_BYTE, colorBytes);
            gl.pixelStorei(gl.PACK_ALIGNMENT, packAlignment); // restore the pack alignment

            for (var i = 0, len = pickRectangle.width * pickRectangle.height; i < len; i++) {
                var k = i * 4;
                color = Color.colorFromBytes(colorBytes[k], colorBytes[k + 1], colorBytes[k + 2], colorBytes[k + 3]);
                if (color.equals(this.clearColor) || color.equals(blankColor))
                    continue;
                uniqueColors[color.toByteString()] = color;
            }

            return uniqueColors;
        };

        /**
         * Determines whether a specified picked object is under the pick point, and if it is adds it to this draw
         * context's list of picked objects. This method should be called by shapes during ordered rendering
         * after the shape is drawn. If this draw context is in single-picking mode, the specified pickable object
         * is added to the list of picked objects whether or not it is under the pick point.
         * @param pickableObject
         * @returns {null}
         */
        DrawContext.prototype.resolvePick = function (pickableObject) {
            if (!(pickableObject.userObject instanceof SurfaceShape) && this.deepPicking && !this.regionPicking) {
                var color = this.readPickColor(this.pickPoint);
                if (!color) { // getPickColor returns null if the pick point selects the clear color
                    return null;
                }

                if (pickableObject.color.equals(color)) {
                    this.addPickedObject(pickableObject);
                }
            } else {
                // Don't resolve. Just add the object to the pick list. It will be resolved later.
                this.addPickedObject(pickableObject);
            }
        };

        /**
         * Adds an object to the current picked-object list. The list identifies objects that are at the pick point
         * but not necessarily the top-most object.
         * @param  {PickedObject} pickedObject The object to add.
         */
        DrawContext.prototype.addPickedObject = function (pickedObject) {
            if (pickedObject) {
                this.objectsAtPickPoint.add(pickedObject);
            }
        };

        /**
         * Computes a unique color to use as a pick color.
         * @returns {Color} A unique color.
         */
        DrawContext.prototype.uniquePickColor = function () {
            var color = this.pickColor.nextColor().clone();

            return color.equals(this.clearColor) ? color.nextColor() : color;
        };

        /**
         * Creates an off-screen WebGL framebuffer for use during picking and stores it in this draw context. The
         * framebuffer width and height match the WebGL rendering context's drawingBufferWidth and drawingBufferHeight.
         */
        DrawContext.prototype.makePickFramebuffer = function () {
            var gl = this.currentGlContext,
                width = gl.drawingBufferWidth,
                height = gl.drawingBufferHeight;

            if (!this.pickFramebuffer ||
                this.pickFramebuffer.width != width ||
                this.pickFramebuffer.height != height) {

                this.pickFramebuffer = new FramebufferTexture(gl, width, height, true); // enable depth buffering
            }

            return this.pickFramebuffer;
        };

        /**
         * Creates a pick frustum for the current pick point and stores it in this draw context. If this context's
         * pick rectangle is null or undefined then a pick rectangle is also computed and assigned to this context.
         * If the existing pick rectangle extends beyond the viewport then it is truncated by this method to fit
         * within the viewport.
         * This method assumes that this draw context's pick point or pick rectangle has been set. It returns
         * false if neither one of these exists.
         *
         * @returns {Boolean} <code>true</code> if the pick frustum could be created, otherwise <code>false</code>.
         */
        DrawContext.prototype.makePickFrustum = function () {
            if (!this.pickPoint && !this.pickRectangle) {
                return false;
            }

            var lln, llf, lrn, lrf, uln, ulf, urn, urf, // corner points of frustum
                nl, nr, nt, nb, nn, nf, // normal vectors of frustum planes
                l, r, t, b, n, f, // frustum planes
                va, vb = new Vec3(0, 0, 0), // vectors formed by the corner points
                apertureRadius = 2, // radius of pick window in screen coordinates
                screenPoint = new Vec3(0, 0, 0),
                pickPoint,
                pickRectangle = this.pickRectangle,
                viewport = this.navigatorState.viewport;

            // Compute the pick rectangle if necessary.
            if (!pickRectangle) {
                pickPoint = this.navigatorState.convertPointToViewport(this.pickPoint, new Vec2(0, 0));
                pickRectangle = new Rectangle(
                    pickPoint[0] - apertureRadius,
                    pickPoint[1] - apertureRadius,
                    2 * apertureRadius,
                    2 * apertureRadius);
            }

            // Clamp the pick rectangle to the viewport.

            var xl = pickRectangle.x,
                xr = pickRectangle.x + pickRectangle.width,
                yb = pickRectangle.y,
                yt = pickRectangle.y + pickRectangle.height;

            if (xr < 0 || yt < 0 || xl > viewport.x + viewport.width || yb > viewport.y + viewport.height) {
                return false; // pick rectangle is outside the viewport.
            }

            pickRectangle.x = WWMath.clamp(xl, viewport.x, viewport.x + viewport.width);
            pickRectangle.y = WWMath.clamp(yb, viewport.y, viewport.y + viewport.height);
            pickRectangle.width = WWMath.clamp(xr, viewport.x, viewport.x + viewport.width) - pickRectangle.x;
            pickRectangle.height = WWMath.clamp(yt, viewport.y, viewport.y + viewport.height) - pickRectangle.y;
            this.pickRectangle = pickRectangle;

            // Compute the pick frustum.

            screenPoint[0] = pickRectangle.x;
            screenPoint[1] = pickRectangle.y;
            screenPoint[2] = 0;
            this.navigatorState.unProject(screenPoint, lln = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x;
            screenPoint[1] = pickRectangle.y;
            screenPoint[2] = 1;
            this.navigatorState.unProject(screenPoint, llf = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x + pickRectangle.width;
            screenPoint[1] = pickRectangle.y;
            screenPoint[2] = 0;
            this.navigatorState.unProject(screenPoint, lrn = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x + pickRectangle.width;
            screenPoint[1] = pickRectangle.y;
            screenPoint[2] = 1;
            this.navigatorState.unProject(screenPoint, lrf = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x;
            screenPoint[1] = pickRectangle.y + pickRectangle.height;
            screenPoint[2] = 0;
            this.navigatorState.unProject(screenPoint, uln = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x;
            screenPoint[1] = pickRectangle.y + pickRectangle.height;
            screenPoint[2] = 1;
            this.navigatorState.unProject(screenPoint, ulf = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x + pickRectangle.width;
            screenPoint[1] = pickRectangle.y + pickRectangle.height;
            screenPoint[2] = 0;
            this.navigatorState.unProject(screenPoint, urn = new Vec3(0, 0, 0));

            screenPoint[0] = pickRectangle.x + pickRectangle.width;
            screenPoint[1] = pickRectangle.y + pickRectangle.height;
            screenPoint[2] = 1;
            this.navigatorState.unProject(screenPoint, urf = new Vec3(0, 0, 0));

            va = new Vec3(ulf[0] - lln[0], ulf[1] - lln[1], ulf[2] - lln[2]);
            vb.set(uln[0] - llf[0], uln[1] - llf[1], uln[2] - llf[2]);
            nl = va.cross(vb);
            l = new Plane(nl[0], nl[1], nl[2], -nl.dot(lln));
            l.normalize();

            va = new Vec3(urn[0] - lrf[0], urn[1] - lrf[1], urn[2] - lrf[2]);
            vb.set(urf[0] - lrn[0], urf[1] - lrn[1], urf[2] - lrn[2]);
            nr = va.cross(vb);
            r = new Plane(nr[0], nr[1], nr[2], -nr.dot(lrn));
            r.normalize();

            va = new Vec3(ulf[0] - urn[0], ulf[1] - urn[1], ulf[2] - urn[2]);
            vb.set(urf[0] - uln[0], urf[1] - uln[1], urf[2] - uln[2]);
            nt = va.cross(vb);
            t = new Plane(nt[0], nt[1], nt[2], -nt.dot(uln));
            t.normalize();

            va = new Vec3(lrf[0] - lln[0], lrf[1] - lln[1], lrf[2] - lln[2]);
            vb.set(llf[0] - lrn[0], llf[1] - lrn[1], llf[2] - lrn[2]);
            nb = va.cross(vb);
            b = new Plane(nb[0], nb[1], nb[2], -nb.dot(lrn));
            b.normalize();

            va = new Vec3(uln[0] - lrn[0], uln[1] - lrn[1], uln[2] - lrn[2]);
            vb.set(urn[0] - lln[0], urn[1] - lln[1], urn[2] - lln[2]);
            nn = va.cross(vb);
            n = new Plane(nn[0], nn[1], nn[2], -nn.dot(lln));
            n.normalize();

            va = new Vec3(urf[0] - llf[0], urf[1] - llf[1], urf[2] - llf[2]);
            vb.set(ulf[0] - lrf[0], ulf[1] - lrf[1], ulf[2] - lrf[2]);
            nf = va.cross(vb);
            f = new Plane(nf[0], nf[1], nf[2], -nf.dot(llf));
            f.normalize();

            this.pickFrustum = new Frustum(l, r, b, t, n, f);

            return true;
        };

        /**
         * Indicates whether an extent is smaller than a specified number of pixels.
         * @param {BoundingBox} extent The extent to test.
         * @param {Number} numPixels The number of pixels below which the extent is considered small.
         * @returns {Boolean} True if the extent is smaller than the specified number of pixels, otherwise false.
         * Returns false if the extent is null or undefined.
         */
        DrawContext.prototype.isSmall = function (extent, numPixels) {
            if (!extent) {
                return false;
            }

            var distance = this.navigatorState.eyePoint.distanceTo(extent.center),
                pixelSize = this.navigatorState.pixelSizeAtDistance(distance);

            return (2 * extent.radius) < (numPixels * pixelSize); // extent diameter less than size of num pixels
        };

        /**
         * Returns the VBO ID of an array buffer containing a unit cube expressed as eight 3D vertices at (0, 1, 0),
         * (0, 0, 0), (1, 1, 0), (1, 0, 0), (0, 1, 1), (0, 0, 1), (1, 1, 1) and (1, 0, 1). The buffer is created on
         * first use and cached. Subsequent calls to this method return the cached buffer.
         * @returns {Object} The VBO ID identifying the array buffer.
         */
        DrawContext.prototype.unitCubeBuffer = function () {
            var vboId = this.gpuResourceCache.resourceForKey(DrawContext.unitCubeKey);

            if (!vboId) {
                var gl = this.currentGlContext,
                    points = new Float32Array(24),
                    i = 0;

                points[i++] = 0; // upper left corner, z = 0
                points[i++] = 1;
                points[i++] = 0;
                points[i++] = 0; // lower left corner, z = 0
                points[i++] = 0;
                points[i++] = 0;
                points[i++] = 1; // upper right corner, z = 0
                points[i++] = 1;
                points[i++] = 0;
                points[i++] = 1; // lower right corner, z = 0
                points[i++] = 0;
                points[i++] = 0;

                points[i++] = 0; // upper left corner, z = 1
                points[i++] = 1;
                points[i++] = 1;
                points[i++] = 0; // lower left corner, z = 1
                points[i++] = 0;
                points[i++] = 1;
                points[i++] = 1; // upper right corner, z = 1
                points[i++] = 1;
                points[i++] = 1;
                points[i++] = 1; // lower right corner, z = 1
                points[i++] = 0;
                points[i] = 1;

                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);
                this.frameStatistics.incrementVboLoadCount(1);

                this.gpuResourceCache.putResource(DrawContext.unitCubeKey, vboId, points.length * 4);
            }

            return vboId;
        };

        /**
         * Returns the VBO ID of a element array buffer containing the tessellation of a unit cube expressed as
         * a single buffer containing both triangle indices and line indices. This is intended for use in conjunction
         * with <code>unitCubeBuffer</code>. The unit cube's interior and outline may be rasterized as shown in the
         * following WebGL pseudocode:
         * <code><pre>
         * // Assumes that the VBO returned by unitCubeBuffer is used as the source of vertex positions.
         * bindBuffer(ELEMENT_ARRAY_BUFFER, drawContext.unitCubeElements());
         * drawElements(TRIANGLES, 36, UNSIGNED_SHORT, 0); // draw the unit cube interior
         * drawElements(LINES, 24, UNSIGNED_SHORT, 72); // draw the unit cube outline
         * </pre></code>
         * The buffer is created on first use
         * and cached. Subsequent calls to this method return the cached buffer.
         * @returns {Object} The VBO ID identifying the element array buffer.
         */
        DrawContext.prototype.unitCubeElements = function () {
            var vboId = this.gpuResourceCache.resourceForKey(DrawContext.unitCubeElementsKey);

            if (!vboId) {
                var gl = this.currentGlContext,
                    elems = new Int16Array(60),
                    i = 0;

                // interior

                elems[i++] = 1; // -z face
                elems[i++] = 0;
                elems[i++] = 3;
                elems[i++] = 3;
                elems[i++] = 0;
                elems[i++] = 2;

                elems[i++] = 4; // +z face
                elems[i++] = 5;
                elems[i++] = 6;
                elems[i++] = 6;
                elems[i++] = 5;
                elems[i++] = 7;

                elems[i++] = 5; // -y face
                elems[i++] = 1;
                elems[i++] = 7;
                elems[i++] = 7;
                elems[i++] = 1;
                elems[i++] = 3;

                elems[i++] = 6; // +y face
                elems[i++] = 2;
                elems[i++] = 4;
                elems[i++] = 4;
                elems[i++] = 2;
                elems[i++] = 0;

                elems[i++] = 4; // -x face
                elems[i++] = 0;
                elems[i++] = 5;
                elems[i++] = 5;
                elems[i++] = 0;
                elems[i++] = 1;

                elems[i++] = 7; // +x face
                elems[i++] = 3;
                elems[i++] = 6;
                elems[i++] = 6;
                elems[i++] = 3;
                elems[i++] = 2;

                // outline

                elems[i++] = 0; // left, -z
                elems[i++] = 1;
                elems[i++] = 1; // bottom, -z
                elems[i++] = 3;
                elems[i++] = 3; // right, -z
                elems[i++] = 2;
                elems[i++] = 2; // top, -z
                elems[i++] = 0;

                elems[i++] = 4; // left, +z
                elems[i++] = 5;
                elems[i++] = 5; // bottom, +z
                elems[i++] = 7;
                elems[i++] = 7; // right, +z
                elems[i++] = 6;
                elems[i++] = 6; // top, +z
                elems[i++] = 4;

                elems[i++] = 0; // upper left
                elems[i++] = 4;
                elems[i++] = 5; // lower left
                elems[i++] = 1;
                elems[i++] = 2; // upper right
                elems[i++] = 6;
                elems[i++] = 7; // lower right
                elems[i] = 3;

                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, elems, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
                this.frameStatistics.incrementVboLoadCount(1);

                this.gpuResourceCache.putResource(DrawContext.unitCubeElementsKey, vboId, elems.length * 2);
            }

            return vboId;
        };

        /**
         * Returns the VBO ID of a buffer containing a unit quadrilateral expressed as four 2D vertices at (0, 1),
         * (0, 0), (1, 1) and (1, 0). The four vertices are in the order required by a triangle strip. The buffer is
         * created on first use and cached. Subsequent calls to this method return the cached buffer.
         * @returns {Object} The VBO ID identifying the vertex buffer.
         */
        DrawContext.prototype.unitQuadBuffer = function () {
            var vboId = this.gpuResourceCache.resourceForKey(DrawContext.unitQuadKey);

            if (!vboId) {
                var gl = this.currentGlContext,
                    points = new Float32Array(8);

                points[0] = 0; // upper left corner
                points[1] = 1;
                points[2] = 0; // lower left corner
                points[3] = 0;
                points[4] = 1; // upper right corner
                points[5] = 1;
                points[6] = 1; // lower right corner
                points[7] = 0;

                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);
                this.frameStatistics.incrementVboLoadCount(1);

                this.gpuResourceCache.putResource(DrawContext.unitQuadKey, vboId, points.length * 4);
            }

            return vboId;
        };

        /**
         * Returns the VBO ID of a buffer containing a unit quadrilateral expressed as four 3D vertices at (0, 1, 0),
         * (0, 0, 0), (1, 1, 0) and (1, 0, 0).
         * The four vertices are in the order required by a triangle strip. The buffer is created
         * on first use and cached. Subsequent calls to this method return the cached buffer.
         * @returns {Object} The VBO ID identifying the vertex buffer.
         */
        DrawContext.prototype.unitQuadBuffer3 = function () {
            var vboId = this.gpuResourceCache.resourceForKey(DrawContext.unitQuadKey3);

            if (!vboId) {
                var gl = this.currentGlContext,
                    points = new Float32Array(12);

                points[0] = 0; // upper left corner
                points[1] = 1;
                points[2] = 0;
                points[3] = 0; // lower left corner
                points[4] = 0;
                points[5] = 0;
                points[6] = 1; // upper right corner
                points[7] = 1;
                points[8] = 0;
                points[9] = 1; // lower right corner
                points[10] = 0;
                points[11] = 0;

                vboId = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);
                this.frameStatistics.incrementVboLoadCount(1);

                this.gpuResourceCache.putResource(DrawContext.unitQuadKey3, vboId, points.length * 4);
            }

            return vboId;
        };

        /**
         * Computes a Cartesian point at a location on the surface of this terrain according to a specified
         * altitude mode. If there is no current terrain, this function approximates the returned point by assuming
         * the terrain is the globe's ellipsoid.
         * @param {Number} latitude The location's latitude.
         * @param {Number} longitude The location's longitude.
         * @param {Number} offset Distance above the terrain, in meters relative to the specified altitude mode, at
         * which to compute the point.
         * @param {String} altitudeMode The altitude mode to use to compute the point. Recognized values are
         * WorldWind.ABSOLUTE, WorldWind.CLAMP_TO_GROUND and
         * WorldWind.RELATIVE_TO_GROUND. The mode WorldWind.ABSOLUTE is used if the
         * specified mode is null, undefined or unrecognized, or if the specified location is outside this terrain.
         * @param {Vec3} result A pre-allocated Vec3 in which to return the computed point.
         * @returns {Vec3} The specified result parameter, set to the coordinates of the computed point.
         * @throws {ArgumentError} If the specified result argument is null or undefined.
         */
        DrawContext.prototype.surfacePointForMode = function (latitude, longitude, offset, altitudeMode, result) {
            if (!result) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DrawContext", "surfacePointForMode", "missingResult"));
            }

            if (this.terrain) {
                this.terrain.surfacePointForMode(latitude, longitude, offset, altitudeMode, result);
            } else {
                var h = offset + this.globe.elevationAtLocation(latitude, longitude) * this.verticalExaggeration;
                this.globe.computePointFromPosition(latitude, longitude, h, result);
            }

            return result;
        };

        return DrawContext;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports EarthElevationModel
 */
define('globe/EarthElevationModel',[
        '../geom/Location',
        '../geom/Sector',
        '../globe/ElevationModel',
        '../util/WmsUrlBuilder'
    ],
    function (Location,
              Sector,
              ElevationModel,
              WmsUrlBuilder) {
        "use strict";

        /**
         * Constructs an Earth elevation model.
         * @alias EarthElevationModel
         * @constructor
         * @augments ElevationModel
         * @classdesc Provides elevations for Earth. Elevations are drawn from the NASA WorldWind elevation service.
         */
        var EarthElevationModel = function () {
            ElevationModel.call(this,
                Sector.FULL_SPHERE, new Location(45, 45), 12, "application/bil16", "EarthElevations256", 256, 256);

            this.displayName = "Earth Elevation Model";
            this.minElevation = -11000; // Depth of Marianas Trench, in meters
            this.maxElevation = 8850; // Height of Mt. Everest
            this.pixelIsPoint = false; // WorldWind WMS elevation layers return pixel-as-area images

            this.urlBuilder = new WmsUrlBuilder("https://worldwind26.arc.nasa.gov/elev",
                "GEBCO,aster_v2,USGS-NED", "", "1.3.0");
        };

        EarthElevationModel.prototype = Object.create(ElevationModel.prototype);

        return EarthElevationModel;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports EarthRestElevationModel
 */
define('globe/EarthRestElevationModel',[
        '../geom/Location',
        '../geom/Sector',
        '../globe/ElevationModel',
        '../util/LevelRowColumnUrlBuilder'
    ],
    function (Location,
              Sector,
              ElevationModel,
              LevelRowColumnUrlBuilder) {
        "use strict";

        // THIS CLASS IS NOT YET MEANT TO BE EXPOSED.
        ///**
        // * Constructs an elevation model for Earth using a REST interface to retrieve the elevations from the server.
        // * @alias EarthRestElevationModel
        // * @constructor
        // * @classdesc Represents an Earth elevation model spanning the globe and using a REST interface to retrieve
        // * the elevations from the server.
        // * See [LevelRowColumnUrlBuilder]{@link LevelRowColumnUrlBuilder} for a description of the REST interface.
        // * @param {String} serverAddress The server address of the tile service. May be null, in which case the
        // * current origin is used (see <code>window.location</code>.
        // * @param {String} pathToData The path to the data directory relative to the specified server address.
        // * May be null, in which case the server address is assumed to be the full path to the data directory.
        // * @param {String} displayName The display name to associate with this elevation model.
        // */
        var EarthRestElevationModel = function (serverAddress, pathToData, displayName) {
            ElevationModel.call(this,
                Sector.FULL_SPHERE, new Location(60, 60), 5, "application/bil16", "EarthElevations", 512, 512);

            this.displayName = displayName;
            this.minElevation = -11000; // Depth of Marianas Trench, in meters
            this.maxElevation = 8850; // Height of Mt. Everest
            this.urlBuilder = new LevelRowColumnUrlBuilder(serverAddress, pathToData);
        };

        EarthRestElevationModel.prototype = Object.create(ElevationModel.prototype);

        return EarthRestElevationModel;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports FrameStatisticsLayer
 */
define('layer/FrameStatisticsLayer',[
        '../error/ArgumentError',
        '../util/Color',
        '../util/Font',
        '../layer/Layer',
        '../util/Logger',
        '../util/Offset',
        '../shapes/ScreenText',
        '../shapes/TextAttributes'
    ],
    function (ArgumentError,
              Color,
              Font,
              Layer,
              Logger,
              Offset,
              ScreenText,
              TextAttributes) {
        "use strict";

        /**
         * Constructs a layer that displays the current performance statistics.
         * @alias FrameStatisticsLayer
         * @constructor
         * @augments Layer
         * @classDesc Displays the current performance statistics, which are collected each frame in the WorldWindow's
         * {@link FrameStatistics}. A frame statics layer cannot be shared among WorldWindows. Each WorldWindow if it
         * is to have a frame statistics layer must have its own.
         * @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
         * This layer may not be associated with more than one WorldWindow. Each WorldWindow must have it's own
         * instance of this layer if each window is to have a frame statistics display.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var FrameStatisticsLayer = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "FrameStatisticsLayer", "constructor", "missingWorldWindow"));
            }

            Layer.call(this, "Frame Statistics");

            // No picking of this layer's screen elements.
            this.pickEnabled = false;

            var textAttributes = new TextAttributes(null);
            textAttributes.color = Color.GREEN;
            textAttributes.font = new Font(12);
            textAttributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 1);

            // Intentionally not documented.
            this.frameTime = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 5, WorldWind.OFFSET_INSET_PIXELS, 5), " ");
            this.frameTime.attributes = textAttributes;

            // Intentionally not documented.
            this.frameRate = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 5, WorldWind.OFFSET_INSET_PIXELS, 25), " ");
            this.frameRate.attributes = textAttributes;

            // Register a redraw callback on the WorldWindow.
            var thisLayer = this;

            function redrawCallback(worldWindow, stage) {
                thisLayer.handleRedraw(worldWindow, stage);
            }

            worldWindow.redrawCallbacks.push(redrawCallback);
        };

        FrameStatisticsLayer.prototype = Object.create(Layer.prototype);

        // Documented in superclass.
        FrameStatisticsLayer.prototype.doRender = function (dc) {
            this.frameRate.render(dc);
            this.frameTime.render(dc);
            this.inCurrentFrame = true;
        };

        // Intentionally not documented.
        FrameStatisticsLayer.prototype.handleRedraw = function (worldWindow, stage) {
            if (stage != WorldWind.BEFORE_REDRAW) {
                return; // ignore after redraw events
            }

            var frameStats = worldWindow.frameStatistics;
            this.frameTime.text = "Frame time  " + frameStats.frameTimeAverage.toFixed(0) + " ms  (" + frameStats.frameTimeMin.toFixed(0) + " - " + frameStats.frameTimeMax.toFixed(0) + ")";
            this.frameRate.text = "Frame rate  " + frameStats.frameRateAverage.toFixed(0) + " fps";
        };

        return FrameStatisticsLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AbstractShape
 */
define('shapes/AbstractShape',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Matrix',
        '../cache/MemoryCache',
        '../render/Renderable',
        '../shapes/ShapeAttributes',
        '../error/UnsupportedOperationError',
        '../geom/Vec3'
    ],
    function (ArgumentError,
              Logger,
              Matrix,
              MemoryCache,
              Renderable,
              ShapeAttributes,
              UnsupportedOperationError,
              Vec3) {
        "use strict";

        /**
         * Constructs an abstract shape instance. Meant to be called only by subclasses.
         * @alias AbstractShape
         * @constructor
         * @augments Renderable
         * @protected
         * @classdesc Provides a base class for shapes other than surface shapes. Implements common attribute handling
         * and rendering flow. This is an abstract class and is meant to be instantiated only by subclasses.
         * <p>
         *     In order to support simultaneous use of this shape by multiple windows and 2D globes, this shape
         *     maintains a cache of data computed relative to the globe displayed in each window. During rendering,
         *     the data for the currently active globe, as indicated in the draw context, is made current.
         *     Subsequently called methods rely on the existence of this data cache entry.
         *
         * @param {ShapeAttributes} attributes The attributes to associate with this shape. May be null, in which case
         * default attributes are associated.
         */
        var AbstractShape = function (attributes) {

            Renderable.call(this);

            // Documented with its property accessor below.
            this._attributes = attributes ? attributes : new ShapeAttributes(null);

            // Documented with its property accessor below.
            this._highlightAttributes = null;

            /**
             * Indicates whether this shape uses its normal attributes or its highlight attributes when displayed.
             * If true, the highlight attributes are used, otherwise the normal attributes are used. The normal
             * attributes are also used if no highlight attributes have been specified.
             * @type {Boolean}
             * @default false
             */
            this.highlighted = false;

            // Private. See defined property below for documentation.
            this._altitudeMode = WorldWind.ABSOLUTE;

            // Internal use only. Intentionally not documented.
            // A position used to compute relative coordinates for the shape.
            this.referencePosition = null;

            // Internal use only. Intentionally not documented.
            // Holds the per-globe data generated during makeOrderedRenderable.
            this.shapeDataCache = new MemoryCache(3, 2);

            // Internal use only. Intentionally not documented.
            // The shape-data-cache data that is for the currently active globe. This field is made current prior to
            // calls to makeOrderedRenderable and doRenderOrdered.
            this.currentData = null;

            // Internal use only. Intentionally not documented.
            this.activeAttributes = null;

            /**
             * Indicates how long to use terrain-specific shape data before regenerating it, in milliseconds. A value
             * of zero specifies that shape data should be regenerated every frame. While this causes the shape to
             * adapt more frequently to the terrain, it decreases performance.
             * @type {Number}
             * @default 2000 (milliseconds)
             */
            this.expirationInterval = 2000;

            /**
             * Indicates whether to use a surface shape to represent this shape when drawn on a 2D globe.
             * @type {Boolean}
             * @default false
             */
            this.useSurfaceShapeFor2D = false;

            this.scratchMatrix = Matrix.fromIdentity(); // scratch variable
        };

        AbstractShape.prototype = Object.create(Renderable.prototype);

        Object.defineProperties(AbstractShape.prototype, {
            /**
             * This shape's normal (non-highlight) attributes.
             * @type {ShapeAttributes}
             * @memberof AbstractShape.prototype
             */
            attributes: {
                get: function () {
                    return this._attributes;
                },
                set: function (value) {
                    this._attributes = value;

                    if (this.surfaceShape) {
                        this.surfaceShape.attributes = this._attributes;
                    }
                }
            },

            /**
             * This shape's highlight attributes. If null or undefined and this shape's highlight flag is true, this
             * shape's normal attributes are used. If they in turn are null or undefined, this shape is not drawn.
             * @type {ShapeAttributes}
             * @default null
             * @memberof AbstractShape.prototype
             */
            highlightAttributes: {
                get: function () {
                    return this._highlightAttributes;
                },
                set: function (value) {
                    this._highlightAttributes = value;

                    if (this.surfaceShape) {
                        this.surfaceShape.highlightAttributes = this._highlightAttributes;
                    }
                }
            },

            /**
             * The altitude mode to use when drawing this shape. Recognized values are:
             * <ul>
             *     <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
             *     <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
             *     <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
             * </ul>
             * @type {String}
             * @default WorldWind.ABSOLUTE
             * @memberof AbstractShape.prototype
             */
            altitudeMode: {
                get: function () {
                    return this._altitudeMode;
                },
                set: function (altitudeMode) {
                    if (!altitudeMode) {
                        throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "AbstractShape",
                            "altitudeMode", "missingAltitudeMode"));
                    }

                    this._altitudeMode = altitudeMode;
                    this.reset();
                }
            }
        });

        /**
         * Clears this shape's data cache. Should be called by subclasses when state changes invalidate
         * cached data.
         * @protected
         */
        AbstractShape.prototype.reset = function () {
            this.shapeDataCache.clear(false);
            this.surfaceShape = null;
        };

        AbstractShape.prototype.updateSurfaceShape = function () {
            // Synchronize this AbstractShape's properties with its SurfaceShape's properties. Note that the attributes
            // and the highlightAttributes are synchronized separately.
            this.surfaceShape.displayName = this.displayName;
            this.surfaceShape.highlighted = this.highlighted;
            this.surfaceShape.enabled = this.enabled;
            this.surfaceShape.pathType = this.pathType;
            this.surfaceShape.pickDelegate = this.pickDelegate ? this.pickDelegate : this;
        };

        AbstractShape.prototype.createSurfaceShape = function () {
            return null;
        };

        AbstractShape.prototype.render = function (dc) {
            if (!this.enabled) {
                return;
            }

            if (!dc.accumulateOrderedRenderables) {
                return;
            }

            if (dc.globe.is2D() && this.useSurfaceShapeFor2D) {
                if (!this.surfaceShape) {
                    this.surfaceShape = this.createSurfaceShape();
                    if (this.surfaceShape) {
                        this.surfaceShape.attributes = this._attributes;
                        this.surfaceShape.highlightAttributes = this._highlightAttributes;
                    }
                }

                if (this.surfaceShape) {
                    this.updateSurfaceShape();
                    this.surfaceShape.render(dc);
                    return;
                }
            }

            if (!dc.terrain && (this.altitudeMode != WorldWind.ABSOLUTE)) {
                return;
            }

            this.establishCurrentData(dc);

            if (dc.globe.projectionLimits && !this.isWithinProjectionLimits(dc)) {
                return;
            }

            // Use the last computed extent to see if this shape is out of view.
            if (this.currentData.extent && !this.intersectsFrustum(dc)) {
                return;
            }

            this.determineActiveAttributes(dc);
            if (!this.activeAttributes) {
                return;
            }

            var orderedRenderable = this.makeOrderedRenderable(dc);
            if (orderedRenderable) {

                // Use the updated extent to see if this shape is out of view.
                if (!this.intersectsFrustum(dc)) {
                    return;
                }

                if (dc.isSmall(this.currentData.extent, 1)) {
                    return;
                }

                orderedRenderable.layer = dc.currentLayer;
                dc.addOrderedRenderable(orderedRenderable, this.currentData.eyeDistance);
            }
        };

        /**
         * Draws this shape during ordered rendering. Implements the {@link OrderedRenderable} interface.
         * This method is called by the WorldWindow and is not intended to be called by applications.
         * @param {DrawContext} dc The current draw context.
         */
        AbstractShape.prototype.renderOrdered = function (dc) {
            this.currentData = this.shapeDataCache.entryForKey(dc.globeStateKey);

            this.beginDrawing(dc);
            try {
                this.doRenderOrdered(dc);
            } finally {
                this.endDrawing(dc);
            }
        };

        // Internal. Intentionally not documented.
        AbstractShape.prototype.makeOrderedRenderable = function (dc) {
            var or = this.doMakeOrderedRenderable(dc);
            this.currentData.verticalExaggeration = dc.verticalExaggeration;

            return or;
        };

        /**
         * Called during rendering. Subclasses must override this method with one that creates and enques an
         * ordered renderable for this shape if this shape is to be displayed. Applications do not call this method.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.doMakeOrderedRenderable = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "AbstractShape", "makeOrderedRenderable", "abstractInvocation"));
        };

        /**
         * Called during ordered rendering. Subclasses must override this method to render the shape using the current
         * shape data.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.doRenderOrdered = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "AbstractShape", "doRenderOrdered", "abstractInvocation"));
        };

        /**
         * Called during ordered rendering. Subclasses may override this method in order to perform operations prior
         * to drawing the shape. Applications do not call this method.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.beginDrawing = function (dc) {
        };

        /**
         * Called during ordered rendering. Subclasses may override this method in order to perform operations after
         * the shape is drawn. Applications do not call this method.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.endDrawing = function (dc) {
        };

        // Internal. Intentionally not documented.
        AbstractShape.prototype.intersectsFrustum = function (dc) {
            if (this.currentData && this.currentData.extent) {
                if (dc.pickingMode) {
                    return this.currentData.extent.intersectsFrustum(dc.pickFrustum);
                } else {
                    return this.currentData.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
                }
            } else {
                return true;
            }
        };

        // Internal. Intentionally not documented.
        AbstractShape.prototype.establishCurrentData = function (dc) {
            this.currentData = this.shapeDataCache.entryForKey(dc.globeStateKey);
            if (!this.currentData) {
                this.currentData = this.createShapeDataObject();
                this.resetExpiration(this.currentData);
                this.shapeDataCache.putEntry(dc.globeStateKey, this.currentData, 1);
            }

            this.currentData.isExpired = !this.isShapeDataCurrent(dc, this.currentData);
        };

        /**
         * Creates a new shape data object for the current globe state. Subclasses may override this method to
         * modify the shape data object that this method creates, but must also call this method on this base class.
         * Applications do not call this method.
         * @returns {Object} The shape data object.
         * @protected
         */
        AbstractShape.prototype.createShapeDataObject = function () {
            return {
                transformationMatrix: Matrix.fromIdentity(),
                referencePoint: new Vec3(0, 0, 0)
            };
        };

        // Intentionally not documented.
        AbstractShape.prototype.resetExpiration = function (shapeData) {
            // The random addition in the line below prevents all shapes from regenerating during the same frame.
            shapeData.expiryTime = Date.now() + this.expirationInterval + 1e3 * Math.random();
        };

        /**
         * Indicates whether a specified shape data object is current. Subclasses may override this method to add
         * criteria indicating whether the shape data object is current, but must also call this method on this base
         * class. Applications do not call this method.
         * @param {DrawContext} dc The current draw context.
         * @param {Object} shapeData The object to validate.
         * @returns {Boolean} true if the object is current, otherwise false.
         * @protected
         */
        AbstractShape.prototype.isShapeDataCurrent = function (dc, shapeData) {
            return shapeData.verticalExaggeration === dc.verticalExaggeration
                && shapeData.expiryTime > Date.now();
        };

        // Internal. Intentionally not documented.
        AbstractShape.prototype.determineActiveAttributes = function (dc) {
            if (this.highlighted && this._highlightAttributes) {
                this.activeAttributes = this.highlightAttributes;
            } else {
                this.activeAttributes = this._attributes;
            }
        };

        /**
         * Indicates whether this shape is within the current globe's projection limits. Subclasses may implement
         * this method to perform the test. The default implementation returns true. Applications do not call this
         * method.
         * @param {DrawContext} dc The current draw context.
         * @returns {Boolean} true if this shape is is within or intersects the current globe's projection limits,
         * otherwise false.
         * @protected
         */
        AbstractShape.prototype.isWithinProjectionLimits = function (dc) {
            return true;
        };

        /**
         * Apply the current navigator's model-view-projection matrix.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.applyMvpMatrix = function (dc) {
            this.scratchMatrix.copy(dc.navigatorState.modelviewProjection);
            this.scratchMatrix.multiplyMatrix(this.currentData.transformationMatrix);
            dc.currentProgram.loadModelviewProjection(dc.currentGlContext, this.scratchMatrix);
        };

        /**
         * Apply the current navigator's model-view-projection matrix with an offset to make this shape's outline
         * stand out.
         * @param {DrawContext} dc The current draw context.
         * @protected
         */
        AbstractShape.prototype.applyMvpMatrixForOutline = function (dc) {
            // Causes the outline to stand out from the interior.
            this.scratchMatrix.copy(dc.navigatorState.projection);
            this.scratchMatrix.offsetProjectionDepth(-0.001);
            this.scratchMatrix.multiplyMatrix(dc.navigatorState.modelview);
            this.scratchMatrix.multiplyMatrix(this.currentData.transformationMatrix);
            dc.currentProgram.loadModelviewProjection(dc.currentGlContext, this.scratchMatrix);
        };

        return AbstractShape;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports AbstractMesh
 */
define('shapes/AbstractMesh',[
        '../shapes/AbstractShape',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../geom/BoundingBox',
        '../util/Color',
        '../util/ImageSource',
        '../geom/Line',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../geom/Position',
        '../shapes/ShapeAttributes',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (AbstractShape,
              ArgumentError,
              BasicTextureProgram,
              BoundingBox,
              Color,
              ImageSource,
              Line,
              Location,
              Logger,
              Matrix,
              PickedObject,
              Position,
              ShapeAttributes,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs an abstract mesh. Applications do not call this constructor. It is called only by subclasses of
         * this abstract class.
         * @alias AbstractMesh
         * @constructor
         * @augments AbstractShape
         * @classdesc Provides an abstract base class for mesh shapes.
         *
         * @param {ShapeAttributes} attributes The attributes to associate with this mesh. May be null, in which case
         * default attributes are associated.
         */
        var AbstractMesh = function (attributes) {
            AbstractShape.call(this, attributes);

            /**
             * Indicates whether this mesh is pickable when the pick point intersects transparent pixels of the
             * image applied to this mesh. If no image is applied to this mesh, this property is ignored. If this
             * property is true and an image with fully transparent pixels is applied to the mesh, the mesh is
             * pickable at those transparent pixels, otherwise this mesh is not pickable at those transparent pixels.
             * @type {Boolean}
             * @default true
             */
            this.pickTransparentImagePixels = true;

            // Private. Documentation is with the defined property below.
            this._altitudeScale = 1;
        };

        AbstractMesh.prototype = Object.create(AbstractShape.prototype);

        Object.defineProperties(AbstractMesh.prototype, {
            /**
             * Scales the altitudes of this mesh.
             * @type {Number}
             * @default 1
             * @memberof AbstractMesh.prototype
             */
            altitudeScale: {
                get: function () {
                    return this._altitudeScale;
                },
                set: function (value) {
                    this._altitudeScale = value;
                    this.reset();
                }
            }
        });

        // Internal. Determines whether this shape's geometry must be re-computed.
        AbstractMesh.prototype.mustGenerateGeometry = function (dc) {
            if (!this.currentData.meshPoints) {
                return true;
            }

            if (this.currentData.drawInterior !== this.activeAttributes.drawInterior) {
                return true;
            }

            if (this.activeAttributes.applyLighting && !this.currentData.normals) {
                return true;
            }

            if (this.altitudeMode === WorldWind.ABSOLUTE) {
                return false;
            }

            return this.currentData.isExpired
        };

        // Overridden from AbstractShape base class.
        AbstractMesh.prototype.doMakeOrderedRenderable = function (dc) {
            if (!this.activeAttributes.drawInterior && !this.activeAttributes.drawOutline) {
                return null;
            }

            // See if the current shape data can be re-used.
            if (!this.mustGenerateGeometry(dc)) {
                return this;
            }

            var currentData = this.currentData;

            // Set the transformation matrix to correspond to the reference position.
            var refPt = currentData.referencePoint;
            dc.surfacePointForMode(this.referencePosition.latitude, this.referencePosition.longitude,
                this.referencePosition.altitude * this._altitudeScale, this._altitudeMode, refPt);
            currentData.transformationMatrix.setToTranslation(refPt[0], refPt[1], refPt[2]);

            // Convert the geographic coordinates to the Cartesian coordinates that will be rendered.
            currentData.meshPoints = this.computeMeshPoints(dc, currentData);
            currentData.refreshVertexBuffer = true;

            // Capture texture coordinates in a parallel array to the mesh points. These are associated with this
            // shape, itself, because they're independent of elevation or globe state.
            if (this.activeAttributes.imageSource && !this.texCoords) {
                this.texCoords = this.computeTexCoords();
                if (this.texCoords) {
                    currentData.refreshTexCoordBuffer = true;
                }
            }

            // Compute the mesh and outline indices. These are associated with this shape, itself, because they're
            // independent of elevation and globe state.
            if (!this.meshIndices) {
                this.meshIndices = this.computeMeshIndices();
                currentData.refreshMeshIndices = true;
            }

            if (!this.meshOutlineIndices) {
                this.meshOutlineIndices = this.computeOutlineIndices();
                if (this.meshOutlineIndices) {
                    currentData.refreshOutlineIndices = true;
                }
            }

            if (this.activeAttributes.applyLighting) {
                this.computeNormals(currentData);
            }

            currentData.drawInterior = this.activeAttributes.drawInterior; // remember for validation
            this.resetExpiration(currentData);

            // Create the extent from the Cartesian points. Those points are relative to this path's reference point,
            // so translate the computed extent to the reference point.
            if (!currentData.extent) {
                currentData.extent = new BoundingBox();
            }

            currentData.extent.setToPoints(currentData.meshPoints);
            currentData.extent.translate(currentData.referencePoint);

            return this;
        };

        // Private. Intentionally not documented.
        /**
         * Computes this mesh's Cartesian points. Called by this abstract class during rendering to compute
         * Cartesian points from geographic positions. This method must be overridden by subclasses. An
         * exception is thrown if it is not.
         *
         * This method must also assign currentData.eyeDistance to be the minimum distance from this mesh to the
         * current eye point.
         *
         * @param {DrawContext} dc The current draw context.
         * @param {{}} currentData The current data for this shape.
         * @returns {Float32Array} The Cartesian mesh points.
         * @protected
         */
        AbstractMesh.prototype.computeMeshPoints = function (dc, currentData) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "AbstractMesh", "computeMeshPoints", "abstractInvocation"));
        };

        // Intentionally not documented.
        /**
         * Computes the texture coordinates for this shape. Called by this abstract class during rendering to copy or
         * compute texture coordinates into a typed array. Subclasses should implement this method if the shape they
         * define has texture coordinates. The default implementation returns null.
         *
         * @returns {Float32Array} The texture coordinates.
         * @protected
         */
        AbstractMesh.prototype.computeTexCoords = function () {
            // Default implementation does nothing.
            return null;
        };

        /**
         * Computes or copies the indices of this mesh into a Uint16Array. Subclasses must implement this method.
         * An exception is thrown if it is not implemented.
         * @param {{}} currentData This shape's current data.
         * @protected
         */
        AbstractMesh.prototype.computeMeshIndices = function (currentData) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "AbstractMesh", "computeMeshIndices", "abstractInvocation"));
        };

        /**
         * Computes or copies the outline indices of this mesh into a Uint16Array. Subclasses must implement this
         * method if they have outlines. The default implementation returns null.
         * @param {{}} currentData This shape's current data.
         * @protected
         */
        AbstractMesh.prototype.computeOutlineIndices = function (currentData) {
            // Default implementation does nothing.
        };

        // Internal. Intentionally not documented.
        AbstractMesh.prototype.computeNormals = function (currentData) {
            var normalsBuffer = new Float32Array(currentData.meshPoints.length),
                indices = this.meshIndices,
                vertices = currentData.meshPoints,
                normals = [],
                triPoints = [new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0)],
                k;

            // For each triangle, compute its normal assign it to each participating index.
            for (var i = 0; i < indices.length; i += 3) {
                for (var j = 0; j < 3; j++) {
                    k = indices[i + j];
                    triPoints[j].set(vertices[3 * k], vertices[3 * k + 1], vertices[3 * k + 2]);
                }

                var n = Vec3.computeTriangleNormal(triPoints[0], triPoints[1], triPoints[2]);

                for (j = 0; j < 3; j++) {
                    k = indices[i + j];
                    if (!normals[k]) {
                        normals[k] = [];
                    }

                    normals[k].push(n);
                }
            }

            // Average the normals associated with each index and add the result to the normals buffer.
            n = new Vec3(0, 0, 0);
            for (i = 0; i < normals.length; i++) {
                if (normals[i]) {
                    Vec3.average(normals[i], n);
                    n.normalize();
                    normalsBuffer[i * 3] = n[0];
                    normalsBuffer[i * 3 + 1] = n[1];
                    normalsBuffer[i * 3 + 2] = n[2];
                } else {
                    normalsBuffer[i * 3] = 0;
                    normalsBuffer[i * 3 + 1] = 0;
                    normalsBuffer[i * 3 + 2] = 0;
                }
            }

            currentData.normals = normalsBuffer;
            currentData.refreshNormalsBuffer = true;
        };

        // Overridden from AbstractShape base class.
        AbstractMesh.prototype.doRenderOrdered = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                currentData = this.currentData,
                hasTexture = this.texCoords && !!this.activeAttributes.imageSource,
                vboId, opacity, color, pickColor, textureBound;

            if (dc.pickingMode) {
                pickColor = dc.uniquePickColor();
            }

            // Load the vertex data since both the interior and outline use it.

            if (!currentData.pointsVboCacheKey) {
                currentData.pointsVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(currentData.pointsVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(currentData.pointsVboCacheKey, vboId,
                    currentData.meshPoints.length * 4);
                currentData.refreshVertexBuffer = true;
            }

            gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            if (currentData.refreshVertexBuffer) {
                gl.bufferData(gl.ARRAY_BUFFER, currentData.meshPoints,
                    gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                currentData.refreshVertexBuffer = false;
            }

            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);

            program.loadTextureEnabled(gl, false);

            // Draw the mesh if the interior requested.
            if (this.activeAttributes.drawInterior) {
                var applyLighting = !dc.pickingMode && currentData.normals && this.activeAttributes.applyLighting;

                this.applyMvpMatrix(dc);

                if (!currentData.meshIndicesVboCacheKey) {
                    currentData.meshIndicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                }

                vboId = dc.gpuResourceCache.resourceForKey(currentData.meshIndicesVboCacheKey);
                if (!vboId) {
                    vboId = gl.createBuffer();
                    dc.gpuResourceCache.putResource(currentData.meshIndicesVboCacheKey, vboId,
                        this.meshIndices.length * 2);
                    currentData.refreshMeshIndices = true;
                }

                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
                if (currentData.refreshMeshIndices) {
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.meshIndices,
                        gl.STATIC_DRAW);
                    dc.frameStatistics.incrementVboLoadCount(1);
                    currentData.refreshMeshIndices = false;
                }

                color = this.activeAttributes.interiorColor;
                opacity = color.alpha * dc.currentLayer.opacity;

                // Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
                gl.depthMask(opacity >= 1 || dc.pickingMode);
                program.loadColor(gl, dc.pickingMode ? pickColor : color);
                program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);

                if (hasTexture && (!dc.pickingMode || !this.pickTransparentImagePixels)) {
                    this.activeTexture = dc.gpuResourceCache.resourceForKey(this.activeAttributes.imageSource);
                    if (!this.activeTexture) {
                        this.activeTexture =
                            dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.activeAttributes.imageSource);
                    }

                    textureBound = this.activeTexture && this.activeTexture.bind(dc);
                    if (textureBound) {
                        if (!currentData.texCoordsVboCacheKey) {
                            currentData.texCoordsVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                        }

                        vboId = dc.gpuResourceCache.resourceForKey(currentData.texCoordsVboCacheKey);
                        if (!vboId) {
                            vboId = gl.createBuffer();
                            dc.gpuResourceCache.putResource(currentData.texCoordsVboCacheKey, vboId,
                                this.texCoords.length * 4);
                            currentData.refreshTexCoordBuffer = true;
                        }

                        gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                        if (currentData.refreshTexCoordBuffer) {
                            gl.bufferData(gl.ARRAY_BUFFER, this.texCoords,
                                gl.STATIC_DRAW);
                            dc.frameStatistics.incrementVboLoadCount(1);
                            currentData.refreshTexCoordBuffer = false;
                        }

                        gl.enableVertexAttribArray(program.vertexTexCoordLocation);
                        gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT,
                            false, 0, 0);

                        this.scratchMatrix.setToIdentity();
                        this.scratchMatrix.multiplyByTextureTransform(this.activeTexture);

                        program.loadTextureEnabled(gl, true);
                        program.loadTextureUnit(gl, gl.TEXTURE0);
                        program.loadTextureMatrix(gl, this.scratchMatrix);
                        program.loadModulateColor(gl, dc.pickingMode);
                    }
                }

                // Apply lighting.
                if (applyLighting) {
                    program.loadApplyLighting(gl, true);

                    if (!currentData.normalsVboCacheKey) {
                        currentData.normalsVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                    }

                    vboId = dc.gpuResourceCache.resourceForKey(currentData.normalsVboCacheKey);
                    if (!vboId) {
                        vboId = gl.createBuffer();
                        dc.gpuResourceCache.putResource(currentData.normalsVboCacheKey, vboId,
                            currentData.normals.length * 4);
                        currentData.refreshNormalsBuffer = true;
                    }

                    gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                    if (currentData.refreshNormalsBuffer) {
                        gl.bufferData(gl.ARRAY_BUFFER, currentData.normals,
                            gl.STATIC_DRAW);
                        dc.frameStatistics.incrementVboLoadCount(1);
                        currentData.refreshNormalsBuffer = false;
                    }

                    gl.enableVertexAttribArray(program.normalVectorLocation);
                    gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, 0, 0);
                }

                gl.drawElements(gl.TRIANGLES, this.meshIndices.length,
                    gl.UNSIGNED_SHORT, 0);

                if (hasTexture) {
                    gl.disableVertexAttribArray(program.vertexTexCoordLocation);
                }

                if (applyLighting) {
                    program.loadApplyLighting(gl, false);
                    gl.disableVertexAttribArray(program.normalVectorLocation);
                }
            }

            // Draw the outline.
            if (this.activeAttributes.drawOutline && this.meshOutlineIndices) {
                program.loadTextureEnabled(gl, false);
                gl.disableVertexAttribArray(program.vertexTexCoordLocation); // we're not texturing in this clause

                // Make the outline stand out from the interior.
                this.applyMvpMatrixForOutline(dc);

                color = this.activeAttributes.outlineColor;
                opacity = color.alpha * dc.currentLayer.opacity;

                // Disable writing the shape's fragments to the depth buffer when the interior is
                // semi-transparent.
                gl.depthMask(opacity >= 1 || dc.pickingMode);
                program.loadColor(gl, dc.pickingMode ? pickColor : color);
                program.loadOpacity(gl, dc.pickingMode ? 1 : opacity);

                gl.lineWidth(this.activeAttributes.outlineWidth);

                if (!currentData.outlineIndicesVboCacheKey) {
                    currentData.outlineIndicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                }

                vboId = dc.gpuResourceCache.resourceForKey(currentData.outlineIndicesVboCacheKey);
                if (!vboId) {
                    vboId = gl.createBuffer();
                    dc.gpuResourceCache.putResource(currentData.outlineIndicesVboCacheKey, vboId,
                        this.meshOutlineIndices.length * 2);
                    currentData.refreshOutlineIndices = true;
                }

                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
                if (currentData.refreshOutlineIndices) {
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.meshOutlineIndices,
                        gl.STATIC_DRAW);
                    dc.frameStatistics.incrementVboLoadCount(1);
                    currentData.refreshOutlineIndices = false;
                }

                gl.drawElements(gl.LINE_STRIP, this.meshOutlineIndices.length,
                    gl.UNSIGNED_SHORT, 0);
            }

            if (dc.pickingMode) {
                var pickPosition = this.computePickPosition(dc);
                var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, pickPosition,
                    dc.currentLayer, false);
                dc.resolvePick(po);
            }
        };

        AbstractMesh.prototype.computePickPosition = function (dc) {
            var currentData = this.currentData,
                line = dc.navigatorState.rayFromScreenPoint(dc.pickPoint),
                localLineOrigin = new Vec3(line.origin[0], line.origin[1], line.origin[2]).subtract(
                    currentData.referencePoint),
                localLine = new Line(localLineOrigin, line.direction),
                intersectionPoints = [];

            if (WWMath.computeIndexedTrianglesIntersection(localLine, currentData.meshPoints, this.meshIndices,
                    intersectionPoints)) {
                var iPoint = intersectionPoints[0];

                if (intersectionPoints.length > 1) {
                    // Find the intersection nearest the eye point.
                    var distance2 = iPoint.distanceToSquared(dc.navigatorState.eyePoint);

                    for (var i = 1; i < intersectionPoints.length; i++) {
                        var d2 = intersectionPoints[i].distanceToSquared(dc.navigatorState.eyePoint);
                        if (d2 < distance2) {
                            distance2 = d2;
                            iPoint = intersectionPoints[i];
                        }
                    }
                }

                var pos = new Position(0, 0, 0);
                dc.globe.computePositionFromPoint(
                    iPoint[0] + currentData.referencePoint[0],
                    iPoint[1] + currentData.referencePoint[1],
                    iPoint[2] + currentData.referencePoint[2],
                    pos);

                pos.altitude /= this._altitudeScale;

                return pos;
            }

            return null;
        };

        // Overridden from AbstractShape base class.
        AbstractMesh.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext;

            if (this.activeAttributes.drawInterior) {
                gl.disable(gl.CULL_FACE);

                dc.findAndBindProgram(BasicTextureProgram);

                var applyLighting = !dc.pickMode && this.currentData.normals && this.activeAttributes.applyLighting;
                if (applyLighting) {
                    dc.currentProgram.loadModelviewInverse(gl, dc.navigatorState.modelviewNormalTransform);
                }
            }

            gl.enableVertexAttribArray(dc.currentProgram.vertexPointLocation);
        };

        // Overridden from AbstractShape base class.
        AbstractMesh.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext;

            gl.disableVertexAttribArray(dc.currentProgram.vertexPointLocation);
            gl.depthMask(true);
            gl.lineWidth(1);
            gl.enable(gl.CULL_FACE);
        };

        return AbstractMesh;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfacePolygon
 */
define('shapes/SurfacePolygon',[
        '../error/ArgumentError',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape'
    ],
    function (ArgumentError,
              Logger,
              ShapeAttributes,
              SurfaceShape) {
        "use strict";

        /**
         * Constructs a surface polygon.
         * @alias SurfacePolygon
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents a polygon draped over the terrain surface. The polygon may have multiple boundaries in
         * order to define holes or empty regions.
         * <p>
         * SurfacePolygon uses the following attributes from its associated shape attributes bundle:
         * <ul>
         *         <li>Draw interior</li>
         *         <li>Draw outline</li>
         *         <li>Interior color</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         * </ul>
         * @param {Array} boundaries The polygons boundary locations. If this argument is an array of
         * [Locations]{@link Location} they define this polygon's outer boundary. If it is an array of arrays of
         * Locations then each array entry defines one of this polygon's boundaries.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         *
         * @throws {ArgumentError} If the specified boundaries are null or undefined.
         */
        var SurfacePolygon = function (boundaries, attributes) {
            if (!Array.isArray(boundaries)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfacePolygon", "constructor",
                        "The specified boundary is not an array."));
            }

            SurfaceShape.call(this, attributes);

            this._boundaries = boundaries;

            this._stateId = SurfacePolygon.stateId++;
        };

        SurfacePolygon.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfacePolygon.prototype, {
            /**
             * This polygon's boundaries. The polygons boundary locations. If this argument is an array of
             * [Locations]{@link Location} they define this polygon's outer boundary. If it is an array of arrays of
             * Locations then each array entry defines one of this polygon's boundaries.
             * @type {Location[][] | Location[]}
             * @memberof SurfacePolygon.prototype
             */
            boundaries: {
                get: function () {
                    return this._boundaries;
                },
                set: function (boundaries) {
                    if (!Array.isArray(boundaries)) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "SurfacePolygon", "set boundaries",
                                "The specified value is not an array."));
                    }
                    this.resetBoundaries();
                    this._boundaries = boundaries;
                    this._stateId = SurfacePolygon.stateId++;
                    this.stateKeyInvalid = true;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfacePolygon.stateId = Number.MIN_SAFE_INTEGER;

        // Internal use only. Intentionally not documented.
        SurfacePolygon.staticStateKey = function (shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey +
                " pg " + shape._stateId;
        };

        // Internal use only. Intentionally not documented.
        SurfacePolygon.prototype.computeStateKey = function () {
            return SurfacePolygon.staticStateKey(this);
        };

        // Internal. Polygon doesn't generate its own boundaries. See SurfaceShape.prototype.computeBoundaries.
        SurfacePolygon.prototype.computeBoundaries = function(dc) {
        };

        return SurfacePolygon;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeographicMesh
 */
define('shapes/GeographicMesh',[
        '../shapes/AbstractMesh',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../geom/BoundingBox',
        '../util/Color',
        '../util/ImageSource',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../geom/Position',
        '../shapes/ShapeAttributes',
        '../shapes/SurfacePolygon',
        '../geom/Vec2',
        '../geom/Vec3',
    ],
    function (AbstractMesh,
              ArgumentError,
              BasicTextureProgram,
              BoundingBox,
              Color,
              ImageSource,
              Location,
              Logger,
              Matrix,
              PickedObject,
              Position,
              ShapeAttributes,
              SurfacePolygon,
              Vec2,
              Vec3) {
        "use strict";

        /**
         * Constructs a geographic mesh.
         * @alias GeographicMesh
         * @constructor
         * @augments AbstractMesh
         * @classdesc Represents a 3D geographic mesh.
         * <p>
         *     Altitudes within the mesh's positions are interpreted according to the mesh's altitude mode, which
         *     can be one of the following:
         * <ul>
         *     <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
         *     <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
         *     <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
         * </ul>
         * If the latter, the mesh positions' altitudes are ignored. (If the mesh should be draped onto the
         * terrain, you might want to use {@link SurfacePolygon} instead.)
         * <p>
         *     Meshes have separate attributes for normal display and highlighted display. They use the interior and
         *     outline attributes of {@link ShapeAttributes}. If those attributes identify an image, that image is
         *     applied to the mesh. Texture coordinates for the image may be specified, but if not specified the full
         *     image is stretched over the full mesh. If texture coordinates are specified, there must be one texture
         *     coordinate for each vertex in the mesh.
         *
         * @param {Position[][]} positions A two-dimensional array containing the mesh vertices.
         * Each entry of the array specifies the vertices of one row of the mesh. The arrays for all rows must
         * have the same length. There must be at least two rows, and each row must have at least two vertices.
         * There must be no more than 65536 positions.
         * @param {ShapeAttributes} attributes The attributes to associate with this mesh. May be null, in which case
         * default attributes are associated.
         *
         * @throws {ArgumentError} If the specified positions array is null or undefined, the number of rows or the
         * number of vertices per row is less than 2, the array lengths are inconsistent, or too many positions are
         * specified (limit is 65536).
         */
        var GeographicMesh = function (positions, attributes) {
            if (!positions) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "constructor", "missingPositions"));
            }

            if (positions.length < 2 || positions[0].length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "constructor",
                        "Number of positions is insufficient."));
            }

            // Check for size limit, which is the max number of available indices for a 16-bit unsigned int.
            if (positions.length * positions[0].length > 65536) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "constructor",
                        "Too many positions. Must be fewer than 65536. Try using multiple meshes."));
            }

            var length = positions[0].length;
            for (var i = 1; i < positions.length; i++) {
                if (positions[i].length !== length) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "constructor",
                            "Array lengths are inconsistent."));
                }
            }

            var numRows = positions.length,
                numCols = positions[0].length;

            AbstractMesh.call(this, attributes);

            /**
             * Indicates whether this mesh is pickable when the pick point intersects transparent pixels of the
             * image applied to this mesh. If no image is applied to this mesh, this property is ignored. If this
             * property is true and an image with fully transparent pixels is applied to the mesh, the mesh is
             * pickable at those transparent pixels, otherwise this mesh is not pickable at those transparent pixels.
             * @type {Boolean}
             * @default true
             */
            this.pickTransparentImagePixels = true;

            // Private. Documentation is with the defined property below and the constructor description above.
            this._positions = positions;

            // Private. Documentation is with the defined property below.
            this._altitudeScale = 1;

            // Internal. Intentionally not documented.
            this.numRows = numRows;
            this.numColumns = numCols;

            // Internal. Intentionally not documented.
            this._textureCoordinates = null;

            // Internal. Intentionally not documented.
            this.referencePosition = this.determineReferencePosition(this._positions);
        };

        GeographicMesh.prototype = Object.create(AbstractMesh.prototype);

        Object.defineProperties(GeographicMesh.prototype, {
            /**
             * This mesh's positions. Each array in the positions array specifies the geographic positions of one
             * row of the mesh.
             *
             * @type {Position[][]}
             * @memberof GeographicMesh.prototype
             */
            positions: {
                get: function () {
                    return this._positions;
                },
                set: function (positions) {
                    if (!positions) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "positions", "missingPositions"));
                    }

                    if (positions.length < 2 || positions[0].length < 2) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "positions",
                                "Number of positions is insufficient."));
                    }

                    var length = positions[0].length;
                    for (var i = 1; i < positions.length; i++) {
                        if (positions[i].length !== length) {
                            throw new ArgumentError(
                                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "positions",
                                    "Array lengths are inconsistent."));
                        }
                    }

                    this.numRows = positions.length;
                    this.numColumns = positions[0].length;

                    this._positions = positions;
                    this.referencePosition = this.determineReferencePosition(this._positions);
                    this.reset();

                    this.meshIndices = null;
                    this.outlineIndices = null;
                }
            },

            /**
             * This mesh's texture coordinates if this mesh is textured. A texture coordinate must be
             * provided for each mesh position. The texture coordinates are specified as a two-dimensional array,
             * each entry of which specifies the texture coordinates for one row of the mesh. Each texture coordinate
             * is a {@link Vec2} containing the s and t coordinates. If no texture coordinates are specified and
             * the attributes associated with this mesh indicate an image source, then texture coordinates are
             * automatically generated for the mesh.
             * @type {Vec2[][]}
             * @default null
             * @memberof GeographicMesh.prototype
             */
            textureCoordinates: {
                get: function () {
                    return this._textureCoordinates;
                },
                set: function (coords) {

                    if (coords && coords.length != this.numRows) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "textureCoordinates",
                                "Number of texture coordinate rows is inconsistent with the currently specified positions."));
                    }

                    for (var i = 0; i < this.numRows; i++) {
                        if (coords[i].length !== this.numColumns) {
                            throw new ArgumentError(
                                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicMesh", "textureCoordinates",
                                    "Texture coordinate row lengths are inconsistent with the currently specified positions."));
                        }
                    }

                    this._textureCoordinates = coords;
                    this.reset();
                    this.texCoords = null;
                }
            },
        });

        GeographicMesh.makeGridIndices = function (nRows, nCols) {
            // Compute indices for individual triangles.

            var gridIndices = [],
                i = 0;

            for (var r = 0; r < nRows - 1; r++) {
                for (var c = 0; c < nCols - 1; c++) {
                    var k = r * nCols + c;

                    gridIndices[i++] = k;
                    gridIndices[i++] = k + 1;
                    gridIndices[i++] = k + nCols;
                    gridIndices[i++] = k + 1;
                    gridIndices[i++] = k + 1 + nCols;
                    gridIndices[i++] = k + nCols;
                }
            }

            return gridIndices;
        };

        // Intentionally not documented.
        GeographicMesh.prototype.determineReferencePosition = function (positions) {
            // Assign the first position as the reference position.
            return positions[0][0];
        };

        // Overridden from AbstractShape base class.
        GeographicMesh.prototype.createSurfaceShape = function () {
            var boundaries = [];

            for (var c = 0; c < this.numColumns; c++) {
                boundaries.push(this._positions[0][c]);
            }

            for (var r = 1; r < this.numRows; r++) {
                boundaries.push(this._positions[r][this.numColumns - 1]);
            }

            for (c = this.numColumns - 2; c >= 0; c--) {
                boundaries.push(this._positions[this.numRows - 1][c]);
            }

            for (r = this.numRows - 2; r > 0; r--) {
                boundaries.push(this._positions[r][0]);
            }

            return new SurfacePolygon(boundaries, null);
        };

        GeographicMesh.prototype.computeMeshPoints = function (dc, currentData) {
            // Unwrap the mesh row arrays into one long array.

            var eyeDistSquared = Number.MAX_VALUE,
                eyePoint = dc.navigatorState.eyePoint,
                meshPoints = new Float32Array((this.numRows * this.numColumns) * 3),
                pt = new Vec3(0, 0, 0),
                k = 0,
                pos, dSquared;

            for (var r = 0; r < this._positions.length; r++) {
                for (var c = 0, len = this._positions[r].length; c < len; c++) {
                    pos = this._positions[r][c];

                    dc.surfacePointForMode(pos.latitude, pos.longitude, pos.altitude * this._altitudeScale,
                        this.altitudeMode, pt);

                    dSquared = pt.distanceToSquared(eyePoint);
                    if (dSquared < eyeDistSquared) {
                        eyeDistSquared = dSquared;
                    }

                    pt.subtract(this.currentData.referencePoint);

                    meshPoints[k++] = pt[0];
                    meshPoints[k++] = pt[1];
                    meshPoints[k++] = pt[2];
                }
            }

            currentData.eyeDistance = Math.sqrt(eyeDistSquared);

            return meshPoints;
        };

        GeographicMesh.prototype.computeTexCoords = function () {
            if (this._textureCoordinates) {
                return this.computeExplicitTexCoords();
            } else {
                return this.computeImplicitTexCoords();
            }
        };

        // Intentionally not documented.
        GeographicMesh.prototype.computeExplicitTexCoords = function () {
            // Capture the texture coordinates to a single array parallel to the mesh points array.

            var texCoords = new Float32Array(2 * this.numRows * this.numColumns),
                k = 0;

            for (var r = 0; r < this._textureCoordinates.length; r++) {
                for (var c = 0, len = this._textureCoordinates[r].length; c < len; c++) {
                    var texCoord = this._textureCoordinates[r][c];

                    texCoords[k++] = texCoord[0];
                    texCoords[k++] = texCoord[1];
                }
            }

            return texCoords;
        };

        // Intentionally not documented.
        GeographicMesh.prototype.computeImplicitTexCoords = function () {
            // Create texture coordinates that map the full image source into the full mesh.

            var texCoords = new Float32Array(2 * this.numRows * this.numColumns),
                rowDelta = 1.0 / (this.numRows - 1),
                columnDelta = 1.0 / (this.numColumns - 1),
                k = 0;

            for (var r = 0; r < this._positions.length; r++) {
                var t = (r === this.numRows - 1) ? 1.0 : r * rowDelta;

                for (var c = 0, len = this._positions[r].length; c < len; c++) {
                    texCoords[k++] = (c === this.numColumns - 1) ? 1.0 : c * columnDelta;
                    texCoords[k++] = t;
                }
            }

            return texCoords;
        };

        GeographicMesh.prototype.computeMeshIndices = function () {
            // Compute indices for individual triangles.

            var meshIndices = new Uint16Array((this.numRows - 1) * (this.numColumns - 1) * 6),
                i = 0;

            for (var r = 0; r < this.numRows - 1; r++) {
                for (var c = 0; c < this.numColumns - 1; c++) {
                    var k = r * this.numColumns + c;

                    meshIndices[i++] = k;
                    meshIndices[i++] = k + 1;
                    meshIndices[i++] = k + this.numColumns;
                    meshIndices[i++] = k + 1;
                    meshIndices[i++] = k + 1 + this.numColumns;
                    meshIndices[i++] = k + this.numColumns;
                }
            }

            return meshIndices;
        };

        GeographicMesh.prototype.computeOutlineIndices = function () {
            // Walk the mesh boundary and capture those positions for the outline.

            var outlineIndices = new Uint16Array(2 * this.numRows + 2 * this.numColumns),
                k = 0;

            for (var c = 0; c < this.numColumns; c++) {
                outlineIndices[k++] = c;
            }

            for (var r = 1; r < this.numRows; r++) {
                outlineIndices[k++] = (r + 1) * this.numColumns - 1;
            }

            for (c = this.numRows * this.numColumns - 2; c >= (this.numRows - 1) * this.numColumns; c--) {
                outlineIndices[k++] = c;
            }

            for (r = this.numRows - 2; r >= 0; r--) {
                outlineIndices[k++] = r * this.numColumns;
            }

            return outlineIndices;
        };

        return GeographicMesh;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeographicText
 */
define('shapes/GeographicText',[
        '../error/ArgumentError',
        '../util/Logger',
        '../shapes/Text',
        '../geom/Vec3'
    ],
    function (ArgumentError,
              Logger,
              Text,
              Vec3) {
        "use strict";

        /**
         * Constructs a geographic text shape at a specified position.
         * @alias GeographicText
         * @constructor
         * @augments Text
         * @classdesc Represents a string of text displayed at a geographic position.
         * <p>
         * See also {@link ScreenText}.
         *
         * @param {Position} position The text's geographic position.
         * @param {String} text The text to display.
         * @throws {ArgumentError} If either the specified position or text is null or undefined.
         */
        var GeographicText = function (position, text) {
            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Text", "constructor", "missingPosition"));
            }

            Text.call(this, text);

            /**
             * This text's geographic position.
             * The [TextAttributes.offset]{@link TextAttributes#offset} property indicates the relationship of the
             * text string to this position.
             * @type {Position}
             */
            this.position = position;

            /**
             * Indicates the group ID of the declutter group to include this Text shape. This shape
             * is decluttered relative to all other shapes within its group by the default
             * [declutter filter]{@link WorldWindow#declutter}. To prevent decluttering of this shape, set its
             * declutter group to 0.
             * @type {Number}
             * @default 1
             */
            this.declutterGroup = 1;
        };

        // Internal use only. Intentionally not documented.
        GeographicText.placePoint = new Vec3(0, 0, 0); // Cartesian point corresponding to this placemark's geographic position

        GeographicText.prototype = Object.create(Text.prototype);

        /**
         * Creates a new geographic text object that is a copy of this one.
         * @returns {GeographicText} The new geographic text object.
         */
        GeographicText.prototype.clone = function () {
            var clone = new GeographicText(this.position, this.text);

            clone.copy(this);
            clone.pickDelegate = this.pickDelegate ? this.pickDelegate : this;

            return clone;
        };

        // Documented in superclass.
        GeographicText.prototype.render = function (dc) {
            // Filter out instances outside any projection limits.
            if (dc.globe.projectionLimits
                && !dc.globe.projectionLimits.containsLocation(this.position.latitude, this.position.longitude)) {
                return;
            }

            Text.prototype.render.call(this, dc);
        };

        // Documented in superclass.
        GeographicText.prototype.computeScreenPointAndEyeDistance = function (dc) {
            // Compute the text's model point and corresponding distance to the eye point.
            dc.surfacePointForMode(this.position.latitude, this.position.longitude, this.position.altitude,
                this.altitudeMode, GeographicText.placePoint);

            if (!dc.navigatorState.frustumInModelCoordinates.containsPoint(GeographicText.placePoint)) {
                return false;
            }

            this.eyeDistance = this.alwaysOnTop ? 0 : dc.navigatorState.eyePoint.distanceTo(GeographicText.placePoint);

            // Compute the text's screen point in the OpenGL coordinate system of the WorldWindow by projecting its model
            // coordinate point onto the viewport. Apply a depth offset in order to cause the text to appear above nearby
            // terrain. When text is displayed near the terrain portions of its geometry are often behind the terrain,
            // yet as a screen element the text is expected to be visible. We adjust its depth values rather than moving
            // the text itself to avoid obscuring its actual position.
            if (!dc.navigatorState.projectWithDepth(GeographicText.placePoint, this.depthOffset, this.screenPoint)) {
                return false;
            }

            return true;
        };

        return GeographicText;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONConstants
 */
define('formats/geojson/GeoJSONConstants',[],
    function() {
        "use strict";

        /**
         * Provides GeoJSON string constants.
         * @alias GeoJSONConstants
         * @constructor
         * @classdesc Contains some GeoJSON string constants.
         */
        var GeoJSONConstants = function () {};

        GeoJSONConstants.FIELD_TYPE = "type";
        GeoJSONConstants.FIELD_CRS = "crs";
        GeoJSONConstants.FIELD_NAME = "name";
        GeoJSONConstants.FIELD_BBOX = "bbox";
        GeoJSONConstants.FIELD_COORDINATES = "coordinates";
        GeoJSONConstants.FIELD_GEOMETRIES = "geometries";
        GeoJSONConstants.FIELD_GEOMETRY = "geometry";
        GeoJSONConstants.FIELD_PROPERTIES = "properties";
        GeoJSONConstants.FIELD_FEATURES = "features";
        GeoJSONConstants.FIELD_ID = "id";

        GeoJSONConstants.TYPE_POINT = "Point";
        GeoJSONConstants.TYPE_MULTI_POINT = "MultiPoint";
        GeoJSONConstants.TYPE_LINE_STRING = "LineString";
        GeoJSONConstants.TYPE_MULTI_LINE_STRING = "MultiLineString";
        GeoJSONConstants.TYPE_POLYGON = "Polygon";
        GeoJSONConstants.TYPE_MULTI_POLYGON = "MultiPolygon";
        GeoJSONConstants.TYPE_GEOMETRY_COLLECTION = "GeometryCollection";
        GeoJSONConstants.TYPE_FEATURE = "Feature";
        GeoJSONConstants.TYPE_FEATURE_COLLECTION = "FeatureCollection";

        GeoJSONConstants.FIELD_CRS_NAME = "name";
        GeoJSONConstants.FIELD_CRS_LINK = "link";

        // Default Named CRS string
        // OGC CRS URNs such as "urn:ogc:def:crs:OGC:1.3:CRS84" shall be preferred over legacy identifiers
        // such as "EPSG:4326"
        GeoJSONConstants.WGS84_CRS = "urn:ogc:def:crs:OGC:1.3:CRS84";
        GeoJSONConstants.EPSG4326_CRS = "EPSG:4326";

        return GeoJSONConstants;
    }
);



/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometry
 */
define('formats/geojson/GeoJSONGeometry',['../../error/ArgumentError',
        './GeoJSONConstants',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONConstants,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON Geometry object. Applications typically do not call this constructor. It is called by
         * {@link GeoJSON} as GeoJSON is read.
         * @alias GeoJSONGeometry
         * @constructor
         * @classdesc A geometry is a GeoJSON object where the type member's value is one of the following strings:
         * "Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", or "GeometryCollection".
         * A GeoJSON geometry object of any type other than "GeometryCollection" must have a member with the name
         * "coordinates". The value of the coordinates member is always an array.
         * The structure for the elements in this array is determined by the type of geometry.
         * @param {Number[]} coordinates An array containing geometry coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An array containing information on the coordinate range for geometries.
         * @throws {ArgumentError} If the specified mandatory coordinates or type are null or undefined.
         */
        var GeoJSONGeometry = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometry", "constructor",
                        "missingCoordinates"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometry", "constructor",
                        "missingType"));
            }

            // Documented in defineProperties below.
            this._coordinates = coordinates;

            // Documented in defineProperties below.
            this._type = type;

            // Documented in defineProperties below.
            this._bbox = bbox ? bbox : null;
        };

        Object.defineProperties(GeoJSONGeometry.prototype, {
            /**
             * The GeoJSON geometry coordinates as specified to this GeoJSONGeometry's constructor.
             * @memberof GeoJSONGeometry.prototype
             * @type {Number[]}
             * @readonly
             */
            coordinates: {
                get: function () {
                    return this._coordinates;
                }
            },
            /**
             * The GeoJSON geometry type as specified to this GeoJSONGeometry's constructor.
             * @memberof GeoJSONGeometry.prototype
             * @type {String}
             * @readonly
             */
            type: {
                get: function () {
                    return this._type;
                }
            },
            /**
             * The GeoJSON bbox object as specified to this GeoJSONGeometry's constructor.
             * @memberof GeoJSONGeometry.prototype
             * @type {Object}
             * @readonly
             */
            bbox: {
                get: function () {
                    return this._bbox;
                }
            }
        });

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_POINT]
         *
         * @return {Boolean} True if the geometry is a Point type.
         */
        GeoJSONGeometry.prototype.isPointType = function () {
            return (this.type === GeoJSONConstants.TYPE_POINT);
        };

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_MULTI_POINT]
         *
         * @return {Boolean} True if the geometry is a MultiPoint type.
         */
        GeoJSONGeometry.prototype.isMultiPointType = function () {
            return (this.type === GeoJSONConstants.TYPE_MULTI_POINT);
        };

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_LINE_STRING]
         *
         * @return {Boolean} True if the geometry is a LineString type.
         */
        GeoJSONGeometry.prototype.isLineStringType = function () {
            return (this.type === GeoJSONConstants.TYPE_LINE_STRING);
        };

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_MULTI_LINE_STRING]
         *
         * @return {Boolean} True if the geometry is a MultiLineString type.
         */
        GeoJSONGeometry.prototype.isMultiLineStringType = function () {
            return (this.type === GeoJSONConstants.TYPE_MULTI_LINE_STRING);
        };

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_POLYGON]
         *
         * @return {Boolean} True if the geometry is a Polygon type.
         */
        GeoJSONGeometry.prototype.isPolygonType = function () {
            return (this.type === GeoJSONConstants.TYPE_POLYGON);
        };

        /**
         * Indicates whether this GeoJSON geometry is
         * [GeoJSONConstants.TYPE_MULTI_POLYGON]
         *
         * @return {Boolean} True if the geometry is a MultiPolygon type.
         */
        GeoJSONGeometry.prototype.isMultiPolygonType = function () {
            return (this.type === GeoJSONConstants.TYPE_MULTI_POLYGON);
        };

        return GeoJSONGeometry;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryCollection
 */
define('formats/geojson/GeoJSONGeometryCollection',['../../error/ArgumentError',
        './GeoJSONConstants',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONConstants,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a GeometryCollection. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryCollection
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON GeometryCollection geometry.
         * A geometry collection must have a member with the name "geometries".
         * The value corresponding to "geometries" is an array. Each element in this array is a GeoJSON
         * geometry object. To include information on the coordinate range for features, a GeoJSON object may have a
         * member named "bbox".
         * @param {Object} geometries An array containing GeoJSONGeometry objects.
         * @param {Object} bbox An object containing the value of GeoJSON GeometryCollection bbox member.
         * @throws {ArgumentError} If the specified mandatory geometries is null or undefined or if the geometries
         * parameter is not an array of GeoJSONGeometry.
         */
        var GeoJSONGeometryCollection = function (geometries, bbox) {
            if (!geometries) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryCollection", "constructor",
                        "missingGeometries"));
            }

            if (Object.prototype.toString.call(geometries) !== '[object Array]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryCollection", "constructor",
                        "invalidGeometries"));
            }

            // Documented in defineProperties below.
            this._geometries = geometries;

            // Documented in defineProperties below.
            this._bbox = bbox;
        };

        Object.defineProperties(GeoJSONGeometryCollection.prototype, {
            /**
             * The GeoJSON GeometryCollection geometries as specified to this GeoJSON GeometryCollection's constructor.
             * @memberof GeoJSONGeometryCollection.prototype
             * @type {Object}
             * @readonly
             */
            geometries: {
                get: function () {
                    return this._geometries;
                }
            },

            /**
             * The GeoJSON GeometryCollection bbox member as specified to this GeoJSONGeometryCollection's constructor.
             * @memberof GeoJSONGeometryCollection.prototype
             * @type {Object}
             * @readonly
             */
            bbox: {
                get: function () {
                    return this._bbox;
                }
            }
        });

        return GeoJSONGeometryCollection;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryLineString
 */
define('formats/geojson/GeoJSONGeometryLineString',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a LineString. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryLineString
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON LineString geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing LineString coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the coordinates
         * parameter is not an array of two or more positions.
         */
        var GeoJSONGeometryLineString = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryLineString", "constructor",
                        "missingCoordinates"));
            }

            if (coordinates.length < 2 || coordinates[0].length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryLineString", "constructor",
                        "invalidNumberOfCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates[0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryLineString", "constructor",
                        "invalidCoordinatesType"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryLineString", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryLineString.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryLineString;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryMultiLineString
 */
define('formats/geojson/GeoJSONGeometryMultiLineString',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a MultiLineString. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryMultiLineString
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON MultiLineString geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing MultiLineString coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the coordinates
         * parameter is not an array of LineString coordinates array.
         */
        var GeoJSONGeometryMultiLineString = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiLineString", "constructor",
                        "missingCoordinates"));
            }

            if (coordinates[0].length < 2 || coordinates[0][0].length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiLineString", "constructor",
                        "invalidNumberOfCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates[0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0][0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiLineString", "constructor",
                        "invalidCoordinatesType"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryLineString", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryMultiLineString.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryMultiLineString;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryMultiPoint
 */
define('formats/geojson/GeoJSONGeometryMultiPoint',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a MultiPoint. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryMultiPoint
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON MultiPoint geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing MultiPoint coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the coordinates
         * parameter is not an array of positions.
         */
        var GeoJSONGeometryMultiPoint = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiPoint", "constructor",
                        "missingCoordinates"));
            }

            if (coordinates[0].length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiPoint", "constructor",
                        "invalidNumberOfCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates[0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiPoint", "constructor",
                        "invalidCoordinatesType"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPoint", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryMultiPoint.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryMultiPoint;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryMultiPolygon
 */
define('formats/geojson/GeoJSONGeometryMultiPolygon',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a MultiPolygon. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryMultiPolygon
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON MultiPolygon geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing MultiPolygon coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the coordinates
         * parameter is not an array of Polygon coordinate arrays.
         */
        var GeoJSONGeometryMultiPolygon = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryMultiPolygon", "constructor",
                        "missingCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates[0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0][0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0][0][0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "invalidCoordinatesType"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryMultiPolygon.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryMultiPolygon;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryPoint
 */
define('formats/geojson/GeoJSONGeometryPoint',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a Point. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryPoint
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON Point geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing Point coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the coordinates
         * parameter is not a single position.
         */
        var GeoJSONGeometryPoint = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPoint", "constructor",
                        "missingCoordinates"));
            }

            if (coordinates.length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPoint", "constructor",
                        "invalidNumberOfCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPoint", "constructor",
                        "invalidCoordinatesType"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPoint", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryPoint.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryPoint;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONGeometryPolygon
 */
define('formats/geojson/GeoJSONGeometryPolygon',['../../error/ArgumentError',
        './GeoJSONGeometry',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONGeometry,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON geometry for a Polygon. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON geometries are read.
         * @alias GeoJSONGeometryPolygon
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON Polygon geometry.
         * @augments GeoJSONGeometry
         * @param {Number[]} coordinates The array containing Polygon coordinates.
         * @param {String} type A string containing type of geometry.
         * @param {Object} bbox An object containing GeoJSON bbox information.
         * @throws {ArgumentError} If the specified coordinates or type are null or undefined or if the
         * coordinates parameter is not an array of LinearRing coordinate arrays.
         */
        var GeoJSONGeometryPolygon = function (coordinates, type, bbox) {

            if (!coordinates) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "missingCoordinates"));
            }

            if (coordinates[0].length < 2 || coordinates[0][0].length < 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "invalidNumberOfCoordinates"));
            }

            if (Object.prototype.toString.call(coordinates[0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0]) !== '[object Array]' ||
                Object.prototype.toString.call(coordinates[0][0][0]) !== '[object Number]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "invalidCoordinatesType"));
            }

            if (coordinates[0][0] !== coordinates[0][coordinates.length - 1]) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "invalidLinearRing"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONGeometryPolygon", "constructor",
                        "missingType"));
            }

            GeoJSONGeometry.call(this, coordinates, type, bbox);
        };

        GeoJSONGeometryPolygon.prototype = Object.create(GeoJSONGeometry.prototype);

        return GeoJSONGeometryPolygon;
    }
);

define('util/proj4-src',[], function () {
    'use strict';

    var globals = 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'];
    };

    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;
    var HALF_PI = Math.PI/2;
    // ellipoid pj_set_ell.c
    var SIXTH = 0.1666666666666666667;
    /* 1/6 */
    var RA4 = 0.04722222222222222222;
    /* 17/360 */
    var RA6 = 0.02215608465608465608;
    var EPSLN = (typeof Number.EPSILON === 'undefined') ? 1.0e-10 : Number.EPSILON;
    var D2R = 0.01745329251994329577;
    var R2D = 57.29577951308232088;
    var FORTPI = Math.PI/4;
    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 exports$1 = {};
    exports$1.greenwich = 0.0; //"0dE",
    exports$1.lisbon = -9.131906111111; //"9d07'54.862\"W",
    exports$1.paris = 2.337229166667; //"2d20'14.025\"E",
    exports$1.bogota = -74.080916666667; //"74d04'51.3\"W",
    exports$1.madrid = -3.687938888889; //"3d41'16.58\"W",
    exports$1.rome = 12.452333333333; //"12d27'8.4\"E",
    exports$1.bern = 7.439583333333; //"7d26'22.5\"E",
    exports$1.jakarta = 106.807719444444; //"106d48'27.79\"E",
    exports$1.ferro = -17.666666666667; //"17d40'W",
    exports$1.brussels = 4.367975; //"4d22'4.71\"E",
    exports$1.stockholm = 18.058277777778; //"18d3'29.8\"E",
    exports$1.athens = 23.7163375; //"23d42'58.815\"E",
    exports$1.oslo = 10.722916666667; //"10d43'22.5\"E"

    var units = {
        ft: {to_meter: 0.3048},
        'us-ft': {to_meter: 1200 / 3937}
    };

    var ignoredChar = /[\s_\-\/\(\)]/g;
    function match(obj, key) {
        if (obj[key]) {
            return obj[key];
        }
        var keys = Object.keys(obj);
        var lkey = key.toLowerCase().replace(ignoredChar, '');
        var i = -1;
        var testkey, processedKey;
        while (++i < keys.length) {
            testkey = keys[i];
            processedKey = testkey.toLowerCase().replace(ignoredChar, '');
            if (processedKey === lkey) {
                return obj[testkey];
            }
        }
    }

    var parseProj = 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;
                var unit = match(units, v);
                if (unit) {
                    self.to_meter = unit.to_meter;
                }
            },
            from_greenwich: function(v) {
                self.from_greenwich = v * D2R;
            },
            pm: function(v) {
                var pm = match(exports$1, v);
                self.from_greenwich = (pm ? pm : 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;
    };

    var NEUTRAL = 1;
    var KEYWORD = 2;
    var NUMBER = 3;
    var QUOTED = 4;
    var AFTERQUOTE = 5;
    var ENDED = -1;
    var whitespace = /\s/;
    var latin = /[A-Za-z]/;
    var keyword = /[A-Za-z84]/;
    var endThings = /[,\]]/;
    var digets = /[\d\.E\-\+]/;
    // const ignoredChar = /[\s_\-\/\(\)]/g;
    function Parser(text) {
        if (typeof text !== 'string') {
            throw new Error('not a string');
        }
        this.text = text.trim();
        this.level = 0;
        this.place = 0;
        this.root = null;
        this.stack = [];
        this.currentObject = null;
        this.state = NEUTRAL;
    }
    Parser.prototype.readCharicter = function() {
        var char = this.text[this.place++];
        if (this.state !== QUOTED) {
            while (whitespace.test(char)) {
                if (this.place >= this.text.length) {
                    return;
                }
                char = this.text[this.place++];
            }
        }
        switch (this.state) {
            case NEUTRAL:
                return this.neutral(char);
            case KEYWORD:
                return this.keyword(char)
            case QUOTED:
                return this.quoted(char);
            case AFTERQUOTE:
                return this.afterquote(char);
            case NUMBER:
                return this.number(char);
            case ENDED:
                return;
        }
    };
    Parser.prototype.afterquote = function(char) {
        if (char === '"') {
            this.word += '"';
            this.state = QUOTED;
            return;
        }
        if (endThings.test(char)) {
            this.word = this.word.trim();
            this.afterItem(char);
            return;
        }
        throw new Error('havn\'t handled "' +char + '" in afterquote yet, index ' + this.place);
    };
    Parser.prototype.afterItem = function(char) {
        if (char === ',') {
            if (this.word !== null) {
                this.currentObject.push(this.word);
            }
            this.word = null;
            this.state = NEUTRAL;
            return;
        }
        if (char === ']') {
            this.level--;
            if (this.word !== null) {
                this.currentObject.push(this.word);
                this.word = null;
            }
            this.state = NEUTRAL;
            this.currentObject = this.stack.pop();
            if (!this.currentObject) {
                this.state = ENDED;
            }

            return;
        }
    };
    Parser.prototype.number = function(char) {
        if (digets.test(char)) {
            this.word += char;
            return;
        }
        if (endThings.test(char)) {
            this.word = parseFloat(this.word);
            this.afterItem(char);
            return;
        }
        throw new Error('havn\'t handled "' +char + '" in number yet, index ' + this.place);
    };
    Parser.prototype.quoted = function(char) {
        if (char === '"') {
            this.state = AFTERQUOTE;
            return;
        }
        this.word += char;
        return;
    };
    Parser.prototype.keyword = function(char) {
        if (keyword.test(char)) {
            this.word += char;
            return;
        }
        if (char === '[') {
            var newObjects = [];
            newObjects.push(this.word);
            this.level++;
            if (this.root === null) {
                this.root = newObjects;
            } else {
                this.currentObject.push(newObjects);
            }
            this.stack.push(this.currentObject);
            this.currentObject = newObjects;
            this.state = NEUTRAL;
            return;
        }
        if (endThings.test(char)) {
            this.afterItem(char);
            return;
        }
        throw new Error('havn\'t handled "' +char + '" in keyword yet, index ' + this.place);
    };
    Parser.prototype.neutral = function(char) {
        if (latin.test(char)) {
            this.word = char;
            this.state = KEYWORD;
            return;
        }
        if (char === '"') {
            this.word = '';
            this.state = QUOTED;
            return;
        }
        if (digets.test(char)) {
            this.word = char;
            this.state = NUMBER;
            return;
        }
        if (endThings.test(char)) {
            this.afterItem(char);
            return;
        }
        throw new Error('havn\'t handled "' +char + '" in neutral yet, index ' + this.place);
    };
    Parser.prototype.output = function() {
        while (this.place < this.text.length) {
            this.readCharicter();
        }
        if (this.state === ENDED) {
            return this.root;
        }
        throw new Error('unable to parse string "' +this.text + '". State is ' + this.state);
    };

    function parseString(txt) {
        var parser = new Parser(txt);
        return parser.output();
    }

    function mapit(obj, key, value) {
        if (Array.isArray(key)) {
            value.unshift(key);
            key = null;
        }
        var thing = key ? {} : obj;

        var out = value.reduce(function(newObj, item) {
            sExpr(item, newObj);
            return newObj
        }, thing);
        if (key) {
            obj[key] = out;
        }
    }

    function sExpr(v, obj) {
        if (!Array.isArray(v)) {
            obj[v] = true;
            return;
        }
        var 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]);
                return;
            }
            obj[key] = v[0];
            return;
        }
        if (!v.length) {
            obj[key] = true;
            return;
        }
        if (key === 'TOWGS84') {
            obj[key] = v;
            return;
        }
        if (!Array.isArray(key)) {
            obj[key] = {};
        }

        var i;
        switch (key) {
            case 'UNIT':
            case 'PRIMEM':
            case 'VERT_DATUM':
                obj[key] = {
                    name: v[0].toLowerCase(),
                    convert: v[1]
                };
                if (v.length === 3) {
                    sExpr(v[2], obj[key]);
                }
                return;
            case 'SPHEROID':
            case 'ELLIPSOID':
                obj[key] = {
                    name: v[0],
                    a: v[1],
                    rf: v[2]
                };
                if (v.length === 4) {
                    sExpr(v[3], obj[key]);
                }
                return;
            case 'PROJECTEDCRS':
            case 'PROJCRS':
            case 'GEOGCS':
            case 'GEOCCS':
            case 'PROJCS':
            case 'LOCAL_CS':
            case 'GEODCRS':
            case 'GEODETICCRS':
            case 'GEODETICDATUM':
            case 'EDATUM':
            case 'ENGINEERINGDATUM':
            case 'VERT_CS':
            case 'VERTCRS':
            case 'VERTICALCRS':
            case 'COMPD_CS':
            case 'COMPOUNDCRS':
            case 'ENGINEERINGCRS':
            case 'ENGCRS':
            case 'FITTED_CS':
            case 'LOCAL_DATUM':
            case 'DATUM':
                v[0] = ['name', v[0]];
                mapit(obj, key, v);
                return;
            default:
                i = -1;
                while (++i < v.length) {
                    if (!Array.isArray(v[i])) {
                        return sExpr(v, obj[key]);
                    }
                }
                return mapit(obj, key, v);
        }
    }

    var D2R$1 = 0.01745329251994329577;
    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$1;
    }

    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 = wkt.UNIT.convert*wkt.DATUM.SPHEROID.a;
                    }
                } else {
                    wkt.to_meter = wkt.UNIT.convert, 10;
                }
            }
        }
        var geogcs = wkt.GEOGCS;
        if (wkt.type === 'GEOGCS') {
            geogcs = wkt;
        }
        if (geogcs) {
            //if(wkt.GEOGCS.PRIMEM&&wkt.GEOGCS.PRIMEM.convert){
            //  wkt.from_greenwich=wkt.GEOGCS.PRIMEM.convert*D2R;
            //}
            if (geogcs.DATUM) {
                wkt.datumCode = geogcs.DATUM.name.toLowerCase();
            } else {
                wkt.datumCode = 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 (geogcs.DATUM && geogcs.DATUM.SPHEROID) {
                wkt.ellps = geogcs.DATUM.SPHEROID.name.replace('_19', '').replace(/[Cc]larke\_18/, 'clrk');
                if (wkt.ellps.toLowerCase().slice(0, 13) === 'international') {
                    wkt.ellps = 'intl';
                }

                wkt.a = geogcs.DATUM.SPHEROID.a;
                wkt.rf = parseFloat(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 input * 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;
        }
    }
    var wkt = function(wkt) {
        var lisp = parseString(wkt);
        var type = lisp.shift();
        var name = lisp.shift();
        lisp.unshift(['name', name]);
        lisp.unshift(['type', type]);
        var obj = {};
        sExpr(lisp, obj);
        cleanWKT(obj);
        return obj;
    };

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

    function testObj(code){
        return typeof code === 'string';
    }
    function testDef(code){
        return code in defs;
    }
    var codeWords = ['PROJECTEDCRS', 'PROJCRS', 'GEOGCS','GEOCCS','PROJCS','LOCAL_CS', 'GEODCRS', 'GEODETICCRS', 'GEODETICDATUM', 'ENGCRS', 'ENGINEERINGCRS'];
    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 parseProj(code);
            }
        }else{
            return code;
        }
    }

    var extend = 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;
    };

    var msfnz = function(eccent, sinphi, cosphi) {
        var con = eccent * sinphi;
        return cosphi / (Math.sqrt(1 - con * con));
    };

    var sign = function(x) {
        return x<0 ? -1 : 1;
    };

    var adjust_lon = function(x) {
        return (Math.abs(x) <= SPI) ? x : (x - (sign(x) * TWO_PI));
    };

    var tsfnz = 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);
    };

    var phi2z = 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;
    };

    function init() {
        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
     --------------------------------------------------*/

    function forward(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
     --------------------------------------------------*/
    function inverse(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;
    }

    var names$1 = ["Mercator", "Popular Visualisation Pseudo Mercator", "Mercator_1SP", "Mercator_Auxiliary_Sphere", "merc"];
    var merc = {
        init: init,
        forward: forward,
        inverse: inverse,
        names: names$1
    };

    function init$1() {
        //no-op for longlat
    }

    function identity(pt) {
        return pt;
    }
    var names$2 = ["longlat", "identity"];
    var longlat = {
        init: init$1,
        forward: identity,
        inverse: identity,
        names: names$2
    };

    var projs = [merc, longlat];
    var names$$1 = {};
    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$$1[n.toLowerCase()] = len;
        });
        return this;
    }

    function get(name) {
        if (!name) {
            return false;
        }
        var n = name.toLowerCase();
        if (typeof names$$1[n] !== 'undefined' && projStore[names$$1[n]]) {
            return projStore[names$$1[n]];
        }
    }

    function start() {
        projs.forEach(add);
    }
    var projections = {
        start: start,
        add: add,
        get: get
    };

    var exports$2 = {};
    exports$2.MERIT = {
        a: 6378137.0,
        rf: 298.257,
        ellipseName: "MERIT 1983"
    };

    exports$2.SGS85 = {
        a: 6378136.0,
        rf: 298.257,
        ellipseName: "Soviet Geodetic System 85"
    };

    exports$2.GRS80 = {
        a: 6378137.0,
        rf: 298.257222101,
        ellipseName: "GRS 1980(IUGG, 1980)"
    };

    exports$2.IAU76 = {
        a: 6378140.0,
        rf: 298.257,
        ellipseName: "IAU 1976"
    };

    exports$2.airy = {
        a: 6377563.396,
        b: 6356256.910,
        ellipseName: "Airy 1830"
    };

    exports$2.APL4 = {
        a: 6378137,
        rf: 298.25,
        ellipseName: "Appl. Physics. 1965"
    };

    exports$2.NWL9D = {
        a: 6378145.0,
        rf: 298.25,
        ellipseName: "Naval Weapons Lab., 1965"
    };

    exports$2.mod_airy = {
        a: 6377340.189,
        b: 6356034.446,
        ellipseName: "Modified Airy"
    };

    exports$2.andrae = {
        a: 6377104.43,
        rf: 300.0,
        ellipseName: "Andrae 1876 (Den., Iclnd.)"
    };

    exports$2.aust_SA = {
        a: 6378160.0,
        rf: 298.25,
        ellipseName: "Australian Natl & S. Amer. 1969"
    };

    exports$2.GRS67 = {
        a: 6378160.0,
        rf: 298.2471674270,
        ellipseName: "GRS 67(IUGG 1967)"
    };

    exports$2.bessel = {
        a: 6377397.155,
        rf: 299.1528128,
        ellipseName: "Bessel 1841"
    };

    exports$2.bess_nam = {
        a: 6377483.865,
        rf: 299.1528128,
        ellipseName: "Bessel 1841 (Namibia)"
    };

    exports$2.clrk66 = {
        a: 6378206.4,
        b: 6356583.8,
        ellipseName: "Clarke 1866"
    };

    exports$2.clrk80 = {
        a: 6378249.145,
        rf: 293.4663,
        ellipseName: "Clarke 1880 mod."
    };

    exports$2.clrk58 = {
        a: 6378293.645208759,
        rf: 294.2606763692654,
        ellipseName: "Clarke 1858"
    };

    exports$2.CPM = {
        a: 6375738.7,
        rf: 334.29,
        ellipseName: "Comm. des Poids et Mesures 1799"
    };

    exports$2.delmbr = {
        a: 6376428.0,
        rf: 311.5,
        ellipseName: "Delambre 1810 (Belgium)"
    };

    exports$2.engelis = {
        a: 6378136.05,
        rf: 298.2566,
        ellipseName: "Engelis 1985"
    };

    exports$2.evrst30 = {
        a: 6377276.345,
        rf: 300.8017,
        ellipseName: "Everest 1830"
    };

    exports$2.evrst48 = {
        a: 6377304.063,
        rf: 300.8017,
        ellipseName: "Everest 1948"
    };

    exports$2.evrst56 = {
        a: 6377301.243,
        rf: 300.8017,
        ellipseName: "Everest 1956"
    };

    exports$2.evrst69 = {
        a: 6377295.664,
        rf: 300.8017,
        ellipseName: "Everest 1969"
    };

    exports$2.evrstSS = {
        a: 6377298.556,
        rf: 300.8017,
        ellipseName: "Everest (Sabah & Sarawak)"
    };

    exports$2.fschr60 = {
        a: 6378166.0,
        rf: 298.3,
        ellipseName: "Fischer (Mercury Datum) 1960"
    };

    exports$2.fschr60m = {
        a: 6378155.0,
        rf: 298.3,
        ellipseName: "Fischer 1960"
    };

    exports$2.fschr68 = {
        a: 6378150.0,
        rf: 298.3,
        ellipseName: "Fischer 1968"
    };

    exports$2.helmert = {
        a: 6378200.0,
        rf: 298.3,
        ellipseName: "Helmert 1906"
    };

    exports$2.hough = {
        a: 6378270.0,
        rf: 297.0,
        ellipseName: "Hough"
    };

    exports$2.intl = {
        a: 6378388.0,
        rf: 297.0,
        ellipseName: "International 1909 (Hayford)"
    };

    exports$2.kaula = {
        a: 6378163.0,
        rf: 298.24,
        ellipseName: "Kaula 1961"
    };

    exports$2.lerch = {
        a: 6378139.0,
        rf: 298.257,
        ellipseName: "Lerch 1979"
    };

    exports$2.mprts = {
        a: 6397300.0,
        rf: 191.0,
        ellipseName: "Maupertius 1738"
    };

    exports$2.new_intl = {
        a: 6378157.5,
        b: 6356772.2,
        ellipseName: "New International 1967"
    };

    exports$2.plessis = {
        a: 6376523.0,
        rf: 6355863.0,
        ellipseName: "Plessis 1817 (France)"
    };

    exports$2.krass = {
        a: 6378245.0,
        rf: 298.3,
        ellipseName: "Krassovsky, 1942"
    };

    exports$2.SEasia = {
        a: 6378155.0,
        b: 6356773.3205,
        ellipseName: "Southeast Asia"
    };

    exports$2.walbeck = {
        a: 6376896.0,
        b: 6355834.8467,
        ellipseName: "Walbeck"
    };

    exports$2.WGS60 = {
        a: 6378165.0,
        rf: 298.3,
        ellipseName: "WGS 60"
    };

    exports$2.WGS66 = {
        a: 6378145.0,
        rf: 298.25,
        ellipseName: "WGS 66"
    };

    exports$2.WGS7 = {
        a: 6378135.0,
        rf: 298.26,
        ellipseName: "WGS 72"
    };

    var WGS84 = exports$2.WGS84 = {
        a: 6378137.0,
        rf: 298.257223563,
        ellipseName: "WGS 84"
    };

    exports$2.sphere = {
        a: 6370997.0,
        b: 6370997.0,
        ellipseName: "Normal Sphere (r=6370997)"
    };

    function eccentricity(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
        };
    }
    function sphere(a, b, rf, ellps, sphere) {
        if (!a) { // do we have an ellipsoid?
            var ellipse = match(exports$2, ellps);
            if (!ellipse) {
                ellipse = 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
        };
    }

    var exports$3 = {};
    exports$3.wgs84 = {
        towgs84: "0,0,0",
        ellipse: "WGS84",
        datumName: "WGS84"
    };

    exports$3.ch1903 = {
        towgs84: "674.374,15.056,405.346",
        ellipse: "bessel",
        datumName: "swiss"
    };

    exports$3.ggrs87 = {
        towgs84: "-199.87,74.79,246.62",
        ellipse: "GRS80",
        datumName: "Greek_Geodetic_Reference_System_1987"
    };

    exports$3.nad83 = {
        towgs84: "0,0,0",
        ellipse: "GRS80",
        datumName: "North_American_Datum_1983"
    };

    exports$3.nad27 = {
        nadgrids: "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat",
        ellipse: "clrk66",
        datumName: "North_American_Datum_1927"
    };

    exports$3.potsdam = {
        towgs84: "606.0,23.0,413.0",
        ellipse: "bessel",
        datumName: "Potsdam Rauenberg 1950 DHDN"
    };

    exports$3.carthage = {
        towgs84: "-263.0,6.0,431.0",
        ellipse: "clark80",
        datumName: "Carthage 1934 Tunisia"
    };

    exports$3.hermannskogel = {
        towgs84: "653.0,-212.0,449.0",
        ellipse: "bessel",
        datumName: "Hermannskogel"
    };

    exports$3.ire65 = {
        towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15",
        ellipse: "mod_airy",
        datumName: "Ireland 1965"
    };

    exports$3.rassadiran = {
        towgs84: "-133.63,-157.5,-158.62",
        ellipse: "intl",
        datumName: "Rassadiran"
    };

    exports$3.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$3.osgb36 = {
        towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894",
        ellipse: "airy",
        datumName: "Airy 1830"
    };

    exports$3.s_jtsk = {
        towgs84: "589,76,480",
        ellipse: 'bessel',
        datumName: 'S-JTSK (Ferro)'
    };

    exports$3.beduaram = {
        towgs84: '-106,-87,188',
        ellipse: 'clrk80',
        datumName: 'Beduaram'
    };

    exports$3.gunung_segara = {
        towgs84: '-403,684,41',
        ellipse: 'bessel',
        datumName: 'Gunung Segara Jakarta'
    };

    exports$3.rnb72 = {
        towgs84: "106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1",
        ellipse: "intl",
        datumName: "Reseau National Belge 1972"
    };

    function datum(datumCode, datum_params, a, b, es, ep2) {
        var out = {};

        if (datumCode === undefined || datumCode === 'none') {
            out.datum_type = PJD_NODATUM;
        } else {
            out.datum_type = PJD_WGS84;
        }

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

    function Projection$1(srsCode,callback) {
        if (!(this instanceof Projection$1)) {
            return new Projection$1(srsCode);
        }
        callback = callback || function(error){
                if(error){
                    throw error;
                }
            };
        var json = parse(srsCode);
        if(typeof json !== 'object'){
            callback(srsCode);
            return;
        }
        var ourProj = Projection$1.projections.get(json.projName);
        if(!ourProj){
            callback(srsCode);
            return;
        }
        if (json.datumCode && json.datumCode !== 'none') {
            var datumDef = match(exports$3, 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';
        json.ellps = json.ellps || 'wgs84';
        var sphere_ = sphere(json.a, json.b, json.rf, json.ellps, json.sphere);
        var ecc = 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$1.projections = projections;
    Projection$1.projections.start();

    function compareDatums(source, dest) {
        if (source.datum_type !== dest.datum_type) {
            return false; // false, datums are not equal
        } else if (source.a !== dest.a || Math.abs(source.es - dest.es) > 0.000000000050) {
            // the tolerance for es is to ensure that GRS80 and WGS84
            // are considered identical
            return false;
        } else if (source.datum_type === PJD_3PARAM) {
            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]);
        } 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)
     *
     */
    function geodeticToGeocentric(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()

    function geocentricToGeodetic(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.
     */
    function geocentricToWgs84(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)
    function geocentricFromWgs84(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()
    }

    function checkParams(type) {
        return (type === PJD_3PARAM || type === PJD_7PARAM);
    }

    var datum_transform = function(source, dest, point) {
        // Short cut if the datums are identical.
        if (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 = geodeticToGeocentric(point, source.es, source.a);
        // Convert between datums
        if (checkParams(source.datum_type)) {
            point = geocentricToWgs84(point, source.datum_type, source.datum_params);
        }
        if (checkParams(dest.datum_type)) {
            point = geocentricFromWgs84(point, dest.datum_type, dest.datum_params);
        }
        return geocentricToGeodetic(point, dest.es, dest.a, dest.b);

    };

    var adjust_axis = 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;
    };

    var toPoint = 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;
    };

    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');
    }

    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 Projection$1('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.from_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;
    }

    var wgs84 = Projection$1('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 Projection$1) {
            return item;
        }
        if (item.oProj) {
            return item.oProj;
        }
        return Projection$1(item);
    }
    function proj4$1(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;
        }
    }

    /**
     * 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
    var mgrs = {
        forward: forward$1,
        inverse: inverse$1,
        toPoint: toPoint$1
    };
    /**
     * 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.
     */
    function forward$1(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.
     */
    function inverse$1(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];
    }

    function toPoint$1(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);
        }

    }

    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(toPoint$1(mgrsStr));
    };
    Point.prototype.toMGRS = function(accuracy) {
        return forward$1([this.x, this.y], accuracy);
    };

    var version = "2.4.3";

    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;

    var pj_enfn = 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;
    };

    var pj_mlfn = 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]))));
    };

    var MAX_ITER = 20;

    var pj_inv_mlfn = 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;
    };

    // Heavily based on this tmerc projection implementation
    // https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/tmerc.js

    function init$2() {
        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
     */
    function forward$2(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
     */
    function inverse$2(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;
    }

    var names$3 = ["Transverse_Mercator", "Transverse Mercator", "tmerc"];
    var tmerc = {
        init: init$2,
        forward: forward$2,
        inverse: inverse$2,
        names: names$3
    };

    var sinh = function(x) {
        var r = Math.exp(x);
        r = (r - 1 / r) / 2;
        return r;
    };

    var hypot = function(x, y) {
        x = Math.abs(x);
        y = Math.abs(y);
        var a = Math.max(x, y);
        var b = Math.min(x, y) / (a ? a : 1);

        return a * Math.sqrt(1 + Math.pow(b, 2));
    };

    var log1py = function(x) {
        var y = 1 + x;
        var z = y - 1;

        return z === 0 ? x : x * Math.log(y) / z;
    };

    var asinhy = function(x) {
        var y = Math.abs(x);
        y = log1py(y * (1 + y / (hypot(1, y) + 1)));

        return x < 0 ? -y : y;
    };

    var gatg = function(pp, B) {
        var cos_2B = 2 * Math.cos(2 * B);
        var i = pp.length - 1;
        var h1 = pp[i];
        var h2 = 0;
        var h;

        while (--i >= 0) {
            h = -h2 + cos_2B * h1 + pp[i];
            h2 = h1;
            h1 = h;
        }

        return (B + h * Math.sin(2 * B));
    };

    var clens = function(pp, arg_r) {
        var r = 2 * Math.cos(arg_r);
        var i = pp.length - 1;
        var hr1 = pp[i];
        var hr2 = 0;
        var hr;

        while (--i >= 0) {
            hr = -hr2 + r * hr1 + pp[i];
            hr2 = hr1;
            hr1 = hr;
        }

        return Math.sin(arg_r) * hr;
    };

    var cosh = function(x) {
        var r = Math.exp(x);
        r = (r + 1 / r) / 2;
        return r;
    };

    var clens_cmplx = function(pp, arg_r, arg_i) {
        var sin_arg_r = Math.sin(arg_r);
        var cos_arg_r = Math.cos(arg_r);
        var sinh_arg_i = sinh(arg_i);
        var cosh_arg_i = cosh(arg_i);
        var r = 2 * cos_arg_r * cosh_arg_i;
        var i = -2 * sin_arg_r * sinh_arg_i;
        var j = pp.length - 1;
        var hr = pp[j];
        var hi1 = 0;
        var hr1 = 0;
        var hi = 0;
        var hr2;
        var hi2;

        while (--j >= 0) {
            hr2 = hr1;
            hi2 = hi1;
            hr1 = hr;
            hi1 = hi;
            hr = -hr2 + r * hr1 - i * hi1 + pp[j];
            hi = -hi2 + i * hr1 + r * hi1;
        }

        r = sin_arg_r * cosh_arg_i;
        i = cos_arg_r * sinh_arg_i;

        return [r * hr - i * hi, r * hi + i * hr];
    };

    // Heavily based on this etmerc projection implementation
    // https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/etmerc.js

    function init$3() {
        if (this.es === undefined || this.es <= 0) {
            throw new Error('incorrect elliptical usage');
        }

        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;

        this.cgb = [];
        this.cbg = [];
        this.utg = [];
        this.gtu = [];

        var f = this.es / (1 + Math.sqrt(1 - this.es));
        var n = f / (2 - f);
        var np = n;

        this.cgb[0] = n * (2 + n * (-2 / 3 + n * (-2 + n * (116 / 45 + n * (26 / 45 + n * (-2854 / 675 ))))));
        this.cbg[0] = n * (-2 + n * ( 2 / 3 + n * ( 4 / 3 + n * (-82 / 45 + n * (32 / 45 + n * (4642 / 4725))))));

        np = np * n;
        this.cgb[1] = np * (7 / 3 + n * (-8 / 5 + n * (-227 / 45 + n * (2704 / 315 + n * (2323 / 945)))));
        this.cbg[1] = np * (5 / 3 + n * (-16 / 15 + n * ( -13 / 9 + n * (904 / 315 + n * (-1522 / 945)))));

        np = np * n;
        this.cgb[2] = np * (56 / 15 + n * (-136 / 35 + n * (-1262 / 105 + n * (73814 / 2835))));
        this.cbg[2] = np * (-26 / 15 + n * (34 / 21 + n * (8 / 5 + n * (-12686 / 2835))));

        np = np * n;
        this.cgb[3] = np * (4279 / 630 + n * (-332 / 35 + n * (-399572 / 14175)));
        this.cbg[3] = np * (1237 / 630 + n * (-12 / 5 + n * ( -24832 / 14175)));

        np = np * n;
        this.cgb[4] = np * (4174 / 315 + n * (-144838 / 6237));
        this.cbg[4] = np * (-734 / 315 + n * (109598 / 31185));

        np = np * n;
        this.cgb[5] = np * (601676 / 22275);
        this.cbg[5] = np * (444337 / 155925);

        np = Math.pow(n, 2);
        this.Qn = this.k0 / (1 + n) * (1 + np * (1 / 4 + np * (1 / 64 + np / 256)));

        this.utg[0] = n * (-0.5 + n * ( 2 / 3 + n * (-37 / 96 + n * ( 1 / 360 + n * (81 / 512 + n * (-96199 / 604800))))));
        this.gtu[0] = n * (0.5 + n * (-2 / 3 + n * (5 / 16 + n * (41 / 180 + n * (-127 / 288 + n * (7891 / 37800))))));

        this.utg[1] = np * (-1 / 48 + n * (-1 / 15 + n * (437 / 1440 + n * (-46 / 105 + n * (1118711 / 3870720)))));
        this.gtu[1] = np * (13 / 48 + n * (-3 / 5 + n * (557 / 1440 + n * (281 / 630 + n * (-1983433 / 1935360)))));

        np = np * n;
        this.utg[2] = np * (-17 / 480 + n * (37 / 840 + n * (209 / 4480 + n * (-5569 / 90720 ))));
        this.gtu[2] = np * (61 / 240 + n * (-103 / 140 + n * (15061 / 26880 + n * (167603 / 181440))));

        np = np * n;
        this.utg[3] = np * (-4397 / 161280 + n * (11 / 504 + n * (830251 / 7257600)));
        this.gtu[3] = np * (49561 / 161280 + n * (-179 / 168 + n * (6601661 / 7257600)));

        np = np * n;
        this.utg[4] = np * (-4583 / 161280 + n * (108847 / 3991680));
        this.gtu[4] = np * (34729 / 80640 + n * (-3418889 / 1995840));

        np = np * n;
        this.utg[5] = np * (-20648693 / 638668800);
        this.gtu[5] = np * (212378941 / 319334400);

        var Z = gatg(this.cbg, this.lat0);
        this.Zb = -this.Qn * (Z + clens(this.gtu, 2 * Z));
    }

    function forward$3(p) {
        var Ce = adjust_lon(p.x - this.long0);
        var Cn = p.y;

        Cn = gatg(this.cbg, Cn);
        var sin_Cn = Math.sin(Cn);
        var cos_Cn = Math.cos(Cn);
        var sin_Ce = Math.sin(Ce);
        var cos_Ce = Math.cos(Ce);

        Cn = Math.atan2(sin_Cn, cos_Ce * cos_Cn);
        Ce = Math.atan2(sin_Ce * cos_Cn, hypot(sin_Cn, cos_Cn * cos_Ce));
        Ce = asinhy(Math.tan(Ce));

        var tmp = clens_cmplx(this.gtu, 2 * Cn, 2 * Ce);

        Cn = Cn + tmp[0];
        Ce = Ce + tmp[1];

        var x;
        var y;

        if (Math.abs(Ce) <= 2.623395162778) {
            x = this.a * (this.Qn * Ce) + this.x0;
            y = this.a * (this.Qn * Cn + this.Zb) + this.y0;
        }
        else {
            x = Infinity;
            y = Infinity;
        }

        p.x = x;
        p.y = y;

        return p;
    }

    function inverse$3(p) {
        var Ce = (p.x - this.x0) * (1 / this.a);
        var Cn = (p.y - this.y0) * (1 / this.a);

        Cn = (Cn - this.Zb) / this.Qn;
        Ce = Ce / this.Qn;

        var lon;
        var lat;

        if (Math.abs(Ce) <= 2.623395162778) {
            var tmp = clens_cmplx(this.utg, 2 * Cn, 2 * Ce);

            Cn = Cn + tmp[0];
            Ce = Ce + tmp[1];
            Ce = Math.atan(sinh(Ce));

            var sin_Cn = Math.sin(Cn);
            var cos_Cn = Math.cos(Cn);
            var sin_Ce = Math.sin(Ce);
            var cos_Ce = Math.cos(Ce);

            Cn = Math.atan2(sin_Cn * cos_Ce, hypot(sin_Ce, cos_Ce * cos_Cn));
            Ce = Math.atan2(sin_Ce, cos_Ce * cos_Cn);

            lon = adjust_lon(Ce + this.long0);
            lat = gatg(this.cgb, Cn);
        }
        else {
            lon = Infinity;
            lat = Infinity;
        }

        p.x = lon;
        p.y = lat;

        return p;
    }

    var names$4 = ["Extended_Transverse_Mercator", "Extended Transverse Mercator", "etmerc"];
    var etmerc = {
        init: init$3,
        forward: forward$3,
        inverse: inverse$3,
        names: names$4
    };

    var adjust_zone = function(zone, lon) {
        if (zone === undefined) {
            zone = Math.floor((adjust_lon(lon) + Math.PI) * 30 / Math.PI) + 1;

            if (zone < 0) {
                return 0;
            } else if (zone > 60) {
                return 60;
            }
        }
        return zone;
    };

    var dependsOn = 'etmerc';
    function init$4() {
        var zone = adjust_zone(this.zone, this.long0);
        if (zone === undefined) {
            throw new Error('unknown utm zone');
        }
        this.lat0 = 0;
        this.long0 =  ((6 * Math.abs(zone)) - 183) * D2R;
        this.x0 = 500000;
        this.y0 = this.utmSouth ? 10000000 : 0;
        this.k0 = 0.9996;

        etmerc.init.apply(this);
        this.forward = etmerc.forward;
        this.inverse = etmerc.inverse;
    }

    var names$5 = ["Universal Transverse Mercator System", "utm"];
    var utm = {
        init: init$4,
        names: names$5,
        dependsOn: dependsOn
    };

    var srat = function(esinp, exp) {
        return (Math.pow((1 - esinp) / (1 + esinp), exp));
    };

    var MAX_ITER$1 = 20;
    function init$6() {
        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));
    }

    function forward$5(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;
    }

    function inverse$5(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$1; 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;
    }

    var names$7 = ["gauss"];
    var gauss = {
        init: init$6,
        forward: forward$5,
        inverse: inverse$5,
        names: names$7
    };

    function init$5() {
        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";
        }
    }

    function forward$4(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;
    }

    function inverse$4(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;
    }

    var names$6 = ["Stereographic_North_Pole", "Oblique_Stereographic", "Polar_Stereographic", "sterea","Oblique Stereographic Alternative"];
    var sterea = {
        init: init$5,
        forward: forward$4,
        inverse: inverse$4,
        names: names$6
    };

    function ssfn_(phit, sinphi, eccen) {
        sinphi *= eccen;
        return (Math.tan(0.5 * (HALF_PI + phit)) * Math.pow((1 - sinphi) / (1 + sinphi), 0.5 * eccen));
    }

    function init$7() {
        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
    function forward$6(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
    function inverse$6(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;

    }

    var names$8 = ["stere", "Stereographic_South_Pole", "Polar Stereographic (variant B)"];
    var stere = {
        init: init$7,
        forward: forward$6,
        inverse: inverse$6,
        names: names$8,
        ssfn_: ssfn_
    };

    /*
     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
     */

    function init$8() {
        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;
    }

    function forward$7(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;
    }

    function inverse$7(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;
    }

    var names$9 = ["somerc"];
    var somerc = {
        init: init$8,
        forward: forward$7,
        inverse: inverse$7,
        names: names$9
    };

    /* Initialize the Oblique Mercator  projection
     ------------------------------------------*/
    function init$9() {
        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
     ----------------------------------------------------------*/
    function forward$8(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;
    }

    function inverse$8(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;
    }

    var names$10 = ["Hotine_Oblique_Mercator", "Hotine Oblique Mercator", "Hotine_Oblique_Mercator_Azimuth_Natural_Origin", "Hotine_Oblique_Mercator_Azimuth_Center", "omerc"];
    var omerc = {
        init: init$9,
        forward: forward$8,
        inverse: inverse$8,
        names: names$10
    };

    function init$10() {

        // 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
    // -----------------------------------------------------------------
    function forward$9(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
    // -----------------------------------------------------------------
    function inverse$9(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;
    }

    var names$11 = ["Lambert Tangential Conformal Conic Projection", "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_2SP", "lcc"];
    var lcc = {
        init: init$10,
        forward: forward$9,
        inverse: inverse$9,
        names: names$11
    };

    function init$11() {
        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 */
    function forward$10(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 */
    function inverse$10(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);
    }

    var names$12 = ["Krovak", "krovak"];
    var krovak = {
        init: init$11,
        forward: forward$10,
        inverse: inverse$10,
        names: names$12
    };

    var mlfn = function(e0, e1, e2, e3, phi) {
        return (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi));
    };

    var e0fn = function(x) {
        return (1 - 0.25 * x * (1 + x / 16 * (3 + 1.25 * x)));
    };

    var e1fn = function(x) {
        return (0.375 * x * (1 + 0.25 * x * (1 + 0.46875 * x)));
    };

    var e2fn = function(x) {
        return (0.05859375 * x * x * (1 + 0.75 * x));
    };

    var e3fn = function(x) {
        return (x * x * x * (35 / 3072));
    };

    var gN = function(a, e, sinphi) {
        var temp = e * sinphi;
        return a / Math.sqrt(1 - temp * temp);
    };

    var adjust_lat = function(x) {
        return (Math.abs(x) < HALF_PI) ? x : (x - (sign(x) * Math.PI));
    };

    var imlfn = 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;
    };

    function init$12() {
        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
     -----------------------------------------------------------------------*/
    function forward$11(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
     -----------------*/
    function inverse$11(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;

    }

    var names$13 = ["Cassini", "Cassini_Soldner", "cass"];
    var cass = {
        init: init$12,
        forward: forward$11,
        inverse: inverse$11,
        names: names$13
    };

    var qsfnz = 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);
        }
    };

    /*
     reference
     "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
     The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
     */

    var S_POLE = 1;

    var N_POLE = 2;
    var EQUIT = 3;
    var OBLIQ = 4;

    /* Initialize the Lambert Azimuthal Equal Area projection
     ------------------------------------------------------*/
    function init$13() {
        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 = 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
     -----------------------------------------------------------------------*/
    function forward$12(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
     -----------------*/
    function inverse$12(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 = authlat(Math.asin(ab), this.apa);
        }

        p.x = adjust_lon(this.long0 + lam);
        p.y = phi;
        return p;
    }

    /* determine latitude from authalic latitude */
    var P00 = 0.33333333333333333333;

    var P01 = 0.17222222222222222222;
    var P02 = 0.10257936507936507936;
    var P10 = 0.06388888888888888888;
    var P11 = 0.06640211640211640211;
    var P20 = 0.01641501294219154443;

    function authset(es) {
        var t;
        var APA = [];
        APA[0] = es * P00;
        t = es * es;
        APA[0] += t * P01;
        APA[1] = t * P10;
        t *= es;
        APA[0] += t * P02;
        APA[1] += t * P11;
        APA[2] = t * P20;
        return APA;
    }

    function authlat(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));
    }

    var names$14 = ["Lambert Azimuthal Equal Area", "Lambert_Azimuthal_Equal_Area", "laea"];
    var laea = {
        init: init$13,
        forward: forward$12,
        inverse: inverse$12,
        names: names$14,
        S_POLE: S_POLE,
        N_POLE: N_POLE,
        EQUIT: EQUIT,
        OBLIQ: OBLIQ
    };

    var asinz = function(x) {
        if (Math.abs(x) > 1) {
            x = (x > 1) ? 1 : -1;
        }
        return Math.asin(x);
    };

    function init$14() {

        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
     -------------------------------------------------------------------*/
    function forward$13(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;
    }

    function inverse$13(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.
     -------------------------------------------*/
    function phi1z(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;
    }

    var names$15 = ["Albers_Conic_Equal_Area", "Albers", "aea"];
    var aea = {
        init: init$14,
        forward: forward$13,
        inverse: inverse$13,
        names: names$15,
        phi1z: phi1z
    };

    /*
     reference:
     Wolfram Mathworld "Gnomonic Projection"
     http://mathworld.wolfram.com/GnomonicProjection.html
     Accessed: 12th November 2009
     */
    function init$15() {

        /* 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
     ---------------------------------------------------*/
    function forward$14(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;
    }

    function inverse$14(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;
    }

    var names$16 = ["gnom"];
    var gnom = {
        init: init$15,
        forward: forward$14,
        inverse: inverse$14,
        names: names$16
    };

    var iqsfnz = 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;
    };

    /*
     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)
     */
    function init$16() {
        //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
     ------------------------------------------------------------*/
    function forward$15(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
     ------------------------------------------------------------*/
    function inverse$15(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;
    }

    var names$17 = ["cea"];
    var cea = {
        init: init$16,
        forward: forward$15,
        inverse: inverse$15,
        names: names$17
    };

    function init$17() {

        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
    // -----------------------------------------------------------------
    function forward$16(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
    // -----------------------------------------------------------------
    function inverse$16(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;
    }

    var names$18 = ["Equirectangular", "Equidistant_Cylindrical", "eqc"];
    var eqc = {
        init: init$17,
        forward: forward$16,
        inverse: inverse$16,
        names: names$18
    };

    var MAX_ITER$2 = 20;

    function init$18() {
        /* 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
     ---------------------------------------------------*/
    function forward$17(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
     -----------------*/
    function inverse$17(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$2; 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$2; 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;
    }

    var names$19 = ["Polyconic", "poly"];
    var poly = {
        init: init$18,
        forward: forward$17,
        inverse: inverse$17,
        names: names$19
    };

    /*
     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
     */


    function init$19() {
        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
     */
    function forward$18(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
     */
    function inverse$18(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;
    }

    var names$20 = ["New_Zealand_Map_Grid", "nzmg"];
    var nzmg = {
        init: init$19,
        forward: forward$18,
        inverse: inverse$18,
        names: names$20
    };

    /*
     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
     -------------------------------------------*/
    function init$20() {
        //no-op
    }

    /* Miller Cylindrical forward equations--mapping lat,long to x,y
     ------------------------------------------------------------*/
    function forward$19(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
     ------------------------------------------------------------*/
    function inverse$19(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;
    }

    var names$21 = ["Miller_Cylindrical", "mill"];
    var mill = {
        init: init$20,
        forward: forward$19,
        inverse: inverse$19,
        names: names$21
    };

    var MAX_ITER$3 = 20;
    function init$21() {
        /* 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
     -----------------------------------------------------*/
    function forward$20(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$3; 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;
    }

    function inverse$20(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;
    }

    var names$22 = ["Sinusoidal", "sinu"];
    var sinu = {
        init: init$21,
        forward: forward$20,
        inverse: inverse$20,
        names: names$22
    };

    function init$22() {}
    /* Mollweide forward equations--mapping lat,long to x,y
     ----------------------------------------------------*/
    function forward$21(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;
    }

    function inverse$21(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;
    }

    var names$23 = ["Mollweide", "moll"];
    var moll = {
        init: init$22,
        forward: forward$21,
        inverse: inverse$21,
        names: names$23
    };

    function init$23() {

        /* 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
     -----------------------------------------------------------*/
    function forward$22(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
     -----------------*/
    function inverse$22(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;
        }

    }

    var names$24 = ["Equidistant_Conic", "eqdc"];
    var eqdc = {
        init: init$23,
        forward: forward$22,
        inverse: inverse$22,
        names: names$24
    };

    /* Initialize the Van Der Grinten projection
     ----------------------------------------*/
    function init$24() {
        //this.R = 6370997; //Radius of earth
        this.R = this.a;
    }

    function forward$23(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
     ---------------------------------------------------------*/
    function inverse$23(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;
    }

    var names$25 = ["Van_der_Grinten_I", "VanDerGrinten", "vandg"];
    var vandg = {
        init: init$24,
        forward: forward$23,
        inverse: inverse$23,
        names: names$25
    };

    function init$25() {
        this.sin_p12 = Math.sin(this.lat0);
        this.cos_p12 = Math.cos(this.lat0);
    }

    function forward$24(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;
            }
        }


    }

    function inverse$24(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;
            }
        }

    }

    var names$26 = ["Azimuthal_Equidistant", "aeqd"];
    var aeqd = {
        init: init$25,
        forward: forward$24,
        inverse: inverse$24,
        names: names$26
    };

    function init$26() {
        //double temp;      /* temporary variable    */

        /* Place parameters in static storage for common use
         -------------------------------------------------*/
        this.sin_p14 = Math.sin(this.lat0);
        this.cos_p14 = Math.cos(this.lat0);
    }

    /* Orthographic forward equations--mapping lat,long to x,y
     ---------------------------------------------------*/
    function forward$25(p) {
        var sinphi, cosphi; /* sin and cos value        */
        var dlon; /* delta longitude value      */
        var coslon; /* cos of longitude        */
        var ksp; /* scale factor          */
        var g, 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.a * ksp * cosphi * Math.sin(dlon);
            y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
        }
        p.x = x;
        p.y = y;
        return p;
    }

    function inverse$25(p) {
        var rh; /* height above ellipsoid      */
        var z; /* angle          */
        var sinz, cosz; /* sin of z and cos of z      */
        var con;
        var lon, lat;
        /* Inverse equations
         -----------------*/
        p.x -= this.x0;
        p.y -= this.y0;
        rh = Math.sqrt(p.x * p.x + p.y * p.y);
        z = asinz(rh / this.a);

        sinz = Math.sin(z);
        cosz = Math.cos(z);

        lon = this.long0;
        if (Math.abs(rh) <= EPSLN) {
            lat = this.lat0;
            p.x = lon;
            p.y = lat;
            return p;
        }
        lat = asinz(cosz * this.sin_p14 + (p.y * sinz * this.cos_p14) / 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));
            }
            p.x = lon;
            p.y = lat;
            return p;
        }
        lon = adjust_lon(this.long0 + Math.atan2((p.x * sinz), rh * this.cos_p14 * cosz - p.y * this.sin_p14 * sinz));
        p.x = lon;
        p.y = lat;
        return p;
    }

    var names$27 = ["ortho"];
    var ortho = {
        init: init$26,
        forward: forward$25,
        inverse: inverse$25,
        names: names$27
    };

    var includedProjections = function(proj4){
        proj4.Proj.projections.add(tmerc);
        proj4.Proj.projections.add(etmerc);
        proj4.Proj.projections.add(utm);
        proj4.Proj.projections.add(sterea);
        proj4.Proj.projections.add(stere);
        proj4.Proj.projections.add(somerc);
        proj4.Proj.projections.add(omerc);
        proj4.Proj.projections.add(lcc);
        proj4.Proj.projections.add(krovak);
        proj4.Proj.projections.add(cass);
        proj4.Proj.projections.add(laea);
        proj4.Proj.projections.add(aea);
        proj4.Proj.projections.add(gnom);
        proj4.Proj.projections.add(cea);
        proj4.Proj.projections.add(eqc);
        proj4.Proj.projections.add(poly);
        proj4.Proj.projections.add(nzmg);
        proj4.Proj.projections.add(mill);
        proj4.Proj.projections.add(sinu);
        proj4.Proj.projections.add(moll);
        proj4.Proj.projections.add(eqdc);
        proj4.Proj.projections.add(vandg);
        proj4.Proj.projections.add(aeqd);
        proj4.Proj.projections.add(ortho);
    };

    proj4$1.defaultDatum = 'WGS84'; //default datum
    proj4$1.Proj = Projection$1;
    proj4$1.WGS84 = new proj4$1.Proj('WGS84');
    proj4$1.Point = Point;
    proj4$1.toPoint = toPoint;
    proj4$1.defs = defs;
    proj4$1.transform = transform;
    proj4$1.mgrs = mgrs;
    proj4$1.version = version;
    includedProjections(proj4$1);

    return proj4$1;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONCRS
 */
define('formats/geojson/GeoJSONCRS',['../../error/ArgumentError',
        './GeoJSONConstants',
        '../../util/Logger',
        '../../util/proj4-src'
    ],
    function (ArgumentError,
              GeoJSONConstants,
              Logger,
              Proj4){
        "use strict";

        /**
         * Constructs a GeoJSON CRS object. Applications typically do not call this constructor. It is called by
         * {@link GeoJSONGeometry}, {@link GeoJSONGeometryCollection}, {@link GeoJSONFeature} or
         * {@link GeoJSONFeatureCollection}.
         * @alias GeoJSONCRS
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON Coordinate Reference System object.
         * The coordinate reference system (CRS) of a GeoJSON object is determined by its "crs" member (referred to as
         * the CRS object below).
         * If an object has no crs member, then its parent or grandparent object's crs member may be acquired.
         * If no crs member can be so acquired, the default CRS shall apply to the GeoJSON object.
         * The default CRS is a geographic coordinate reference system, using the WGS84 datum, and with longitude and
         * latitude units of decimal degrees.
         * <p>
         * There are two types of CRS objects:
         * <ul>
         *     <li>Named CRS</li>
         *     <li>Linked CRS</li>
         * </ul>
         * In this implementation we consider only named CRS. In this case, the value of its "type" member must be
         * the string "name". The value of its "properties" member must be an object containing a "name" member.
         * The value of that "name" member must be a string identifying a coordinate reference system.
         * OGC CRS URNs such as "urn:ogc:def:crs:OGC:1.3:CRS84" shall be preferred over legacy identifiers
         * such as "EPSG:4326".
         * <p>
         * For reprojecton is used Proj4js JavaScript library.
         * @param {String} type A string, indicating the type of CRS object.
         * @param {Object} properties An object containing the properties of CRS object.
         * @throws {ArgumentError} If the specified type or properties are null or undefined.
         */
        var GeoJSONCRS = function (type, properties) {
            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONCRS", "constructor",
                        "missingType"));
            }

            if (!properties) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONCRS", "constructor",
                        "missingProperties"));
            }

            // Documented in defineProperties below.
            this._type = type;

            // Documented in defineProperties below.
            this._properties = properties;

            //
            this._projectionString = null;
        };

        Object.defineProperties(GeoJSONCRS.prototype, {
            /**
             * The GeoJSON CRS object type as specified to this GeoJSON CRS's constructor.
             * @memberof GeoJSONCRS.prototype
             * @type {String}
             * @readonly
             */
            type: {
                get: function () {
                    return this._type;
                }
            },
            /**
             * The GeoJSON CRS object properties as specified to this GeoJSON CRS's constructor.
             * @memberof GeoJSONCRS.prototype
             * @type {Object}
             * @readonly
             */
            properties: {
                get: function () {
                    return this._properties;
                }
            },

            projectionString: {
                get: function () {
                    return this._projectionString;
                }
            }
        });

        /**
         * Indicates whether this CRS is the default GeoJSON one, respectively a geographic coordinate
         * reference system, using the WGS84 datum, and with longitude and latitude units of decimal degrees.
         *
         * @return {Boolean} True if the CRS is the default GeoJSON CRS
         */
        GeoJSONCRS.prototype.isDefault = function () {
            if (this.isNamed()){
                if (this._projectionString === GeoJSONConstants.EPSG4326_CRS ||
                    this._projectionString === GeoJSONConstants.WGS84_CRS)
                {
                    return true;
                }
            }
            return false;
        };

        /**
         * Indicates whether the type of this CRS object is named CRS.
         *
         * @return {Boolean} True if the type of CRS object is named CRS
         */
        GeoJSONCRS.prototype.isNamed = function () {
            return (this._type === GeoJSONConstants.FIELD_CRS_NAME);
        };

        /**
         * Indicates whether the type of this CRS object is linked CRS.
         *
         * @return {Boolean} True if the type of CRS object is linked CRS
         */
        GeoJSONCRS.prototype.isLinked = function () {
            return (this._type === GeoJSONConstants.FIELD_CRS_LINK);
        };

        /**
         * Indicates whether the CRS is supported by proj4js.
         *
         * @return {Boolean} True if the CRS is supported by proj4js
         */
        GeoJSONCRS.prototype.isCRSSupported = function () {
            try{
                Proj4(this._projectionString, GeoJSONConstants.EPSG4326_CRS);
            }
            catch(e){
                Logger.log(Logger.LEVEL_WARNING,
                    "Unknown GeoJSON coordinate reference system (" + e + "): " + this._properties.name);
                return false;
            }
            return true;
        };

        // Get GeoJSON Linked CRS string using XMLHttpRequest. Internal use only.
        GeoJSONCRS.prototype.getLinkedCRSString = function (url, crsCallback) {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", url, true);
            xhr.responseType = 'text';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        this._projectionString = xhr.response;
                        crsCallback();
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "GeoJSON Linked CRS retrieval failed (" + xhr.statusText + "): " + url);
                    }
                }
            }).bind(this);

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoJSON Linked CRS retrieval failed: " + url);
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoJSON Linked CRS retrieval timed out: " + url);
            };

            xhr.send(null);
        };

        // Set CRS string. Internal use only
        GeoJSONCRS.prototype.setCRSString = function (crsCallback) {
            if (this.isNamed()){
                this._projectionString = this._properties.name;
                crsCallback();
            }
            else if (this.isLinked()){
                this.getLinkedCRSString(this._properties.href, crsCallback);
            }
        };

        return GeoJSONCRS;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONFeature
 */
define('formats/geojson/GeoJSONFeature',['../../error/ArgumentError',
        './GeoJSONConstants',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONConstants,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON Feature object. Applications typically do not call this constructor. It is called by
         * {@link GeoJSON} as GeoJSON is read.
         * @alias GeoJSONFeature
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON Feature Object.
         * A feature object must have a member with the name "geometry".
         * The value of the geometry member is a geometry object or a JSON null value.
         * A feature object must have a member with the name "properties".
         * The value of the properties member is an object (any JSON object or a JSON null value).
         * If a feature has a commonly used identifier, that identifier should be included as a member of the
         * feature object with the name "id".
         * To include information on the coordinate range for features, a GeoJSON object may have a member
         * named "bbox".
         * @param {Object} geometry An object containing the value of GeoJSON geometry member.
         * @param {Object} properties An object containing the value of GeoJSON properties member.
         * @param {Object} id An object containing the value of GeoJSON Feature id member.
         * @param {Object} bbox An object containing the value of GeoJSON Feature bbox member.
         * @throws {ArgumentError} If the specified mandatory geometries or properties are null or undefined.
         */
        var GeoJSONFeature = function (geometry, properties, id, bbox) {

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONFeature", "constructor",
                        "missingGeometry"));
            }

            if (!geometry[GeoJSONConstants.FIELD_TYPE]) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONFeature", "constructor",
                        "missingFeatureGeometryType"));
            }

            if (!properties) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONFeature", "constructor",
                        "missingProperties"));
            }

            // Documented in defineProperties below.
            this._geometry = geometry;

            // Documented in defineProperties below.
            this._properties =  properties;

            // Documented in defineProperties below.
            this._id = id;

            // Documented in defineProperties below.
            this._bbox = bbox;
        };

        Object.defineProperties(GeoJSONFeature.prototype, {
            /**
             * The GeoJSON Feature geometry as specified to this GeoJSONFeature's constructor.
             * @memberof GeoJSONFeature.prototype
             * @type {Object}
             * @readonly
             */
            geometry: {
                get: function () {
                    return this._geometry;
                }
            },
            /**
             * The GeoJSON Feature properties as specified to this GeoJSONFeature's constructor.
             * @memberof GeoJSONFeature.prototype
             * @type {Object}
             * @readonly
             */
            properties: {
                get: function () {
                    return this._properties;
                }
            },
            /**
             * The GeoJSON Feature id as specified to this GeoJSONFeature's constructor.
             * @memberof GeoJSONFeature.prototype
             * @type {Object}
             * @readonly
             */
            id: {
                get: function () {
                    return this._id;
                }
            },
            /**
             * The GeoJSON Feature bbox member as specified to this GeoJSONFeature's constructor.
             * @memberof GeoJSONFeature.prototype
             * @type {Object}
             * @readonly
             */
            bbox: {
                get: function () {
                    return this._bbox;
                }
            }
        });

        return GeoJSONFeature;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONFeatureCollection
 */
define('formats/geojson/GeoJSONFeatureCollection',['../../error/ArgumentError',
        './GeoJSONConstants',
        '../../util/Logger'
    ],
    function (ArgumentError,
              GeoJSONConstants,
              Logger) {
        "use strict";

        /**
         * Constructs a GeoJSON FeatureCollection object. Applications typically do not call this constructor.
         * It is called by {@link GeoJSON} as GeoJSON is read.
         * @alias GeoJSONFeatureCollection
         * @constructor
         * @classdesc Contains the data associated with a GeoJSON Feature Collection Object.
         * An object of type "FeatureCollection" must have a member with the name "features".
         * The value corresponding to "features" is an array. Each element in the array is a feature object as
         * defined in {@link GeoJSONFeature}.
         * To include information on the coordinate range for feature collections, a GeoJSON object may have a member
         * named "bbox".
         * @param {Object} features An object containing the data associated with the GeoJSON FeatureCollection
         * features.
         * @param {Object} bbox An object containing the value of GeoJSON FeatureCollection bbox member.
         * @throws {ArgumentError} If the specified mandatory features parameter is null or undefined.
         */
        var GeoJSONFeatureCollection = function (features,  bbox) {

            if (!features) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONFeatureCollection", "constructor",
                        "missingFeatures"));
            }

            if (Object.prototype.toString.call(features) !== '[object Array]') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSONFeatureCollection", "constructor",
                        "invalidFeatures"));
            }

            // Documented in defineProperties below.
            this._features = features;

            // Documented in defineProperties below.
            this._bbox = bbox;
        };

        Object.defineProperties(GeoJSONFeatureCollection.prototype, {
            /**
             * The GeoJSON Feature Collection features as specified to this GeoJSONFeatureCollection's constructor.
             * @memberof GeoJSONFeatureCollection.prototype
             * @type {Object}
             * @readonly
             */
            features: {
                get: function () {
                    return this._features;
                }
            },
            /**
             * The GeoJSON Collection bbox member as specified to this GeoJSONFeatureCollection's constructor.
             * @memberof GeoJSONFeatureCollection.prototype
             * @type {Object}
             * @readonly
             */
            bbox: {
                get: function () {
                    return this._bbox;
                }
            }
        });

        return GeoJSONFeatureCollection;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PlacemarkAttributes
 */
define('shapes/PlacemarkAttributes',[
        '../util/Color',
        '../util/Font',
        '../util/Offset',
        '../shapes/ShapeAttributes',
        '../shapes/TextAttributes'
    ],
    function (Color,
              Font,
              Offset,
              ShapeAttributes,
              TextAttributes) {
        "use strict";

        /**
         * Constructs a placemark attributes bundle.
         * The defaults indicate a placemark displayed as a white 1x1 pixel square centered on the placemark's
         * geographic position.
         * @alias PlacemarkAttributes
         * @constructor
         * @classdesc Holds attributes applied to {@link Placemark} shapes.
         *
         * @param {PlacemarkAttributes} attributes Attributes to initialize this attributes instance to. May be null,
         * in which case the new instance contains default attributes.
         */
        var PlacemarkAttributes = function (attributes) {
            // These are all documented with their property accessors below.
            this._imageColor = attributes ? attributes._imageColor : new Color(1, 1, 1, 1);
            this._imageOffset = attributes ? attributes._imageOffset
                : new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5);
            this._imageScale = attributes ? attributes._imageScale : 1;
            this._imageSource = attributes ? attributes._imageSource : null;
            this._depthTest = attributes ? attributes._depthTest : true;
            this._labelAttributes = attributes ? attributes._labelAttributes : new TextAttributes(null);
            this._drawLeaderLine = attributes ? attributes._drawLeaderLine : false;
            this._leaderLineAttributes = attributes ? attributes._leaderLineAttributes : new ShapeAttributes(null);

            /**
             * Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
             * attributes change. The state key will be automatically computed the next time it's requested. This flag
             * will be set to false when that occurs.
             * @type {Boolean}
             * @protected
             */
            this.stateKeyInvalid = true;
        };

        /**
         * Computes the state key for this attributes object. Subclasses that define additional attributes must
         * override this method, call it from that method, and append the state of their attributes to its
         * return value.
         * @returns {String} The state key for this object.
         * @protected
         */
        PlacemarkAttributes.prototype.computeStateKey = function () {
            return "ic " + this._imageColor.toHexString(true)
                + " io " + this._imageOffset.toString()
                + " is " + this._imageScale
                + " ip " + this._imageSource
                + " dt " + this._depthTest
                + " la " + this._labelAttributes.stateKey
                + " dll " + this._drawLeaderLine
                + " lla " + this._leaderLineAttributes.stateKey;
        };

        Object.defineProperties(PlacemarkAttributes.prototype, {
            /**
             * A string identifying the state of this attributes object. The string encodes the current values of all
             * this object's properties. It's typically used to validate cached representations of shapes associated
             * with this attributes object.
             * @type {String}
             * @readonly
             * @memberof PlacemarkAttributes.prototype
             */
            stateKey: {
                get: function () {
                    if (this.stateKeyInvalid) {
                        this._stateKey = this.computeStateKey();
                        this.stateKeyInvalid = false;
                    }
                    return this._stateKey;
                }
            },

            /**
             * The image color.
             * When this attribute bundle has a valid image path the placemark's image is composed with this image
             * color to achieve the final placemark color. Otherwise the placemark is drawn in this color. The color
             * white, the default, causes the image to be drawn in its native colors.
             * @type {Color}
             * @default White (1, 1, 1, 1)
             * @memberof PlacemarkAttributes.prototype
             */
            imageColor: {
                get: function () {
                    return this._imageColor;
                },
                set: function (value) {
                    this._imageColor = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the location within the placemark's image to align with the placemark's geographic position.
             * May be null, in which case the image's bottom-left corner is placed at the geographic position.
             * @type {Offset}
             * @default 0.5, 0.5, both fractional (Centers the image on the geographic position.)
             * @memberof PlacemarkAttributes.prototype
             */
            imageOffset: {
                get: function () {
                    return this._imageOffset;
                },
                set: function (value) {
                    this._imageOffset = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the amount to scale the placemark's image.
             * When this attribute bundle has a valid image path the scale is applied to the image's dimensions. Otherwise the
             * scale indicates the dimensions in pixels of a square drawn at the placemark's geographic position.
             * A scale of 0 causes the placemark to disappear; however, the placemark's label, if any, is still drawn.
             * @type {Number}
             * @default 1
             * @memberof PlacemarkAttributes.prototype
             */
            imageScale: {
                get: function () {
                    return this._imageScale;
                },
                set: function (value) {
                    this._imageScale = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * The image source of the placemark's image. May be either a string giving the URL of the image, or an
             * {@link ImageSource} object identifying an Image created dynamically.
             * If null, the placemark is drawn as a square whose width and height are
             * the value of this attribute object's [imageScale]{@link PlacemarkAttributes#imageScale} property.
             * @type {String|ImageSource}
             * @default null
             * @memberof PlacemarkAttributes.prototype
             */
            imageSource: {
                get: function () {
                    return this._imageSource;
                },
                set: function (value) {
                    this._imageSource = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether the placemark should be depth-tested against other objects in the scene. If true,
             * the placemark may be occluded by terrain and other objects in certain viewing situations. If false,
             * the placemark will not be occluded by terrain and other objects. If this value is true, the placemark's
             * label, if any, has an independent depth-test control.
             * See [PlacemarkAttributes.labelAttributes]{@link PlacemarkAttributes#labelAttributes}
             * and [TextAttributes.depthTest]{@link TextAttributes#depthTest}.
             * @type {Boolean}
             * @default true
             * @memberof PlacemarkAttributes.prototype
             */
            depthTest: {
                get: function () {
                    return this._depthTest;
                },
                set: function (value) {
                    this._depthTest = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates the attributes to apply to the placemark's label, if any. If null, the placemark's label is
             * not drawn.
             * @type {TextAttributes}
             * @default The defaults of {@link TextAttributes}.
             * @memberof PlacemarkAttributes.prototype
             */
            labelAttributes: {
                get: function () {
                    return this._labelAttributes;
                },
                set: function (value) {
                    this._labelAttributes = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * Indicates whether to draw a line from the placemark's geographic position to the ground.
             * @type {Boolean}
             * @default false
             * @memberof PlacemarkAttributes.prototype
             */
            drawLeaderLine: {
                get: function () {
                    return this._drawLeaderLine;
                },
                set: function (value) {
                    this._drawLeaderLine = value;
                    this.stateKeyInvalid = true;
                }
            },

            /**
             * The attributes to apply to the leader line if it's drawn. If null, the placemark's leader line is
             * not drawn.
             * @type {ShapeAttributes}
             * @default The defaults of {@link ShapeAttributes}
             * @memberof PlacemarkAttributes.prototype
             */
            leaderLineAttributes: {
                get: function () {
                    return this._leaderLineAttributes;
                },
                set: function (value) {
                    this._leaderLineAttributes = value;
                    this.stateKeyInvalid = true;
                }
            }
        });

        return PlacemarkAttributes;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Placemark
 */
define('shapes/Placemark',[
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../util/Color',
        '../util/Font',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../shapes/PlacemarkAttributes',
        '../render/Renderable',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (ArgumentError,
              BasicTextureProgram,
              Color,
              Font,
              Logger,
              Matrix,
              PickedObject,
              PlacemarkAttributes,
              Renderable,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a placemark.
         * @alias Placemark
         * @constructor
         * @augments Renderable
         * @classdesc Represents a Placemark shape. A placemark displays an image, a label and a leader line connecting
         * the placemark's geographic position to the ground. All three of these items are optional. By default, the
         * leader line is not pickable. See [enableLeaderLinePicking]{@link Placemark#enableLeaderLinePicking}.
         * <p>
         * Placemarks may be drawn with either an image or as single-color square with a specified size. When the
         * placemark attributes indicate a valid image, the placemark's image is drawn as a rectangle in the
         * image's original dimensions, scaled by the image scale attribute. Otherwise, the placemark is drawn as a
         * square with width and height equal to the value of the image scale attribute, in pixels, and color equal
         * to the image color attribute.
         * <p>
         * By default, placemarks participate in decluttering with a [declutterGroupID]{@link Placemark#declutterGroup}
         * of 2. Only placemark labels are decluttered relative to other placemark labels. The placemarks themselves
         * are optionally scaled with eye distance to achieve decluttering of the placemark as a whole.
         * See [eyeDistanceScaling]{@link Placemark#eyeDistanceScaling}.
         * @param {Position} position The placemark's geographic position.
         * @param {Boolean} eyeDistanceScaling Indicates whether the size of this placemark scales with eye distance.
         * See [eyeDistanceScalingThreshold]{@link Placemark#eyeDistanceScalingThreshold} and
         * [eyeDistanceScalingLabelThreshold]{@link Placemark#eyeDistanceScalingLabelThreshold}.
         * @param {PlacemarkAttributes} attributes The attributes to associate with this placemark. May be null,
         * in which case default attributes are associated.
         * @throws {ArgumentError} If the specified position is null or undefined.
         */
        var Placemark = function (position, eyeDistanceScaling, attributes) {
            if (!position) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Placemark", "constructor", "missingPosition"));
            }

            Renderable.call(this);

            /**
             * The placemark's attributes. If null and this placemark is not highlighted, this placemark is not
             * drawn.
             * @type {PlacemarkAttributes}
             * @default see [PlacemarkAttributes]{@link PlacemarkAttributes}
             */
            this.attributes = attributes ? attributes : new PlacemarkAttributes(null);

            /**
             * The attributes used when this placemark's highlighted flag is true. If null and the
             * highlighted flag is true, this placemark's normal attributes are used. If they, too, are null, this
             * placemark is not drawn.
             * @type {PlacemarkAttributes}
             * @default null
             */
            this.highlightAttributes = null;

            /**
             * Indicates whether this placemark uses its highlight attributes rather than its normal attributes.
             * @type {Boolean}
             * @default false
             */
            this.highlighted = false;

            /**
             * This placemark's geographic position.
             * @type {Position}
             */
            this.position = position;

            /**
             * Indicates whether this placemark's size is reduced at higher eye distances. If true, this placemark's
             * size is scaled inversely proportional to the eye distance if the eye distance is greater than the
             * value of the [eyeDistanceScalingThreshold]{@link Placemark#eyeDistanceScalingThreshold} property.
             * When the eye distance is below the threshold, this placemark is scaled only according to the
             * [imageScale]{@link PlacemarkAttributes#imageScale}.
             * @type {Boolean}
             */
            this.eyeDistanceScaling = eyeDistanceScaling;

            /**
             * The eye distance above which to reduce the size of this placemark, in meters. If
             * [eyeDistanceScaling]{@link Placemark#eyeDistanceScaling} is true, this placemark's image, label and leader
             * line sizes are reduced as the eye distance increases beyond this threshold.
             * @type {Number}
             * @default 1e6 (meters)
             */
            this.eyeDistanceScalingThreshold = 1e6;

            /**
             * The eye altitude above which this placemark's label is not displayed.
             * @type {number}
             */
            this.eyeDistanceScalingLabelThreshold = 1.5 * this.eyeDistanceScalingThreshold;

            /**
             * This placemark's textual label. If null, no label is drawn.
             * @type {String}
             * @default null
             */
            this.label = null;

            /**
             * This placemark's altitude mode. May be one of
             * <ul>
             *  <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
             *  <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
             *  <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
             * </ul>
             * @default WorldWind.ABSOLUTE
             */
            this.altitudeMode = WorldWind.ABSOLUTE;

            /**
             * Indicates whether this placemark has visual priority over other shapes in the scene.
             * @type {Boolean}
             * @default false
             */
            this.alwaysOnTop = false;

            /**
             * Indicates whether this placemark's leader line, if any, is pickable.
             * @type {Boolean}
             * @default false
             */
            this.enableLeaderLinePicking = false;

            /**
             * Indicates whether this placemark's image should be re-retrieved even if it has already been retrieved.
             * Set this property to true when the image has changed but has the same image path.
             * The property is set to false when the image is re-retrieved.
             * @type {Boolean}
             */
            this.updateImage = true;

            /**
             * Indicates the group ID of the declutter group to include this Text shape. If non-zero, this shape
             * is decluttered relative to all other shapes within its group.
             * @type {Number}
             * @default 2
             */
            this.declutterGroup = 2;

            /**
             * This shape's target visibility, a value between 0 and 1. During ordered rendering this shape modifies its
             * [current visibility]{@link Text#currentVisibility} towards its target visibility at the rate
             * specified by the draw context's [fade time]{@link DrawContext#fadeTime} property. The target
             * visibility and current visibility are used to control the fading in and out of this shape.
             * @type {Number}
             * @default 1
             */
            this.targetVisibility = 1;

            /**
             * This shape's current visibility, a value between 0 and 1. This property scales the shape's effective
             * opacity. It is incremented or decremented each frame according to the draw context's
             * [fade time]{@link DrawContext#fadeTime} property in order to achieve this shape's current
             * [target visibility]{@link Text#targetVisibility}. This current visibility and target visibility are
             * used to control the fading in and out of this shape.
             * @type {Number}
             * @default 1
             * @readonly
             */
            this.currentVisibility = 1;

            /**
             * The amount of rotation to apply to the image, measured in degrees clockwise and relative to this
             * placemark's [imageRotationReference]{@link Placemark#imageRotationReference}.
             * @type {Number}
             * @default 0
             */
            this.imageRotation = 0;

            /**
             * The amount of tilt to apply to the image, measured in degrees away from the eye point and relative
             * to this placemark's [imageTiltReference]{@link Placemark#imageTiltReference}. While any positive or
             * negative number may be specified, values outside the range [0. 90] cause some or all of the image to
             * be clipped.
             * @type {Number}
             * @default 0
             */
            this.imageTilt = 0;

            /**
             * Indicates whether to apply this placemark's image rotation relative to the screen or the globe.
             * If WorldWind.RELATIVE_TO_SCREEN, this placemark's image is rotated in the plane of the screen and
             * its orientation relative to the globe changes as the view changes.
             * If WorldWind.RELATIVE_TO_GLOBE, this placemark's image is rotated in a plane tangent to the globe
             * at this placemark's position and retains its orientation relative to the globe.
             * @type {String}
             * @default WorldWind.RELATIVE_TO_SCREEN
             */
            this.imageRotationReference = WorldWind.RELATIVE_TO_SCREEN;

            /**
             * Indicates whether to apply this placemark's image tilt relative to the screen or the globe.
             * If WorldWind.RELATIVE_TO_SCREEN, this placemark's image is tilted inwards (for positive tilts)
             * relative to the plane of the screen, and its orientation relative to the globe changes as the view
             * changes. If WorldWind.RELATIVE_TO_GLOBE, this placemark's image is tilted towards the globe's surface,
             * and retains its orientation relative to the surface.
             * @type {string}
             * @default WorldWind.RELATIVE_TO_SCREEN
             */
            this.imageTiltReference = WorldWind.RELATIVE_TO_SCREEN;

            // Internal use only. Intentionally not documented.
            this.activeAttributes = null;

            // Internal use only. Intentionally not documented.
            this.activeTexture = null;

            // Internal use only. Intentionally not documented.
            this.labelTexture = null;

            // Internal use only. Intentionally not documented.
            this.placePoint = new Vec3(0, 0, 0); // Cartesian point corresponding to this placemark's geographic position

            // Internal use only. Intentionally not documented.
            this.groundPoint = new Vec3(0, 0, 0); // Cartesian point corresponding to ground position below this placemark

            // Internal use only. Intentionally not documented.
            this.imageTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.labelTransform = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.texCoordMatrix = Matrix.fromIdentity();

            // Internal use only. Intentionally not documented.
            this.imageBounds = null;

            // Internal use only. Intentionally not documented.
            this.layer = null;

            // Internal use only. Intentionally not documented.
            this.depthOffset = -0.003;
        };

        // Internal use only. Intentionally not documented.
        Placemark.screenPoint = new Vec3(0, 0, 0); // scratch variable
        Placemark.matrix = Matrix.fromIdentity(); // scratch variable
        Placemark.scratchPoint = new Vec3(0, 0, 0); // scratch variable

        Placemark.prototype = Object.create(Renderable.prototype);

        Object.defineProperties(Placemark.prototype, {
            /**
             * Indicates the screen coordinate bounds of this shape during ordered rendering.
             * @type {Rectangle}
             * @readonly
             * @memberof Placemark.prototype
             */
            screenBounds: {
                get: function () {
                    return this.labelBounds;
                }
            }
        });

        /**
         * Copies the contents of a specified placemark to this placemark.
         * @param {Placemark} that The placemark to copy.
         */
        Placemark.prototype.copy = function (that) {
            this.position = that.position;
            this.attributes = that.attributes;
            this.highlightAttributes = that.highlightAttributes;
            this.highlighted = that.highlighted;
            this.enabled = that.enabled;
            this.label = that.label;
            this.altitudeMode = that.altitudeMode;
            this.pickDelegate = that.pickDelegate;
            this.alwaysOnTop = that.alwaysOnTop;
            this.depthOffset = that.depthOffset;
            this.targetVisibility = that.targetVisibility;
            this.currentVisibility = that.currentVisibility;
            this.imageRotation = that.imageRotation;
            this.imageTilt = that.imageTilt;
            this.imageRotationReference = that.imageRotationReference;
            this.imageTiltReference = that.imageTiltReference;

            return this;
        };

        /**
         * Creates a new placemark that is a copy of this placemark.
         * @returns {Placemark} The new placemark.
         */
        Placemark.prototype.clone = function () {
            var clone = new Placemark(this.position);

            clone.copy(this);
            clone.pickDelegate = this.pickDelegate ? this.pickDelegate : this;

            return clone;
        };

        /**
         * Renders this placemark. This method is typically not called by applications but is called by
         * {@link RenderableLayer} during rendering. For this shape this method creates and
         * enques an ordered renderable with the draw context and does not actually draw the placemark.
         * @param {DrawContext} dc The current draw context.
         */
        Placemark.prototype.render = function (dc) {
            if (!this.enabled) {
                return;
            }

            if (!dc.accumulateOrderedRenderables) {
                return;
            }

            if (dc.globe.projectionLimits
                && !dc.globe.projectionLimits.containsLocation(this.position.latitude, this.position.longitude)) {
                return;
            }

            // Create an ordered renderable for this placemark. If one has already been created this frame then we're
            // in 2D-continuous mode and another needs to be created for one of the alternate globe offsets.
            var orderedPlacemark;
            if (this.lastFrameTime !== dc.timestamp) {
                orderedPlacemark = this.makeOrderedRenderable(dc);
            } else {
                var placemarkCopy = this.clone();
                orderedPlacemark = placemarkCopy.makeOrderedRenderable(dc);
            }

            if (!orderedPlacemark) {
                return;
            }

            if (!orderedPlacemark.isVisible(dc)) {
                return;
            }

            orderedPlacemark.layer = dc.currentLayer;

            this.lastFrameTime = dc.timestamp;
            dc.addOrderedRenderable(orderedPlacemark);
        };

        /**
         * Draws this shape as an ordered renderable. Applications do not call this function. It is called by
         * [WorldWindow]{@link WorldWindow} during rendering.
         * @param {DrawContext} dc The current draw context.
         */
        Placemark.prototype.renderOrdered = function (dc) {
            this.drawOrderedPlacemark(dc);

            if (dc.pickingMode) {
                var po = new PickedObject(this.pickColor.clone(), this.pickDelegate ? this.pickDelegate : this,
                    this.position, this.layer, false);

                if (dc.pickPoint && this.mustDrawLabel()) {
                    if (this.labelBounds.containsPoint(
                            dc.navigatorState.convertPointToViewport(dc.pickPoint, Placemark.scratchPoint))) {
                        po.labelPicked = true;
                    }
                }
                dc.resolvePick(po);
            }
        };

        /* INTENTIONALLY NOT DOCUMENTED
         * Creates an ordered renderable for this shape.
         * @protected
         * @param {DrawContext} dc The current draw context.
         * @returns {OrderedRenderable} The ordered renderable. May be null, in which case an ordered renderable
         * cannot be created or should not be created at the time this method is called.
         */
        Placemark.prototype.makeOrderedRenderable = function (dc) {
            var w, h, s,
                offset;

            this.determineActiveAttributes(dc);
            if (!this.activeAttributes) {
                return null;
            }

            // Compute the placemark's model point and corresponding distance to the eye point. If the placemark's
            // position is terrain-dependent but off the terrain, then compute it ABSOLUTE so that we have a point for
            // the placemark and are thus able to draw it. Otherwise its image and label portion that are potentially
            // over the terrain won't get drawn, and would disappear as soon as there is no terrain at the placemark's
            // position. This can occur at the window edges.
            dc.surfacePointForMode(this.position.latitude, this.position.longitude, this.position.altitude,
                this.altitudeMode, this.placePoint);

            this.eyeDistance = this.alwaysOnTop ? 0 : dc.navigatorState.eyePoint.distanceTo(this.placePoint);

            if (this.mustDrawLeaderLine(dc)) {
                dc.surfacePointForMode(this.position.latitude, this.position.longitude, 0,
                    this.altitudeMode, this.groundPoint);
            }

            // Compute the placemark's screen point in the OpenGL coordinate system of the WorldWindow by projecting its model
            // coordinate point onto the viewport. Apply a depth offset in order to cause the placemark to appear above nearby
            // terrain. When a placemark is displayed near the terrain portions of its geometry are often behind the terrain,
            // yet as a screen element the placemark is expected to be visible. We adjust its depth values rather than moving
            // the placemark itself to avoid obscuring its actual position.
            if (!dc.navigatorState.projectWithDepth(this.placePoint, this.depthOffset, Placemark.screenPoint)) {
                return null;
            }

            var visibilityScale = this.eyeDistanceScaling ?
                Math.max(0.0, Math.min(1, this.eyeDistanceScalingThreshold / this.eyeDistance)) : 1;

            // Compute the placemark's transform matrix and texture coordinate matrix according to its screen point, image size,
            // image offset and image scale. The image offset is defined with its origin at the image's bottom-left corner and
            // axes that extend up and to the right from the origin point. When the placemark has no active texture the image
            // scale defines the image size and no other scaling is applied.
            if (this.activeTexture) {
                w = this.activeTexture.originalImageWidth;
                h = this.activeTexture.originalImageHeight;
                s = this.activeAttributes.imageScale * visibilityScale;
                offset = this.activeAttributes.imageOffset.offsetForSize(w, h);

                this.imageTransform.setTranslation(
                    Placemark.screenPoint[0] - offset[0] * s,
                    Placemark.screenPoint[1] - offset[1] * s,
                    Placemark.screenPoint[2]);

                this.imageTransform.setScale(w * s, h * s, 1);
            } else {
                s = this.activeAttributes.imageScale * visibilityScale;
                offset = this.activeAttributes.imageOffset.offsetForSize(s, s);

                this.imageTransform.setTranslation(
                    Placemark.screenPoint[0] - offset[0],
                    Placemark.screenPoint[1] - offset[1],
                    Placemark.screenPoint[2]);

                this.imageTransform.setScale(s, s, 1);
            }

            this.imageBounds = WWMath.boundingRectForUnitQuad(this.imageTransform);

            // If there's a label, perform these same operations for the label texture, creating that texture if it
            // doesn't already exist.

            if (this.mustDrawLabel()) {
                var labelFont = this.activeAttributes.labelAttributes.font,
                    labelKey = this.label + labelFont.toString();

                this.labelTexture = dc.gpuResourceCache.resourceForKey(labelKey);
                if (!this.labelTexture) {
                    this.labelTexture = dc.textSupport.createTexture(dc, this.label, labelFont, true);
                    dc.gpuResourceCache.putResource(labelKey, this.labelTexture, this.labelTexture.size);
                }

                w = this.labelTexture.imageWidth;
                h = this.labelTexture.imageHeight;
                s = this.activeAttributes.labelAttributes.scale * visibilityScale;
                offset = this.activeAttributes.labelAttributes.offset.offsetForSize(w, h);

                this.labelTransform.setTranslation(
                    Placemark.screenPoint[0] - offset[0] * s,
                    Placemark.screenPoint[1] - offset[1] * s,
                    Placemark.screenPoint[2]);

                this.labelTransform.setScale(w * s, h * s, 1);

                this.labelBounds = WWMath.boundingRectForUnitQuad(this.labelTransform);
            }

            return this;
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.determineActiveAttributes = function (dc) {
            if (this.highlighted && this.highlightAttributes) {
                this.activeAttributes = this.highlightAttributes;
            } else {
                this.activeAttributes = this.attributes;
            }

            if (this.activeAttributes && this.activeAttributes.imageSource) {
                this.activeTexture = dc.gpuResourceCache.resourceForKey(this.activeAttributes.imageSource);

                if (!this.activeTexture || this.updateImage) {
                    this.activeTexture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext,
                        this.activeAttributes.imageSource);
                    this.updateImage = false;
                }
            }
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.isVisible = function (dc) {
            if (dc.pickingMode) {
                return dc.pickRectangle && (this.imageBounds.intersects(dc.pickRectangle)
                    || (this.mustDrawLabel() && this.labelBounds.intersects(dc.pickRectangle))
                    || (this.mustDrawLeaderLine(dc)
                    && dc.pickFrustum.intersectsSegment(this.groundPoint, this.placePoint)));
            } else {
                return this.imageBounds.intersects(dc.navigatorState.viewport)
                    || (this.mustDrawLabel() && this.labelBounds.intersects(dc.navigatorState.viewport))
                    || (this.mustDrawLeaderLine(dc)
                    && dc.navigatorState.frustumInModelCoordinates.intersectsSegment(this.groundPoint, this.placePoint));
            }
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.drawOrderedPlacemark = function (dc) {
            this.beginDrawing(dc);

            try {
                this.doDrawOrderedPlacemark(dc);
                if (!dc.pickingMode) {
                    this.drawBatchOrderedPlacemarks(dc);
                }
            } finally {
                this.endDrawing(dc);
            }
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.drawBatchOrderedPlacemarks = function (dc) {
            // Draw any subsequent placemarks in the ordered renderable queue, removing each from the queue as it's
            // processed. This avoids the overhead of setting up and tearing down OpenGL state for each placemark.

            var or;

            while ((or = dc.peekOrderedRenderable()) && or.doDrawOrderedPlacemark) {
                dc.popOrderedRenderable(); // remove it from the queue

                try {
                    or.doDrawOrderedPlacemark(dc)
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_WARNING, 'Placemark', 'drawBatchOrderedPlacemarks',
                        "Error occurred while rendering placemark using batching: " + e.message);
                }
                // Keep going. Render the rest of the ordered renderables.
            }
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program;

            dc.findAndBindProgram(BasicTextureProgram);

            // Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
            // Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
            // that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
            // buffer for texture coords.
            program = dc.currentProgram;
            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
            gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.vertexPointLocation);
            gl.enableVertexAttribArray(program.vertexTexCoordLocation);

            // Tell the program which texture unit to use.
            program.loadTextureUnit(gl, gl.TEXTURE0);
            program.loadModulateColor(gl, dc.pickingMode);
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram;

            // Clear the vertex attribute state.
            gl.disableVertexAttribArray(program.vertexPointLocation);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            // Clear GL bindings.
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindTexture(gl.TEXTURE_2D, null);
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.doDrawOrderedPlacemark = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                depthTest = true,
                textureBound;

            if (dc.pickingMode) {
                this.pickColor = dc.uniquePickColor();
            }

            if (this.eyeDistanceScaling && (this.eyeDistance > this.eyeDistanceScalingLabelThreshold)) {
                // Target visibility is set to 0 to cause the label to be faded in or out. Nothing else
                // here uses target visibility.
                this.targetVisibility = 0;
            }

            // Compute the effective visibility. Use the current value if picking.
            if (!dc.pickingMode && this.mustDrawLabel()) {
                if (this.currentVisibility != this.targetVisibility) {
                    var visibilityDelta = (dc.timestamp - dc.previousRedrawTimestamp) / dc.fadeTime;
                    if (this.currentVisibility < this.targetVisibility) {
                        this.currentVisibility = Math.min(1, this.currentVisibility + visibilityDelta);
                    } else {
                        this.currentVisibility = Math.max(0, this.currentVisibility - visibilityDelta);
                    }
                    dc.redrawRequested = true;
                }
            }

            program.loadOpacity(gl, dc.pickingMode ? 1 : this.layer.opacity);

            // Draw the leader line first so that the image and label have visual priority.
            if (this.mustDrawLeaderLine(dc)) {
                if (!this.leaderLinePoints) {
                    this.leaderLinePoints = new Float32Array(6);
                }

                this.leaderLinePoints[0] = this.groundPoint[0]; // computed during makeOrderedRenderable
                this.leaderLinePoints[1] = this.groundPoint[1];
                this.leaderLinePoints[2] = this.groundPoint[2];
                this.leaderLinePoints[3] = this.placePoint[0]; // computed during makeOrderedRenderable
                this.leaderLinePoints[4] = this.placePoint[1];
                this.leaderLinePoints[5] = this.placePoint[2];

                if (!this.leaderLineCacheKey) {
                    this.leaderLineCacheKey = dc.gpuResourceCache.generateCacheKey();
                }

                var leaderLineVboId = dc.gpuResourceCache.resourceForKey(this.leaderLineCacheKey);
                if (!leaderLineVboId) {
                    leaderLineVboId = gl.createBuffer();
                    dc.gpuResourceCache.putResource(this.leaderLineCacheKey, leaderLineVboId,
                        this.leaderLinePoints.length * 4);
                }

                program.loadTextureEnabled(gl, false);
                program.loadColor(gl, dc.pickingMode ? this.pickColor :
                    this.activeAttributes.leaderLineAttributes.outlineColor);

                Placemark.matrix.copy(dc.navigatorState.modelviewProjection);
                program.loadModelviewProjection(gl, Placemark.matrix);

                if (!this.activeAttributes.leaderLineAttributes.depthTest) {
                    gl.disable(gl.DEPTH_TEST);
                }

                gl.lineWidth(this.activeAttributes.leaderLineAttributes.outlineWidth);

                gl.bindBuffer(gl.ARRAY_BUFFER, leaderLineVboId);
                gl.bufferData(gl.ARRAY_BUFFER, this.leaderLinePoints, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
                gl.drawArrays(gl.LINES, 0, 2);
            }

            // Turn off depth testing for the placemark image if requested. The placemark label and leader line have
            // their own depth-test controls.
            if (!this.activeAttributes.depthTest) {
                depthTest = false;
                gl.disable(gl.DEPTH_TEST);
            }

            // Suppress frame buffer writes for the placemark image and its label.
            // tag, 6/17/15: It's not clear why this call was here. It was carried over from WWJ.
            //gl.depthMask(false);

            gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);

            // Compute and specify the MVP matrix.
            Placemark.matrix.copy(dc.screenProjection);
            Placemark.matrix.multiplyMatrix(this.imageTransform);

            var actualRotation = this.imageRotationReference === WorldWind.RELATIVE_TO_GLOBE ?
                dc.navigatorState.heading - this.imageRotation : -this.imageRotation;
            Placemark.matrix.multiplyByTranslation(0.5, 0.5, 0);
            Placemark.matrix.multiplyByRotation(0, 0, 1, actualRotation);
            Placemark.matrix.multiplyByTranslation(-0.5, -0.5, 0);

            // Perform the tilt before applying the rotation so that the image tilts back from its base into
            // the view volume.
            var actualTilt = this.imageTiltReference === WorldWind.RELATIVE_TO_GLOBE ?
            dc.navigatorState.tilt + this.imageTilt : this.imageTilt;
            Placemark.matrix.multiplyByRotation(-1, 0, 0, actualTilt);

            program.loadModelviewProjection(gl, Placemark.matrix);

            // Enable texture for both normal display and for picking. If picking is enabled in the shader (set in
            // beginDrawing() above) then the texture's alpha component is still needed in order to modulate the
            // pick color to mask off transparent pixels.
            program.loadTextureEnabled(gl, true);

            if (dc.pickingMode) {
                program.loadColor(gl, this.pickColor);
            } else {
                program.loadColor(gl, this.activeAttributes.imageColor);
            }

            this.texCoordMatrix.setToIdentity();
            if (this.activeTexture) {
                this.texCoordMatrix.multiplyByTextureTransform(this.activeTexture);
            }
            program.loadTextureMatrix(gl, this.texCoordMatrix);

            if (this.activeTexture) {
                textureBound = this.activeTexture.bind(dc); // returns false if active texture is null or cannot be bound
                program.loadTextureEnabled(gl, textureBound);
            } else {
                program.loadTextureEnabled(gl, false);
            }

            // Draw the placemark's image quad.
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

            if (this.mustDrawLabel() && this.currentVisibility > 0) {
                program.loadOpacity(gl, dc.pickingMode ? 1 : this.layer.opacity * this.currentVisibility);

                Placemark.matrix.copy(dc.screenProjection);
                Placemark.matrix.multiplyMatrix(this.labelTransform);
                program.loadModelviewProjection(gl, Placemark.matrix);

                if (!dc.pickingMode && this.labelTexture) {
                    this.texCoordMatrix.setToIdentity();
                    this.texCoordMatrix.multiplyByTextureTransform(this.labelTexture);

                    program.loadTextureMatrix(gl, this.texCoordMatrix);
                    program.loadColor(gl, this.activeAttributes.labelAttributes.color);

                    textureBound = this.labelTexture.bind(dc);
                    program.loadTextureEnabled(gl, textureBound);
                } else {
                    program.loadTextureEnabled(gl, false);
                    program.loadColor(gl, this.pickColor);
                }

                if (this.activeAttributes.labelAttributes.depthTest) {
                    if (!depthTest) {
                        depthTest = true;
                        gl.enable(gl.DEPTH_TEST);
                    }
                } else {
                    depthTest = false;
                    gl.disable(gl.DEPTH_TEST);
                }

                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }

            if (!depthTest) {
                gl.enable(gl.DEPTH_TEST);
            }

            // tag, 6/17/15: See note on depthMask above in this function.
            //gl.depthMask(true);
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.mustDrawLabel = function () {
            return this.label && this.label.length > 0 && this.activeAttributes.labelAttributes;
        };

        // Internal. Intentionally not documented.
        Placemark.prototype.mustDrawLeaderLine = function (dc) {
            return this.activeAttributes.drawLeaderLine && this.activeAttributes.leaderLineAttributes
                && (!dc.pickingMode || this.enableLeaderLinePicking);
        };

        return Placemark;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Polygon
 */
define('shapes/Polygon',[
        '../shapes/AbstractShape',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../geom/BoundingBox',
        '../util/Color',
        '../util/ImageSource',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../geom/Position',
        '../shapes/ShapeAttributes',
        '../shapes/SurfacePolygon',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/libtess'
    ],
    function (AbstractShape,
              ArgumentError,
              BasicTextureProgram,
              BoundingBox,
              Color,
              ImageSource,
              Location,
              Logger,
              Matrix,
              PickedObject,
              Position,
              ShapeAttributes,
              SurfacePolygon,
              Vec2,
              Vec3,
              libtessDummy) {
        "use strict";

        /**
         * Constructs a Polygon.
         * @alias Polygon
         * @constructor
         * @augments AbstractShape
         * @classdesc Represents a 3D polygon. The polygon may be extruded to the ground to form a prism. It may have
         * multiple boundaries defining empty portions. See also {@link SurfacePolygon}.
         * <p>
         *     Altitudes within the polygon's positions are interpreted according to the polygon's altitude mode, which
         *     can be one of the following:
         * <ul>
         *     <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
         *     <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
         *     <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
         * </ul>
         * If the latter, the polygon positions' altitudes are ignored. (If the polygon should be draped onto the
         * terrain, you might want to use {@link SurfacePolygon} instead.)
         * <p>
         *     Polygons have separate attributes for normal display and highlighted display. They use the interior and
         *     outline attributes of {@link ShapeAttributes}. If those attributes identify an image, that image is
         *     applied to the polygon.
         * <p>
         *     A polygon displays as a vertical prism if its [extrude]{@link Polygon#extrude} property is true. A
         *     curtain is formed around its boundaries and extends from the polygon's edges to the ground.
         * <p>
         *     A polygon can be textured, including its extruded boundaries. The textures are specified via the
         *     [imageSource]{@link ShapeAttributes#imageSource} property of the polygon's attributes. If that
         *     property is a single string or {@link ImageSource}, then it identifies the image source for the
         *     polygon's texture. If that property is an array of strings, {@link ImageSource}s or a combination of
         *     those, then the first entry in the array specifies the polygon's image source and subsequent entries
         *     specify the image sources of the polygon's extruded boundaries. If the array contains two entries, the
         *     first is the polygon's image source and the second is the common image source for all extruded
         *     boundaries. If the array contains more than two entries, then the first entry is the polygon's image
         *     source and each subsequent entry is the image source for consecutive extruded boundary segments. A null
         *     value for any entry indicates that no texture is applied for the corresponding polygon or extruded edge
         *     segment. If fewer image sources are specified then there are boundary segments, the last image source
         *     specified is applied to the remaining segments. Texture coordinates for the polygon's texture are
         *     specified via this polygon's [textureCoordinates]{@link Polygon#textureCoordinates} property. Texture
         *     coordinates for extruded boundary segments are implicitly defined to fit the full texture to each
         *     boundary segment.
         * <p>
         *     When displayed on a 2D globe, this polygon displays as a {@link SurfacePolygon} if its
         *     [useSurfaceShapeFor2D]{@link AbstractShape#useSurfaceShapeFor2D} property is true.
         *
         * @param {Position[][] | Position[]} boundaries A two-dimensional array containing the polygon boundaries.
         * Each entry of the array specifies the vertices of one boundary.
         * This argument may also be a simple array of positions,
         * in which case the polygon is assumed to have only one boundary.
         * Each boundary is considered implicitly closed, so the last position of the boundary need not and should not
         * duplicate the first position of the boundary.
         * @param {ShapeAttributes} attributes The attributes to associate with this polygon. May be null, in which case
         * default attributes are associated.
         *
         * @throws {ArgumentError} If the specified boundaries array is null or undefined.
         */
        var Polygon = function (boundaries, attributes) {
            if (!boundaries) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Polygon", "constructor", "missingBoundaries"));
            }

            AbstractShape.call(this, attributes);

            if (boundaries.length > 0 && boundaries[0].latitude) {
                boundaries = [boundaries];
                this._boundariesSpecifiedSimply = true;
            }

            // Private. Documentation is with the defined property below and the constructor description above.
            this._boundaries = boundaries;

            this._textureCoordinates = null;

            this.referencePosition = this.determineReferencePosition(this._boundaries);

            this._extrude = false;

            this.scratchPoint = new Vec3(0, 0, 0); // scratch variable
        };

        Polygon.prototype = Object.create(AbstractShape.prototype);

        Object.defineProperties(Polygon.prototype, {
            /**
             * This polygon's boundaries. A two-dimensional array containing the polygon boundaries. Each entry of the
             * array specifies the vertices of one boundary. This property may also be a simple
             * array of positions, in which case the polygon is assumed to have only one boundary.
             * @type {Position[][] | Position[]}
             * @memberof Polygon.prototype
             */
            boundaries: {
                get: function () {
                    return this._boundariesSpecifiedSimply ? this._boundaries[0] : this._boundaries;
                },
                set: function (boundaries) {
                    if (!boundaries) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "Polygon", "boundaries", "missingBoundaries"));
                    }

                    if (boundaries.length > 0 && boundaries[0].latitude) {
                        boundaries = [boundaries];
                        this._boundariesSpecifiedSimply = true;
                    }

                    this._boundaries = boundaries;
                    this.referencePosition = this.determineReferencePosition(this._boundaries);
                    this.reset();
                }
            },

            /**
             * This polygon's texture coordinates if this polygon is to be textured. A texture coordinate must be
             * provided for each boundary position. The texture coordinates are specified as a two-dimensional array,
             * each entry of which specifies the texture coordinates for one boundary. Each texture coordinate is a
             * {@link Vec2} containing the s and t coordinates.
             * @type {Vec2[][]}
             * @default null
             * @memberof Polygon.prototype
             */
            textureCoordinates: {
                get: function () {
                    return this._textureCoordinates;
                },
                set: function (value) {
                    this._textureCoordinates = value;
                    this.reset();
                }
            },

            /**
             * Specifies whether to extrude this polygon to the ground by drawing a filled interior from the polygon
             * to the terrain. The filled interior uses this polygon's interior attributes.
             * @type {Boolean}
             * @default false
             * @memberof Polygon.prototype
             */
            extrude: {
                get: function () {
                    return this._extrude;
                },
                set: function (extrude) {
                    this._extrude = extrude;
                    this.reset();
                }
            }
        });

        // Intentionally not documented.
        Polygon.prototype.determineReferencePosition = function (boundaries) {
            // Assign the first position as the reference position.
            return (boundaries.length > 0 && boundaries[0].length > 2) ? boundaries[0][0] : null;
        };

        // Internal. Determines whether this shape's geometry must be re-computed.
        Polygon.prototype.mustGenerateGeometry = function (dc) {
            if (!this.currentData.boundaryPoints) {
                return true;
            }

            if (this.currentData.drawInterior !== this.activeAttributes.drawInterior) {
                return true;
            }

            if (this.altitudeMode === WorldWind.ABSOLUTE) {
                return false;
            }

            return this.currentData.isExpired
        };

        // Internal. Indicates whether this polygon should be textured.
        Polygon.prototype.hasCapTexture = function () {
            return this.textureCoordinates && this.capImageSource();
        };

        // Internal. Determines source of this polygon's cap texture. See the class description above for the policy.
        Polygon.prototype.capImageSource = function () {
            if (!this.activeAttributes.imageSource) {
                return null;
            }

            if ((typeof this.activeAttributes.imageSource) === "string"
                || this.activeAttributes.imageSource instanceof ImageSource) {
                return this.activeAttributes.imageSource;
            }

            if (Array.isArray(this.activeAttributes.imageSource)
                && this.activeAttributes.imageSource[0]
                && (typeof this.activeAttributes.imageSource[0] === "string"
                || this.activeAttributes.imageSource instanceof ImageSource)) {
                return this.activeAttributes.imageSource[0];
            }

            return null;
        };

        // Internal. Indicates whether this polygon has side textures defined.
        Polygon.prototype.hasSideTextures = function () {
            return this.activeAttributes.imageSource &&
                Array.isArray(this.activeAttributes.imageSource) &&
                this.activeAttributes.imageSource.length > 1;
        };

        // Internal. Determines the side texture for a specified side. See the class description above for the policy.
        Polygon.prototype.sideImageSource = function (side) {
            if (side === 0 || this.activeAttributes.imageSource.length === 2) {
                return this.activeAttributes.imageSource[1];
            }

            var numSideTextures = this.activeAttributes.imageSource.length - 1;
            side = Math.min(side + 1, numSideTextures);
            return this.activeAttributes.imageSource[side];
        };

        Polygon.prototype.createSurfaceShape = function () {
            return new SurfacePolygon(this.boundaries, null);
        };

        // Overridden from AbstractShape base class.
        Polygon.prototype.doMakeOrderedRenderable = function (dc) {
            // A null reference position is a signal that there are no boundaries to render.
            if (!this.referencePosition) {
                return null;
            }

            if (!this.activeAttributes.drawInterior && !this.activeAttributes.drawOutline) {
                return null;
            }

            // See if the current shape data can be re-used.
            if (!this.mustGenerateGeometry(dc)) {
                return this;
            }

            var currentData = this.currentData;

            // Set the transformation matrix to correspond to the reference position.
            var refPt = currentData.referencePoint;
            dc.surfacePointForMode(this.referencePosition.latitude, this.referencePosition.longitude,
                this.referencePosition.altitude, this._altitudeMode, refPt);
            currentData.transformationMatrix.setToTranslation(refPt[0], refPt[1], refPt[2]);

            // Close the boundaries.
            var fullBoundaries = [];
            for (var b = 0; b < this._boundaries.length; b++) {
                fullBoundaries[b] = this._boundaries[b].slice(0); // clones the array
                fullBoundaries[b].push(this._boundaries[b][0]); // appends the first position to the boundary
            }

            // Convert the geographic coordinates to the Cartesian coordinates that will be rendered.
            var boundaryPoints = this.computeBoundaryPoints(dc, fullBoundaries);

            // Tessellate the polygon if its interior is to be drawn.
            if (this.activeAttributes.drawInterior) {
                var capVertices = this.tessellatePolygon(dc, boundaryPoints);
                if (capVertices) {
                    // Must copy the vertices to a typed array. (Can't use typed array to begin with because its size
                    // is unknown prior to tessellation.)
                    currentData.capTriangles = new Float32Array(capVertices.length);
                    for (var i = 0, len = capVertices.length; i < len; i++) {
                        currentData.capTriangles[i] = capVertices[i];
                    }
                }
            }

            currentData.boundaryPoints = boundaryPoints;
            currentData.drawInterior = this.activeAttributes.drawInterior; // remember for validation
            this.resetExpiration(currentData);
            currentData.refreshBuffers = true; // causes VBOs to be reloaded

            // Create the extent from the Cartesian points. Those points are relative to this path's reference point,
            // so translate the computed extent to the reference point.
            if (!currentData.extent) {
                currentData.extent = new BoundingBox();
            }
            if (boundaryPoints.length === 1) {
                currentData.extent.setToPoints(boundaryPoints[0]);
            } else {
                var allPoints = [];
                for (b = 0; b < boundaryPoints.length; b++) {
                    for (var p = 0; p < boundaryPoints[b].length; p++) {
                        allPoints.push(boundaryPoints[b][p]);
                    }
                }
                currentData.extent.setToPoints(allPoints);
            }
            currentData.extent.translate(currentData.referencePoint);

            return this;
        };

        // Private. Intentionally not documented.
        Polygon.prototype.computeBoundaryPoints = function (dc, boundaries) {
            var eyeDistSquared = Number.MAX_VALUE,
                eyePoint = dc.navigatorState.eyePoint,
                boundaryPoints = [],
                stride = this._extrude ? 6 : 3,
                pt = new Vec3(0, 0, 0),
                numBoundaryPoints, pos, k, dSquared;

            for (var b = 0; b < boundaries.length; b++) {
                numBoundaryPoints = (this._extrude ? 2 : 1) * boundaries[b].length;
                boundaryPoints[b] = new Float32Array(numBoundaryPoints * 3);

                for (var i = 0, len = boundaries[b].length; i < len; i++) {
                    pos = boundaries[b][i];

                    dc.surfacePointForMode(pos.latitude, pos.longitude, pos.altitude, this.altitudeMode, pt);

                    dSquared = pt.distanceToSquared(eyePoint);
                    if (dSquared < eyeDistSquared) {
                        eyeDistSquared = dSquared;
                    }

                    pt.subtract(this.currentData.referencePoint);

                    k = stride * i;
                    boundaryPoints[b][k] = pt[0];
                    boundaryPoints[b][k + 1] = pt[1];
                    boundaryPoints[b][k + 2] = pt[2];

                    if (this._extrude) {
                        dc.surfacePointForMode(pos.latitude, pos.longitude, 0, WorldWind.CLAMP_TO_GROUND, pt);

                        dSquared = pt.distanceToSquared(eyePoint);
                        if (dSquared < eyeDistSquared) {
                            eyeDistSquared = dSquared;
                        }

                        pt.subtract(this.currentData.referencePoint);

                        boundaryPoints[b][k + 3] = pt[0];
                        boundaryPoints[b][k + 4] = pt[1];
                        boundaryPoints[b][k + 5] = pt[2];
                    }
                }
            }

            this.currentData.eyeDistance = 0;/*DO NOT COMMITMath.sqrt(eyeDistSquared);*/

            return boundaryPoints;
        };

        Polygon.prototype.tessellatePolygon = function (dc, boundaryPoints) {
            var triangles = [], // the output list of triangles
                error = 0,
                stride = this._extrude ? 6 : 3,
                includeTextureCoordinates = this.hasCapTexture(),
                coords, normal;

            if (!this.polygonTessellator) {
                this.polygonTessellator = new libtess.GluTesselator();

                this.polygonTessellator.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA,
                    function (data, tris) {
                        tris[tris.length] = data[0];
                        tris[tris.length] = data[1];
                        tris[tris.length] = data[2];

                        if (includeTextureCoordinates) {
                            tris[tris.length] = data[3];
                            tris[tris.length] = data[4];
                        }
                    });

                this.polygonTessellator.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE,
                    function (coords, data, weight) {
                        var newCoords = [coords[0], coords[1], coords[2]];

                        if (includeTextureCoordinates) {
                            for (var i = 3; i <= 4; i++) {
                                var value = 0;
                                for (var w = 0; w < 4; w++) {
                                    if (weight[w] > 0) {
                                        value += weight[w] * data[w][i];
                                    }
                                }

                                newCoords[i] = value;
                            }
                        }

                        return newCoords;
                    });

                this.polygonTessellator.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR,
                    function (errno) {
                        error = errno;
                        Logger.logMessage(Logger.LEVEL_WARNING, "Polygon", "tessellatePolygon",
                            "Tessellation error " + errno + ".");
                    });
            }

            // Compute a normal vector for the polygon.
            normal = Vec3.computeBufferNormal(boundaryPoints[0], stride);
            if (!normal) {
                normal = new Vec3(0, 0, 0);
                // The first boundary is colinear. Fall back to the surface normal.
                dc.globe.surfaceNormalAtLocation(this.referencePosition.latitude, this.referencePosition.longitude,
                    normal);
            }
            this.polygonTessellator.gluTessNormal(normal[0], normal[1], normal[2]);
            this.currentData.capNormal = normal;

            // Tessellate the polygon.
            this.polygonTessellator.gluTessBeginPolygon(triangles);
            for (var b = 0; b < boundaryPoints.length; b++) {
                var t = 0;
                this.polygonTessellator.gluTessBeginContour();
                var contour = boundaryPoints[b];
                for (var c = 0; c < contour.length; c += stride) {
                    coords = [contour[c], contour[c + 1], contour[c + 2]];
                    if (includeTextureCoordinates) {
                        if (t < this.textureCoordinates[b].length) {
                            coords[3] = this.textureCoordinates[b][t][0];
                            coords[4] = this.textureCoordinates[b][t][1];
                        } else {
                            coords[3] = this.textureCoordinates[b][0][0];
                            coords[4] = this.textureCoordinates[b][1][1];
                        }
                        ++t;
                    }
                    this.polygonTessellator.gluTessVertex(coords, coords);
                }
                this.polygonTessellator.gluTessEndContour();
            }
            this.polygonTessellator.gluTessEndPolygon();

            return error === 0 ? triangles : null;
        };

        // Private. Intentionally not documented.
        Polygon.prototype.mustDrawVerticals = function (dc) {
            return this._extrude
                && this.activeAttributes.drawOutline
                && this.activeAttributes.drawVerticals
                && this.altitudeMode !== WorldWind.CLAMP_TO_GROUND;
        };

        // Overridden from AbstractShape base class.
        Polygon.prototype.doRenderOrdered = function (dc) {
            var currentData = this.currentData,
                pickColor;

            if (dc.pickingMode) {
                pickColor = dc.uniquePickColor();
            }

            // Draw the cap if the interior requested and we were able to tessellate the polygon.
            if (this.activeAttributes.drawInterior && currentData.capTriangles && currentData.capTriangles.length > 0) {
                this.drawCap(dc, pickColor);
            }

            if (this._extrude && this.activeAttributes.drawInterior) {
                this.drawSides(dc, pickColor);
            }

            if (this.activeAttributes.drawOutline) {
                this.drawOutline(dc, pickColor);
            }

            currentData.refreshBuffers = false;

            if (dc.pickingMode) {
                var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, null,
                    dc.currentLayer, false);
                dc.resolvePick(po);
            }
        };

        Polygon.prototype.drawCap = function (dc, pickColor) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                currentData = this.currentData,
                refreshBuffers = currentData.refreshBuffers,
                hasCapTexture = !!this.hasCapTexture(),
                applyLighting = this.activeAttributes.applyLighting,
                numCapVertices = currentData.capTriangles.length / (hasCapTexture ? 5 : 3),
                vboId, opacity, color, stride, textureBound, capBuffer;

            // Assume no cap texture.
            program.loadTextureEnabled(gl, false);

            this.applyMvpMatrix(dc);

            if (!currentData.capVboCacheKey) {
                currentData.capVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(currentData.capVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(currentData.capVboCacheKey, vboId, currentData.capTriangles.length * 4);
                refreshBuffers = true;
            }

            gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            if (refreshBuffers) {
                capBuffer = applyLighting ? this.makeCapBufferWithNormals() : currentData.capTriangles;
                gl.bufferData(gl.ARRAY_BUFFER, capBuffer, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
            }

            color = this.activeAttributes.interiorColor;
            opacity = color.alpha * dc.currentLayer.opacity;
            // Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
            gl.depthMask(opacity >= 1 || dc.pickingMode);
            program.loadColor(gl, dc.pickingMode ? pickColor : color);
            program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);

            stride = 12 + (hasCapTexture ? 8 : 0) + (applyLighting ? 12 : 0);

            if (hasCapTexture && !dc.pickingMode) {
                this.activeTexture = dc.gpuResourceCache.resourceForKey(this.capImageSource());
                if (!this.activeTexture) {
                    this.activeTexture =
                        dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.capImageSource());
                }

                textureBound = this.activeTexture && this.activeTexture.bind(dc);
                if (textureBound) {
                    gl.enableVertexAttribArray(program.vertexTexCoordLocation);
                    gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, stride, 12);

                    this.scratchMatrix.setToIdentity();
                    this.scratchMatrix.multiplyByTextureTransform(this.activeTexture);

                    program.loadTextureEnabled(gl, true);
                    program.loadTextureUnit(gl, gl.TEXTURE0);
                    program.loadTextureMatrix(gl, this.scratchMatrix);
                    program.loadModulateColor(gl, dc.pickingMode);
                }
            }

            if (applyLighting && !dc.pickingMode) {
                program.loadApplyLighting(gl, true);
                gl.enableVertexAttribArray(program.normalVectorLocation);
                gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, stride, stride - 12);
            }

            gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
            gl.drawArrays(gl.TRIANGLES, 0, numCapVertices);
        };

        Polygon.prototype.makeCapBufferWithNormals = function () {
            var currentData = this.currentData,
                normal = currentData.capNormal,
                numFloatsIn = this.hasCapTexture() ? 5 : 3,
                numFloatsOut = numFloatsIn + 3,
                numVertices = currentData.capTriangles.length / numFloatsIn,
                bufferIn = currentData.capTriangles,
                bufferOut = new Float32Array(numVertices * numFloatsOut),
                k = 0;

            for (var i = 0; i < numVertices; i++) {
                for (var j = 0; j < numFloatsIn; j++) {
                    bufferOut[k++] = bufferIn[i * numFloatsIn + j];
                }

                bufferOut[k++] = normal[0];
                bufferOut[k++] = normal[1];
                bufferOut[k++] = normal[2];
            }

            return bufferOut;
        };

        Polygon.prototype.drawSides = function (dc, pickColor) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                currentData = this.currentData,
                refreshBuffers = currentData.refreshBuffers,
                hasSideTextures = this.hasSideTextures(),
                applyLighting = this.activeAttributes.applyLighting,
                numFloatsPerVertex = 3 + (hasSideTextures ? 2 : 0) + (applyLighting ? 3 : 0),
                numBytesPerVertex = 4 * numFloatsPerVertex,
                vboId, opacity, color, textureBound, sidesBuffer, numSides;

            numSides = 0;
            for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
                numSides += (currentData.boundaryPoints[b].length / 6) - 1; // 6 floats per boundary point: top + bottom
            }

            if (!currentData.sidesVboCacheKey) {
                currentData.sidesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(currentData.sidesVboCacheKey);
            if (!vboId || refreshBuffers) {
                sidesBuffer = this.makeSidesBuffer(numSides);
                currentData.numSideVertices = sidesBuffer.length / numFloatsPerVertex;

                if (!vboId) {
                    vboId = gl.createBuffer();
                }

                dc.gpuResourceCache.putResource(currentData.sidesVboCacheKey, vboId, sidesBuffer.length * 4);
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, sidesBuffer, gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
            } else {
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            }

            color = this.activeAttributes.interiorColor;
            opacity = color.alpha * dc.currentLayer.opacity;
            // Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
            gl.depthMask(opacity >= 1 || dc.pickingMode);
            program.loadColor(gl, dc.pickingMode ? pickColor : color);
            program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);

            if (hasSideTextures && !dc.pickingMode) {
                this.activeTexture = dc.gpuResourceCache.resourceForKey(this.capImageSource());
                if (!this.activeTexture) {
                    this.activeTexture =
                        dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.capImageSource());
                }

                if (applyLighting) {
                    program.loadApplyLighting(gl, true);
                    gl.enableVertexAttribArray(program.normalVectorLocation);
                } else {
                    program.loadApplyLighting(gl, false);
                }

                // Step through the sides buffer rendering each side independently but from the same buffer.
                for (var side = 0; side < numSides; side++) {
                    var sideImageSource = this.sideImageSource(side),
                        sideTexture = dc.gpuResourceCache.resourceForKey(sideImageSource),
                        coordByteOffset = side * 6 * numBytesPerVertex; // 6 vertices (2 triangles) per side

                    if (sideImageSource && !sideTexture) {
                        sideTexture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, sideImageSource);
                    }

                    textureBound = sideTexture && sideTexture.bind(dc);
                    if (textureBound) {
                        gl.enableVertexAttribArray(program.vertexTexCoordLocation);
                        gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, numBytesPerVertex,
                            coordByteOffset + 12);

                        this.scratchMatrix.setToIdentity();
                        this.scratchMatrix.multiplyByTextureTransform(this.activeTexture);

                        program.loadTextureEnabled(gl, true);
                        program.loadTextureUnit(gl, gl.TEXTURE0);
                        program.loadTextureMatrix(gl, this.scratchMatrix);
                    } else {
                        program.loadTextureEnabled(gl, false);
                        gl.disableVertexAttribArray(program.vertexTexCoordLocation);
                    }

                    if (applyLighting) {
                        gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, numBytesPerVertex,
                            coordByteOffset + 20);
                    }

                    gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, numBytesPerVertex,
                        coordByteOffset);
                    gl.drawArrays(gl.TRIANGLES, 0, 6); // 6 vertices per side
                }
            } else {
                program.loadTextureEnabled(gl, false);

                if (applyLighting && !dc.pickingMode) {
                    program.loadApplyLighting(gl, true);
                    gl.enableVertexAttribArray(program.normalVectorLocation);
                    gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, numBytesPerVertex,
                        numBytesPerVertex - 12);
                } else {
                    program.loadApplyLighting(gl, false);
                }

                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, numBytesPerVertex, 0);
                gl.drawArrays(gl.TRIANGLES, 0, currentData.numSideVertices);
            }
        };

        Polygon.prototype.makeSidesBuffer = function (numSides) {
            var currentData = this.currentData,
                hasSideTextures = this.hasSideTextures(),
                applyLighting = this.activeAttributes.applyLighting,
                numFloatsPerVertex = 3 + (hasSideTextures ? 2 : 0) + (applyLighting ? 3 : 0),
                sidesBuffer, sidesBufferIndex, numBufferFloats, v0, v1, v2, v3, t0, t1, t2, t3;

            numBufferFloats = numSides * 2 * 3 * numFloatsPerVertex; // 2 triangles per side, 3 vertices per triangle
            sidesBuffer = new Float32Array(numBufferFloats);
            sidesBufferIndex = 0;

            v0 = new Vec3(0, 0, 0);
            v1 = new Vec3(0, 0, 0);
            v2 = new Vec3(0, 0, 0);
            v3 = new Vec3(0, 0, 0);

            if (hasSideTextures) {
                t0 = new Vec2(0, 1);
                t1 = new Vec2(0, 0);
                t2 = new Vec2(1, 1);
                t3 = new Vec2(1, 0);
            } else {
                t0 = t1 = t2 = t3 = null;
            }

            for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
                var boundaryPoints = currentData.boundaryPoints[b],
                    sideNormal;

                for (var i = 0; i < boundaryPoints.length - 6; i += 6) {
                    v0[0] = boundaryPoints[i];
                    v0[1] = boundaryPoints[i + 1];
                    v0[2] = boundaryPoints[i + 2];

                    v1[0] = boundaryPoints[i + 3];
                    v1[1] = boundaryPoints[i + 4];
                    v1[2] = boundaryPoints[i + 5];

                    v2[0] = boundaryPoints[i + 6];
                    v2[1] = boundaryPoints[i + 7];
                    v2[2] = boundaryPoints[i + 8];

                    v3[0] = boundaryPoints[i + 9];
                    v3[1] = boundaryPoints[i + 10];
                    v3[2] = boundaryPoints[i + 11];

                    sideNormal = applyLighting ? Vec3.computeTriangleNormal(v0, v1, v2) : null;

                    // First triangle.
                    this.addVertexToBuffer(v0, t0, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;

                    this.addVertexToBuffer(v1, t1, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;

                    this.addVertexToBuffer(v2, t2, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;

                    // Second triangle.
                    this.addVertexToBuffer(v1, t1, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;

                    this.addVertexToBuffer(v3, t3, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;

                    this.addVertexToBuffer(v2, t2, sideNormal, sidesBuffer, sidesBufferIndex);
                    sidesBufferIndex += numFloatsPerVertex;
                }
            }

            return sidesBuffer;
        };

        Polygon.prototype.addVertexToBuffer = function (v, texCoord, normal, buffer, bufferIndex) {
            buffer[bufferIndex++] = v[0];
            buffer[bufferIndex++] = v[1];
            buffer[bufferIndex++] = v[2];

            if (texCoord) {
                buffer[bufferIndex++] = texCoord[0];
                buffer[bufferIndex++] = texCoord[1];
            }

            if (normal) {
                buffer[bufferIndex++] = normal[0];
                buffer[bufferIndex++] = normal[1];
                buffer[bufferIndex] = normal[2];
            }
        };

        Polygon.prototype.drawOutline = function (dc, pickColor) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                currentData = this.currentData,
                refreshBuffers = currentData.refreshBuffers,
                numBoundaryPoints, vboId, opacity, color, stride, nPts, textureBound;

            program.loadTextureEnabled(gl, false);
            program.loadApplyLighting(gl, false);

            if (this.hasCapTexture()) {
                gl.disableVertexAttribArray(program.vertexTexCoordLocation); // we're not texturing the outline
            }

            if (this.activeAttributes.applyLighting) {
                gl.disableVertexAttribArray(program.normalVectorLocation); // we're not lighting the outline
            }

            if (!currentData.boundaryVboCacheKeys) {
                this.currentData.boundaryVboCacheKeys = [];
            }

            // Make the outline stand out from the interior.
            this.applyMvpMatrixForOutline(dc);

            program.loadTextureEnabled(gl, false);
            gl.disableVertexAttribArray(program.vertexTexCoordLocation);

            for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
                numBoundaryPoints = currentData.boundaryPoints[b].length / 3;

                if (!currentData.boundaryVboCacheKeys[b]) {
                    currentData.boundaryVboCacheKeys[b] = dc.gpuResourceCache.generateCacheKey();
                }

                vboId = dc.gpuResourceCache.resourceForKey(currentData.boundaryVboCacheKeys[b]);
                if (!vboId) {
                    vboId = gl.createBuffer();
                    dc.gpuResourceCache.putResource(currentData.boundaryVboCacheKeys[b], vboId, numBoundaryPoints * 12);
                    refreshBuffers = true;
                }

                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                if (refreshBuffers) {
                    gl.bufferData(gl.ARRAY_BUFFER, currentData.boundaryPoints[b], gl.STATIC_DRAW);
                    dc.frameStatistics.incrementVboLoadCount(1);
                }

                color = this.activeAttributes.outlineColor;
                opacity = color.alpha * dc.currentLayer.opacity;
                // Disable writing the shape's fragments to the depth buffer when the outline is
                // semi-transparent.
                gl.depthMask(opacity >= 1 || dc.pickingMode);
                program.loadColor(gl, dc.pickingMode ? pickColor : color);
                program.loadOpacity(gl, dc.pickingMode ? 1 : opacity);

                gl.lineWidth(this.activeAttributes.outlineWidth);

                if (this._extrude) {
                    stride = 24;
                    nPts = numBoundaryPoints / 2;
                } else {
                    stride = 12;
                    nPts = numBoundaryPoints;
                }

                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
                gl.drawArrays(gl.LINE_STRIP, 0, nPts);

                if (this.mustDrawVerticals(dc)) {
                    gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
                    gl.drawArrays(gl.LINES, 0, numBoundaryPoints - 2);
                }
            }
        };

        // Overridden from AbstractShape base class.
        Polygon.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext;

            if (this.activeAttributes.drawInterior) {
                gl.disable(gl.CULL_FACE);
            }

            dc.findAndBindProgram(BasicTextureProgram);
            gl.enableVertexAttribArray(dc.currentProgram.vertexPointLocation);

            var applyLighting = !dc.pickMode && this.activeAttributes.applyLighting;
            if (applyLighting) {
                dc.currentProgram.loadModelviewInverse(gl, dc.navigatorState.modelviewNormalTransform);
            }
        };

        // Overridden from AbstractShape base class.
        Polygon.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext;

            gl.disableVertexAttribArray(dc.currentProgram.vertexPointLocation);
            gl.disableVertexAttribArray(dc.currentProgram.normalVectorLocation);
            gl.depthMask(true);
            gl.lineWidth(1);
            gl.enable(gl.CULL_FACE);
        };

        return Polygon;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfacePolyline
 */
define('shapes/SurfacePolyline',[
        '../error/ArgumentError',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape'
    ],
    function (ArgumentError,
              Logger,
              ShapeAttributes,
              SurfaceShape) {
        "use strict";

        /**
         * Constructs a surface polyline.
         * @alias SurfacePolyline
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents a polyline draped over the terrain surface.
         * <p>
         * SurfacePolyline uses the following attributes from its associated shape attributes bundle:
         * <ul>
         *         <li>Draw outline</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         * </ul>
         * @param {Location[]} locations This polyline's locations.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         * @throws {ArgumentError} If the specified locations are null or undefined.
         */
        var SurfacePolyline = function (locations, attributes) {
            if (!locations) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfacePolyline", "constructor",
                        "The specified locations array is null or undefined."));
            }

            SurfaceShape.call(this, attributes);

            /**
             * This shape's locations, specified as an array locations.
             * @type {Array}
             */
            this._boundaries = locations;

            this._stateId = SurfacePolyline.stateId++;

            // Internal use only.
            this._isInteriorInhibited = true;
        };

        SurfacePolyline.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfacePolyline.prototype, {
            /**
             * This polyline's boundaries. The polylines locations.
             * @type {Location[]}
             * @memberof SurfacePolyline.prototype
             */
            boundaries: {
                get: function () {
                    return this._boundaries;
                },
                set: function (boundaries) {
                    if (!Array.isArray(boundaries)) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "SurfacePolyline", "set boundaries",
                                "The specified value is not an array."));
                    }
                    this.resetBoundaries();
                    this._boundaries = boundaries;
                    this._stateId = SurfacePolyline.stateId++;
                    this.stateKeyInvalid = true;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfacePolyline.stateId = Number.MIN_SAFE_INTEGER;

        // Internal use only. Intentionally not documented.
        SurfacePolyline.staticStateKey = function(shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey +
                " pl " + shape._stateId;
        };

        // Internal use only. Intentionally not documented.
        SurfacePolyline.prototype.computeStateKey = function() {
            return SurfacePolyline.staticStateKey(this);
        };

        // Internal. Polyline doesn't generate its own boundaries. See SurfaceShape.prototype.computeBoundaries.
        SurfacePolyline.prototype.computeBoundaries = function(dc) {
        };

        return SurfacePolyline;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoJSONParser
 */
define('formats/geojson/GeoJSONParser',['../../error/ArgumentError',
        '../../util/Color',
        './GeoJSONConstants',
        './GeoJSONCRS',
        './GeoJSONFeature',
        './GeoJSONFeatureCollection',
        './GeoJSONGeometry',
        './GeoJSONGeometryCollection',
        './GeoJSONGeometryLineString',
        './GeoJSONGeometryMultiLineString',
        './GeoJSONGeometryMultiPoint',
        './GeoJSONGeometryMultiPolygon',
        './GeoJSONGeometryPoint',
        './GeoJSONGeometryPolygon',
        '../../geom/Location',
        '../../util/Logger',
        '../../shapes/Placemark',
        '../../shapes/PlacemarkAttributes',
        '../../shapes/Polygon',
        '../../geom/Position',
        '../../util/proj4-src',
        '../../layer/RenderableLayer',
        '../../shapes/ShapeAttributes',
        '../../shapes/SurfacePolygon',
        '../../shapes/SurfacePolyline'
    ],
    function (ArgumentError,
              Color,
              GeoJSONConstants,
              GeoJSONCRS,
              GeoJSONFeature,
              GeoJSONFeatureCollection,
              GeoJSONGeometry,
              GeoJSONGeometryCollection,
              GeoJSONGeometryLineString,
              GeoJSONGeometryMultiLineString,
              GeoJSONGeometryMultiPoint,
              GeoJSONGeometryMultiPolygon,
              GeoJSONGeometryPoint,
              GeoJSONGeometryPolygon,
              Location,
              Logger,
              Placemark,
              PlacemarkAttributes,
              Polygon,
              Position,
              Proj4,
              RenderableLayer,
              ShapeAttributes,
              SurfacePolygon,
              SurfacePolyline) {
        "use strict";

        /**
         * Constructs a GeoJSON object for a specified GeoJSON data source. Call [load]{@link GeoJSONParser#load} to
         * retrieve the GeoJSON and create shapes for it.
         * @alias GeoJSONParser
         * @constructor
         * @classdesc Parses a GeoJSON and creates shapes representing its contents. Points and MultiPoints in
         * the GeoJSON are represented by [Placemarks]{@link Placemark}, Lines and MultiLines are represented by
         * [SurfacePolylines]{@link SurfacePolyline}, and Polygons and MultiPolygons are represented
         * by [SurfacePolygons]{@link SurfacePolygon}.
         * <p>
         * An attribute callback may also be specified to examine each geometry and configure the shape created for it.
         * This function enables the application to assign independent attributes to each
         * shape. An argument to this function provides any attributes specified in a properties member of GeoJSON
         * feature.
         * @param {String} dataSource The data source of the GeoJSON. Can be a string or an URL to a GeoJSON.
         * @throws {ArgumentError} If the specified data source is null or undefined.
         */
        var GeoJSONParser = function (dataSource) {
            if (!dataSource) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "constructor", "missingDataSource"));
            }

            // Documented in defineProperties below.
            this._dataSource = dataSource;

            // Documented in defineProperties below.
            this._geoJSONObject = null;

            // Documented in defineProperties below.
            this._geoJSONType = null;

            // Documented in defineProperties below.
            this._crs = null;

            // Documented in defineProperties below.
            this._layer = null;

            // Documented in defineProperties below.
            this._parserCompletionCallback = null;

            // Documented in defineProperties below.
            this._shapeConfigurationCallback = this.defaultShapeConfigurationCallback;


            this.defaultPlacemarkAttributes = new PlacemarkAttributes(null);

            this.defaultShapeAttributes = new ShapeAttributes(null);

            this.setProj4jsAliases();
        };

        Object.defineProperties(GeoJSONParser.prototype, {
            /**
             * The GeoJSON data source as specified to this GeoJSON's constructor.
             * @memberof GeoJSONParser.prototype
             * @type {String}
             * @readonly
             */
            dataSource: {
                get: function () {
                    return this._dataSource;
                }
            },

            /**
             * The GeoJSON object resulting from the parsing of GeoJSON string.
             * @memberof GeoJSONParser.prototype
             * @type {Object}
             * @readonly
             */
            geoJSONObject: {
                get: function () {
                    return this._geoJSONObject;
                }
            },

            /**
             * The type of the GeoJSON. The type can be one of the following:
             * <ul>
             *     <li>GeoJSONConstants.TYPE_POINT</li>
             *     <li>GeoJSONConstants.TYPE_MULTI_POINT</li>
             *     <li>GeoJSONConstants.TYPE_LINE_STRING</li>
             *     <li>GeoJSONConstants.TYPE_MULTI_LINE_STRING</li>
             *     <li>GeoJSONConstants.TYPE_POLYGON</li>
             *     <li>GeoJSONConstants.TYPE_MULTI_POLYGON</li>
             *     <li>GeoJSONConstants.TYPE_GEOMETRY_COLLECTION</li>
             *     <li>GeoJSONConstants.TYPE_FEATURE</li>
             *     <li>GeoJSONConstants.TYPE_FEATURE_COLLECTION</li>
             * </ul>
             * This value is defined after GeoJSON parsing.
             * @memberof GeoJSONParser.prototype
             * @type {String}
             * @readonly
             */
            geoJSONType: {
                get: function () {
                    return this._geoJSONType;
                }
            },

            /**
             *
             */
            crs: {
                get: function () {
                    return this._crs;
                }
            },

            /**
             * The layer containing the shapes representing the geometries in this GeoJSON, as specified to this
             * GeoJSON's constructor or created by the constructor if no layer was specified.
             * @memberof GeoJSONParser.prototype
             * @type {RenderableLayer}
             * @readonly
             */
            layer: {
                get: function () {
                    return this._layer;
                }
            },

            /** The completion callback specified to [load]{@link GeoJSONParser#load}. An optional function called when
             * the GeoJSON loading is complete and
             * all the shapes have been added to the layer.
             * @memberof GeoJSONParser.prototype
             * @type {Function}
             * @readonly
             */
            parserCompletionCallback: {
                get: function () {
                    return this._parserCompletionCallback;
                }
            },

            /**
             * The attribute callback specified to [load]{@link GeoJSONParser#load}.
             * See that method's description for details.
             * @memberof GeoJSONParser.prototype
             * @type {Function}
             * @default [defaultShapeConfigurationCallback]{@link GeoJSONParser#defaultShapeConfigurationCallback}
             * @readonly
             */
            shapeConfigurationCallback: {
                get: function () {
                    return this._shapeConfigurationCallback;
                }
            }
        });

        /**
         * Retrieves the GeoJSON, parses it and creates shapes representing its contents. The result is a layer
         * containing the created shapes. A function can also be specified to be called for each GeoJSON geometry so
         * that the attributes and other properties of the shape created for it can be assigned.
         * @param {Function} parserCompletionCallback An optional function called when the GeoJSON loading is
         * complete and all the shapes have been added to the layer.
         * @param {Function} shapeConfigurationCallback An optional function called by the addRenderablesFor*
         * methods just prior to creating a shape for the indicated GeoJSON geometry. This function
         * can be used to assign attributes to newly created shapes. The callback function's first argument is the
         * current geometry object.  The second argument to the callback function is the object containing the
         * properties read from the corresponding GeoJSON properties member, if any.
         * See the following methods for descriptions of the configuration properties they recognize:
         * <ul>
         *     <li>[addRenderablesForPoint]{@link GeoJSONParser#addRenderablesForPoint}</li>
         *     <li>[addRenderablesForMultiPoint]{@link GeoJSONParser#addRenderablesForMultiPoint}</li>
         *     <li>[addRenderablesForLineString]{@link GeoJSONParser#addRenderablesForLineString}</li>
         *     <li>[addRenderablesForMultiLineString]{@link GeoJSONParser#addRenderablesForMultiLineString}</li>
         *     <li>[addRenderablesForPolygon]{@link GeoJSONParser#addRenderablesForPolygon}</li>
         *     <li>[addRenderablesForMultiPolygon]{@link GeoJSONParser#addRenderablesForMultiPolygon}</li>
         *     <li>[addRenderablesForGeometryCollection]{@link GeoJSONParser#addRenderablesForGeometryCollection}</li>
         *     <li>[addRenderablesForFeature]{@link GeoJSONParser#addRenderablesForFeature}</li>
         *     <li>[addRenderablesForFeatureCollection]{@link GeoJSONParser#addRenderablesForFeatureCollection}</li>
         * </ul>
         *
         * @param {RenderableLayer} layer A {@link RenderableLayer} to hold the shapes created for each GeoJSON
         * geometry. If null, a new layer is created and assigned to this object's [layer]{@link GeoJSONParser#layer}
         * property.
         */

        GeoJSONParser.prototype.load = function (parserCompletionCallback, shapeConfigurationCallback, layer) {
            if (parserCompletionCallback) {
                this._parserCompletionCallback = parserCompletionCallback;
            }

            if (shapeConfigurationCallback) {
                this._shapeConfigurationCallback = shapeConfigurationCallback;
            }

            this._layer = layer || new RenderableLayer();

            if (this.isDataSourceJson()){
                this.parse(this.dataSource);
            }
            else {
                this.requestUrl(this.dataSource);
            }
        };

        /**
         * The default [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for this GeoJSON.
         * It is called if none was specified to the [load]{@link GeoJSONParser#load} method.
         * This method assigns shared, default attributes to the shapes created for each geometry. Any changes to these
         * attributes will have an effect in all shapes created by this GeoJSON.
         * <p>
         * For all geometry, the GeoJSON's properties are checked for an attribute named "name", "Name" or "NAME".
         * If found, the returned shape configuration contains a name property holding the value associated with
         * the attribute. This value is specified as the label displayName property for all shapes created.
         * For {@link Placemark} shapes it is also specified as the placemark label.
         * It is specified as the displayName for all other shapes.
         *
         * @param {GeoJSONGeometry} geometry An object containing the geometry associated with this GeoJSON.
         * @param {Object} properties An object containing the attribute-value pairs found in GeoJSON feature
         * properties member.
         * @returns {Object} An object with properties as described above.
         */
        GeoJSONParser.prototype.defaultShapeConfigurationCallback = function (geometry, properties) {
            var configuration = {};

            var name = properties.name || properties.Name || properties.NAME;
            if (name) {
                configuration.name = name;
            }

            if (geometry.isPointType() || geometry.isMultiPointType()) {
                configuration.attributes = this.defaultPlacemarkAttributes;
            } else if (geometry.isLineStringType() || geometry.isMultiLineStringType()) {
                configuration.attributes = this.defaultShapeAttributes;
            } else if (geometry.isPolygonType() || geometry.isMultiPolygonType()) {
                configuration.attributes = this.defaultShapeAttributes;
            }

            return configuration;
        };

        // Get GeoJSON string using XMLHttpRequest. Internal use only.
        GeoJSONParser.prototype.requestUrl = function (url) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'text';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        this.parse(xhr.response);
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "GeoJSON retrieval failed (" + xhr.statusText + "): " + url);
                    }
                }
            }).bind(this);

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoJSON retrieval failed: " + url);
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoJSON retrieval timed out: " + url);
            };

            xhr.send(null);
        };

        // Parse GeoJSON string using built in method JSON.parse(). Internal use only.
        GeoJSONParser.prototype.parse = function (geoJSONString) {
            try {
                this._geoJSONObject = JSON.parse(geoJSONString);
            }
            catch (e) {
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "parse",
                    "invalidGeoJSONObject")
            }
            finally {
                if (this.geoJSONObject){
                    if (Object.prototype.toString.call(this.geoJSONObject) === '[object Array]') {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "parse",
                                "invalidGeoJSONObjectLength"));
                    }

                    if (this.geoJSONObject.hasOwnProperty(GeoJSONConstants.FIELD_TYPE)) {
                        this.setGeoJSONType();
                        this.setGeoJSONCRS();
                    }
                    else{
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "parse",
                                "missingGeoJSONType"));
                    }

                    if (!!this._parserCompletionCallback && typeof this._parserCompletionCallback === "function") {
                        this._parserCompletionCallback(this.layer);
                    }
                }
            }
        };

        // Set GeoJSON CRS object.
        // If no crs member can be so acquired, the default CRS shall apply to the GeoJSON object.
        // The crs member should be on the top-level GeoJSON object in a hierarchy (in feature collection, feature,
        // geometry order) and should not be repeated or overridden on children or grandchildren of the object.
        // Internal use only.
        GeoJSONParser.prototype.setGeoJSONCRS = function () {
            if (this.geoJSONObject[GeoJSONConstants.FIELD_CRS]){
                this._crs = new GeoJSONCRS (
                    this.geoJSONObject[GeoJSONConstants.FIELD_CRS][GeoJSONConstants.FIELD_TYPE],
                    this.geoJSONObject[GeoJSONConstants.FIELD_CRS][GeoJSONConstants.FIELD_PROPERTIES]);

                var crsCallback = (function() {
                    this.addRenderablesForGeoJSON(this.layer);
                }).bind(this);

                this.crs.setCRSString(crsCallback);
            }
            else{
                // If no CRS, consider default one
                this.addRenderablesForGeoJSON(this.layer);
            }
        };

        /**
         * Iterates over this GeoJSON's geometries and creates shapes for them. See the following methods for the
         * details of the shapes created and their use of the
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback}:
         * <ul>
         *     <li>[addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}</li>
         *     <li>[addRenderablesForGeometryCollection]{@link GeoJSONParser#addRenderablesForGeometryCollection}</li>
         *     <li>[addRenderablesForFeature]{@link GeoJSONParser#addRenderablesForFeature}</li>
         *     <li>[addRenderablesForFeatureCollection]{@link GeoJSONParser#addRenderablesForFeatureCollection}</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForGeoJSON = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForGeoJSON", "missingLayer"));
            }

            switch(this.geoJSONType) {
                case GeoJSONConstants.TYPE_FEATURE:
                    var feature = new  GeoJSONFeature(
                        this.geoJSONObject[GeoJSONConstants.FIELD_GEOMETRY],
                        this.geoJSONObject[GeoJSONConstants.FIELD_PROPERTIES],
                        this.geoJSONObject[GeoJSONConstants.FIELD_ID],
                        this.geoJSONObject[GeoJSONConstants.FIELD_BBOX]
                    );
                    this.addRenderablesForFeature(
                        layer,
                        feature);
                    break;
                case GeoJSONConstants.TYPE_FEATURE_COLLECTION:
                    var featureCollection = new GeoJSONFeatureCollection(
                        this.geoJSONObject[GeoJSONConstants.FIELD_FEATURES],
                        this.geoJSONObject[GeoJSONConstants.FIELD_BBOX]
                    );
                    this.addRenderablesForFeatureCollection(
                        layer,
                        featureCollection);
                    break;
                case GeoJSONConstants.TYPE_GEOMETRY_COLLECTION:
                    var geometryCollection = new GeoJSONGeometryCollection(
                        this.geoJSONObject[GeoJSONConstants.FIELD_GEOMETRIES],
                        this.geoJSONObject[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForGeometryCollection(
                        layer,
                        geometryCollection,
                        null);
                    break;
                default:
                    this.addRenderablesForGeometry(
                        layer,
                        this.geoJSONObject,
                        null);
                    break;
            }
        };

        /**
         * Creates shape for a geometry. See the following methods for the
         * details of the shapes created and their use of the
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback}:
         * <ul>
         *     <li>[addRenderablesForPoint]{@link GeoJSONParser#addRenderablesForPoint}</li>
         *     <li>[addRenderablesForMultiPoint]{@link GeoJSONParser#addRenderablesForMultiPoint}</li>
         *     <li>[addRenderablesForLineString]{@link GeoJSONParser#addRenderablesForLineString}</li>
         *     <li>[addRenderablesForMultiLineString]{@link GeoJSONParser#addRenderablesForMultiLineString}</li>
         *     <li>[addRenderablesForPolygon]{@link GeoJSONParser#addRenderablesForPolygon}</li>
         *     <li>[addRenderablesForMultiPolygon]{@link GeoJSONParser#addRenderablesForMultiPolygon}</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometry} geometry An object containing the current geometry.
         * @param {Object} properties An object containing the attribute-value pairs found in GeoJSON feature
         * properties member.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForGeometry = function (layer, geometry, properties){
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForGeometry", "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForGeometry", "missingGeometry"));
            }

            switch(geometry[GeoJSONConstants.FIELD_TYPE]){
                case GeoJSONConstants.TYPE_POINT:
                    var pointGeometry = new GeoJSONGeometryPoint(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForPoint(
                        layer,
                        pointGeometry,
                        properties ? properties : null);
                    break;
                case GeoJSONConstants.TYPE_MULTI_POINT:
                    var multiPointGeometry = new GeoJSONGeometryMultiPoint(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForMultiPoint(
                        layer,
                        multiPointGeometry,
                        properties ? properties : null);
                    break;
                case GeoJSONConstants.TYPE_LINE_STRING:
                    var lineStringGeometry = new GeoJSONGeometryLineString(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForLineString(
                        layer,
                        lineStringGeometry,
                        properties ? properties : null);
                    break;
                case GeoJSONConstants.TYPE_MULTI_LINE_STRING:
                    var multiLineStringGeometry = new GeoJSONGeometryMultiLineString(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForMultiLineString(
                        layer,
                        multiLineStringGeometry,
                        properties ? properties : null);
                    break;
                case GeoJSONConstants.TYPE_POLYGON:
                    var polygonGeometry = new GeoJSONGeometryPolygon(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]
                    );
                    this.addRenderablesForPolygon(
                        layer,
                        polygonGeometry,
                        properties ? properties : null);
                    break;
                case GeoJSONConstants.TYPE_MULTI_POLYGON:
                    var multiPolygonGeometry = new GeoJSONGeometryMultiPolygon(
                        geometry[GeoJSONConstants.FIELD_COORDINATES],
                        geometry[GeoJSONConstants.FIELD_TYPE],
                        geometry[GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForMultiPolygon(
                        layer,
                        multiPolygonGeometry,
                        properties ? properties : null);
                    break;
                default:
                    break;
            }
        }

        /**
         * Creates a {@link Placemark} for a Point geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryPoint} geometry The Point geometry object.
         * @param {Object} properties The properties related to the Point geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForPoint = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForPoint", "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForPoint", "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                var longitude = geometry.coordinates[0],
                    latitude = geometry.coordinates[1],
                    altitude = geometry.coordinates[2] ?  geometry.coordinates[2] : 0;

                var reprojectedCoordinate = this.getReprojectedIfRequired(
                    latitude,
                    longitude,
                    this.crs);
                var position = new Position(reprojectedCoordinate[1], reprojectedCoordinate[0], altitude);
                var placemark = new Placemark(
                    position,
                    false,
                    configuration && configuration.attributes ? configuration.attributes : null);

                placemark.altitudeMode = WorldWind.RELATIVE_TO_GROUND;
                if (configuration && configuration.name){
                    placemark.label = configuration.name;
                }
                if (configuration.highlightAttributes) {
                    placemark.highlightAttributes = configuration.highlightAttributes;
                }
                if (configuration && configuration.pickDelegate) {
                    placemark.pickDelegate = configuration.pickDelegate;
                }
                if (configuration && configuration.userProperties) {
                    placemark.userProperties = configuration.userProperties;
                }
                layer.addRenderable(placemark);
            }
        };

        /**
         * Creates {@link Placemark}s for a MultiPoint geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryMultiPoint} geometry The MultiPoint geometry object.
         * @param {Object} properties The properties related to the MultiPoint geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForMultiPoint = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiPoint",
                        "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiPoint",
                        "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                for (var pointIndex = 0, points = geometry.coordinates.length; pointIndex < points; pointIndex += 1){
                    var longitude = geometry.coordinates[pointIndex][0],
                        latitude = geometry.coordinates[pointIndex][1],
                        altitude = geometry.coordinates[pointIndex][2] ?  geometry.coordinates[pointIndex][2] : 0;

                    var reprojectedCoordinate = this.getReprojectedIfRequired(
                        latitude,
                        longitude,
                        this.crs);
                    var position = new Position(reprojectedCoordinate[1], reprojectedCoordinate[0], altitude);
                    var placemark = new Placemark(
                        position,
                        false,
                        configuration && configuration.attributes ? configuration.attributes : null);
                    placemark.altitudeMode = WorldWind.RELATIVE_TO_GROUND;
                    if (configuration && configuration.name){
                        placemark.label = configuration.name;
                    }
                    if (configuration.highlightAttributes) {
                        placemark.highlightAttributes = configuration.highlightAttributes;
                    }
                    if (configuration && configuration.pickDelegate) {
                        placemark.pickDelegate = configuration.pickDelegate;
                    }
                    if (configuration && configuration.userProperties) {
                        placemark.userProperties = configuration.userProperties;
                    }
                    layer.addRenderable(placemark);
                }
            }
        };

        /**
         * Creates a {@link SurfacePolyline} for a LineString geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryLineString} geometry The LineString geometry object.
         * @param {Object} properties The properties related to the LineString geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForLineString = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForLineString",
                        "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForLineString",
                        "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                var positions = [];
                for (var pointsIndex = 0, points = geometry.coordinates; pointsIndex < points.length; pointsIndex++) {
                    var longitude = points[pointsIndex][0],
                        latitude = points[pointsIndex][1];
                    //altitude = points[pointsIndex][2] ?  points[pointsIndex][2] : 0,
                    var reprojectedCoordinate = this.getReprojectedIfRequired(
                        latitude,
                        longitude,
                        this.crs);
                    var position = new Location(reprojectedCoordinate[1], reprojectedCoordinate[0]);
                    positions.push(position);
                }

                var shape;
                shape = new SurfacePolyline(
                    positions,
                    configuration && configuration.attributes ? configuration.attributes : null);
                if (configuration.highlightAttributes) {
                    shape.highlightAttributes = configuration.highlightAttributes;
                }
                if (configuration && configuration.pickDelegate) {
                    shape.pickDelegate = configuration.pickDelegate;
                }
                if (configuration && configuration.userProperties) {
                    shape.userProperties = configuration.userProperties;
                }
                layer.addRenderable(shape);
            }
        };

        /**
         * Creates {@link SurfacePolyline}s for a MultiLineString geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryMultiLineString} geometry The MultiLineString geometry object.
         * @param {Object} properties The properties related to the MultiLineString geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForMultiLineString = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiLineString",
                        "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiLineString",
                        "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                for (var linesIndex = 0, lines = geometry.coordinates; linesIndex < lines.length; linesIndex++) {
                    var positions = [];

                    for (var positionIndex = 0, points = lines[linesIndex]; positionIndex < points.length;
                         positionIndex++) {
                        var longitude = points[positionIndex][0],
                            latitude = points[positionIndex][1];
                        //altitude = points[positionIndex][2] ?  points[positionIndex][2] : 0,
                        var reprojectedCoordinate = this.getReprojectedIfRequired(
                            latitude,
                            longitude,
                            this.crs);
                        var position = new Location(reprojectedCoordinate[1], reprojectedCoordinate[0]);
                        positions.push(position);
                    }

                    var shape;
                    shape = new SurfacePolyline(
                        positions,
                        configuration && configuration.attributes ? configuration.attributes : null);
                    if (configuration.highlightAttributes) {
                        shape.highlightAttributes = configuration.highlightAttributes;
                    }
                    if (configuration && configuration.pickDelegate) {
                        shape.pickDelegate = configuration.pickDelegate;
                    }
                    if (configuration && configuration.userProperties) {
                        shape.userProperties = configuration.userProperties;
                    }
                    layer.addRenderable(shape);
                }
            }
        };

        /**
         * Creates a {@link SurfacePolygon} for a Polygon geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryPolygon} geometry The Polygon geometry object.
         * @param {Object} properties The properties related to the Polygon geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForPolygon = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForPolygon", "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForPolygon", "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                for (var boundariesIndex = 0, boundaries = geometry.coordinates;
                     boundariesIndex < boundaries.length; boundariesIndex++) {
                    var positions = [];

                    for (var positionIndex = 0, points = boundaries[boundariesIndex];
                         positionIndex < points.length; positionIndex++) {
                        var longitude = points[positionIndex][0],
                            latitude = points[positionIndex][1];
                        //altitude = points[positionIndex][2] ?  points[positionIndex][2] : 0,
                        var reprojectedCoordinate = this.getReprojectedIfRequired(
                            latitude,
                            longitude,
                            this.crs);
                        var position = new Location(reprojectedCoordinate[1], reprojectedCoordinate[0]);
                        positions.push(position);
                    }

                    var shape;
                    shape = new SurfacePolygon(
                        positions,
                        configuration && configuration.attributes ? configuration.attributes : null);
                    if (configuration.highlightAttributes) {
                        shape.highlightAttributes = configuration.highlightAttributes;
                    }
                    if (configuration && configuration.pickDelegate) {
                        shape.pickDelegate = configuration.pickDelegate;
                    }
                    if (configuration && configuration.userProperties) {
                        shape.userProperties = configuration.userProperties;
                    }                    layer.addRenderable(shape);
                }
            }
        };

        /**
         * Creates {@link SurfacePolygon}s for a MultiPolygon geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry}.
         * <p>
         * This method invokes this GeoJSON's
         * [shapeConfigurationCallback]{@link GeoJSONParser#shapeConfigurationCallback} for the geometry.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryMultiPolygon} geometry The MultiPolygon geometry object.
         * @param {Object} properties The properties related to the MultiPolygon geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified geometry is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForMultiPolygon = function (layer, geometry, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiPolygon",
                        "missingLayer"));
            }

            if (!geometry) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForMultiPolygon",
                        "missingGeometry"));
            }

            var configuration = this.shapeConfigurationCallback(geometry, properties);

            if (!this.crs || this.crs.isCRSSupported()) {
                for (var polygonsIndex = 0, polygons = geometry.coordinates;
                     polygonsIndex < polygons.length; polygonsIndex++) {
                    var boundaries = [];
                    for (var boundariesIndex = 0; boundariesIndex < polygons[polygonsIndex].length; boundariesIndex++) {
                        var positions = [];
                        for (var positionIndex = 0, points = polygons[polygonsIndex][boundariesIndex];
                             positionIndex < points.length; positionIndex++) {
                            var longitude = points[positionIndex][0],
                                latitude = points[positionIndex][1];
                            //altitude = points[positionIndex][2] ?  points[positionIndex][2] : 0,;

                            var reprojectedCoordinate = this.getReprojectedIfRequired(
                                latitude,
                                longitude,
                                this.crs);
                            var position = new Location(reprojectedCoordinate[1], reprojectedCoordinate[0]);
                            positions.push(position);
                        }
                        boundaries.push(positions);
                    }
                    var shape;
                    shape = new SurfacePolygon(
                        boundaries,
                        configuration && configuration.attributes ? configuration.attributes : null);
                    if (configuration.highlightAttributes) {
                        shape.highlightAttributes = configuration.highlightAttributes;
                    }
                    if (configuration && configuration.pickDelegate) {
                        shape.pickDelegate = configuration.pickDelegate;
                    }
                    if (configuration && configuration.userProperties) {
                        shape.userProperties = configuration.userProperties;
                    }
                    layer.addRenderable(shape);
                }
            }
        };

        /**
         * Iterates over the GeoJSON GeometryCollection geometries and creates {@link GeoJSONGeometry}s for them.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeoJSON]{@link GeoJSONParser#addRenderablesForGeoJSON}.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONGeometryCollection} geometryCollection The GeometryCollection object.
         * @param {Object} properties The properties related to the GeometryCollection geometry.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified featureCollection is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForGeometryCollection = function (layer, geometryCollection, properties) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForGeometryCollection",
                        "missingLayer"));
            }

            if (!geometryCollection) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForGeometryCollection",
                        "missingGeometryCollection"));
            }


            for (var geometryIndex = 0, geometries = geometryCollection.geometries;
                 geometryIndex < geometries.length; geometryIndex++) {
                if(geometries[geometryIndex].hasOwnProperty(GeoJSONConstants.FIELD_TYPE)){
                    this.addRenderablesForGeometry(layer, geometries[geometryIndex], properties);
                }
            }
        };

        /**
         * Calls [addRenderablesForGeometry]{@link GeoJSONParser#addRenderablesForGeometry} or
         * [addRenderablesForGeometryCollection]{@link GeoJSONParser#addRenderablesForGeometryCollection}
         * depending on the type of feature geometry.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeoJSON]{@link GeoJSONParser#addRenderablesForGeoJSON}.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONFeature} feature The Feature object.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified feature is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForFeature = function (layer, feature) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForFeature", "missingLayer"));
            }

            if (!feature) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForFeature", "missingFeature"));
            }

            if (feature.geometry.type === GeoJSONConstants.TYPE_GEOMETRY_COLLECTION) {
                var geometryCollection = new GeoJSONGeometryCollection(
                    feature.geometry.geometries,
                    feature.bbox);
                this.addRenderablesForGeometryCollection(
                    layer,
                    geometryCollection,
                    feature.properties);
            }
            else {
                this.addRenderablesForGeometry(
                    layer,
                    feature.geometry,
                    feature.properties
                );
            }
        };

        /**
         * Iterates over the GeoJSON FeatureCollection features and creates {@link GeoJSONFeature}s for them.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForGeoJSON]{@link GeoJSONParser#addRenderablesForGeoJSON}.
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @param {GeoJSONFeatureCollection} featureCollection The FeatureCollection object.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         * @throws {ArgumentError} If the specified featureCollection is null or undefined.
         */
        GeoJSONParser.prototype.addRenderablesForFeatureCollection = function (layer, featureCollection) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForFeatureCollection",
                        "missingLayer"));
            }

            if (!featureCollection) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "addRenderablesForFeatureCollection",
                        "missingFeatureCollection"));
            }

            if (featureCollection.features.length > 0) {
                for (var featureIndex = 0; featureIndex < featureCollection.features.length; featureIndex++) {
                    var feature = new GeoJSONFeature(
                        featureCollection.features[featureIndex][GeoJSONConstants.FIELD_GEOMETRY],
                        featureCollection.features[featureIndex][GeoJSONConstants.FIELD_PROPERTIES],
                        featureCollection.features[featureIndex][GeoJSONConstants.FIELD_ID],
                        featureCollection.features[featureIndex][GeoJSONConstants.FIELD_BBOX]);
                    this.addRenderablesForFeature(
                        layer,
                        feature);
                }
            }
        };

        // Set type of GeoJSON object. Internal use ony.
        GeoJSONParser.prototype.setGeoJSONType = function () {
            switch (this.geoJSONObject[GeoJSONConstants.FIELD_TYPE]) {
                case GeoJSONConstants.TYPE_POINT:
                    this._geoJSONType = GeoJSONConstants.TYPE_POINT;
                    break;
                case GeoJSONConstants.TYPE_MULTI_POINT:
                    this._geoJSONType = GeoJSONConstants.TYPE_MULTI_POINT;
                    break;
                case GeoJSONConstants.TYPE_LINE_STRING:
                    this._geoJSONType = GeoJSONConstants.TYPE_LINE_STRING;
                    break;
                case GeoJSONConstants.TYPE_MULTI_LINE_STRING:
                    this._geoJSONType = GeoJSONConstants.TYPE_MULTI_LINE_STRING;
                    break;
                case GeoJSONConstants.TYPE_POLYGON:
                    this._geoJSONType = GeoJSONConstants.TYPE_POLYGON;
                    break;
                case GeoJSONConstants.TYPE_MULTI_POLYGON:
                    this._geoJSONType = GeoJSONConstants.TYPE_MULTI_POLYGON;
                    break;
                case GeoJSONConstants.TYPE_GEOMETRY_COLLECTION:
                    this._geoJSONType = GeoJSONConstants.TYPE_GEOMETRY_COLLECTION;
                    break;
                case GeoJSONConstants.TYPE_FEATURE:
                    this._geoJSONType = GeoJSONConstants.TYPE_FEATURE;
                    break;
                case GeoJSONConstants.TYPE_FEATURE_COLLECTION:
                    this._geoJSONType = GeoJSONConstants.TYPE_FEATURE_COLLECTION;
                    break;
                default:
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "setGeoJSONType", "invalidGeoJSONType"));
            }
        };

        /**
         * Reprojects GeoJSON geometry coordinates if required using proj4js.
         *
         * @param {Number} latitude The latitude coordinate of the geometry.
         * @param {Number} longitude The longitude coordinate of the geometry.
         * @param {GeoJSONCRS} crsObject The GeoJSON CRS object.
         * @returns {Number[]} An array containing reprojected coordinates.
         * @throws {ArgumentError} If the specified latitude is null or undefined.
         * @throws {ArgumentError} If the specified longitude is null or undefined.
         * @throws {ArgumentError} If the specified crsObject is null or undefined.
         */
        GeoJSONParser.prototype.getReprojectedIfRequired = function (latitude, longitude, crsObject) {
            if (!latitude && latitude !== 0.0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "getReprojectedIfRequired",
                        "missingLatitude"));
            }

            if (!longitude && longitude !== 0.0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoJSON", "getReprojectedIfRequired",
                        "missingLongitude"));
            }

            if (!crsObject || crsObject.isDefault()){
                return [longitude, latitude];
            }
            else{
                return Proj4(crsObject.projectionString, GeoJSONConstants.EPSG4326_CRS, [longitude, latitude]);
            }
        };

        // Use this function to add aliases for some projection strings that proj4js doesn't recognize.
        GeoJSONParser.prototype.setProj4jsAliases = function () {
            Proj4.defs([
                [
                    'urn:ogc:def:crs:OGC:1.3:CRS84',
                    Proj4.defs('EPSG:4326')
                ],
                [
                    'urn:ogc:def:crs:EPSG::3857',
                    Proj4.defs('EPSG:3857')

                ]
            ]);
        };

        /**
        * Indicate whether the data source is of a JSON type.
        * @returns {Boolean} True if the data source is of JSON type.
        */
        GeoJSONParser.prototype.isDataSourceJson = function() {
            try {
                JSON.parse(this.dataSource);
            } catch (e) {
                return false;
            }
            return true;
        }

        return GeoJSONParser;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoTiffConstants
 */
define('formats/geotiff/GeoTiffConstants',[],
    function () {
        "use strict";

        /**
         * Provides all of the GeoTIFF tag constants.
         * @alias GeoTiff
         * @constructor
         * @classdesc Contains all of the TIFF tags that are used to store GeoTIFF information of any type.
         */
        var GeoTiffConstants = {
            /**
             * An object containing all GeoTiff specific tags.
             * @memberof GeoTiff
             * @type {Object}
             */
            Tag: {
                'MODEL_PIXEL_SCALE': 33550,
                'MODEL_TRANSFORMATION': 34264,
                'MODEL_TIEPOINT': 33922,
                'GEO_KEY_DIRECTORY': 34735,
                'GEO_DOUBLE_PARAMS': 34736,
                'GEO_ASCII_PARAMS': 34737,

                //gdal
                'GDAL_NODATA': 42113,
                'GDAL_METADATA': 42112
            },

            /**
             * An object containing all GeoTiff specific keys.
             * @memberof GeoTiff
             * @type {Object}
             */
            Key: {
                // GeoTIFF Configuration Keys
                'GTModelTypeGeoKey': 1024,
                'GTRasterTypeGeoKey': 1025,
                'GTCitationGeoKey': 1026,

                // Geographic CS Parameter Keys
                'GeographicTypeGeoKey': 2048,
                'GeogCitationGeoKey': 2049,
                'GeogGeodeticDatumGeoKey': 2050,
                'GeogPrimeMeridianGeoKey': 2051,
                'GeogLinearUnitsGeoKey': 2052,
                'GeogLinearUnitSizeGeoKey': 2053,
                'GeogAngularUnitsGeoKey': 2054,
                'GeogAngularUnitSizeGeoKey': 2055,
                'GeogEllipsoidGeoKey': 2056,
                'GeogSemiMajorAxisGeoKey': 2057,
                'GeogSemiMinorAxisGeoKey': 2058,
                'GeogInvFlatteningGeoKey': 2059,
                'GeogAzimuthUnitsGeoKey': 2060,
                'GeogPrimeMeridianLongGeoKey': 2061,
                'GeogTOWGS84GeoKey': 2062,

                // Projected CS Parameter Keys
                'ProjectedCSTypeGeoKey': 3072,
                'PCSCitationGeoKey': 3073,
                'ProjectionGeoKey': 3074,
                'ProjCoordTransGeoKey': 3075,
                'ProjLinearUnitsGeoKey': 3076,
                'ProjLinearUnitSizeGeoKey': 3077,
                'ProjStdParallel1GeoKey': 3078,
                'ProjStdParallel2GeoKey': 3079,
                'ProjNatOriginLongGeoKey': 3080,
                'ProjNatOriginLatGeoKey': 3081,
                'ProjFalseEastingGeoKey': 3082,
                'ProjFalseNorthingGeoKey': 3083,
                'ProjFalseOriginLongGeoKey': 3084,
                'ProjFalseOriginLatGeoKey': 3085,
                'ProjFalseOriginEastingGeoKey': 3086,
                'ProjFalseOriginNorthingGeoKey': 3087,
                'ProjCenterLongGeoKey': 3088,
                'ProjCenterLatGeoKey': 3089,
                'ProjCenterEastingGeoKey': 3090,
                'ProjCenterNorthingGeoKey': 3091,
                'ProjScaleAtNatOriginGeoKey': 3092,
                'ProjScaleAtCenterGeoKey': 3093,
                'ProjAzimuthAngleGeoKey': 3094,
                'ProjStraightVertPoleLongGeoKey': 3095,

                // Vertical CS Keys
                'VerticalCSTypeGeoKey': 4096,
                'VerticalCitationGeoKey': 4097,
                'VerticalDatumGeoKey': 4098,
                'VerticalUnitsGeoKey': 4099
            }
        };

        return GeoTiffConstants;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoTiffKeyEntry
 */
define('formats/geotiff/GeoTiffKeyEntry',[
        '../../error/AbstractError',
        '../../error/ArgumentError',
        './GeoTiffConstants',
        '../../util/Logger'
    ],
    function (AbstractError,
              ArgumentError,
              GeoTiffConstants,
              Logger) {
        "use strict";

        /**
         * Constructs a geotiff KeyEntry. Applications typically do not call this constructor. It is called
         * by {@link GeoTiffReader} as GeoTIFF geo keys are read.
         * @alias GeoTiffKeyEntry
         * @constructor
         * @classdesc Contains the data associated with a GeoTIFF KeyEntry. Each KeyEntry is modeled on the
         * "TiffIFDEntry" format of the TIFF directory header.
         * @param {Number} keyId Gives the key-ID value of the Key (identical in function
         * to TIFF tag ID, but completely independent of TIFF tag-space).
         * @param {Number} tiffTagLocation Indicates which TIFF tag contains the value(s) of the Key.
         * @param {Number} count Indicates the number of values in this key.
         * @param {Number} valueOffset  Indicates the index offset into the TagArray indicated by tiffTagLocation.
         * @throws {ArgumentError} If either the specified keyId, tiffTagLocation, count or valueOffset
         * are null or undefined.
         */
        var GeoTiffKeyEntry = function (keyId, tiffTagLocation, count, valueOffset) {
            if (!keyId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffKeyEntry", "constructor", "missingKeyId"));
            }

            if (tiffTagLocation === null || tiffTagLocation === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffKeyEntry", "constructor", "missingTiffTagLocation"));
            }

            if (!count) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffKeyEntry", "constructor", "missingCount"));
            }

            if (valueOffset === null || valueOffset === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffKeyEntry", "constructor", "missingValueOffset"));
            }

            // Documented in defineProperties below.
            this._keyId = keyId;

            // Documented in defineProperties below.
            this._tiffTagLocation = tiffTagLocation;

            // Documented in defineProperties below.
            this._count = count;

            // Documented in defineProperties below.
            this._valueOffset = valueOffset;
        };

        Object.defineProperties(GeoTiffKeyEntry.prototype, {

            /**
             * The key-ID value of the Key as specified to this GeoTiffKeyEntry's constructor.
             * @memberof GeoTiffKeyEntry.prototype
             * @type {Number}
             * @readonly
             */
            keyId: {
                get: function () {
                    return this._keyId;
                }
            },

            /**
             * The location of the TIFF tag as specified to this GeoTiffKeyEntry's constructor.
             * @memberof GeoTiffKeyEntry.prototype
             * @type {Number}
             * @readonly
             */
            tiffTagLocation: {
                get: function () {
                    return this._tiffTagLocation;
                }
            },

            /**
             * The number of values in the key as specified to this GeoTiffKeyEntry's constructor.
             * @memberof GeoTiffKeyEntry.prototype
             * @type {Number}
             * @readonly
             */
            count: {
                get: function () {
                    return this._count;
                }
            },

            /**
             * The index offset into the TagArray as specified to this GeoTiffKeyEntry's constructor.
             * @memberof GeoTiffKeyEntry.prototype
             * @type {Number}
             * @readonly
             */
            valueOffset: {
                get: function () {
                    return this._valueOffset;
                }
            }
        });

        /**
         * Get the value of a GeoKey.
         * @returns {Number}
         */
        GeoTiffKeyEntry.prototype.getGeoKeyValue = function (geoDoubleParamsValue, geoAsciiParamsValue) {
            var keyValue;

            if (this.tiffTagLocation === 0){
                keyValue = this.valueOffset
            }
            else if (this.tiffTagLocation === GeoTiffConstants.Tag.GEO_ASCII_PARAMS) {
                var retVal = "";

                if (geoAsciiParamsValue){
                    for (var i=this.valueOffset; i < this.count - 1; i++){
                        retVal += geoAsciiParamsValue[i];
                    }
                    if (geoAsciiParamsValue[this.count - 1] != '|'){
                        retVal += geoAsciiParamsValue[this.count - 1];
                    }

                    keyValue = retVal;
                }
            }
            else if (this.tiffTagLocation === GeoTiffConstants.Tag.GEO_DOUBLE_PARAMS) {
                if (geoDoubleParamsValue){
                    keyValue = geoDoubleParamsValue[this.valueOffset];
                }
            }

            return keyValue;
        };

        return GeoTiffKeyEntry;
    }
);

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoTiffMetadata
 */
define('formats/geotiff/GeoTiffMetadata',[
    ],
    function () {
        "use strict";

        /**
         * Provides GeoTIFF metadata.
         * @alias GeoTiffMetadata
         * @constructor
         * @classdesc Contains all of the TIFF and GeoTIFF metadata for a geotiff file.
         */
        var GeoTiffMetadata = function () {

            // Documented in defineProperties below.
            this._bitsPerSample = null;

            // Documented in defineProperties below.
            this._colorMap = null;

            // Documented in defineProperties below.
            this._compression = null;

            // Documented in defineProperties below.
            this._extraSamples = null;

            // Documented in defineProperties below.
            this._imageLength = null;

            // Documented in defineProperties below.
            this._imageWidth = null;

            // Documented in defineProperties below.
            this._maxSampleValue = null;

            // Documented in defineProperties below.
            this._minSampleValue = null;

            // Documented in defineProperties below.
            this._orientation = 0;

            // Documented in defineProperties below.
            this._photometricInterpretation = null;

            // Documented in defineProperties below.
            this._planarConfiguration = null;

            // Documented in defineProperties below.
            this._resolutionUnit = null;

            // Documented in defineProperties below.
            this._rowsPerStrip = null;

            // Documented in defineProperties below.
            this._samplesPerPixel = null;

            // Documented in defineProperties below.
            this._sampleFormat = null;

            // Documented in defineProperties below.
            this._software = null;

            // Documented in defineProperties below.
            this._stripByteCounts = null;

            // Documented in defineProperties below.
            this._stripOffsets = null;

            // Documented in defineProperties below.
            this._tileByteCounts = null;

            // Documented in defineProperties below.
            this._tileOffsets = null;

            // Documented in defineProperties below.
            this._tileLength = null;

            // Documented in defineProperties below.
            this._tileWidth = null;

            // Documented in defineProperties below.
            this._xResolution = null;

            // Documented in defineProperties below.
            this._yResolution = null;

            // Documented in defineProperties below.
            this._geoAsciiParams = null;

            // Documented in defineProperties below.
            this._geoDoubleParams = null;

            // Documented in defineProperties below.
            this._geoKeyDirectory = null;

            // Documented in defineProperties below.
            this._modelPixelScale = null;

            // Documented in defineProperties below.
            this._modelTiepoint = null;

            // Documented in defineProperties below.
            this._modelTransformation = null;

            // Documented in defineProperties below.
            this._noData = null;

            // Documented in defineProperties below.
            this._bbox = null;

            // Documented in defineProperties below.
            this._gtModelTypeGeoKey = null;

            // Documented in defineProperties below.
            this._gtRasterTypeGeoKey = null;

            // Documented in defineProperties below.
            this._gtCitationGeoKey = null;

            // Documented in defineProperties below.
            this._geographicTypeGeoKey = null;

            // Documented in defineProperties below.
            this._geogCitationGeoKey = null;

            // Documented in defineProperties below.
            this._geogAngularUnitsGeoKey = null;

            // Documented in defineProperties below.
            this._geogAngularUnitSizeGeoKey = null;

            // Documented in defineProperties below.
            this._geogSemiMajorAxisGeoKey = null;

            // Documented in defineProperties below.
            this._geogInvFlatteningGeoKey = null;

            // Documented in defineProperties below.
            this._projectedCSType = null;
        };

        Object.defineProperties(GeoTiffMetadata.prototype, {

            /**
             * Contains the number of bits per component.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            bitsPerSample: {
                get: function () {
                    return this._bitsPerSample;
                },

                set: function(value){
                    this._bitsPerSample = value;
                }
            },

            /**
             * Defines a Red-Green-Blue color map (often called a lookup table) for palette color images.
             * In a palette-color image, a pixel value is used to index into an RGB-lookup table.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            colorMap: {
                get: function () {
                    return this._colorMap;
                },

                set: function(value){
                    this._colorMap = value;
                }
            },

            /**
             * Contains the compression type of geotiff data.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            compression: {
                get: function () {
                    return this._compression;
                },

                set: function(value){
                    this._compression = value;
                }
            },

            /**
             * Contains the description of extra components.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            extraSamples: {
                get: function () {
                    return this._extraSamples;
                },

                set: function(value){
                    this._extraSamples = value;
                }
            },

            /**
             * Contains the number of rows in the image.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            imageLength: {
                get: function () {
                    return this._imageLength;
                },

                set: function(value){
                    this._imageLength = value;
                }
            },

            /**
             * Contains the number of columns in the image.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            imageWidth: {
                get: function () {
                    return this._imageWidth;
                },

                set: function(value){
                    this._imageWidth = value;
                }
            },

            /**
             * Contains the maximum component value used.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            maxSampleValue: {
                get: function () {
                    return this._maxSampleValue;
                },

                set: function(value){
                    this._maxSampleValue = value;
                }
            },

            /**
             * Contains the minimum component value used.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            minSampleValue: {
                get: function () {
                    return this._minSampleValue;
                },

                set: function(value){
                    this._minSampleValue = value;
                }
            },

            /**
             * Contains the orientation of the image with respect to the rows and columns.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            orientation: {
                get: function () {
                    return this._orientation;
                },

                set: function(value){
                    this._orientation = value;
                }
            },

            /**
             * Contains the photometric interpretation type of the geotiff file.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            photometricInterpretation: {
                get: function () {
                    return this._photometricInterpretation;
                },

                set: function(value){
                    this._photometricInterpretation = value;
                }
            },

            /**
             * Contains the planar configuration type of the geotiff file.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            planarConfiguration: {
                get: function () {
                    return this._planarConfiguration;
                },

                set: function(value){
                    this._planarConfiguration = value;
                }
            },

            /**
             * Contains the unit of measurement for XResolution and YResolution. The specified values are:
             * <ul>
             *     <li>1 = No absolute unit of measurement</li>
             *     <li>2 = Inch</li>
             *     <li>3 = Centimeter</li>
             * </ul>
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            resolutionUnit: {
                get: function () {
                    return this._resolutionUnit;
                },

                set: function(value){
                    this._resolutionUnit = value;
                }
            },

            /**
             * Contains the number of rows per strip.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            rowsPerStrip: {
                get: function () {
                    return this._rowsPerStrip;
                },

                set: function(value){
                    this._rowsPerStrip = value;
                }
            },

            /**
             * Contains the number of components per pixel.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            samplesPerPixel: {
                get: function () {
                    return this._samplesPerPixel;
                },

                set: function(value){
                    this._samplesPerPixel = value;
                }
            },

            /**
             * This field specifies how to interpret each data sample in a pixel. Possible values are:
             * <ul>
             *     <li>unsigned integer data</li>
             *     <li>two's complement signed integer data</li>
             *     <li>IEEE floating point data</li>
             *     <li>undefined data format</li>
             * </ul>
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            sampleFormat: {
                get: function () {
                    return this._sampleFormat;
                },

                set: function(value){
                    this._sampleFormat = value;
                }
            },

            software: {
                get: function () {
                    return this._software;
                },

                set: function(value){
                    this._software = value;
                }
            },

            /**
             * Contains the number of bytes in that strip after any compression, for each strip.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            stripByteCounts: {
                get: function () {
                    return this._stripByteCounts;
                },

                set: function(value){
                    this._stripByteCounts = value;
                }
            },

            /**
             * Contains the byte offset of that strip, for each strip.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            stripOffsets: {
                get: function () {
                    return this._stripOffsets;
                },

                set: function(value){
                    this._stripOffsets = value;
                }
            },

            tileByteCounts: {
                get: function () {
                    return this._tileByteCounts;
                },

                set: function(value){
                    this._tileByteCounts = value;
                }
            },

            /**
             * Contains the byte offset of that tile, for each tile.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number[]}
             */
            tileOffsets: {
                get: function () {
                    return this._tileOffsets;
                },

                set: function(value){
                    this._tileOffsets = value;
                }
            },

            tileLength: {
                get: function () {
                    return this._tileLength;
                },

                set: function(value){
                    this._tileLength = value;
                }
            },

            tileWidth: {
                get: function () {
                    return this._tileWidth;
                },

                set: function(value){
                    this._tileWidth = value;
                }
            },

            //geotiff
            /**
             * Contains all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag.
             * @memberof GeoTiffMetadata.prototype
             * @type {String[]}
             */
            geoAsciiParams: {
                get: function () {
                    return this._geoAsciiParams;
                },

                set: function(value){
                    this._geoAsciiParams = value;
                }
            },

            /**
             * Contains all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag.
             * @memberof GeoTiffMetadata.prototype
             * @type {Nmber[]}
             */
            geoDoubleParams: {
                get: function () {
                    return this._geoDoubleParams;
                },

                set: function(value){
                    this._geoDoubleParams = value;
                }
            },

            /**
             * Contains the values of GeoKeyDirectoryTag.
             * @memberof GeoTiffMetadata.prototype
             * @type {Nmber[]}
             */
            geoKeyDirectory: {
                get: function () {
                    return this._geoKeyDirectory;
                },

                set: function(value){
                    this._geoKeyDirectory = value;
                }
            },

            /**
             * Contains the values of ModelPixelScaleTag. The ModelPixelScaleTag tag may be used to specify the size
             * of raster pixel spacing in the model space units, when the raster space can be embedded in the model
             * space coordinate system without rotation
             * @memberof GeoTiffMetadata.prototype
             * @type {Nmber[]}
             */
            modelPixelScale: {
                get: function () {
                    return this._modelPixelScale;
                },

                set: function(value){
                    this._modelPixelScale = value;
                }
            },

            /**
             * Stores raster->model tiepoint pairs in the order ModelTiepointTag = (...,I,J,K, X,Y,Z...),
             * where (I,J,K) is the point at location (I,J) in raster space with pixel-value K,
             * and (X,Y,Z) is a vector in model space.
             * @memberof GeoTiffMetadata.prototype
             * @type {Nmber[]}
             */
            modelTiepoint: {
                get: function () {
                    return this._modelTiepoint;
                },

                set: function(value){
                    this._modelTiepoint = value;
                }
            },

            /**
             * Contains the information that may be used to specify the transformation matrix between the raster space
             * (and its dependent pixel-value space) and the model space.
             * @memberof GeoTiffMetadata.prototype
             * @type {Nmber[]}
             */
            modelTransformation: {
                get: function () {
                    return this._modelTransformation;
                },

                set: function(value){
                    this._modelTransformation = value;
                }
            },

            /**
             * Contains the NODATA value.
             * @memberof GeoTiffMetadata.prototype
             * @type {String}
             */
            noData: {
                get: function () {
                    return this._noData;
                },

                set: function(value){
                    this._noData = value;
                }
            },

            /**
             * Contains the extent of the geotiff.
             * @memberof GeoTiffMetadata.prototype
             * @type {Sector}
             */
            bbox: {
                get: function () {
                    return this._bbox;
                },

                set: function(value){
                    this._bbox = value;
                }
            },

            //geokeys
            /**
             * Contains an ID defining the crs model.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            gtModelTypeGeoKey: {
                get: function () {
                    return this._gtModelTypeGeoKey;
                },

                set: function(value){
                    this._gtModelTypeGeoKey = value;
                }
            },

            /**
             * Contains an ID defining the raster sample type.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            gtRasterTypeGeoKey: {
                get: function () {
                    return this._gtRasterTypeGeoKey;
                },

                set: function(value){
                    this._gtRasterTypeGeoKey = value;
                }
            },

            /**
             * Contains an ASCII reference to the overall configuration of the geotiff file.
             * @memberof GeoTiffMetadata.prototype
             * @type {String}
             */
            gtCitationGeoKey: {
                get: function () {
                    return this._gtCitationGeoKey;
                },

                set: function(value){
                    this._gtCitationGeoKey = value;
                }
            },

            /**
             * Contains a value to specify the code for geographic coordinate system used to map lat-long to a specific
             * ellipsoid over the earth
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            geographicTypeGeoKey: {
                get: function () {
                    return this._geographicTypeGeoKey;
                },

                set: function(value){
                    this._geographicTypeGeoKey = value;
                }
            },

            /**
             * Contains a value to specify the code for geographic coordinate system used to map lat-long to a specific
             * ellipsoid over the earth
             * @memberof GeoTiffMetadata.prototype
             * @type {String}
             */
            geogCitationGeoKey: {
                get: function () {
                    return this._geogCitationGeoKey;
                },

                set: function(value){
                    this._geogCitationGeoKey = value;
                }
            },

            /**
             * Allows the definition of geocentric CS Linear units for used-defined GCS and for ellipsoids
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            geogAngularUnitsGeoKey: {
                get: function () {
                    return this._geogAngularUnitsGeoKey;
                },

                set: function(value){
                    this._geogAngularUnitsGeoKey = value;
                }
            },

            /**
             * Allows the definition of user-defined angular geographic units, as measured in radians
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            geogAngularUnitSizeGeoKey: {
                get: function () {
                    return this._geogAngularUnitSizeGeoKey;
                },

                set: function(value){
                    this._geogAngularUnitSizeGeoKey = value;
                }
            },

            /**
             * Allows the specification of user-defined Ellipsoidal Semi-Major Axis
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            geogSemiMajorAxisGeoKey: {
                get: function () {
                    return this._geogSemiMajorAxisGeoKey;
                },

                set: function(value){
                    this._geogSemiMajorAxisGeoKey = value;
                }
            },

            /**
             * Allows the specification of the inverse of user-defined Ellipsoid's flattening parameter f.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            geogInvFlatteningGeoKey: {
                get: function () {
                    return this._geogInvFlatteningGeoKey;
                },

                set: function(value){
                    this._geogInvFlatteningGeoKey = value;
                }
            },

            /**
             * Contains the EPSG code of the geotiff.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            projectedCSType: {
                get: function () {
                    return this._projectedCSType;
                },

                set: function(value){
                    this._projectedCSType = value;
                }
            },

            /**
             * Contains the number of pixels per resolution unit in the image width direction.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            xResolution: {
                get: function () {
                    return this._xResolution;
                },

                set: function(value){
                    this._xResolution = value;
                }
            },

            /**
             * Contains the number of pixels per resolution unit in the image length direction.
             * @memberof GeoTiffMetadata.prototype
             * @type {Number}
             */
            yResolution: {
                get: function () {
                    return this._yResolution;
                },

                set: function(value){
                    this._yResolution = value;
                }
            }
        });

        return GeoTiffMetadata;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Tiff
 */
define('formats/geotiff/TiffConstants',[],
    function () {
        "use strict";

        /**
         * Provides all of the TIFF tag and subtag constants.
         * @alias TiffConstants
         * @constructor
         * @classdesc Contains all of the TIFF tags that are used to store TIFF information of any type.
         */
        var TiffConstants = {
            /**
             * An object containing all TIFF specific tags.
             * @memberof Tiff
             * @type {Object}
             */
            Tag: {
                'NEW_SUBFILE_TYPE': 254,
                'SUBFILE_TYPE': 255,
                'IMAGE_WIDTH': 256,
                'IMAGE_LENGTH': 257,
                'BITS_PER_SAMPLE': 258,
                'COMPRESSION': 259,
                'PHOTOMETRIC_INTERPRETATION': 262,
                'THRESHHOLDING': 263,
                'CELL_WIDTH': 264,
                'CELL_LENGTH': 265,
                'FILL_ORDER': 266,
                'DOCUMENT_NAME': 269,
                'IMAGE_DESCRIPTION': 270,
                'MAKE': 271,
                'MODEL': 272,
                'STRIP_OFFSETS': 273,
                'ORIENTATION': 274,
                'SAMPLES_PER_PIXEL': 277,
                'ROWS_PER_STRIP': 278,
                'STRIP_BYTE_COUNTS': 279,
                'MIN_SAMPLE_VALUE': 280,
                'MAX_SAMPLE_VALUE': 281,
                'X_RESOLUTION': 282,
                'Y_RESOLUTION': 283,
                'PLANAR_CONFIGURATION': 284,
                'PAGE_NAME': 285,
                'X_POSITION': 286,
                'Y_POSITION': 287,
                'FREE_OFFSETS': 288,
                'FREE_BYTE_COUNTS': 289,
                'GRAY_RESPONSE_UNIT': 290,
                'GRAY_RESPONSE_CURVE': 291,
                'T4_OPTIONS': 292,
                'T6_PTIONS': 293,
                'RESOLUTION_UNIT': 296,
                'PAGE_NUMBER': 297,
                'TRANSFER_FUNCTION': 301,
                'SOFTWARE': 305,
                'DATE_TIME': 306,
                'ARTIST': 315,
                'HOST_COMPUTER': 316,
                'PREDICTOR': 317,
                'WHITE_POINT': 318,
                'PRIMARY_CHROMATICITIES': 319,
                'COLOR_MAP': 320,
                'HALFTONE_HINTS': 321,
                'TILE_WIDTH': 322,
                'TILE_LENGTH': 323,
                'TILE_OFFSETS': 324,
                'TILE_BYTE_COUNTS': 325,
                'INK_SET': 332,
                'INK_NAMES': 333,
                'NUMBER_OF_INKS': 334,
                'DOT_RANGE': 336,
                'TARGET_PRINTER': 337,
                'EXTRA_SAMPLES': 338,
                'SAMPLE_FORMAT': 339,
                'S_MIN_SAMPLE_VALUE': 340,
                'S_MAX_SAMPLE_VALUE': 341,
                'TRANSFER_RANGE': 342,
                'JPEG_PROC': 512,
                'JPEG_INTERCHANGE_FORMAT': 513,
                'JPEG_INTERCHANGE_FORMAT_LENGTH': 514,
                'JPEG_RESTART_INTERVAL': 515,
                'JPEG_LOSSLESS_PREDICTORS': 517,
                'JPEG_POINT_TRANSFORMS': 518,
                'JPEG_Q_TABLES': 519,
                'JPEG_DC_TABLES': 520,
                'JPEG_AC_TABLES': 521,
                'Y_Cb_Cr_COEFFICIENTS': 529,
                'Y_Cb_Cr_SUB_SAMPLING': 530,
                'Y_Cb_Cr_POSITIONING': 531,
                'REFERENCE_BLACK_WHITE': 532,
                'COPYRIGHT': 33432
            },

            /**
             * An object containing all TIFF compression types.
             * @memberof Tiff
             * @type {Object}
             */
            Compression: {
                'UNCOMPRESSED': 1,
                'CCITT_1D': 2,
                'GROUP_3_FAX': 3,
                'GROUP_4_FAX': 4,
                'LZW': 5,
                'JPEG': 6,
                'PACK_BITS': 32773
            },

            /**
             * An object containing all TIFF orientation types.
             * @memberof Tiff
             * @type {Object}
             */
            Orientation: {
                'Row0_IS_TOP__Col0_IS_LHS': 1,
                'Row0_IS_TOP__Col0_IS_RHS': 2,
                'Row0_IS_BOTTOM__Col0_IS_RHS': 3,
                'Row0_IS_BOTTOM__Col0_IS_LHS': 4,
                'Row0_IS_LHS__Col0_IS_TOP': 5,
                'Row0_IS_RHS__Col0_IS_TOP': 6,
                'Row0_IS_RHS__Col0_IS_BOTTOM': 7,
                'Row0_IS_LHS__Col0_IS_BOTTOM': 8
            },

            /**
             * An object containing all TIFF photometric interpretation types.
             * @memberof Tiff
             * @type {Object}
             */
            PhotometricInterpretation: {
                'WHITE_IS_ZERO': 0,
                'BLACK_IS_ZERO': 1,
                'RGB': 2,
                'RGB_PALETTE': 3,
                'TRANSPARENCY_MASK': 4,
                'CMYK': 5,
                'Y_Cb_Cr': 6,
                'CIE_LAB': 7
            },

            /**
             * An object containing all TIFF planar configuration types.
             * @memberof Tiff
             * @type {Object}
             */
            PlanarConfiguration: {
                'CHUNKY': 1,
                'PLANAR': 2
            },

            /**
             * An object containing all TIFF resolution unit types.
             * @memberof Tiff
             * @type {Object}
             */
            ResolutionUnit: {
                'NONE': 1,
                'INCH': 2,
                'CENTIMETER': 3
            },

            /**
             * An object containing all TIFF sample format types.
             * @memberof Tiff
             * @type {Object}
             */
            SampleFormat: {
                'UNSIGNED': 1,
                'SIGNED': 2,
                'IEEE_FLOAT': 3,
                'UNDEFINED': 4,
                'DEFAULT': 1
            },

            /**
             * An object containing all TIFF field types.
             * @memberof Tiff
             * @type {Object}
             */
            Type: {
                'BYTE': 1,
                'ASCII': 2,
                'SHORT': 3,
                'LONG': 4,
                'RATIONAL': 5,
                'SBYTE': 6,
                'UNDEFINED': 7,
                'SSHORT': 8,
                'SLONG': 9,
                'SRATIONAL': 10,
                'FLOAT': 11,
                'DOUBLE': 12
            }
        };

        return TiffConstants;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoTiffUtil
 */
define('formats/geotiff/GeoTiffUtil',[
        '../../error/ArgumentError',
        '../../util/Logger',
        './TiffConstants'
    ],
    function (ArgumentError,
              Logger,
              TiffConstants) {
        "use strict";

        var GeoTiffUtil = {

            // Get bytes from an arraybuffer depending on the size.
            getBytes: function (geoTiffData, byteOffset, numOfBytes, isLittleEndian, isSigned) {
                if (numOfBytes <= 0) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "getBytes", "noBytesRequested"));
                } else if (numOfBytes <= 1) {
                    if (isSigned) {
                        return geoTiffData.getInt8(byteOffset, isLittleEndian);
                    }
                    else {
                        return geoTiffData.getUint8(byteOffset, isLittleEndian);
                    }
                } else if (numOfBytes <= 2) {
                    if (isSigned) {
                        return geoTiffData.getInt16(byteOffset, isLittleEndian);
                    }
                    else {
                        return geoTiffData.getUint16(byteOffset, isLittleEndian);
                    }
                } else if (numOfBytes <= 3) {
                    if (isSigned) {
                        return geoTiffData.getInt32(byteOffset, isLittleEndian) >>> 8;
                    }
                    else {
                        return geoTiffData.getUint32(byteOffset, isLittleEndian) >>> 8;
                    }
                } else if (numOfBytes <= 4) {
                    if (isSigned) {
                        return geoTiffData.getInt32(byteOffset, isLittleEndian);
                    }
                    else {
                        return geoTiffData.getUint32(byteOffset, isLittleEndian);
                    }
                } else if (numOfBytes <= 8) {
                    return geoTiffData.getFloat64(byteOffset, isLittleEndian);
                } else {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "getBytes", "tooManyBytesRequested"));
                }
            },

            // Get sample value from an arraybuffer depending on the sample format.
            getSampleBytes: function (geoTiffData, byteOffset, numOfBytes, sampleFormat, isLittleEndian) {
                var res;

                switch (sampleFormat) {
                    case TiffConstants.SampleFormat.UNSIGNED:
                        res = this.getBytes(geoTiffData, byteOffset, numOfBytes, isLittleEndian, false);
                        break;
                    case TiffConstants.SampleFormat.SIGNED:
                        res = this.getBytes(geoTiffData, byteOffset, numOfBytes, isLittleEndian, true);
                        break;
                    case TiffConstants.SampleFormat.IEEE_FLOAT:
                        if (numOfBytes == 3) {
                            res = geoTiffData.getFloat32(byteOffset, isLittleEndian) >>> 8;
                        } else if (numOfBytes == 4) {
                            res = geoTiffData.getFloat32(byteOffset, isLittleEndian);
                        } else if (numOfBytes == 8) {
                            res = geoTiffData.getFloat64(byteOffset, isLittleEndian);
                        }
                        else {
                            Logger.log(Logger.LEVEL_WARNING, "Do not attempt to parse the data  not handled: " +
                                numOfBytes);
                        }
                        break;
                    case TiffConstants.SampleFormat.UNDEFINED:
                    default:
                        res = this.getBytes(geoTiffData, byteOffset, numOfBytes, isLittleEndian, false);
                        break;
                }

                return res;
            },

            // Converts canvas to an image.
            canvasToTiffImage: function (canvas) {
                var image = new Image();
                image.src = canvas.toDataURL();
                return image;
            },

            // Get RGBA fill style for a canvas context as a string.
            getRGBAFillValue: function (r, g, b, a) {
                if (typeof a === 'undefined') {
                    a = 1.0;
                }
                return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
            },

            // Get the tag value as a string.
            getTagValueAsString: function (tagName, tagValue) {
                for (var property in tagName) {
                    if (tagName[property] === tagValue) {
                        return property;
                    }
                }
                return undefined;
            },

            // Clamp color sample from color sample value and number of bits per sample.
            clampColorSample: function (colorSample, bitsPerSample) {
                var multiplier = Math.pow(2, 8 - bitsPerSample);
                return Math.floor((colorSample * multiplier) + (multiplier - 1));
            },

            // Clamp color sample for elevation data from elevation sample values.
            clampColorSampleForElevation: function (elevationSample, minElevation, maxElevation) {
                var slope = 255 / (maxElevation - minElevation);
                return Math.round(slope * (elevationSample - minElevation))
            },

            // Get min and max geotiff sample values.
            getMinMaxGeotiffSamples: function (geotiffSampleArray, noDataValue) {
                var min = Infinity;
                var max = -Infinity;
                for (var i = 0; i < geotiffSampleArray.length; i++) {
                    for (var j = 0; j < geotiffSampleArray[i].length; j++) {
                        for (var k = 0; k < geotiffSampleArray[i][j].length; k++) {
                            if (geotiffSampleArray[i][j][k] == noDataValue)
                                continue;

                            if (geotiffSampleArray[i][j][k] > max) {
                                max = geotiffSampleArray[i][j][k];
                            }
                            if (geotiffSampleArray[i][j][k] < min) {
                                min = geotiffSampleArray[i][j][k];
                            }
                        }
                    }
                }

                return {max: max, min: min};
            }
        };

        return GeoTiffUtil;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TiffIFDEntry
 */
define('formats/geotiff/TiffIFDEntry',[
        '../../error/AbstractError',
        '../../error/ArgumentError',
        './GeoTiffUtil',
        '../../util/Logger',
        './TiffConstants'
    ],
    function (AbstractError,
              ArgumentError,
              GeoTiffUtil,
              Logger,
              TiffConstants) {
        "use strict";

        /**
         * Constructs an image file directory entry. Applications typically do not call this constructor. It is called
         * by {@link GeoTiffReader} as GeoTIFF image file directories are read.
         * @alias TiffIFDEntry
         * @constructor
         * @classdesc Contains the data associated with a GeoTIFF image file directory. An image file directory
         * contains information about the image, as well as pointers to the actual image data.
         * @param {Number} tag The TIFF tag that identifies the field.
         * @param {Number} type The type of the field.
         * @param {Number} count The number of values, count of the indicated type.
         * @param {Number} valueOffset  The file offset (in bytes) of the Value for the field. This file offset may
         * point anywhere in the file, even after the image data.
         * @param {ArrayBuffer} geoTiffData The buffer descriptor of the geotiff file's content.
         * @param {Boolean} isLittleEndian Indicates whether the geotiff byte order is little endian.
         * @throws {ArgumentError} If either the specified tag, type, count, valueOffset, geoTiffData or isLittleEndian
         * are null or undefined.
         */
        var TiffIFDEntry = function (tag, type, count, valueOffset, geoTiffData, isLittleEndian) {
            if (!tag) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingTag"));
            }

            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingType"));
            }

            if (!count) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingCount"));
            }

            if (valueOffset === null || valueOffset === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingValueOffset"));
            }

            if (!geoTiffData) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingGeoTiffData"));
            }

            if (isLittleEndian === null || isLittleEndian === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "constructor", "missingIsLittleEndian"));
            }

            // Documented in defineProperties below.
            this._tag = tag;

            // Documented in defineProperties below.
            this._type = type;

            // Documented in defineProperties below.
            this._count = count;

            // Documented in defineProperties below.
            this._valueOffset = valueOffset;

            // Documented in defineProperties below.
            this._geoTiffData = geoTiffData;

            // Documented in defineProperties below.
            this._isLittleEndian = isLittleEndian;
        };

        Object.defineProperties(TiffIFDEntry.prototype, {

            /**
             * The tag that identifies the field as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {Number}
             * @readonly
             */
            tag: {
                get: function () {
                    return this._tag;
                }
            },

            /**
             * The field type as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {Number}
             * @readonly
             */
            type: {
                get: function () {
                    return this._type;
                }
            },

            /**
             * The number of the values as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {Number}
             * @readonly
             */
            count: {
                get: function () {
                    return this._count;
                }
            },

            /**
             * The file offset as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {Number}
             * @readonly
             */
            valueOffset: {
                get: function () {
                    return this._valueOffset;
                }
            },

            /**
             * The geotiff buffer data as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {ArrayBuffer}
             * @readonly
             */
            geoTiffData: {
                get: function () {
                    return this._geoTiffData;
                }
            },

            /**
             * The little endian byte order flag as specified to this TiffIFDEntry's constructor.
             * @memberof TiffIFDEntry.prototype
             * @type {Boolean}
             * @readonly
             */
            isLittleEndian: {
                get: function () {
                    return this._isLittleEndian;
                }
            }
        });

        /**
         * Get the number of bytes of an image file directory depending on its type.
         * @returns {Number}
         */
        TiffIFDEntry.prototype.getIFDTypeLength = function () {
            switch(this.type){
                case TiffConstants.Type.BYTE:
                case TiffConstants.Type.ASCII:
                case TiffConstants.Type.SBYTE:
                case TiffConstants.Type.UNDEFINED:
                    return 1;
                case TiffConstants.Type.SHORT:
                case TiffConstants.Type.SSHORT:
                    return 2;
                case TiffConstants.Type.LONG:
                case TiffConstants.Type.SLONG:
                case TiffConstants.Type.FLOAT:
                    return 4;
                case TiffConstants.Type.RATIONAL:
                case TiffConstants.Type.SRATIONAL:
                case TiffConstants.Type.DOUBLE:
                    return 8;
                default:
                    return -1;
            }
        }

        /**
         * Get the value of an image file directory.
         * @returns {Number[]}
         */
        TiffIFDEntry.prototype.getIFDEntryValue = function () {
            var ifdValues = [];
            var value = null;
            var ifdTypeLength = this.getIFDTypeLength();
            var ifdValueSize = ifdTypeLength * this.count;

            if (ifdValueSize <= 4) {
                if (this.isLittleEndian === false) {
                    value = this.valueOffset >>> ((4 - ifdTypeLength) * 8);
                } else {
                    value = this.valueOffset;
                }
                ifdValues.push(value);
            } else {
                for (var i = 0; i < this.count; i++) {
                    var indexOffset = ifdTypeLength * i;

                    if (ifdTypeLength >= 8) {
                        if (this.type === TiffConstants.Type.RATIONAL || this.type === TiffConstants.Type.SRATIONAL) {
                            // Numerator
                            ifdValues.push(GeoTiffUtil.getBytes(this.geoTiffData, this.valueOffset + indexOffset, 4,
                                this.isLittleEndian));
                            // Denominator
                            ifdValues.push(GeoTiffUtil.getBytes(this.geoTiffData, this.valueOffset + indexOffset + 4, 4,
                                this.isLittleEndian));
                        } else if (this.type === TiffConstants.Type.DOUBLE) {
                            ifdValues.push(GeoTiffUtil.getBytes(this.geoTiffData, this.valueOffset + indexOffset, 8,
                                this.isLittleEndian));
                        } else {
                            throw new AbstractError(
                                Logger.logMessage(Logger.LEVEL_SEVERE, "TiffIFDEntry", "parse", "invalidTypeOfIFD"));
                        }
                    } else {
                        ifdValues.push(GeoTiffUtil.getBytes(this.geoTiffData, this.valueOffset + indexOffset,
                            ifdTypeLength, this.isLittleEndian));
                    }
                }
            }

            if (this.type === TiffConstants.Type.ASCII) {
                ifdValues.forEach(function (element, index, array) {
                    if (element === 0){
                        array.splice(index, 1);
                    }
                    else{
                        array[index] = String.fromCharCode(element);
                    }
                });

                return ifdValues.join("");
            }

            return ifdValues;
        };

        return TiffIFDEntry;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GeoTiffReader
 */
define('formats/geotiff/GeoTiffReader',[
        '../../error/AbstractError',
        '../../error/ArgumentError',
        './GeoTiffConstants',
        './GeoTiffKeyEntry',
        './GeoTiffMetadata',
        './GeoTiffUtil',
        '../../geom/Location',
        '../../geom/Sector',
        '../../util/Logger',
        '../../util/proj4-src',
        './TiffConstants',
        './TiffIFDEntry',
        '../../util/WWUtil'
    ],
    function (AbstractError,
              ArgumentError,
              GeoTiffConstants,
              GeoTiffKeyEntry,
              GeoTiffMetadata,
              GeoTiffUtil,
              Location,
              Sector,
              Logger,
              Proj4,
              TiffConstants,
              TiffIFDEntry,
              WWUtil) {
        "use strict";

        /**
         * Constructs a geotiff reader object for a specified geotiff URL.
         * Call [readAsImage]{@link GeoTiffReader#readAsImage} to retrieve the image as a canvas or
         * [readAsData]{@link GeoTiffReader#readAsData} to retrieve the elevations as an array of elevation values.
         * @alias GeoTiffReader
         * @constructor
         * @classdesc Parses a geotiff and creates an image or an elevation array representing its contents.
         * @param {String} url The location of the geotiff.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        var GeoTiffReader = function (url) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "constructor", "missingUrl"));
            }

            // Documented in defineProperties below.
            this._url = url;

            // Documented in defineProperties below.
            this._isLittleEndian = false;

            // Documented in defineProperties below.
            this._imageFileDirectories = [];

            // Documented in defineProperties below.
            this._geoTiffData = null;

            // Documented in defineProperties below.
            this._metadata = new GeoTiffMetadata();
        };

        Object.defineProperties(GeoTiffReader.prototype, {

            /**
             * The geotiff URL as specified to this GeoTiffReader's constructor.
             * @memberof GeoTiffReader.prototype
             * @type {String}
             * @readonly
             */
            url: {
                get: function () {
                    return this._url;
                }
            },

            /**
             *Indicates whether the geotiff byte order is little endian..
             * @memberof GeoTiffReader.prototype
             * @type {Boolean}
             * @readonly
             */
            isLittleEndian: {
                get: function () {
                    return this._isLittleEndian;
                }
            },

            /**
             * An array containing all the image file directories of the geotiff file.
             * @memberof GeoTiffReader.prototype
             * @type {TiffIFDEntry[]}
             * @readonly
             */
            imageFileDirectories: {
                get: function () {
                    return this._imageFileDirectories;
                }
            },

            /**
             * The buffer descriptor of the geotiff file's content.
             * @memberof GeoTiffReader.prototype
             * @type {ArrayBuffer}
             * @readonly
             */
            geoTiffData: {
                get: function () {
                    return this._geoTiffData;
                }
            },

            /**
             * An objct containing all tiff and geotiff metadata of the geotiff file.
             * @memberof GeoTiffReader.prototype
             * @type {GeoTiffMetadata}
             * @readonly
             */
            metadata: {
                get: function () {
                    return this._metadata;
                }
            }
        });

        // Get geotiff file as an array buffer using XMLHttpRequest. Internal use only.
        GeoTiffReader.prototype.requestUrl = function (url, callback) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var arrayBuffer = xhr.response;
                        if (arrayBuffer) {
                            this.parse(arrayBuffer);
                            callback();
                        }
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "GeoTiff retrieval failed (" + xhr.statusText + "): " + url);
                    }
                }
            }).bind(this);

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoTiff retrieval failed: " + url);
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "GeoTiff retrieval timed out: " + url);
            };

            xhr.send(null);
        };

        // Parse geotiff file. Internal use only
        GeoTiffReader.prototype.parse = function (arrayBuffer) {
            this._geoTiffData = new DataView(arrayBuffer);
            this.getEndianness();

            if (!this.isTiffFileType()) {
                throw new AbstractError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "parse", "invalidTiffFileType"));
            }

            var firstIFDOffset = GeoTiffUtil.getBytes(this.geoTiffData, 4, 4, this.isLittleEndian);

            this.parseImageFileDirectory(firstIFDOffset);
            this.getMetadataFromImageFileDirectory();
            this.parseGeoKeys();
            this.setBBox();
        };

        // Get byte order of the geotiff file. Internal use only.
        GeoTiffReader.prototype.getEndianness = function () {
            var byteOrderValue = GeoTiffUtil.getBytes(this.geoTiffData, 0, 2, this.isLittleEndian);
            if (byteOrderValue === 0x4949) {
                this._isLittleEndian = true;
            }
            else if (byteOrderValue === 0x4D4D) {
                this._isLittleEndian = false;
            }
            else {
                throw new AbstractError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "getEndianness", "invalidByteOrderValue"));
            }
        };

        /**
         * Indicates whether this geotiff is a tiff file type.
         *
         * @return {Boolean} True if this geotiff file is a tiff file type.
         */
        GeoTiffReader.prototype.isTiffFileType = function () {
            var fileTypeValue = GeoTiffUtil.getBytes(this.geoTiffData, 2, 2, this.isLittleEndian);
            if (fileTypeValue === 42) {
                return true;
            }
            else {
                return false;
            }
        };

        /**
         * Indicates whether this geotiff is a geotiff file type.
         *
         * @return {Boolean} True if this geotiff file is a geotiff file type.
         */
        GeoTiffReader.prototype.isGeoTiff = function () {
            if (this.getIFDByTag(GeoTiffConstants.Tag.GEO_KEY_DIRECTORY)) {
                return true;
            }
            else {
                return false;
            }
        };

        /**
         * Retrieves the GeoTiff file, parses it and creates a canvas of its content. The canvas is passed
         * to the callback function as a parameter.
         *
         * @param {Function} callback A function called when GeoTiff parsing is complete.
         */
        GeoTiffReader.prototype.readAsImage = function (callback) {
            this.requestUrl(this.url, (function () {
                var bitsPerSample = this.metadata.bitsPerSample;
                var samplesPerPixel = this.metadata.samplesPerPixel;
                var photometricInterpretation = this.metadata.photometricInterpretation;
                var imageLength = this.metadata.imageLength;
                var imageWidth = this.metadata.imageWidth;

                if (this.metadata.colorMap) {
                    var colorMapValues = this.metadata.colorMap;
                    var colorMapSampleSize = Math.pow(2, bitsPerSample[0]);
                }

                var canvas = document.createElement('canvas');
                canvas.width = imageWidth;
                canvas.height = imageLength;
                var ctx = canvas.getContext("2d");

                if (this.metadata.stripOffsets) {
                    var strips = this.parseStrips(false);
                    if (this.metadata.rowsPerStrip) {
                        var rowsPerStrip = this.metadata.rowsPerStrip;
                    } else {
                        var rowsPerStrip = imageLength;
                    }
                    var numOfStrips = strips.length;
                    var numRowsInPreviousStrip = 0;
                    var numRowsInStrip = rowsPerStrip;
                    var imageLengthModRowsPerStrip = imageLength % rowsPerStrip;
                    var rowsInLastStrip = (imageLengthModRowsPerStrip === 0) ? rowsPerStrip :
                        imageLengthModRowsPerStrip;

                    for (var i = 0; i < numOfStrips; i++) {
                        if ((i + 1) === numOfStrips) {
                            numRowsInStrip = rowsInLastStrip;
                        }

                        var numOfPixels = strips[i].length;
                        var yPadding = numRowsInPreviousStrip * i;

                        for (var y = 0, j = 0; y < numRowsInStrip, j < numOfPixels; y++) {
                            for (var x = 0; x < imageWidth; x++, j++) {
                                var pixelSamples = strips[i][j];

                                ctx.fillStyle = this.getFillStyle(
                                    pixelSamples,
                                    photometricInterpretation,
                                    bitsPerSample,
                                    samplesPerPixel,
                                    colorMapValues,
                                    colorMapSampleSize
                                );
                                ctx.fillRect(x, yPadding + y, 1, 1);
                            }
                        }
                        numRowsInPreviousStrip = rowsPerStrip;
                    }
                }
                else if (this.metadata.tileOffsets) {
                    var tiles = this.parseTiles(false);
                    var tileWidth = this.metadata.tileWidth;
                    var tileLength = this.metadata.tileLength;
                    var tilesAcross = Math.ceil(imageWidth / tileWidth);

                    for (var y = 0; y < imageLength; y++) {
                        for (var x = 0; x < imageWidth; x++) {
                            var tileAcross = Math.floor(x / tileWidth);
                            var tileDown = Math.floor(y / tileLength);
                            var tileIndex = tileDown * tilesAcross + tileAcross;
                            var xInTile = x % tileWidth;
                            var yInTile = y % tileLength;
                            var sampleIndex = yInTile * tileWidth + xInTile;
                            var pixelSamples = tiles[tileIndex][sampleIndex];
                            ctx.fillStyle = this.getFillStyle(
                                pixelSamples,
                                photometricInterpretation,
                                bitsPerSample,
                                samplesPerPixel,
                                colorMapValues,
                                colorMapSampleSize
                            );
                            ctx.fillRect(x, y, 1, 1);
                        }
                    }
                }

                this._geoTiffData = null;
                callback(canvas);
            }).bind(this));
        };

        // Get pixel fill style. Internal use only.
        GeoTiffReader.prototype.getFillStyle = function (pixelSamples, photometricInterpretation, bitsPerSample,
                                                         samplesPerPixel, colorMapValues, colorMapSampleSize) {
            var red = 0.0;
            var green = 0.0;
            var blue = 0.0;
            var opacity = 1.0;

            if (this.metadata.noData && pixelSamples[0] == this.metadata.noData) {
                opacity = 0.0;
            }

            switch (photometricInterpretation) {
                case TiffConstants.PhotometricInterpretation.WHITE_IS_ZERO:
                    var invertValue = Math.pow(2, bitsPerSample) - 1;
                    pixelSamples[0] = invertValue - pixelSamples[0];
                case TiffConstants.PhotometricInterpretation.BLACK_IS_ZERO:
                    red = green = blue = GeoTiffUtil.clampColorSample(
                        pixelSamples[0],
                        bitsPerSample[0]);
                    break;
                case TiffConstants.PhotometricInterpretation.RGB:
                    red = GeoTiffUtil.clampColorSample(pixelSamples[0], bitsPerSample[0]);
                    green = GeoTiffUtil.clampColorSample(pixelSamples[1], bitsPerSample[1]);
                    blue = GeoTiffUtil.clampColorSample(pixelSamples[2], bitsPerSample[2]);

                    if (samplesPerPixel === 4 && this.metadata.extraSamples[0] === 2) {
                        var maxValue = Math.pow(2, bitsPerSample[3]);
                        opacity = pixelSamples[3] / maxValue;
                    }
                    break;
                case TiffConstants.PhotometricInterpretation.RGB_PALETTE:
                    if (colorMapValues) {
                        var colorMapIndex = pixelSamples[0];

                        red = GeoTiffUtil.clampColorSample(
                            colorMapValues[colorMapIndex],
                            16);
                        green = GeoTiffUtil.clampColorSample(
                            colorMapValues[colorMapSampleSize + colorMapIndex],
                            16);
                        blue = GeoTiffUtil.clampColorSample(
                            colorMapValues[(2 * colorMapSampleSize) + colorMapIndex],
                            16);
                    }
                    break;
                case TiffConstants.PhotometricInterpretation.TRANSPARENCY_MASK:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Photometric interpretation not yet implemented: " +
                        "TRANSPARENCY_MASK");
                    break;
                case TiffConstants.PhotometricInterpretation.CMYK:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Photometric interpretation not yet implemented: CMYK");
                    break;
                case TiffConstants.PhotometricInterpretation.Y_Cb_Cr:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Photometric interpretation not yet implemented: Y_Cb_Cr");
                    break;
                case TiffConstants.PhotometricInterpretation.CIE_LAB:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Photometric interpretation not yet implemented: CIE_LAB");
                    break;
                default:
                    //todo
                    Logger.log("Unknown photometric interpretation: " + photometricInterpretation);
                    break;
            }

            return GeoTiffUtil.getRGBAFillValue(red, green, blue, opacity);
        }

        GeoTiffReader.prototype.createTypedElevationArray = function () {
            var elevationArray = [], typedElevationArray;
            var bitsPerSample = this.metadata.bitsPerSample[0];

            if (this.metadata.stripOffsets) {
                var strips = this.parseStrips(true);

                for (var i = 0; i < strips.length; i++) {
                    elevationArray = elevationArray.concat(strips[i]);
                }
            }
            else if (this.metadata.tileOffsets) {
                var tiles = this.parseTiles(true);
                var imageWidth = this.metadata.imageWidth;
                var imageLength = this.metadata.imageLength;
                var tileWidth = this.metadata.tileWidth;
                var tileLength = this.metadata.tileLength;
                var tilesAcross = Math.ceil(imageWidth / tileWidth);

                for (var y = 0; y < imageLength; y++) {
                    for (var x = 0; x < imageWidth; x++) {
                        var tileAcross = Math.floor(x / tileWidth);
                        var tileDown = Math.floor(y / tileLength);
                        var tileIndex = tileDown * tilesAcross + tileAcross;
                        var xInTile = x % tileWidth;
                        var yInTile = y % tileLength;
                        var sampleIndex = yInTile * tileWidth + xInTile;
                        var pixelSamples = tiles[tileIndex][sampleIndex];
                        elevationArray.push(pixelSamples);//todo de 0??? servet
                    }
                }
            }

            if (this.metadata.sampleFormat) {
                var sampleFormat = this.metadata.sampleFormat[0];
            }
            else {
                var sampleFormat = TiffConstants.SampleFormat.UNSIGNED;
            }

            switch (bitsPerSample) {
                case 8:
                    if (sampleFormat === TiffConstants.SampleFormat.SIGNED) {
                        typedElevationArray = new Int8Array(elevationArray);
                    }
                    else {
                        typedElevationArray = new Uint8Array(elevationArray);
                    }
                    break
                case 16:
                    if (sampleFormat === TiffConstants.SampleFormat.SIGNED) {
                        typedElevationArray = new Int16Array(elevationArray);
                    }
                    else {
                        typedElevationArray = new Uint16Array(elevationArray);
                    }
                    break;
                case 32:
                    if (sampleFormat === TiffConstants.SampleFormat.SIGNED) {
                        typedElevationArray = new Int32Array(elevationArray);
                    }
                    else if (sampleFormat === TiffConstants.SampleFormat.IEEE_FLOAT) {
                        typedElevationArray = new Float32Array(elevationArray);
                    }
                    else {
                        typedElevationArray = new Uint32Array(elevationArray);
                    }
                    break;
                case 64:
                    typedElevationArray = new Float64Array(elevationArray);
                    break;
                default:
                    break;
            }

            return typedElevationArray;
        }

        /**
         * Retrieves the GeoTiff file, parses it and creates a typed array of its content. The array is passed
         * to the callback function as a parameter.
         *
         * @param {Function} callback A function called when GeoTiff parsing is complete.
         */
        GeoTiffReader.prototype.readAsData = function (callback) {
            this.requestUrl(this.url, (function () {
                callback(
                    this.createTypedElevationArray()
                );
            }).bind(this));
        };

        // Parse geotiff strips. Internal use only
        GeoTiffReader.prototype.parseStrips = function (returnElevation) {
            var samplesPerPixel = this.metadata.samplesPerPixel;
            var bitsPerSample = this.metadata.bitsPerSample;
            var stripOffsets = this.metadata.stripOffsets;
            var stripByteCounts = this.metadata.stripByteCounts;
            var compression = this.metadata.compression;
            if (this.metadata.sampleFormat) {
                var sampleFormat = this.metadata.sampleFormat;
            }
            else {
                var sampleFormat = TiffConstants.SampleFormat.UNSIGNED;
            }

            var bitsPerPixel = samplesPerPixel * bitsPerSample[0];
            var bytesPerPixel = bitsPerPixel / 8;

            var strips = [];
            // Loop through strips
            for (var i = 0; i < stripOffsets.length; i++) {
                var stripOffset = stripOffsets[i];
                var stripByteCount = stripByteCounts[i];

                strips[i] = this.parseBlock(returnElevation, compression, bytesPerPixel, stripByteCount, stripOffset,
                    bitsPerSample, sampleFormat);
            }

            return strips;
        }

        // Parse geotiff block. A block may be a strip or a tile. Internal use only.
        GeoTiffReader.prototype.parseBlock = function (returnElevation, compression, bytesPerPixel, blockByteCount,
                                                       blockOffset, bitsPerSample, sampleFormat) {
            var block = [];
            switch (compression) {
                case TiffConstants.Compression.UNCOMPRESSED:
                    // Loop through pixels.
                    for (var byteOffset = 0, increment = bytesPerPixel;
                         byteOffset < blockByteCount; byteOffset += increment) {
                        // Loop through samples (sub-pixels).
                        for (var m = 0, pixel = []; m < bitsPerSample.length; m++) {
                            var bytesPerSample = bitsPerSample[m] / 8;
                            var sampleOffset = m * bytesPerSample;

                            pixel.push(GeoTiffUtil.getSampleBytes(
                                this.geoTiffData,
                                blockOffset + byteOffset + sampleOffset,
                                bytesPerSample,
                                sampleFormat[m],
                                this.isLittleEndian));
                        }
                        if (returnElevation) {
                            block.push(pixel[0]);
                        }
                        else {
                            block.push(pixel);
                        }
                    }
                    break;
                case TiffConstants.Compression.CCITT_1D:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Compression type not yet implemented: CCITT_1D");
                    break;
                case TiffConstants.Compression.GROUP_3_FAX:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Compression type not yet implemented: GROUP_3_FAX");
                    break;
                case TiffConstants.Compression.GROUP_4_FAX:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Compression type not yet implemented: GROUP_4_FAX");
                    break;
                case TiffConstants.Compression.LZW:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Compression type not yet implemented: LZW");
                    break;
                case TiffConstants.Compression.JPEG:
                    //todo
                    Logger.log(Logger.LEVEL_WARNING, "Compression type not yet implemented: JPEG");
                    break;
                case TiffConstants.Compression.PACK_BITS:
                    if (this.metadata.tileOffsets) {
                        var tileWidth = this.metadata.tileWidth;
                        var tileLength = this.metadata.tileWidth;
                        var arrayBuffer = new ArrayBuffer(tileWidth * tileLength * bytesPerPixel);
                    }
                    else {
                        var rowsPerStrip = this.metadata.rowsPerStrip;
                        var imageWidth = this.metadata.imageWidth;
                        var arrayBuffer = new ArrayBuffer(rowsPerStrip * imageWidth * bytesPerPixel);
                    }

                    var uncompressedDataView = new DataView(arrayBuffer);

                    var newBlock = true;
                    var pixel = [];
                    var blockLength = 0;
                    var numOfIterations = 0;
                    var uncompressedOffset = 0;


                    for (var byteOffset = 0; byteOffset < blockByteCount; byteOffset += 1) {

                        if (newBlock) {
                            blockLength = 1;
                            numOfIterations = 1;
                            newBlock = false;

                            var nextSourceByte = this.geoTiffData.getInt8(blockOffset + byteOffset,
                                this.isLittleEndian);

                            if (nextSourceByte >= 0 && nextSourceByte <= 127) {
                                blockLength = nextSourceByte + 1;
                            }
                            else if (nextSourceByte >= -127 && nextSourceByte <= -1) {
                                numOfIterations = -nextSourceByte + 1;
                            }
                            else {
                                newBlock = true;
                            }
                        }
                        else {
                            var currentByte = GeoTiffUtil.getBytes(
                                this.geoTiffData,
                                blockOffset + byteOffset,
                                1,
                                this.isLittleEndian);

                            for (var currentIteration = 0; currentIteration < numOfIterations; currentIteration++) {
                                uncompressedDataView.setInt8(uncompressedOffset, currentByte);
                                uncompressedOffset++;
                            }

                            blockLength--;

                            if (blockLength === 0) {
                                newBlock = true;
                            }
                        }
                    }

                    for (var byteOffset = 0, increment = bytesPerPixel;
                         byteOffset < arrayBuffer.byteLength; byteOffset += increment) {
                        // Loop through samples (sub-pixels).
                        for (var m = 0, pixel = []; m < bitsPerSample.length; m++) {
                            var bytesPerSample = bitsPerSample[m] / 8;
                            var sampleOffset = m * bytesPerSample;

                            pixel.push(GeoTiffUtil.getSampleBytes(
                                uncompressedDataView,
                                byteOffset + sampleOffset,
                                bytesPerSample,
                                sampleFormat[m],
                                this.isLittleEndian));
                        }
                        if (returnElevation) {
                            block.push(pixel[0]);
                        }
                        else {
                            block.push(pixel);
                        }
                    }
                    break;
            }

            return block;
        }

        // Parse geotiff tiles. Internal use only
        GeoTiffReader.prototype.parseTiles = function (returnElevation) {
            var samplesPerPixel = this.metadata.samplesPerPixel;
            var bitsPerSample = this.metadata.bitsPerSample;
            var compression = this.metadata.compression;
            if (this.metadata.sampleFormat) {
                var sampleFormat = this.metadata.sampleFormat;
            }
            else {
                var sampleFormat = new Array(samplesPerPixel);
                WWUtil.fillArray(sampleFormat, TiffConstants.SampleFormat.UNSIGNED);
            }
            var bitsPerPixel = samplesPerPixel * bitsPerSample[0];
            var bytesPerPixel = bitsPerPixel / 8;
            var tileWidth = this.metadata.tileWidth;
            var tileLength = this.metadata.tileLength;
            var tileOffsets = this.metadata.tileOffsets;
            var tileByteCounts = this.metadata.tileByteCounts;
            var imageLength = this.metadata.imageLength;
            var imageWidth = this.metadata.imageWidth;

            var tilesAcross = Math.ceil(imageWidth / tileWidth);
            var tilesDown = Math.ceil(imageLength / tileLength);

            var tiles = [];

            for (var i = 0; i < tilesDown; i++) {
                for (var j = 0; j < tilesAcross; j++) {
                    var index = tilesAcross * i + j;
                    var tileOffset = tileOffsets[index];
                    var tileByteCount = tileByteCounts[index];
                    tiles[index] = this.parseBlock(returnElevation, compression, bytesPerPixel, tileByteCount,
                        tileOffset, bitsPerSample, sampleFormat);
                }
            }

            return tiles;
        }

        // Translate a pixel/line coordinates to projection coordinate. Internal use only.
        GeoTiffReader.prototype.geoTiffImageToPCS = function (xValue, yValue) {
            if (xValue === null || xValue === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "geoTiffImageToPCS", "missingXValue"));
            }
            if (yValue === null || yValue === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "geoTiffImageToPCS", "missingYValue"));
            }

            var res = [xValue, yValue];

            var tiePointValues = this.metadata.modelTiepoint;
            var modelPixelScaleValues = this.metadata.modelPixelScale;
            var modelTransformationValues = this.metadata.modelTransformation;

            var tiePointCount = tiePointValues ? tiePointValues.length : 0;
            var modelPixelScaleCount = modelPixelScaleValues ? modelPixelScaleValues.length : 0;
            var modelTransformationCount = modelTransformationValues ? modelTransformationValues.length : 0;

            if (tiePointCount > 6 && modelPixelScaleCount === 0) {
                //todo
            }
            else if (modelTransformationCount === 16) {
                var x_in = xValue;
                var y_in = yValue;

                xValue = x_in * modelTransformationValues[0] + y_in * modelTransformationValues[1] +
                    modelTransformationValues[3];
                yValue = x_in * modelTransformationValues[4] + y_in * modelTransformationValues[5] +
                    modelTransformationValues[7];

                res = [xValue, yValue];
            }
            else if (modelPixelScaleCount < 3 || tiePointCount < 6) {
                res = [xValue, yValue];
            }
            else {
                xValue = (xValue - tiePointValues[0]) * modelPixelScaleValues[0] + tiePointValues[3];
                yValue = (yValue - tiePointValues[1]) * (-1 * modelPixelScaleValues[1]) + tiePointValues[4];

                res = [xValue, yValue];
            }

            Proj4.defs([
                [
                    'EPSG:26771',
                    '+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +' +
                    'x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs '
                ],
                [
                    'EPSG:32633',
                    '+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs'
                ]
            ]);

            if (this.metadata.projectedCSType) {
                res = Proj4('EPSG:' + this.metadata.projectedCSType, 'EPSG:4326', res);
            }

            return new Location(res[1], res[0]);
        };

        /**
         * Set the bounding box of the geotiff file. Internal use only.
         */
        GeoTiffReader.prototype.setBBox = function () {
            var upperLeft = this.geoTiffImageToPCS(0, 0);
            var upperRight = this.geoTiffImageToPCS(this.metadata.imageWidth, 0);
            var lowerLeft = this.geoTiffImageToPCS(0, this.metadata.imageLength);
            var lowerRight = this.geoTiffImageToPCS(
                this.metadata.imageWidth, this.metadata.imageLength);

            this.metadata.bbox = new Sector(
                lowerLeft.latitude,
                upperLeft.latitude,
                upperLeft.longitude,
                upperRight.longitude
            );
        }

        // Get metadata from image file directory. Internal use only.
        GeoTiffReader.prototype.getMetadataFromImageFileDirectory = function () {
            for (var i = 0; i < this.imageFileDirectories[0].length; i++) {

                switch (this.imageFileDirectories[0][i].tag) {
                    case TiffConstants.Tag.BITS_PER_SAMPLE:
                        this.metadata.bitsPerSample = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.COLOR_MAP:
                        this.metadata.colorMap = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.COMPRESSION:
                        this.metadata.compression = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.EXTRA_SAMPLES:
                        this.metadata.extraSamples = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.IMAGE_LENGTH:
                        this.metadata.imageLength = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.IMAGE_WIDTH:
                        this.metadata.imageWidth = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.MAX_SAMPLE_VALUE:
                        this.metadata.maxSampleValue = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.MIN_SAMPLE_VALUE:
                        this.metadata.minSampleValue = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.ORIENTATION:
                        this.metadata.orientation = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.PHOTOMETRIC_INTERPRETATION:
                        this.metadata.photometricInterpretation = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.PLANAR_CONFIGURATION:
                        this.metadata.planarConfiguration = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.ROWS_PER_STRIP:
                        this.metadata.rowsPerStrip = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.RESOLUTION_UNIT:
                        this.metadata.resolutionUnit = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.SAMPLES_PER_PIXEL:
                        this.metadata.samplesPerPixel = this.imageFileDirectories[0][i].getIFDEntryValue()[0];
                        break;
                    case TiffConstants.Tag.SAMPLE_FORMAT:
                        this.metadata.sampleFormat = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.SOFTWARE:
                        this.metadata.software = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.STRIP_BYTE_COUNTS:
                        this.metadata.stripByteCounts = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.STRIP_OFFSETS:
                        this.metadata.stripOffsets = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.TILE_BYTE_COUNTS:
                        this.metadata.tileByteCounts = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.TILE_OFFSETS:
                        this.metadata.tileOffsets = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.TILE_LENGTH:
                        this.metadata.tileLength = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.TILE_WIDTH:
                        this.metadata.tileWidth = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.X_RESOLUTION:
                        this.metadata.xResolution = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case TiffConstants.Tag.Y_RESOLUTION:
                        this.metadata.tileWidth = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;

                    //geotiff
                    case GeoTiffConstants.Tag.GEO_ASCII_PARAMS:
                        this.metadata.geoAsciiParams = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case GeoTiffConstants.Tag.GEO_DOUBLE_PARAMS:
                        this.metadata.geoDubleParams = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case GeoTiffConstants.Tag.GEO_KEY_DIRECTORY:
                        this.metadata.geoKeyDirectory = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case GeoTiffConstants.Tag.MODEL_PIXEL_SCALE:
                        this.metadata.modelPixelScale = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case GeoTiffConstants.Tag.MODEL_TIEPOINT:
                        this.metadata.modelTiepoint = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    case GeoTiffConstants.Tag.GDAL_NODATA:
                        this.metadata.noData = this.imageFileDirectories[0][i].getIFDEntryValue();
                        break;
                    default:
                        Logger.log(Logger.LEVEL_WARNING, "Ignored GeoTiff tag: " + this.imageFileDirectories[0][i].tag);
                }
            }
        }

        // Get metadata from GeoKeys. Internal use only.
        GeoTiffReader.prototype.getMetadataFromGeoKeys = function () {
            for (var i = 0; i < this.geoKeys.length; i++) {
                var keyAsString = GeoTiffUtil.getTagValueAsString(GeoTiffConstants.Key, this.geoKeys[i].keyId);

                if (keyAsString) {
                    this._metadata.geotiff.geoKeys[keyAsString] = this.geoKeys[i].getGeoKeyValue(
                        this.metadata.geoDoubleParams,
                        this.metadata.geoAsciiParams);
                }
                else {
                    Logger.log(Logger.LEVEL_WARNING, "Unknown GeoTiff key: " + this.geoKeys[i].keyId);
                }
            }
        }

        // Parse GeoKeys. Internal use only.
        GeoTiffReader.prototype.parseGeoKeys = function () {
            if (!this.isGeoTiff()) {
                throw new AbstractError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "parse", "invalidGeoTiffFile"));
            }

            var geoKeyDirectory = this.metadata.geoKeyDirectory;
            if (geoKeyDirectory) {
                var keyDirectoryVersion = geoKeyDirectory[0];
                var keyRevision = geoKeyDirectory[1];
                var minorRevision = geoKeyDirectory[2];
                var numberOfKeys = geoKeyDirectory[3];

                for (var i = 0; i < numberOfKeys; i++) {
                    var keyId = geoKeyDirectory[4 + i * 4];
                    var tiffTagLocation = geoKeyDirectory[5 + i * 4];
                    var count = geoKeyDirectory[6 + i * 4];
                    var valueOffset = geoKeyDirectory[7 + i * 4];

                    switch (keyId) {
                        case GeoTiffConstants.Key.GTModelTypeGeoKey:
                            this.metadata.gtModelTypeGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GTRasterTypeGeoKey:
                            this.metadata.gtRasterTypeGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GTCitationGeoKey:
                            this.metadata.gtCitationGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeographicTypeGeoKey:
                            this.metadata.geographicTypeGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeogCitationGeoKey:
                            this.metadata.geogCitationGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeogAngularUnitsGeoKey:
                            this.metadata.geogAngularUnitsGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeogAngularUnitSizeGeoKey:
                            this.metadata.geogAngularUnitSizeGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeogSemiMajorAxisGeoKey:
                            this.metadata.geogSemiMajorAxisGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.GeogInvFlatteningGeoKey:
                            this.metadata.geogInvFlatteningGeoKey =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        case GeoTiffConstants.Key.ProjectedCSTypeGeoKey:
                            this.metadata.projectedCSType =
                                new GeoTiffKeyEntry(keyId, tiffTagLocation, count, valueOffset).getGeoKeyValue(
                                    this.metadata.geoDoubleParams,
                                    this.metadata.geoAsciiParams);
                            break;
                        default:
                            Logger.log(Logger.LEVEL_WARNING, "Ignored GeoTiff key: " + keyId);
                            break;

                    }
                }
            }
            else {
                throw new AbstractError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "parseGeoKeys",
                        "missingGeoKeyDirectoryTag"));
            }
        };

        // Parse image file directory. Internal use only.
        GeoTiffReader.prototype.parseImageFileDirectory = function (offset) {
            if (!offset) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "parseImageFileDirectory",
                        "missingOffset"));
            }

            var noOfDirectoryEntries = GeoTiffUtil.getBytes(this.geoTiffData, offset, 2, this.isLittleEndian);

            var directoryEntries = [];

            for (var i = offset + 2, directoryEntryCounter = 0; directoryEntryCounter < noOfDirectoryEntries;
                 i += 12, directoryEntryCounter++) {
                var tag = GeoTiffUtil.getBytes(this.geoTiffData, i, 2, this.isLittleEndian);
                var type = GeoTiffUtil.getBytes(this.geoTiffData, i + 2, 2, this.isLittleEndian);
                var count = GeoTiffUtil.getBytes(this.geoTiffData, i + 4, 4, this.isLittleEndian);
                var valueOffset = GeoTiffUtil.getBytes(this.geoTiffData, i + 8, 4, this.isLittleEndian);

                directoryEntries.push(new TiffIFDEntry(
                    tag,
                    type,
                    count,
                    valueOffset,
                    this.geoTiffData,
                    this.isLittleEndian));
            }

            this._imageFileDirectories.push(directoryEntries);

            var nextIFDOffset = GeoTiffUtil.getBytes(this.geoTiffData, i, 4, this.isLittleEndian);

            if (nextIFDOffset === 0) {
                return;
            }
            else {
                this.parseImageFileDirectory(nextIFDOffset);
            }
        };

        // Get image file directory by tag value. Internal use only.
        GeoTiffReader.prototype.getIFDByTag = function (tag) {
            if (!tag) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "GeoTiffReader", "getIFDByTag", "missingTag"));
            }

            for (var i = 0; i < this.imageFileDirectories[0].length; i++) {
                if (this.imageFileDirectories[0][i].tag === tag) {
                    return this.imageFileDirectories[0][i];
                }
            }

            return null;
        }

        return GeoTiffReader;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionEquirectangular
 */
define('projections/ProjectionEquirectangular',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger',
        '../geom/Vec3'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger,
              Vec3) {
        "use strict";

        /**
         * Constructs an Equirectangular geographic projection, also known as Equidistant Cylindrical, Plate Carree and
         * Rectangular. The projected globe is spherical, not ellipsoidal.
         * @alias ProjectionEquirectangular
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents an equirectangular geographic projection.
         */
        var ProjectionEquirectangular = function () {

            GeographicProjection.call(this, "Equirectangular", true, null);
        };

        ProjectionEquirectangular.prototype = Object.create(GeographicProjection.prototype);

        Object.defineProperties(ProjectionEquirectangular.prototype, {
            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionEquirectangular.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return "projection equirectangular ";
                }
            }
        });

        // Documented in base class.
        ProjectionEquirectangular.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                              offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesian", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesian", "missingResult"));
            }

            result[0] = globe.equatorialRadius * longitude * Angle.DEGREES_TO_RADIANS + (offset ? offset[0] : 0);
            result[1] = globe.equatorialRadius * latitude * Angle.DEGREES_TO_RADIANS;
            result[2] = elevation;

            return result;
        };

        // Documented in base class.
        ProjectionEquirectangular.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon,
                                                                                  elevations, referencePoint,
                                                                                  offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesianGrid", "missingSector"));
            }

            if (!elevations || elevations.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesianGrid",
                    "The specified elevations array is null, undefined or insufficient length"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "geographicToCartesianGrid", "missingResult"));
            }

            var eqr = globe.equatorialRadius,
                minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                refPoint = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                offsetX = offset ? offset[0] : 0,
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                lat, lon, y;

            // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian
            // point corresponding to each latitude and longitude.
            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                y = eqr * lat - refPoint[1];

                for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                    if (lonIndex === numLon - 1) {
                        lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                    }

                    result[resultIndex++] = eqr * lon - refPoint[0] + offsetX;
                    result[resultIndex++] = y;
                    result[resultIndex++] = elevations[elevIndex++] - refPoint[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionEquirectangular.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "cartesianToGeographic", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "cartesianToGeographic", "missingResult"));
            }

            result.latitude = (y / globe.equatorialRadius) * Angle.RADIANS_TO_DEGREES;
            result.longitude = ((x - (offset ? offset[0] : 0)) / globe.equatorialRadius) * Angle.RADIANS_TO_DEGREES;
            result.altitude = z;

            return result;
        };

        return ProjectionEquirectangular;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ZeroElevationModel
 */
define('globe/ZeroElevationModel',[
        '../error/ArgumentError',
        '../globe/ElevationModel',
        '../geom/Location',
        '../util/Logger',
        '../geom/Sector'],
    function (ArgumentError,
              ElevationModel,
              Location,
              Logger,
              Sector) {
        "use strict";

        /**
         * Constructs a Zero elevation model whose elevations are zero at every location.
         * @alias ZeroElevationModel
         * @constructor
         * @classdesc Represents an elevation model whose elevations are zero at all locations.
         * @augments ElevationModel
         */
        var ZeroElevationModel = function () {
            ElevationModel.call(this, Sector.FULL_SPHERE, new Location(45, 45), 1, " ", " ", 150, 150);

            /**
             * Indicates this elevation model's display name.
             * @type {string}
             * @default "Zero Elevations"
             */
            this.displayName = "Zero Elevations";

            /**
             * Indicates the last time this elevation model changed. Since a zero elevation model never changes, this
             * property always returns the date and time at which the elevation model was constructed, in milliseconds
             * since midnight Jan 1, 1970.
             * @type {number}
             * @default Date.getTime() at construction
             * @readonly
             */
            this.timestamp = Date.now();

            /**
             * This elevation model's minimum elevation, which is always 0.
             * @type {number}
             * @default 0
             * @readonly
             */
            this.minElevation = 0;

            /**
             * This elevation model's maximum elevation, which is always 0.
             * @type {number}
             * @default 0
             * @readonly
             */
            this.maxElevation = 0;
        };

        // Inherit from the abstract elevation model class.
        ZeroElevationModel.prototype = Object.create(ElevationModel.prototype);

        /**
         * Returns minimum and maximum elevations of 0.
         * @param {Sector} sector The sector for which to determine extreme elevations.
         * @returns {Number[]} An array containing minimum and maximum elevations of 0.
         */
        ZeroElevationModel.prototype.minAndMaxElevationsForSector = function (sector) {
            return [0, 0];
        };

        /**
         * Returns 0 as the elevation at a specified location.
         * @param {Number} latitude The location's latitude in degrees.
         * @param {Number} longitude The location's longitude in degrees.
         * @returns {Number} 0.
         */
        ZeroElevationModel.prototype.elevationAtLocation = function (latitude, longitude) {
            return 0;
        };

        /**
         * Returns the elevations at locations within a specified sector. For this elevation model they are all 0.
         * @param {Sector} sector The sector for which to determine the elevations.
         * @param {Number} numLat The number of latitudinal sample locations within the sector.
         * @param {Number} numLon The number of longitudinal sample locations within the sector.
         * @param {Number} targetResolution The desired elevation resolution.
         * @param {Number[]} result An array of size numLat x numLon to contain the requested elevations.
         * This array must be allocated when passed to this function.
         * @returns {Number} The resolution actually achieved, which may be greater than that requested if the
         * elevation data for the requested resolution is not currently available.
         * @throws {ArgumentError} If the specified sector or result array is null or undefined, if either of the
         * specified numLat or numLon values is less than 1, or the result array is not of sufficient length
         * to hold numLat x numLon values.
         */
        ZeroElevationModel.prototype.elevationsForGrid = function (sector, numLat, numLon, targetResolution, result) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ZeroElevationModel", "elevationsForSector", "missingSector"));
            }

            if (numLat <= 0 || numLon <= 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ZeroElevationModel",
                    "elevationsForSector", "numLat or numLon is less than 1"));
            }

            if (!result || result.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ZeroElevationModel",
                    "elevationsForSector", "missingArray"));
            }

            for (var i = 0, len = result.length; i < len; i++) {
                result[i] = 0;
            }

            return 0;
        };

        return ZeroElevationModel;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Globe2D
 */
define('globe/Globe2D',[
        '../globe/Globe',
        '../projections/ProjectionEquirectangular',
        '../globe/ZeroElevationModel'
    ],
    function (Globe,
              ProjectionEquirectangular,
              ZeroElevationModel) {
        "use strict";

        /**
         * Constructs a 2D globe with a default {@link ZeroElevationModel} and
         * [equirectangular projection]{@link ProjectionEquirectangular}.
         * @alias Globe2D
         * @constructor
         * @augments Globe
         * @classdesc Represents a 2D flat globe with a configurable projection.
         * The default rectangular projection scrolls longitudinally.
         */
        var Globe2D = function () {
            Globe.call(this, new ZeroElevationModel(), new ProjectionEquirectangular());
        };

        Globe2D.prototype = Object.create(Globe.prototype);

        return Globe2D;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports GoToAnimator
 */
define('util/GoToAnimator',[
        '../geom/Location',
        '../util/Logger',
        '../geom/Position',
        '../geom/Vec3'
    ],
    function (Location,
              Logger,
              Position,
              Vec3) {
        "use strict";

        /**
         * Constructs a GoTo animator.
         * @alias GoToAnimator
         * @constructor
         * @classdesc Incrementally and smoothly moves a {@link Navigator} to a specified position.
         * @param {WorldWindow} worldWindow The WorldWindow in which to perform the animation.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var GoToAnimator = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GoToAnimator", "constructor",
                    "missingWorldWindow"));
            }

            /**
             * The WorldWindow associated with this animator.
             * @type {WorldWindow}
             * @readonly
             */
            this.wwd = worldWindow;

            /**
             * The frequency in milliseconds at which to animate the position change.
             * @type {Number}
             * @default 20
             */
            this.animationFrequency = 20;

            /**
             * The animation's duration, in milliseconds. When the distance is short, less than twice the viewport
             * size, the travel time is reduced proportionally to the distance to travel. It therefore takes less
             * time to move shorter distances.
             * @type {Number}
             * @default 3000
             */
            this.travelTime = 3000;

            /**
             * Indicates whether the current or most recent animation has been cancelled. Use the cancel() function
             * to cancel an animation.
             * @type {Boolean}
             * @default false
             * @readonly
             */
            this.cancelled = false;
        };

        // Stop the current animation.
        GoToAnimator.prototype.cancel = function () {
            this.cancelled = true;
        };

        /**
         * Moves the navigator to a specified location or position.
         * @param {Location | Position} position The location or position to move the navigator to. If this
         * argument contains an "altitude" property, as {@link Position} does, the end point of the navigation is
         * at the specified altitude. Otherwise the end point is at the current altitude of the navigator.
         * @param {Function} completionCallback If not null or undefined, specifies a function to call when the
         * animation completes. The completion callback is called with a single argument, this animator.
         * @throws {ArgumentError} If the specified location or position is null or undefined.
         */
        GoToAnimator.prototype.goTo = function (position, completionCallback) {
            if (!position) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GoToAnimator", "goTo",
                    "missingPosition"));
            }

            this.completionCallback = completionCallback;

            // Reset the cancellation flag.
            this.cancelled = false;

            // Capture the target position and determine its altitude.
            this.targetPosition = new Position(position.latitude, position.longitude,
                position.altitude || this.wwd.navigator.range);

            // Capture the start position and start time.
            this.startPosition = new Position(
                this.wwd.navigator.lookAtLocation.latitude,
                this.wwd.navigator.lookAtLocation.longitude,
                this.wwd.navigator.range);
            this.startTime = Date.now();

            // Determination of the pan and range velocities requires the distance to be travelled.
            var animationDuration = this.travelTime,
                panDistance = Location.greatCircleDistance(this.startPosition, this.targetPosition),
                rangeDistance;

            // Determine how high we need to go to give the user context. The max altitude computed is approximately
            // that needed to fit the start and end positions in the same viewport assuming a 45 degree field of view.
            var pA = this.wwd.globe.computePointFromLocation(
                    this.startPosition.latitude, this.startPosition.longitude, new Vec3(0, 0, 0)),
                pB = this.wwd.globe.computePointFromLocation(
                    this.targetPosition.latitude, this.targetPosition.longitude, new Vec3(0, 0, 0));
            this.maxAltitude = pA.distanceTo(pB);

            // Determine an approximate viewport size in radians in order to determine whether we actually change
            // the range as we pan to the new location. We don't want to change the range if the distance between
            // the start and target positions is small relative to the current viewport.
            var viewportSize = this.wwd.navigator.currentState().pixelSizeAtDistance(this.startPosition.altitude)
                * this.wwd.canvas.clientWidth / this.wwd.globe.equatorialRadius;

            if (panDistance <= 2 * viewportSize) {
                // Start and target positions are close, so don't back out.
                this.maxAltitude = this.startPosition.altitude;
            }

            // We need to capture the time the max altitude is reached in order to begin decreasing the range
            // midway through the animation. If we're already above the max altitude, then that time is now since
            // we don't back out if the current altitude is above the computed max altitude.
            this.maxAltitudeReachedTime = this.maxAltitude <= this.wwd.navigator.range ? Date.now() : null;

            // Compute the total range to travel since we need that to compute the range velocity.
            // Note that the range velocity and pan velocity are computed so that the respective animations, which
            // operate independently, finish at the same time.
            if (this.maxAltitude > this.startPosition.altitude) {
                rangeDistance = Math.max(0, this.maxAltitude - this.startPosition.altitude);
                rangeDistance += Math.abs(this.targetPosition.altitude - this.maxAltitude);
            } else {
                rangeDistance = Math.abs(this.targetPosition.altitude - this.startPosition.altitude);
            }

            // Determine which distance governs the animation duration.
            var animationDistance = Math.max(panDistance, rangeDistance / this.wwd.globe.equatorialRadius);
            if (animationDistance === 0) {
                return; // current and target positions are the same
            }

            if (animationDistance < 2 * viewportSize) {
                // Start and target positions are close, so reduce the travel time based on the
                // distance to travel relative to the viewport size.
                animationDuration = Math.min((animationDistance / viewportSize) * this.travelTime, this.travelTime);
            }

            // Don't let the animation duration go to 0.
            animationDuration = Math.max(1, animationDuration);

            // Determine the pan velocity, in radians per millisecond.
            this.panVelocity = panDistance / animationDuration;

            // Determine the range velocity, in meters per millisecond.
            this.rangeVelocity = rangeDistance / animationDuration; // meters per millisecond

            // Set up the animation timer.
            var thisAnimator = this;
            var timerCallback = function () {
                if (thisAnimator.cancelled) {
                    if (thisAnimator.completionCallback) {
                        thisAnimator.completionCallback(thisAnimator);
                    }
                    return;
                }

                if (thisAnimator.update()) {
                    setTimeout(timerCallback, thisAnimator.animationFrequency);
                } else if (thisAnimator.completionCallback) {
                    thisAnimator.completionCallback(thisAnimator);
                }
            };
            setTimeout(timerCallback, this.animationFrequency); // invoke it the first time
        };

        // Intentionally not documented.
        GoToAnimator.prototype.update = function () {
            // This is the timer callback function. It invokes the range animator and the pan animator.

            var currentPosition = new Position(
                this.wwd.navigator.lookAtLocation.latitude,
                this.wwd.navigator.lookAtLocation.longitude,
                this.wwd.navigator.range);

            var continueAnimation = this.updateRange(currentPosition);
            continueAnimation = this.updateLocation(currentPosition) || continueAnimation;

            this.wwd.redraw();

            return continueAnimation;
        };

        // Intentionally not documented.
        GoToAnimator.prototype.updateRange = function (currentPosition) {
            // This function animates the range.
            var continueAnimation = false,
                nextRange, elapsedTime;

            // If we haven't reached the maximum altitude, then step-wise increase it. Otherwise step-wise change
            // the range towards the target altitude.
            if (!this.maxAltitudeReachedTime) {
                elapsedTime = Date.now() - this.startTime;
                nextRange = Math.min(this.startPosition.altitude + this.rangeVelocity * elapsedTime, this.maxAltitude);
                // We're done if we get withing 1 meter of the desired range.
                if (Math.abs(this.wwd.navigator.range - nextRange) < 1) {
                    this.maxAltitudeReachedTime = Date.now();
                }
                this.wwd.navigator.range = nextRange;
                continueAnimation = true;
            } else {
                elapsedTime = Date.now() - this.maxAltitudeReachedTime;
                if (this.maxAltitude > this.targetPosition.altitude) {
                    nextRange = this.maxAltitude - (this.rangeVelocity * elapsedTime);
                    nextRange = Math.max(nextRange, this.targetPosition.altitude);
                } else {
                    nextRange = this.maxAltitude + (this.rangeVelocity * elapsedTime);
                    nextRange = Math.min(nextRange, this.targetPosition.altitude);
                }
                this.wwd.navigator.range = nextRange;
                // We're done if we get withing 1 meter of the desired range.
                continueAnimation = Math.abs(this.wwd.navigator.range - this.targetPosition.altitude) > 1;
            }

            return continueAnimation;
        };

        // Intentionally not documented.
        GoToAnimator.prototype.updateLocation = function (currentPosition) {
            // This function animates the pan to the desired location.
            var elapsedTime = Date.now() - this.startTime,
                distanceTravelled = Location.greatCircleDistance(this.startPosition, currentPosition),
                distanceRemaining = Location.greatCircleDistance(currentPosition, this.targetPosition),
                azimuthToTarget = Location.greatCircleAzimuth(currentPosition, this.targetPosition),
                distanceForNow = this.panVelocity * elapsedTime,
                nextDistance = Math.min(distanceForNow - distanceTravelled, distanceRemaining),
                nextLocation = Location.greatCircleLocation(currentPosition, azimuthToTarget, nextDistance,
                    new Location(0, 0)),
                locationReached = false;

            this.wwd.navigator.lookAtLocation.latitude = nextLocation.latitude;
            this.wwd.navigator.lookAtLocation.longitude = nextLocation.longitude;

            // We're done if we're within a meter of the desired location.
            if (nextDistance < 1 / this.wwd.globe.equatorialRadius) {
                locationReached = true;
            }

            return !locationReached;
        };

        return GoToAnimator;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports HighlightController
 */
define('util/HighlightController',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a highlight controller and associates it with a specified WorldWindow.
         * @alias HighlightController
         * @constructor
         * @classdesc Monitors mouse-move and touch-device tap events and highlights shapes they identify.
         * @param {WorldWindow} worldWindow The WorldWindow to monitor for mouse-move and tap events.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var HighlightController = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "HighlightController", "constructor",
                    "missingWorldWindow"));
            }

            /**
             * This controller's WorldWindow
             * @type {WorldWindow}
             * @readonly
             */
            this.worldWindow = worldWindow;

            var highlightedItems = [];

            var handlePick = function (o) {
                // The input argument is either an Event or a TapRecognizer. Both have the same properties for determining
                // the mouse or tap location.
                var x = o.clientX,
                    y = o.clientY;

                var redrawRequired = highlightedItems.length > 0; // must redraw if we de-highlight previous shapes

                // De-highlight any previously highlighted shapes.
                for (var h = 0; h < highlightedItems.length; h++) {
                    highlightedItems[h].highlighted = false;
                }
                highlightedItems = [];

                // Perform the pick. Must first convert from window coordinates to canvas coordinates, which are
                // relative to the upper left corner of the canvas rather than the upper left corner of the page.
                var pickList = worldWindow.pick(worldWindow.canvasCoordinates(x, y));
                if (pickList.objects.length > 0) {
                    redrawRequired = true;
                }

                // Highlight the items picked by simply setting their highlight flag to true.
                if (pickList.objects.length > 0) {
                    for (var p = 0; p < pickList.objects.length; p++) {
                        if (!pickList.objects[p].isTerrain) {
                            pickList.objects[p].userObject.highlighted = true;

                            // Keep track of highlighted items in order to de-highlight them later.
                            highlightedItems.push(pickList.objects[p].userObject);
                        }
                    }
                }

                // Update the window if we changed anything.
                if (redrawRequired) {
                    worldWindow.redraw(); // redraw to make the highlighting changes take effect on the screen
                }
            };

            // Listen for mouse moves and highlight the placemarks that the cursor rolls over.
            this.worldWindow.addEventListener("mousemove", handlePick);

            // Listen for taps on mobile devices and highlight the placemarks that the user taps.
            var tapRecognizer = new WorldWind.TapRecognizer(this.worldWindow, handlePick);
        };

        return HighlightController;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlElements',[], function () {
    "use strict";

    //noinspection UnnecessaryLocalVariableJS
    /**
     * Map representing the available Elements. This is solution to circular dependency when
     * parsing some of the elements may be dependent on elements, in which they may be present.
     * Like MultiGeometry present inside of some of the Geometries.
     * @exports KmlElements
     */
    var KmlElements =  {
        /**
         * Internal storage for all key-values pairs
         */
        keys: {},

        /**
         * Adds key representing name of the node and constructor to be used.
         * @param key {String} Name of the node, by which it is retrieved. Name is case sensitive.
         * @param value {KmlObject} Value represent constructor function to be instantiated
         */
        addKey: function (key, value) {
            this.keys[key] = value;
        },

        /**
         * Returns constructor function to be instantiated.
         * @param key {String} Name of the node.
         * @returns {*} Constructor function to be instantiated.
         */
        getKey: function (key) {
            return this.keys[key];
        }
    };

    return KmlElements;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/Attribute',[], function () {
    "use strict";

    /**
     * This class represents abstraction for Attribute. It is possible to test its existence, retrieve value and set
     * value.
     * @alias Attribute
     * @param node {Node} Node on which the attribute exists
     * @param name {String} Name of the attribute
     * @constructor
     */
    var Attribute = function(node, name) {
        this.node = node;
        this.name = name;
    };

    /**
     * It returns value of the attribute. If the attribute doesn't exists it returns null.
     * @returns {String|null}
     */
    Attribute.prototype.value = function(){
        return (this.node.attributes && this.node.attributes.getNamedItem(this.name)&&
            this.node.attributes.getNamedItem(this.name).value) || null;
    };

    /**
     * It returns true if there exists attribute with given name.
     * @returns {boolean}
     */
    Attribute.prototype.exists = function() {
        return this.value() != null;
    };

    /**
     * Value which should be set to the attribute. 
     * @param value {String}
     */
    Attribute.prototype.save = function(value) {
        this.node.setAttribute(this.name, value);
    };

    return Attribute;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/NodeTransformers',[
    './Attribute',
    '../KmlElements',
    '../../../geom/Position',
    '../../../util/WWUtil'
], function(Attribute,
            KmlElements,
            Position,
            WWUtil){
    /**
     * Provides ways for transforming xml nodes to KML objects.
     * @exports NodeTransformers
     */
    var NodeTransformers = function(){};

    // Primitives
    /**
     * Transforms node to its String value.
     * @param node {Node} Node to transform
     * @returns {String} Text representation of node value.
     */
    NodeTransformers.string = function (node) {
        return String(getTextOfNode(node));
    };

    /**
     * Transforms node to its Numeric value.
     * @param node {Node} Node to transform
     * @returns {Number} Numeric representation of node value.
     */
    NodeTransformers.number = function (node) {
        return Number(getTextOfNode(node));
    };

    /**
     * Transforms node to its boolean value.
     * @param node {Node} Node to transform
     * @returns {Boolean} Boolean representation of node value.
     */
    NodeTransformers.boolean = function (node) {
        return WWUtil.transformToBoolean(getTextOfNode(node));
    };

    /**
     * Transform node to the date
     * @param node {Node} Node to transform
     * @returns {Date} Date representing current node.
     */
    NodeTransformers.date = function(node) {
        return WWUtil.date(getTextOfNode(node));
    };

    /**
     * This function retrieves the current value for node.
     * @param node {Node} Node for which we want to retrieve the value.
     * @returns {String} Text value of the node.
     */
    function getTextOfNode(node) {
        var result;
        if (node != null && node.childNodes[0]) {
            result = node.childNodes[0].nodeValue;
        } else if (node != null) {
            result = "";
        }
        return result;
    }
    // End of primitive transformers

    /**
     * This function retrieves relevant KmlObject to the Node. If there is such element it returns created element,
     * otherwise it returns null
     * @param node {Node} Node to transform
     * @param parent {KmlObject} Parent to current node.
     * @param controls {Array} Array of controls.
     * @returns {KmlObject|null} KmlObject representation for the node.
     */
    NodeTransformers.kmlObject = function (node, parent, controls) {
        var nameOfElement = node.nodeName;
        var constructor = KmlElements.getKey(nameOfElement);
        if (!constructor) {
            return null;
        }
        return new constructor({objectNode: node, parent: parent, controls: controls});
    };

    /**
     * It takes the node and transforms it to the LinearRing this was created to solve the mismatch between name of the
     * element and type of the element.
     * @param node {Node} Node to transform
     * @param parent {KmlObject} Parent to current node.
     * @param controls {Array} Array of controls.
     * @returns {KmlLinearRing} Transformed Linear Ring.
     */
    NodeTransformers.linearRing = function(node, parent, controls) {
        var constructor = KmlElements.getKey("LinearRing");
        if (!constructor) {
            return null;
        }
        var linearRingNode = null;
        Array.prototype.forEach.call(node.childNodes, function(pNode){
            if(pNode.nodeName.toUpperCase() == "LinearRing".toUpperCase()) {
                linearRingNode = pNode;
            }
        });
        return new constructor({objectNode: linearRingNode, parent: parent, controls: controls});
    };

    /**
     * It takes the node and returns al positions included in it.
     * @param node {Node} Node to transform
     * @returns {Position[]} All included positions. Positions are separated by space.
     */
    NodeTransformers.positions = function(node) {
        var positions = [];
        var coordinates = getTextOfNode(node).trim().replace(/\s+/g, ' ').split(' ');
        coordinates.forEach(function (pCoordinates) {
            pCoordinates = pCoordinates.split(',');
            positions.push(new Position(Number(pCoordinates[1]), Number(pCoordinates[0]), Number(pCoordinates[2] || 0)));
        });
        return positions;
    };

    /**
     * This transforming function works with attributes.
     * @param name {String} Name of the attribute to retrieve.
     * @returns {Function} Transformer function.
     */
    NodeTransformers.attribute = function(name) {
        return function(node) {
            return new Attribute(node, name).value();
        };
    };
    
    return NodeTransformers;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/KmlElementsFactory',[
    './NodeTransformers'
], function (NodeTransformers) {
    "use strict";

    /**
     * Simple factory, which understands the mapping between the XML and the internal Elements.
     * @constructor
     * @alias KmlElementsFactory
     * @params options {Object}
     * @params options.controls {Control[]} Defaults to empty array
     */
    var KmlElementsFactory = function (options) {
        this.options = options || {};
        this.options.controls = this.options.controls || [];
    };

    /**
     * It retrieves specific child of the element. This one can retrieve primitive as well as KmlObject. Transformer
     * is used to get relevant value from the node.
     * @param element {KmlObject} Element whose children are considered.
     * @param options {Object}
     * @param options.name {String} Name of the element to retrieve from the element
     * @param options.transformer {Function} Function returning correct value. It accepts the node and returns value.
     *  This mechanism can be used for the attributes as well.
     * @return Relevant value.
     */
    KmlElementsFactory.prototype.specific = function (element, options) {
        var parentNode = element.node;
        var result = null;
        var self = this;
        [].forEach.call(parentNode.childNodes, function (node) {
            if (node.nodeName == options.name) {
                result = options.transformer(node, element, self.options.controls);
            }
        });
        return result;
    };

    /**
     * It returns child which is among the ones in the options.name. It is meant to be used when any descendant is
     * accepted.
     * @param element {KmlObject} Element whose children are scanned.
     * @param options {Object}
     * @param options.name {String[]} All names which are accepted to return.
     * @return {KmlObject} Kml representation of given node
     */
    KmlElementsFactory.prototype.any = function (element, options) {
        var parentNode = element.node;

        var result = null;
        var self = this;
        [].forEach.call(parentNode.childNodes, function (node) {
            if (options.name.indexOf(node.nodeName) != -1) {
                result = NodeTransformers.kmlObject(node, element, self.options.controls);
            }
        });
        return result;
    };

    /**
     * It returns all children, which it is possible to map on the KmlObject.
     * @param element {KmlObject} Element whose children we want to retrieve.
     * @return {KmlObject[]} All KmlObjects present in given node.
     */
    KmlElementsFactory.prototype.all = function (element) {
        var parentNode = element.node;
        
        var results = [];
        var self = this;
        [].forEach.call(parentNode.childNodes, function (node) {
            var createdElement = NodeTransformers.kmlObject(node, element, self.options.controls);
            if (createdElement) {
                results.push(createdElement);
            }
        });
        return results;
    };

    var applicationWide = new KmlElementsFactory();
    /**
     * It returns application wide instance of the factory.
     * @returns {KmlElementsFactory} Singleton instance of factory for Application.
     */
    KmlElementsFactory.applicationWide = function () {
        return applicationWide;
    };

    return KmlElementsFactory;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/TreeKeyValueCache',[
    '../../../util/WWUtil'
], function (WWUtil) {
    "use strict";

    /**
     * Cache working on a basic principle of storing the data as a pair of key, value. Currently the values are
     * never invalidated.
     * @alias TreeKeyValueCache
     * @constructor
     * @classdesc Represents internally used cache which stores data in a tree like structure.
     */
    var TreeKeyValueCache = function() {
        this.map = {};
    };

	/**
     * Adds new element to the cache. It accepts level, key and value in order
     * @param level {Object} Anything that can be used as a key in JavaScript object
     * @param key {Object} Anything that can be used as a key in JavaScript object
     * @param value {Object} The value to be stored in the cache on given level and value. Value must started with #
     */
    TreeKeyValueCache.prototype.add = function(level, key, value){
        if(!this.map[level]) {
            this.map[level] = {};
        }
        this.map[level][key] = value;
    };

	/**
     * It returns value for key stored at certain level. If there is no such level, it returns null. If there is such leave then the key starting with # gets treated a bit differently.
     * @param level {Object} Anything that can be used as a key in JavaScript object
     * @param key {Object} Anything that can be used as a key in JavaScript object
     * @returns {Object|null}
     */
    TreeKeyValueCache.prototype.value = function(level, key) {
        if(!this.map[level]){
            return null;
        }
        if(key.indexOf("#") == -1) {
            var currentLevel = this.level(level);
            for(var keyFromLevel in currentLevel) {
                if(!currentLevel.hasOwnProperty(keyFromLevel)){
                    continue;
                }

                if(WWUtil.startsWith(keyFromLevel, key)){
                    return currentLevel[keyFromLevel];
                }
            }
        }
        return this.map[level][key] || null;
    };

	/**
     * It returns the whole level of the data. If there is none then undefined is returned.
     * @param level {Object} Anything that can be used as a key in JavaScript object
     * @returns {Object|null}
     */
    TreeKeyValueCache.prototype.level = function(level) {
        return this.map[level];
    };

	/**
     * It removes the data from the map if such data exists.
     * @param level {Object} Anything that can be used as a key in JavaScript object
     * @param key {Object} Anything that can be used as a key in JavaScript object
     */
    TreeKeyValueCache.prototype.remove = function(level, key) {
        delete this.map[level][key];
    };

    var applicationLevelCache = new TreeKeyValueCache();
    TreeKeyValueCache.applicationLevelCache = function() {
        return applicationLevelCache;
    };

    return TreeKeyValueCache;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/KmlElementsFactoryCached',[
    './Attribute',
    './KmlElementsFactory',
    './TreeKeyValueCache',
    '../../../util/WWUtil'
], function (
    Attribute,
    KmlElementsFactory,
    TreeKeyValueCache,
    WWUtil
) {
    "use strict";

    /**
     * More complex factory, which retrieves the values from cache and in case the value isn't present there it
     * stores the value in cache.
     * @constructor
     * @alias KmlElementsFactoryCached
     */
    var KmlElementsFactoryCached = function(options) {
        this.internalFactory = new KmlElementsFactory(options);
        this.cache = TreeKeyValueCache.applicationLevelCache();
    };

    /**
     * It adds caching functionality on top of the KmlElementsFactory all method.
     * @param element {KmlObject} Element whose children are considered
     * @returns {KmlObject[]} All objects among the elements children
     * @see KmlElementsFactory.prototype.all
     */
    KmlElementsFactoryCached.prototype.all = function(element){
        var parentNode = element.node;
        var children = this.cache.level(this.cacheKey(element.node, "All"));
        if (children) {
            var results = [];
            for(var key in children) {
                if(children.hasOwnProperty(key)) {
                    results.push(children[key]);
                }
            }
            return results;
        }

        var elements = this.internalFactory.all(element);

        if(elements && elements.length) {
            var self = this;
            elements.forEach(function (pElement) {
                self.cache.add(self.cacheKey(parentNode, "All"), self.cacheKey(pElement.node), pElement);
            });
        }
        return elements;
    };

    /**
     * It adds caching functionality on top of the KmlElementsFactory specific method.
     * @param element {KmlObject} Element whose children are considered
     * @param options {Object}
     * @param options.name {String} Name of the element to retrieve from the element
     * @param options.transformer {Function} Function returning correct value. It accepts the node and returns value.
     *  This mechanism can be used for the attributes as well.
     * @returns Relevant value.
     * @see KmlElementsFactory.prototype.specific
     */
    KmlElementsFactoryCached.prototype.specific = function(element, options){
        var parentNode = element.node;
        var name = options.name;
        if(options.attribute) {
            name = options.attribute + name;
        }
        var child = this.cache.value(this.cacheKey(parentNode), name);
        if (child) {
            return child;
        }

        var result = this.internalFactory.specific(element, options);
        if(result && result.node) {
            this.cache.add(this.cacheKey(parentNode), this.cacheKey(result.node), result);
        } else if(result) {
            this.cache.add(this.cacheKey(parentNode), name, result);
        }
        return result;
    };

    /**
     * It adds caching functionality on top of the KmlElementsFactory any method.
     * @param element {KmlObject} Element whose children are considered
     * @param options {Object}
     * @param options.name {String[]} Array of the names among which should be the one we are looking for.
     * @returns {KmlObject|null} KmlObject if there is one with the passed in name.
     * @see KmlElementsFactory.prototype.any
     */
    KmlElementsFactoryCached.prototype.any = function(element, options){
        var parentNode = element.node;

        var self = this;
        var child = null;
        var potentialChild;
        options.name.forEach(function(name){
            potentialChild = self.cache.value(self.cacheKey(parentNode), name);
            if(potentialChild) {
                child = potentialChild;
            }
        });
        if (child) {
            return child;
        }

        var result = this.internalFactory.any(element, options);

        if(result) {
            this.cache.add(self.cacheKey(parentNode), self.cacheKey(result.node), result);
        }
        return result;
    };

    /**
     * It creates cache key based on the node. In case the node doesn't have any id, it also creates id for this
     * element. This id is used for storing the value in the cache.
     * @param node {Node} Node for which generate the key.
     * @param prefix {String|undefined} Prefix for the level
     * @returns {String} Value representing the key.
     */
    KmlElementsFactoryCached.prototype.cacheKey = function(node, prefix) {
        var idAttribute = new Attribute(node, "id");
        if (!idAttribute.exists()) {
            idAttribute.save(WWUtil.guid());
        }
        var result = node.nodeName + "#" + idAttribute.value();
        if(prefix) {
            result = prefix + result;
        }
        return result;
    };

    var applicationWide = new KmlElementsFactoryCached();
    /**
     * It returns application wide instance of the factory.
     * @returns {KmlElementsFactoryCached} Singleton instance of factory for Application.
     */
    KmlElementsFactoryCached.applicationWide = function(){
        return applicationWide;
    };

    return KmlElementsFactoryCached;
});
/*!
 * @overview es6-promise - a tiny implementation of Promises/A+.
 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
 * @license   Licensed under MIT license
 *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
 * @version   3.0.2
 */

(function() {
    "use strict";
    function lib$es6$promise$utils$$objectOrFunction(x) {
      return typeof x === 'function' || (typeof x === 'object' && x !== null);
    }

    function lib$es6$promise$utils$$isFunction(x) {
      return typeof x === 'function';
    }

    function lib$es6$promise$utils$$isMaybeThenable(x) {
      return typeof x === 'object' && x !== null;
    }

    var lib$es6$promise$utils$$_isArray;
    if (!Array.isArray) {
      lib$es6$promise$utils$$_isArray = function (x) {
        return Object.prototype.toString.call(x) === '[object Array]';
      };
    } else {
      lib$es6$promise$utils$$_isArray = Array.isArray;
    }

    var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
    var lib$es6$promise$asap$$len = 0;
    var lib$es6$promise$asap$$toString = {}.toString;
    var lib$es6$promise$asap$$vertxNext;
    var lib$es6$promise$asap$$customSchedulerFn;

    var lib$es6$promise$asap$$asap = function asap(callback, arg) {
      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
      lib$es6$promise$asap$$len += 2;
      if (lib$es6$promise$asap$$len === 2) {
        // If len is 2, that means that we need to schedule an async flush.
        // If additional callbacks are queued before the queue is flushed, they
        // will be processed by this flush that we are scheduling.
        if (lib$es6$promise$asap$$customSchedulerFn) {
          lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush);
        } else {
          lib$es6$promise$asap$$scheduleFlush();
        }
      }
    }

    function lib$es6$promise$asap$$setScheduler(scheduleFn) {
      lib$es6$promise$asap$$customSchedulerFn = scheduleFn;
    }

    function lib$es6$promise$asap$$setAsap(asapFn) {
      lib$es6$promise$asap$$asap = asapFn;
    }

    var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
    var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
    var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
    var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';

    // test for web worker but not in IE10
    var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
      typeof importScripts !== 'undefined' &&
      typeof MessageChannel !== 'undefined';

    // node
    function lib$es6$promise$asap$$useNextTick() {
      // node version 0.10.x displays a deprecation warning when nextTick is used recursively
      // see https://github.com/cujojs/when/issues/410 for details
      return function() {
        process.nextTick(lib$es6$promise$asap$$flush);
      };
    }

    // vertx
    function lib$es6$promise$asap$$useVertxTimer() {
      return function() {
        lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
      };
    }

    function lib$es6$promise$asap$$useMutationObserver() {
      var iterations = 0;
      var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
      var node = document.createTextNode('');
      observer.observe(node, { characterData: true });

      return function() {
        node.data = (iterations = ++iterations % 2);
      };
    }

    // web worker
    function lib$es6$promise$asap$$useMessageChannel() {
      var channel = new MessageChannel();
      channel.port1.onmessage = lib$es6$promise$asap$$flush;
      return function () {
        channel.port2.postMessage(0);
      };
    }

    function lib$es6$promise$asap$$useSetTimeout() {
      return function() {
        setTimeout(lib$es6$promise$asap$$flush, 1);
      };
    }

    var lib$es6$promise$asap$$queue = new Array(1000);
    function lib$es6$promise$asap$$flush() {
      for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
        var callback = lib$es6$promise$asap$$queue[i];
        var arg = lib$es6$promise$asap$$queue[i+1];

        callback(arg);

        lib$es6$promise$asap$$queue[i] = undefined;
        lib$es6$promise$asap$$queue[i+1] = undefined;
      }

      lib$es6$promise$asap$$len = 0;
    }

    function lib$es6$promise$asap$$attemptVertx() {
      try {
        var r = require;
        var vertx = r('vertx');
        lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
        return lib$es6$promise$asap$$useVertxTimer();
      } catch(e) {
        return lib$es6$promise$asap$$useSetTimeout();
      }
    }

    var lib$es6$promise$asap$$scheduleFlush;
    // Decide what async method to use to triggering processing of queued callbacks:
    if (lib$es6$promise$asap$$isNode) {
      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
    } else if (lib$es6$promise$asap$$BrowserMutationObserver) {
      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
    } else if (lib$es6$promise$asap$$isWorker) {
      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
    } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') {
      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx();
    } else {
      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
    }

    function lib$es6$promise$$internal$$noop() {}

    var lib$es6$promise$$internal$$PENDING   = void 0;
    var lib$es6$promise$$internal$$FULFILLED = 1;
    var lib$es6$promise$$internal$$REJECTED  = 2;

    var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();

    function lib$es6$promise$$internal$$selfFulfillment() {
      return new TypeError("You cannot resolve a promise with itself");
    }

    function lib$es6$promise$$internal$$cannotReturnOwn() {
      return new TypeError('A promises callback cannot return that same promise.');
    }

    function lib$es6$promise$$internal$$getThen(promise) {
      try {
        return promise.then;
      } catch(error) {
        lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
        return lib$es6$promise$$internal$$GET_THEN_ERROR;
      }
    }

    function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
      try {
        then.call(value, fulfillmentHandler, rejectionHandler);
      } catch(e) {
        return e;
      }
    }

    function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
       lib$es6$promise$asap$$asap(function(promise) {
        var sealed = false;
        var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
          if (sealed) { return; }
          sealed = true;
          if (thenable !== value) {
            lib$es6$promise$$internal$$resolve(promise, value);
          } else {
            lib$es6$promise$$internal$$fulfill(promise, value);
          }
        }, function(reason) {
          if (sealed) { return; }
          sealed = true;

          lib$es6$promise$$internal$$reject(promise, reason);
        }, 'Settle: ' + (promise._label || ' unknown promise'));

        if (!sealed && error) {
          sealed = true;
          lib$es6$promise$$internal$$reject(promise, error);
        }
      }, promise);
    }

    function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
      if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
        lib$es6$promise$$internal$$fulfill(promise, thenable._result);
      } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
        lib$es6$promise$$internal$$reject(promise, thenable._result);
      } else {
        lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
          lib$es6$promise$$internal$$resolve(promise, value);
        }, function(reason) {
          lib$es6$promise$$internal$$reject(promise, reason);
        });
      }
    }

    function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) {
      if (maybeThenable.constructor === promise.constructor) {
        lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
      } else {
        var then = lib$es6$promise$$internal$$getThen(maybeThenable);

        if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
        } else if (then === undefined) {
          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
        } else if (lib$es6$promise$utils$$isFunction(then)) {
          lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
        } else {
          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
        }
      }
    }

    function lib$es6$promise$$internal$$resolve(promise, value) {
      if (promise === value) {
        lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment());
      } else if (lib$es6$promise$utils$$objectOrFunction(value)) {
        lib$es6$promise$$internal$$handleMaybeThenable(promise, value);
      } else {
        lib$es6$promise$$internal$$fulfill(promise, value);
      }
    }

    function lib$es6$promise$$internal$$publishRejection(promise) {
      if (promise._onerror) {
        promise._onerror(promise._result);
      }

      lib$es6$promise$$internal$$publish(promise);
    }

    function lib$es6$promise$$internal$$fulfill(promise, value) {
      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }

      promise._result = value;
      promise._state = lib$es6$promise$$internal$$FULFILLED;

      if (promise._subscribers.length !== 0) {
        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise);
      }
    }

    function lib$es6$promise$$internal$$reject(promise, reason) {
      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
      promise._state = lib$es6$promise$$internal$$REJECTED;
      promise._result = reason;

      lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise);
    }

    function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
      var subscribers = parent._subscribers;
      var length = subscribers.length;

      parent._onerror = null;

      subscribers[length] = child;
      subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
      subscribers[length + lib$es6$promise$$internal$$REJECTED]  = onRejection;

      if (length === 0 && parent._state) {
        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent);
      }
    }

    function lib$es6$promise$$internal$$publish(promise) {
      var subscribers = promise._subscribers;
      var settled = promise._state;

      if (subscribers.length === 0) { return; }

      var child, callback, detail = promise._result;

      for (var i = 0; i < subscribers.length; i += 3) {
        child = subscribers[i];
        callback = subscribers[i + settled];

        if (child) {
          lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail);
        } else {
          callback(detail);
        }
      }

      promise._subscribers.length = 0;
    }

    function lib$es6$promise$$internal$$ErrorObject() {
      this.error = null;
    }

    var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();

    function lib$es6$promise$$internal$$tryCatch(callback, detail) {
      try {
        return callback(detail);
      } catch(e) {
        lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
        return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
      }
    }

    function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) {
      var hasCallback = lib$es6$promise$utils$$isFunction(callback),
          value, error, succeeded, failed;

      if (hasCallback) {
        value = lib$es6$promise$$internal$$tryCatch(callback, detail);

        if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
          failed = true;
          error = value.error;
          value = null;
        } else {
          succeeded = true;
        }

        if (promise === value) {
          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
          return;
        }

      } else {
        value = detail;
        succeeded = true;
      }

      if (promise._state !== lib$es6$promise$$internal$$PENDING) {
        // noop
      } else if (hasCallback && succeeded) {
        lib$es6$promise$$internal$$resolve(promise, value);
      } else if (failed) {
        lib$es6$promise$$internal$$reject(promise, error);
      } else if (settled === lib$es6$promise$$internal$$FULFILLED) {
        lib$es6$promise$$internal$$fulfill(promise, value);
      } else if (settled === lib$es6$promise$$internal$$REJECTED) {
        lib$es6$promise$$internal$$reject(promise, value);
      }
    }

    function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
      try {
        resolver(function resolvePromise(value){
          lib$es6$promise$$internal$$resolve(promise, value);
        }, function rejectPromise(reason) {
          lib$es6$promise$$internal$$reject(promise, reason);
        });
      } catch(e) {
        lib$es6$promise$$internal$$reject(promise, e);
      }
    }

    function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
      var enumerator = this;

      enumerator._instanceConstructor = Constructor;
      enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop);

      if (enumerator._validateInput(input)) {
        enumerator._input     = input;
        enumerator.length     = input.length;
        enumerator._remaining = input.length;

        enumerator._init();

        if (enumerator.length === 0) {
          lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
        } else {
          enumerator.length = enumerator.length || 0;
          enumerator._enumerate();
          if (enumerator._remaining === 0) {
            lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
          }
        }
      } else {
        lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError());
      }
    }

    lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) {
      return lib$es6$promise$utils$$isArray(input);
    };

    lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() {
      return new Error('Array Methods must be provided an Array');
    };

    lib$es6$promise$enumerator$$Enumerator.prototype._init = function() {
      this._result = new Array(this.length);
    };

    var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;

    lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
      var enumerator = this;

      var length  = enumerator.length;
      var promise = enumerator.promise;
      var input   = enumerator._input;

      for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
        enumerator._eachEntry(input[i], i);
      }
    };

    lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
      var enumerator = this;
      var c = enumerator._instanceConstructor;

      if (lib$es6$promise$utils$$isMaybeThenable(entry)) {
        if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) {
          entry._onerror = null;
          enumerator._settledAt(entry._state, i, entry._result);
        } else {
          enumerator._willSettleAt(c.resolve(entry), i);
        }
      } else {
        enumerator._remaining--;
        enumerator._result[i] = entry;
      }
    };

    lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
      var enumerator = this;
      var promise = enumerator.promise;

      if (promise._state === lib$es6$promise$$internal$$PENDING) {
        enumerator._remaining--;

        if (state === lib$es6$promise$$internal$$REJECTED) {
          lib$es6$promise$$internal$$reject(promise, value);
        } else {
          enumerator._result[i] = value;
        }
      }

      if (enumerator._remaining === 0) {
        lib$es6$promise$$internal$$fulfill(promise, enumerator._result);
      }
    };

    lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
      var enumerator = this;

      lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
        enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
      }, function(reason) {
        enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
      });
    };
    function lib$es6$promise$promise$all$$all(entries) {
      return new lib$es6$promise$enumerator$$default(this, entries).promise;
    }
    var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
    function lib$es6$promise$promise$race$$race(entries) {
      /*jshint validthis:true */
      var Constructor = this;

      var promise = new Constructor(lib$es6$promise$$internal$$noop);

      if (!lib$es6$promise$utils$$isArray(entries)) {
        lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
        return promise;
      }

      var length = entries.length;

      function onFulfillment(value) {
        lib$es6$promise$$internal$$resolve(promise, value);
      }

      function onRejection(reason) {
        lib$es6$promise$$internal$$reject(promise, reason);
      }

      for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
        lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
      }

      return promise;
    }
    var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
    function lib$es6$promise$promise$resolve$$resolve(object) {
      /*jshint validthis:true */
      var Constructor = this;

      if (object && typeof object === 'object' && object.constructor === Constructor) {
        return object;
      }

      var promise = new Constructor(lib$es6$promise$$internal$$noop);
      lib$es6$promise$$internal$$resolve(promise, object);
      return promise;
    }
    var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;
    function lib$es6$promise$promise$reject$$reject(reason) {
      /*jshint validthis:true */
      var Constructor = this;
      var promise = new Constructor(lib$es6$promise$$internal$$noop);
      lib$es6$promise$$internal$$reject(promise, reason);
      return promise;
    }
    var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;

    var lib$es6$promise$promise$$counter = 0;

    function lib$es6$promise$promise$$needsResolver() {
      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
    }

    function lib$es6$promise$promise$$needsNew() {
      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
    }

    var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
    /**
      Promise objects represent the eventual result of an asynchronous operation. The
      primary way of interacting with a promise is through its `then` method, which
      registers callbacks to receive either a promise's eventual value or the reason
      why the promise cannot be fulfilled.

      Terminology
      -----------

      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
      - `thenable` is an object or function that defines a `then` method.
      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
      - `exception` is a value that is thrown using the throw statement.
      - `reason` is a value that indicates why a promise was rejected.
      - `settled` the final resting state of a promise, fulfilled or rejected.

      A promise can be in one of three states: pending, fulfilled, or rejected.

      Promises that are fulfilled have a fulfillment value and are in the fulfilled
      state.  Promises that are rejected have a rejection reason and are in the
      rejected state.  A fulfillment value is never a thenable.

      Promises can also be said to *resolve* a value.  If this value is also a
      promise, then the original promise's settled state will match the value's
      settled state.  So a promise that *resolves* a promise that rejects will
      itself reject, and a promise that *resolves* a promise that fulfills will
      itself fulfill.


      Basic Usage:
      ------------

      ```js
      var promise = new Promise(function(resolve, reject) {
        // on success
        resolve(value);

        // on failure
        reject(reason);
      });

      promise.then(function(value) {
        // on fulfillment
      }, function(reason) {
        // on rejection
      });
      ```

      Advanced Usage:
      ---------------

      Promises shine when abstracting away asynchronous interactions such as
      `XMLHttpRequest`s.

      ```js
      function getJSON(url) {
        return new Promise(function(resolve, reject){
          var xhr = new XMLHttpRequest();

          xhr.open('GET', url);
          xhr.onreadystatechange = handler;
          xhr.responseType = 'json';
          xhr.setRequestHeader('Accept', 'application/json');
          xhr.send();

          function handler() {
            if (this.readyState === this.DONE) {
              if (this.status === 200) {
                resolve(this.response);
              } else {
                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
              }
            }
          };
        });
      }

      getJSON('/posts.json').then(function(json) {
        // on fulfillment
      }, function(reason) {
        // on rejection
      });
      ```

      Unlike callbacks, promises are great composable primitives.

      ```js
      Promise.all([
        getJSON('/posts'),
        getJSON('/comments')
      ]).then(function(values){
        values[0] // => postsJSON
        values[1] // => commentsJSON

        return values;
      });
      ```

      @class Promise
      @param {function} resolver
      Useful for tooling.
      @constructor
    */
    function lib$es6$promise$promise$$Promise(resolver) {
      this._id = lib$es6$promise$promise$$counter++;
      this._state = undefined;
      this._result = undefined;
      this._subscribers = [];

      if (lib$es6$promise$$internal$$noop !== resolver) {
        if (!lib$es6$promise$utils$$isFunction(resolver)) {
          lib$es6$promise$promise$$needsResolver();
        }

        if (!(this instanceof lib$es6$promise$promise$$Promise)) {
          lib$es6$promise$promise$$needsNew();
        }

        lib$es6$promise$$internal$$initializePromise(this, resolver);
      }
    }

    lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
    lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
    lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
    lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
    lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler;
    lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap;
    lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap;

    lib$es6$promise$promise$$Promise.prototype = {
      constructor: lib$es6$promise$promise$$Promise,

    /**
      The primary way of interacting with a promise is through its `then` method,
      which registers callbacks to receive either a promise's eventual value or the
      reason why the promise cannot be fulfilled.

      ```js
      findUser().then(function(user){
        // user is available
      }, function(reason){
        // user is unavailable, and you are given the reason why
      });
      ```

      Chaining
      --------

      The return value of `then` is itself a promise.  This second, 'downstream'
      promise is resolved with the return value of the first promise's fulfillment
      or rejection handler, or rejected if the handler throws an exception.

      ```js
      findUser().then(function (user) {
        return user.name;
      }, function (reason) {
        return 'default name';
      }).then(function (userName) {
        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
        // will be `'default name'`
      });

      findUser().then(function (user) {
        throw new Error('Found user, but still unhappy');
      }, function (reason) {
        throw new Error('`findUser` rejected and we're unhappy');
      }).then(function (value) {
        // never reached
      }, function (reason) {
        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
      });
      ```
      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.

      ```js
      findUser().then(function (user) {
        throw new PedagogicalException('Upstream error');
      }).then(function (value) {
        // never reached
      }).then(function (value) {
        // never reached
      }, function (reason) {
        // The `PedgagocialException` is propagated all the way down to here
      });
      ```

      Assimilation
      ------------

      Sometimes the value you want to propagate to a downstream promise can only be
      retrieved asynchronously. This can be achieved by returning a promise in the
      fulfillment or rejection handler. The downstream promise will then be pending
      until the returned promise is settled. This is called *assimilation*.

      ```js
      findUser().then(function (user) {
        return findCommentsByAuthor(user);
      }).then(function (comments) {
        // The user's comments are now available
      });
      ```

      If the assimliated promise rejects, then the downstream promise will also reject.

      ```js
      findUser().then(function (user) {
        return findCommentsByAuthor(user);
      }).then(function (comments) {
        // If `findCommentsByAuthor` fulfills, we'll have the value here
      }, function (reason) {
        // If `findCommentsByAuthor` rejects, we'll have the reason here
      });
      ```

      Simple Example
      --------------

      Synchronous Example

      ```javascript
      var result;

      try {
        result = findResult();
        // success
      } catch(reason) {
        // failure
      }
      ```

      Errback Example

      ```js
      findResult(function(result, err){
        if (err) {
          // failure
        } else {
          // success
        }
      });
      ```

      Promise Example;

      ```javascript
      findResult().then(function(result){
        // success
      }, function(reason){
        // failure
      });
      ```

      Advanced Example
      --------------

      Synchronous Example

      ```javascript
      var author, books;

      try {
        author = findAuthor();
        books  = findBooksByAuthor(author);
        // success
      } catch(reason) {
        // failure
      }
      ```

      Errback Example

      ```js

      function foundBooks(books) {

      }

      function failure(reason) {

      }

      findAuthor(function(author, err){
        if (err) {
          failure(err);
          // failure
        } else {
          try {
            findBoooksByAuthor(author, function(books, err) {
              if (err) {
                failure(err);
              } else {
                try {
                  foundBooks(books);
                } catch(reason) {
                  failure(reason);
                }
              }
            });
          } catch(error) {
            failure(err);
          }
          // success
        }
      });
      ```

      Promise Example;

      ```javascript
      findAuthor().
        then(findBooksByAuthor).
        then(function(books){
          // found books
      }).catch(function(reason){
        // something went wrong
      });
      ```

      @method then
      @param {Function} onFulfilled
      @param {Function} onRejected
      Useful for tooling.
      @return {Promise}
    */
      then: function(onFulfillment, onRejection) {
        var parent = this;
        var state = parent._state;

        if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) {
          return this;
        }

        var child = new this.constructor(lib$es6$promise$$internal$$noop);
        var result = parent._result;

        if (state) {
          var callback = arguments[state - 1];
          lib$es6$promise$asap$$asap(function(){
            lib$es6$promise$$internal$$invokeCallback(state, child, callback, result);
          });
        } else {
          lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
        }

        return child;
      },

    /**
      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
      as the catch block of a try/catch statement.

      ```js
      function findAuthor(){
        throw new Error('couldn't find that author');
      }

      // synchronous
      try {
        findAuthor();
      } catch(reason) {
        // something went wrong
      }

      // async with promises
      findAuthor().catch(function(reason){
        // something went wrong
      });
      ```

      @method catch
      @param {Function} onRejection
      Useful for tooling.
      @return {Promise}
    */
      'catch': function(onRejection) {
        return this.then(null, onRejection);
      }
    };
    function lib$es6$promise$polyfill$$polyfill() {
      var local;

      if (typeof global !== 'undefined') {
          local = global;
      } else if (typeof self !== 'undefined') {
          local = self;
      } else {
          try {
              local = Function('return this')();
          } catch (e) {
              throw new Error('polyfill failed because global object is unavailable in this environment');
          }
      }

      var P = local.Promise;

      if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) {
        return;
      }

      local.Promise = lib$es6$promise$promise$$default;
    }
    var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;

    var lib$es6$promise$umd$$ES6Promise = {
      'Promise': lib$es6$promise$promise$$default,
      'polyfill': lib$es6$promise$polyfill$$default
    };

    /* global define:true module:true window: true */
    if (typeof define === 'function' && define['amd']) {
      define('util/es6-promise',[],function() { return lib$es6$promise$umd$$ES6Promise; });
    } else if (typeof module !== 'undefined' && module['exports']) {
      module['exports'] = lib$es6$promise$umd$$ES6Promise;
    } else if (typeof this !== 'undefined') {
      this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
    }

    var global = {};
    lib$es6$promise$polyfill$$default();
}).call(this);


/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('util/Promise',['./es6-promise'],
    function (LegacyPromise) {
        "use strict";
        if (window.Promise) {
            return window.Promise;
        } else {
            return LegacyPromise.Promise;
        }
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlObject
 */
define('formats/kml/KmlObject',[
    '../../error/ArgumentError',
    './util/Attribute',
    './KmlElements',
    './util/KmlElementsFactoryCached',
    '../../util/Logger',
    '../../util/Promise',
    '../../render/Renderable'
], function (ArgumentError,
             Attribute,
             KmlElements,
             KmlElementsFactoryCached,
             Logger,
             Promise,
             Renderable) {
    "use strict";

    /**
     * Constructs an Kml object. Every node in the Kml document is either basic type or Kml object. Applications usually
     * don't call this constructor. It is usually called only by its descendants.
     * It should be treated as mixin.
     * @alias KmlObject
     * @classdesc Contains the data associated with every Kml object.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Kml Object
     * @param options.controls {KmlControls[]} Controls associated with current Node
     * @constructor
     * @throws {ArgumentError} If either node is null or id isn't present on the object.
     * @augments Renderable
     * @see https://developers.google.com/kml/documentation/kmlreference#object
     */
    var KmlObject = function (options) {
        Renderable.call(this);

        options = options || {};
        if (!options.objectNode) {
            throw new ArgumentError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "KmlObject", "constructor", "Passed node isn't defined.")
            );
        }
        this._node = options.objectNode;
        this._cache = {};
        
        this._controls = options.controls || [];
        this._factory = new KmlElementsFactoryCached({controls: this._controls});
        
        this.hook(this._controls, options);
    };

    KmlObject.prototype = Object.create(Renderable.prototype);

    Object.defineProperties(KmlObject.prototype, {
        /**
         * Every object, which is part of the KML document has its identity. We will use it for changes in the
         * document for binding.
         * @memberof KmlObject.prototype
         * @type {String}
         * @readonly
         */
        id: {
            get: function () {
                return new Attribute(this.node, "id").value();
            }
        },

        /**
         * Node of this object. It may be overridden by other users of some functions like parse.
         * @memberof KmlObject.prototype
         * @type {Node}
         * @readonly
         */
        node: {
            get: function () {
                //noinspection JSPotentiallyInvalidUsageOfThis
                return this._node;
            }
        }
    });

    /**
     * It calls all controls associated with current KmlFile with the link to this.
     * @param controls {KmlControls[]} Controls associated with current tree.
     * @param options {Object} Options to pass into the controls.
     */
    KmlObject.prototype.hook = function (controls, options) {
        var self = this;
        controls.forEach(function (control) {
            control.hook(self, options);
        });
    };

    /**
     * @inheritDoc
     */
    KmlObject.prototype.render = function (dc) {
    };

    /**
     * Returns tag name of all descendants of abstract node or the tag name for current node.
     * @returns {String[]}
     */
    KmlObject.prototype.getTagNames = function () {
        return [];
    };

    return KmlObject;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/ImagePyramid',[
    '../KmlElements',
    '../KmlObject',
    '../util/NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";
    /**
     * Constructs an ImagePyramid. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias ImagePyramid
     * @constructor
     * @classdesc Contains the data associated with Kml Image Pyramid
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml Image Pyramid.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#imagepyramid
     * @augments KmlObject
     */
    var ImagePyramid = function (options) {
        KmlObject.call(this, options);
    };

    ImagePyramid.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(ImagePyramid.prototype, {
        /**
         * Size of the tiles, in pixels. Tiles must be square, and &lt;tileSize&gt; must be a power of 2. A tile size of
         * 256
         * (the default) or 512 is recommended. The original image is divided into tiles of this size, at varying
         * resolutions.
         * @memberof ImagePyramid.prototype
         * @readonly
         * @type {Number}
         */
        kmlTileSize: {
            get: function () {
                return this._factory.specific(this, {name: 'tileSize', transformer: NodeTransformers.number});
            }
        },

        /**
         * Width in pixels of the original image.
         * @memberof ImagePyramid.prototype
         * @readonly
         * @type {Number}
         */
        kmlMaxWidth: {
            get: function () {
                return this._factory.specific(this, {name: 'maxWidth', transformer: NodeTransformers.number});
            }
        },

        /**
         * Height in pixels of the original image.
         * @memberof ImagePyramid.prototype
         * @readonly
         * @type {Number}
         */
        kmlMaxHeight: {
            get: function () {
                return this._factory.specific(this, {name: 'maxHeight', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies where to begin numbering the tiles in each layer of the pyramid. A value of lowerLeft specifies
         * that row 1, column 1 of each layer is in the bottom left corner of the grid.
         * @memberof ImagePyramid.prototype
         * @readonly
         * @type {String}
         */
        kmlGridOrigin: {
            get: function () {
                return this._factory.specific(this, {name: 'gridOrigin', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    ImagePyramid.prototype.getTagNames = function () {
        return ['ImagePyramid'];
    };

    KmlElements.addKey(ImagePyramid.prototype.getTagNames()[0], ImagePyramid);

    return ImagePyramid;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/ItemIcon',[
    './../KmlElements',
    '../KmlObject',
    './NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an ItemIcon. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias ItemIcon
     * @constructor
     * @classdesc Contains the data associated with Kml Item Icon
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml Item Icon.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#itemicon
     * @augments KmlObject
     */
    var ItemIcon = function (options) {
        KmlObject.call(this, options);
    };

    ItemIcon.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(ItemIcon.prototype, {
        /**
         * Specifies the current state of the NetworkLink or Folder. Possible values are open, closed, error,
         * fetching0, fetching1, and fetching2. These values can be combined by inserting a space between two values
         * (no comma).
         * @memberof ItemIcon.prototype
         * @readonly
         * @type {String}
         */
        kmlState: {
            get: function () {
                return this._factory.specific(this, {name: 'state', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies the URI of the image used in the List View for the Feature.
         * @memberof ItemIcon.prototype
         * @readonly
         * @type {String}
         */
        kmlHref: {
            get: function () {
                return this._factory.specific(this, {name: 'href', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    ItemIcon.prototype.getTagNames = function () {
        return ['ItemIcon'];
    };

    KmlElements.addKey(ItemIcon.prototype.getTagNames()[0], ItemIcon);

    return ItemIcon;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlTimePrimitive
 */
define('formats/kml/KmlTimePrimitive',[
    './KmlObject'
], function (KmlObject) {
    "use strict";
    /**
     * Constructs an KmlTimePrimitive. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from KmlFile are read.
     * @alias KmlTimePrimitive
     * @classdesc It is ancestor for all TimePrimitives - TimeSpan and TimeStamp
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Kml TimePrimitive.
     * @constructor
     * @see https://developers.google.com/kml/documentation/kmlreference#timeprimitive
     * @augments KmlObject
     */
    var KmlTimePrimitive = function (options) {
        KmlObject.call(this, options);
    };

    KmlTimePrimitive.prototype = Object.create(KmlObject.prototype);

    /**
     * It returns range applicable to current time.
     * @returns {{from: Date, to: Date}}
     */
    KmlTimePrimitive.prototype.timeRange = function() {
        var from, to;
        if(this.kmlBegin) {
            to = from = this.kmlBegin.valueOf();
        }
        if(this.kmlEnd) {
            to = this.kmlEnd.valueOf();
            if(!from) {
                from = to;
            }
        }

        if(this.kmlWhen) {
            to = from = this.kmlWhen.valueOf();
        }

        return {
            from: from,
            to: to
        };
    };

    /**
     * @inheritDoc
     */
    KmlTimePrimitive.prototype.getTagNames = function () {
        return ['TimeSpan', 'TimeStamp'];
    };

    return KmlTimePrimitive;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlAbstractView',[
    './KmlObject',
    './KmlElements',
    './KmlTimePrimitive'
], function(
    KmlObject,
    KmlElements,
    KmlTimePrimitive
){
    // TODO Fix to use current implementations.
    "use strict";
    /**
     * Constructs an KmlAbstractView. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlAbstractView
     * @classdesc Contains the data associated with AbstractView node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing abstract view in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#abstractview
     * @augments KmlObject
     */
    var KmlAbstractView = function (options) {
        KmlObject.call(this, options);
    };

    KmlAbstractView.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlAbstractView.prototype, {
        /**
         * Time associated with current view. It shouldn't be displayed outside of this time frame.
         * @memberof KmlAbstractView.prototype
         * @readonly
         * @type {KmlTimePrimitive}
         */
        kmlTimePrimitive: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlTimePrimitive.prototype.getTagNames()
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlAbstractView.prototype.getTagNames = function () {
        return ['Camera', 'LookAt'];
    };

    return KmlAbstractView;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlSubStyle',[
    './../KmlObject'
], function (KmlObject) {
    "use strict";

    /**
     * Constructs an KmlSubStyle. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read.
     * @alias KmlSubStyle
     * @constructor
     * @classdesc Contains the data associated with Kml sub style
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml sub style.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#substyle
     * @augments KmlObject
     */
    var KmlSubStyle = function (options) {
        KmlObject.call(this, options);
    };

    KmlSubStyle.prototype = Object.create(KmlObject.prototype);

    /**
     * @inheritDoc
     */
    KmlSubStyle.prototype.getTagNames = function () {
        return ['LineStyle', 'PolyStyle', 'IconStyle', 'LabelStyle', 'BalloonStyle', 'ListStyle'];
    };

    return KmlSubStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlBalloonStyle',[
    '../KmlElements',
    './KmlSubStyle',
    '../util/NodeTransformers'
], function (
    KmlElements,
    KmlSubStyle,
    NodeTransformers
) {
    "use strict";
    /**
     * Constructs an KmlBalloonStyle. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from KmlFile are read. This object is already concrete implementation.
     * @alias KmlBalloonStyle
     * @classdesc Contains the data associated with BalloonStyle node
     * @param options {Object}
     * @param options.objectNode {Node} Node representing BalloonStyle
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined
     * @see https://developers.google.com/kml/documentation/kmlreference#balloonstyle
     * @augments KmlSubStyle
     */
    var KmlBalloonStyle = function (options) {
        KmlSubStyle.call(this, options);
    };

    KmlBalloonStyle.prototype = Object.create(KmlSubStyle.prototype);

    Object.defineProperties(KmlBalloonStyle.prototype, {
        /**
         * Represents background color of the balloon. It expects hexadecimal notation without #.
         * @memberof KmlBalloonStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlBgColor: {
            get: function(){
                return this._factory.specific(this, {name: 'bgColor', transformer: NodeTransformers.string});
            }
        },

        /**
         * Represents color of the text in the balloon. It expects hexadecimal notation without #.
         * @memberof KmlBalloonStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlTextColor: {
            get: function() {
                return this._factory.specific(this, {name: 'textColor', transformer: NodeTransformers.string});
            }
        },

        /**
         * Text which should be displayed in the balloon, otherwise feature name and description is displayed.
         * @memberof KmlBalloonStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlText: {
            get: function(){
                return this._factory.specific(this, {name: 'text', transformer: NodeTransformers.string});
            }
        },

        /**
         * Either display or hide. When hide don't show the balloon at all.
         * @memberof KmlBalloonStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlDisplayMode: {
            get: function() {
                return this._factory.specific(this, {name: 'displayMode', transformer: NodeTransformers.string});
            }
        }
    });

    KmlBalloonStyle.update = function(){

    };

    /**
     * @inheritDoc
     */
    KmlBalloonStyle.prototype.getTagNames = function() {
        return ['BalloonStyle'];
    };

    KmlElements.addKey(KmlBalloonStyle.prototype.getTagNames()[0], KmlBalloonStyle);

    return KmlBalloonStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlCamera',[
    './KmlElements',
    './KmlAbstractView',
    './util/NodeTransformers'
], function (KmlElements,
             KmlAbstractView,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlCamera. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlCamera
     * @classdesc Contains the data associated with Camera node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing camera in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#camera
     * @augments KmlAbstractView
     */
    var KmlCamera = function (options) {
        KmlAbstractView.call(this, options);
    };

    KmlCamera.prototype = Object.create(KmlAbstractView.prototype);

    Object.defineProperties(KmlCamera.prototype, {
        /**
         * Longitude of the virtual camera (eye point). Angular distance in degrees, relative to the Prime Meridian.
         * Values west of the Meridian range from +-180 to 0 degrees. Values east of the Meridian range from 0
         * to 180 degrees.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlLongitude: {
            get: function () {
                return this._factory.specific(this, {name: 'longitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Latitude of the virtual camera. Degrees north or south of the Equator (0 degrees). Values range from -90
         * degrees to 90 degrees.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlLatitude: {
            get: function () {
                return this._factory.specific(this, {name: 'latitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Distance of the camera from the earth's surface, in meters. Interpreted according to the Camera's
         * &lt;altitudeMode&gt; or &lt;gx:altitudeMode&gt;.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitude: {
            get: function () {
                return this._factory.specific(this, {name: 'altitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Direction (azimuth) of the camera, in degrees. Default=0 (true North). (See diagram.) Values range from
         * 0 to 360 degrees.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlHeading: {
            get: function () {
                return this._factory.specific(this, {name: 'heading', transformer: NodeTransformers.string});
            }
        },

        /**
         * Rotation, in degrees, of the camera around the X axis. A value of 0 indicates that the view is aimed
         * straight down toward the earth (the most common case). A value for 90 for &lt;tilt&gt; indicates that the
         * view
         * is aimed toward the horizon. Values greater than 90 indicate that the view is pointed up into the sky.
         * Values for &lt;tilt&gt; are clamped at +180 degrees.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlTilt: {
            get: function () {
                return this._factory.specific(this, {name: 'tilt', transformer: NodeTransformers.string});
            }
        },

        /**
         * Rotation, in degrees, of the camera around the Z axis. Values range from -180 to +180 degrees.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlRoll: {
            get: function () {
                return this._factory.specific(this, {name: 'roll', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies how the &lt;altitude&gt; specified for the Camera is interpreted. Possible values are as
         * follows:
         * relativeToGround - (default) Interprets the &lt;altitude&gt; as a value in meters above the ground. If the
         * point is over water, the &lt;altitude&gt; will be interpreted as a value in meters above sea level. See
         * &lt;gx:altitudeMode&gt; below to specify points relative to the sea floor. clampToGround - For a camera, this
         * setting also places the camera relativeToGround, since putting the camera exactly at terrain height
         * would
         * mean that the eye would intersect the terrain (and the view would be blocked). absolute - Interprets the
         * &lt;altitude&gt; as a value in meters above sea level.
         * @memberof KmlCamera.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitudeMode: {
            get: function () {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlCamera.prototype.getTagNames = function () {
        return ['Camera'];
    };

    KmlElements.addKey(KmlCamera.prototype.getTagNames()[0], KmlCamera);

    return KmlCamera;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlColorStyle',[
    './KmlSubStyle',
    '../util/NodeTransformers'
], function (
    KmlSubStyle,
    NodeTransformers
) {
    "use strict";
    /**
     * Constructs an KmlColorStyle. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from KmlFiles are read. This object is abstract. Only its descendants are instantiating it.
     * @alias KmlColorStyle
     * @classdesc Contains the data associated with ColorStyle node
     * @param options {Object}
     * @param options.objectNode {Node}  Node representing ColorStyle from Kml document
     * @constructor
     * @throws {ArgumentError} If the node is null.
     * @see https://developers.google.com/kml/documentation/kmlreference#colorstyle
     * @augments KmlSubStyle
     */
    var KmlColorStyle = function (options) {
        KmlSubStyle.call(this, options);
    };

    KmlColorStyle.prototype = Object.create(KmlSubStyle.prototype);

    Object.defineProperties(KmlColorStyle.prototype, {
        /**
         * Color, which should be used. Shapes supporting colored styles must correctly apply the
         * color.
         * @memberof KmlColorStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlColor: {
            get: function() {
                return this._factory.specific(this, {name: 'color', transformer: NodeTransformers.string});
            }
        },

        /**
         * Either normal or random. Normal means applying of the color as stated. Random applies linear scale based
         * on the color. More on https://developers.google.com/kml/documentation/kmlreference#colorstyle
         * @memberof KmlColorStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlColorMode: {
            get: function() {
                return this._factory.specific(this, {name: 'colorMode', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlColorStyle.prototype.getTagNames = function () {
        return ['LineStyle', 'PolyStyle', 'IconStyle', 'LabelStyle'];
    };

    return KmlColorStyle;
});
/*!
JSZip - A Javascript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2014 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
JSZip uses the library pako released under the MIT license :
https://github.com/nodeca/pako/blob/master/LICENSE

This AMD version of jszip was created with Webpack using the v2.x branch of jszip:
https://github.com/Stuk/jszip/tree/v2.x
*/

define('util/jszip',[], function() { 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] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = 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;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 22);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {



var support = __webpack_require__(2);
var compressions = __webpack_require__(5);
var nodeBuffer = __webpack_require__(7);
/**
 * Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
 * @param {string} str the string to transform.
 * @return {String} the binary string.
 */
exports.string2binary = function(str) {
    var result = "";
    for (var i = 0; i < str.length; i++) {
        result += String.fromCharCode(str.charCodeAt(i) & 0xff);
    }
    return result;
};
exports.arrayBuffer2Blob = function(buffer, mimeType) {
    exports.checkSupport("blob");
	mimeType = mimeType || 'application/zip';

    try {
        // Blob constructor
        return new Blob([buffer], {
            type: mimeType
        });
    }
    catch (e) {

        try {
            // deprecated, browser only, old way
            var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
            var builder = new Builder();
            builder.append(buffer);
            return builder.getBlob(mimeType);
        }
        catch (e) {

            // well, fuck ?!
            throw new Error("Bug : can't construct the Blob.");
        }
    }


};
/**
 * The identity function.
 * @param {Object} input the input.
 * @return {Object} the same input.
 */
function identity(input) {
    return input;
}

/**
 * Fill in an array with a string.
 * @param {String} str the string to use.
 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
 */
function stringToArrayLike(str, array) {
    for (var i = 0; i < str.length; ++i) {
        array[i] = str.charCodeAt(i) & 0xFF;
    }
    return array;
}

/**
 * Transform an array-like object to a string.
 * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
 * @return {String} the result.
 */
function arrayLikeToString(array) {
    // Performances notes :
    // --------------------
    // String.fromCharCode.apply(null, array) is the fastest, see
    // see http://jsperf.com/converting-a-uint8array-to-a-string/2
    // but the stack is limited (and we can get huge arrays !).
    //
    // result += String.fromCharCode(array[i]); generate too many strings !
    //
    // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
    var chunk = 65536;
    var result = [],
        len = array.length,
        type = exports.getTypeOf(array),
        k = 0,
        canUseApply = true;
      try {
         switch(type) {
            case "uint8array":
               String.fromCharCode.apply(null, new Uint8Array(0));
               break;
            case "nodebuffer":
               String.fromCharCode.apply(null, nodeBuffer(0));
               break;
         }
      } catch(e) {
         canUseApply = false;
      }

      // no apply : slow and painful algorithm
      // default browser on android 4.*
      if (!canUseApply) {
         var resultStr = "";
         for(var i = 0; i < array.length;i++) {
            resultStr += String.fromCharCode(array[i]);
         }
    return resultStr;
    }
    while (k < len && chunk > 1) {
        try {
            if (type === "array" || type === "nodebuffer") {
                result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
            }
            else {
                result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
            }
            k += chunk;
        }
        catch (e) {
            chunk = Math.floor(chunk / 2);
        }
    }
    return result.join("");
}

exports.applyFromCharCode = arrayLikeToString;


/**
 * Copy the data from an array-like to an other array-like.
 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
 * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
 * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
 */
function arrayLikeToArrayLike(arrayFrom, arrayTo) {
    for (var i = 0; i < arrayFrom.length; i++) {
        arrayTo[i] = arrayFrom[i];
    }
    return arrayTo;
}

// a matrix containing functions to transform everything into everything.
var transform = {};

// string to ?
transform["string"] = {
    "string": identity,
    "array": function(input) {
        return stringToArrayLike(input, new Array(input.length));
    },
    "arraybuffer": function(input) {
        return transform["string"]["uint8array"](input).buffer;
    },
    "uint8array": function(input) {
        return stringToArrayLike(input, new Uint8Array(input.length));
    },
    "nodebuffer": function(input) {
        return stringToArrayLike(input, nodeBuffer(input.length));
    }
};

// array to ?
transform["array"] = {
    "string": arrayLikeToString,
    "array": identity,
    "arraybuffer": function(input) {
        return (new Uint8Array(input)).buffer;
    },
    "uint8array": function(input) {
        return new Uint8Array(input);
    },
    "nodebuffer": function(input) {
        return nodeBuffer(input);
    }
};

// arraybuffer to ?
transform["arraybuffer"] = {
    "string": function(input) {
        return arrayLikeToString(new Uint8Array(input));
    },
    "array": function(input) {
        return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
    },
    "arraybuffer": identity,
    "uint8array": function(input) {
        return new Uint8Array(input);
    },
    "nodebuffer": function(input) {
        return nodeBuffer(new Uint8Array(input));
    }
};

// uint8array to ?
transform["uint8array"] = {
    "string": arrayLikeToString,
    "array": function(input) {
        return arrayLikeToArrayLike(input, new Array(input.length));
    },
    "arraybuffer": function(input) {
        return input.buffer;
    },
    "uint8array": identity,
    "nodebuffer": function(input) {
        return nodeBuffer(input);
    }
};

// nodebuffer to ?
transform["nodebuffer"] = {
    "string": arrayLikeToString,
    "array": function(input) {
        return arrayLikeToArrayLike(input, new Array(input.length));
    },
    "arraybuffer": function(input) {
        return transform["nodebuffer"]["uint8array"](input).buffer;
    },
    "uint8array": function(input) {
        return arrayLikeToArrayLike(input, new Uint8Array(input.length));
    },
    "nodebuffer": identity
};

/**
 * Transform an input into any type.
 * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
 * If no output type is specified, the unmodified input will be returned.
 * @param {String} outputType the output type.
 * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
 * @throws {Error} an Error if the browser doesn't support the requested output type.
 */
exports.transformTo = function(outputType, input) {
    if (!input) {
        // undefined, null, etc
        // an empty string won't harm.
        input = "";
    }
    if (!outputType) {
        return input;
    }
    exports.checkSupport(outputType);
    var inputType = exports.getTypeOf(input);
    var result = transform[inputType][outputType](input);
    return result;
};

/**
 * Return the type of the input.
 * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
 * @param {Object} input the input to identify.
 * @return {String} the (lowercase) type of the input.
 */
exports.getTypeOf = function(input) {
    if (typeof input === "string") {
        return "string";
    }
    if (Object.prototype.toString.call(input) === "[object Array]") {
        return "array";
    }
    if (support.nodebuffer && nodeBuffer.test(input)) {
        return "nodebuffer";
    }
    if (support.uint8array && input instanceof Uint8Array) {
        return "uint8array";
    }
    if (support.arraybuffer && input instanceof ArrayBuffer) {
        return "arraybuffer";
    }
};

/**
 * Throw an exception if the type is not supported.
 * @param {String} type the type to check.
 * @throws {Error} an Error if the browser doesn't support the requested type.
 */
exports.checkSupport = function(type) {
    var supported = support[type.toLowerCase()];
    if (!supported) {
        throw new Error(type + " is not supported by this browser");
    }
};
exports.MAX_VALUE_16BITS = 65535;
exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1

/**
 * Prettify a string read as binary.
 * @param {string} str the string to prettify.
 * @return {string} a pretty string.
 */
exports.pretty = function(str) {
    var res = '',
        code, i;
    for (i = 0; i < (str || "").length; i++) {
        code = str.charCodeAt(i);
        res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
    }
    return res;
};

/**
 * Find a compression registered in JSZip.
 * @param {string} compressionMethod the method magic to find.
 * @return {Object|null} the JSZip compression object, null if none found.
 */
exports.findCompression = function(compressionMethod) {
    for (var method in compressions) {
        if (!compressions.hasOwnProperty(method)) {
            continue;
        }
        if (compressions[method].magic === compressionMethod) {
            return compressions[method];
        }
    }
    return null;
};
/**
* Cross-window, cross-Node-context regular expression detection
* @param  {Object}  object Anything
* @return {Boolean}        true if the object is a regular expression,
* false otherwise
*/
exports.isRegExp = function (object) {
    return Object.prototype.toString.call(object) === "[object RegExp]";
};

/**
 * Merge the objects passed as parameters into a new one.
 * @private
 * @param {...Object} var_args All objects to merge.
 * @return {Object} a new object with the data of the others.
 */
exports.extend = function() {
    var result = {}, i, attr;
    for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
        for (attr in arguments[i]) {
            if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
                result[attr] = arguments[i][attr];
            }
        }
    }
    return result;
};



/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {





var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
                (typeof Uint16Array !== 'undefined') &&
                (typeof Int32Array !== 'undefined');


exports.assign = function (obj /*from1, from2, from3, ...*/) {
  var sources = Array.prototype.slice.call(arguments, 1);
  while (sources.length) {
    var source = sources.shift();
    if (!source) { continue; }

    if (typeof source !== 'object') {
      throw new TypeError(source + 'must be non-object');
    }

    for (var p in source) {
      if (source.hasOwnProperty(p)) {
        obj[p] = source[p];
      }
    }
  }

  return obj;
};


// reduce buffer size, avoiding mem copy
exports.shrinkBuf = function (buf, size) {
  if (buf.length === size) { return buf; }
  if (buf.subarray) { return buf.subarray(0, size); }
  buf.length = size;
  return buf;
};


var fnTyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    if (src.subarray && dest.subarray) {
      dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
      return;
    }
    // Fallback to ordinary array
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    var i, l, len, pos, chunk, result;

    // calculate data length
    len = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      len += chunks[i].length;
    }

    // join chunks
    result = new Uint8Array(len);
    pos = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      chunk = chunks[i];
      result.set(chunk, pos);
      pos += chunk.length;
    }

    return result;
  }
};

var fnUntyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    return [].concat.apply([], chunks);
  }
};


// Enable/Disable typed arrays use, for testing
//
exports.setTyped = function (on) {
  if (on) {
    exports.Buf8  = Uint8Array;
    exports.Buf16 = Uint16Array;
    exports.Buf32 = Int32Array;
    exports.assign(exports, fnTyped);
  } else {
    exports.Buf8  = Array;
    exports.Buf16 = Array;
    exports.Buf32 = Array;
    exports.assign(exports, fnUntyped);
  }
};

exports.setTyped(TYPED_OK);


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {


/* WEBPACK VAR INJECTION */(function(Buffer) {
exports.base64 = true;
exports.array = true;
exports.string = true;
exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
// contains true if JSZip can read/generate nodejs Buffer, false otherwise.
// Browserify will provide a Buffer implementation for browsers, which is
// an augmented Uint8Array (i.e., can be used as either Buffer or U8).
exports.nodebuffer = typeof Buffer !== "undefined";
// contains true if JSZip can read/generate Uint8Array, false otherwise.
exports.uint8array = typeof Uint8Array !== "undefined";

if (typeof ArrayBuffer === "undefined") {
    exports.blob = false;
}
else {
    var buffer = new ArrayBuffer(0);
    try {
        exports.blob = new Blob([buffer], {
            type: "application/zip"
        }).size === 0;
    }
    catch (e) {
        try {
            var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
            var builder = new Builder();
            builder.append(buffer);
            exports.blob = builder.getBlob('application/zip').size === 0;
        }
        catch (e) {
            exports.blob = false;
        }
    }
}

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8).Buffer))

/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {



// private property
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";


// public method for encoding
exports.encode = function(input, utf8) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    while (i < input.length) {

        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        }
        else if (isNaN(chr3)) {
            enc4 = 64;
        }

        output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4);

    }

    return output;
};

// public method for decoding
exports.decode = function(input, utf8) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {

        enc1 = _keyStr.indexOf(input.charAt(i++));
        enc2 = _keyStr.indexOf(input.charAt(i++));
        enc3 = _keyStr.indexOf(input.charAt(i++));
        enc4 = _keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
        }

    }

    return output;

};


/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {



var support = __webpack_require__(2);
var utils = __webpack_require__(0);
var crc32 = __webpack_require__(37);
var signature = __webpack_require__(14);
var defaults = __webpack_require__(15);
var base64 = __webpack_require__(3);
var compressions = __webpack_require__(5);
var CompressedObject = __webpack_require__(16);
var nodeBuffer = __webpack_require__(7);
var utf8 = __webpack_require__(17);
var StringWriter = __webpack_require__(38);
var Uint8ArrayWriter = __webpack_require__(39);

/**
 * Returns the raw data of a ZipObject, decompress the content if necessary.
 * @param {ZipObject} file the file to use.
 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
 */
var getRawData = function(file) {
    if (file._data instanceof CompressedObject) {
        file._data = file._data.getContent();
        file.options.binary = true;
        file.options.base64 = false;

        if (utils.getTypeOf(file._data) === "uint8array") {
            var copy = file._data;
            // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
            // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
            file._data = new Uint8Array(copy.length);
            // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
            if (copy.length !== 0) {
                file._data.set(copy, 0);
            }
        }
    }
    return file._data;
};

/**
 * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
 * @param {ZipObject} file the file to use.
 * @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
 */
var getBinaryData = function(file) {
    var result = getRawData(file),
        type = utils.getTypeOf(result);
    if (type === "string") {
        if (!file.options.binary) {
            // unicode text !
            // unicode string => binary string is a painful process, check if we can avoid it.
            if (support.nodebuffer) {
                return nodeBuffer(result, "utf-8");
            }
        }
        return file.asBinary();
    }
    return result;
};

/**
 * Transform this._data into a string.
 * @param {function} filter a function String -> String, applied if not null on the result.
 * @return {String} the string representing this._data.
 */
var dataToString = function(asUTF8) {
    var result = getRawData(this);
    if (result === null || typeof result === "undefined") {
        return "";
    }
    // if the data is a base64 string, we decode it before checking the encoding !
    if (this.options.base64) {
        result = base64.decode(result);
    }
    if (asUTF8 && this.options.binary) {
        // JSZip.prototype.utf8decode supports arrays as input
        // skip to array => string step, utf8decode will do it.
        result = out.utf8decode(result);
    }
    else {
        // no utf8 transformation, do the array => string step.
        result = utils.transformTo("string", result);
    }

    if (!asUTF8 && !this.options.binary) {
        result = utils.transformTo("string", out.utf8encode(result));
    }
    return result;
};
/**
 * A simple object representing a file in the zip file.
 * @constructor
 * @param {string} name the name of the file
 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
 * @param {Object} options the options of the file
 */
var ZipObject = function(name, data, options) {
    this.name = name;
    this.dir = options.dir;
    this.date = options.date;
    this.comment = options.comment;
    this.unixPermissions = options.unixPermissions;
    this.dosPermissions = options.dosPermissions;

    this._data = data;
    this.options = options;

    /*
     * This object contains initial values for dir and date.
     * With them, we can check if the user changed the deprecated metadata in
     * `ZipObject#options` or not.
     */
    this._initialMetadata = {
      dir : options.dir,
      date : options.date
    };
};

ZipObject.prototype = {
    /**
     * Return the content as UTF8 string.
     * @return {string} the UTF8 string.
     */
    asText: function() {
        return dataToString.call(this, true);
    },
    /**
     * Returns the binary content.
     * @return {string} the content as binary.
     */
    asBinary: function() {
        return dataToString.call(this, false);
    },
    /**
     * Returns the content as a nodejs Buffer.
     * @return {Buffer} the content as a Buffer.
     */
    asNodeBuffer: function() {
        var result = getBinaryData(this);
        return utils.transformTo("nodebuffer", result);
    },
    /**
     * Returns the content as an Uint8Array.
     * @return {Uint8Array} the content as an Uint8Array.
     */
    asUint8Array: function() {
        var result = getBinaryData(this);
        return utils.transformTo("uint8array", result);
    },
    /**
     * Returns the content as an ArrayBuffer.
     * @return {ArrayBuffer} the content as an ArrayBufer.
     */
    asArrayBuffer: function() {
        return this.asUint8Array().buffer;
    }
};

/**
 * Transform an integer into a string in hexadecimal.
 * @private
 * @param {number} dec the number to convert.
 * @param {number} bytes the number of bytes to generate.
 * @returns {string} the result.
 */
var decToHex = function(dec, bytes) {
    var hex = "",
        i;
    for (i = 0; i < bytes; i++) {
        hex += String.fromCharCode(dec & 0xff);
        dec = dec >>> 8;
    }
    return hex;
};

/**
 * Transforms the (incomplete) options from the user into the complete
 * set of options to create a file.
 * @private
 * @param {Object} o the options from the user.
 * @return {Object} the complete set of options.
 */
var prepareFileAttrs = function(o) {
    o = o || {};
    if (o.base64 === true && (o.binary === null || o.binary === undefined)) {
        o.binary = true;
    }
    o = utils.extend(o, defaults);
    o.date = o.date || new Date();
    if (o.compression !== null) o.compression = o.compression.toUpperCase();

    return o;
};

/**
 * Add a file in the current folder.
 * @private
 * @param {string} name the name of the file
 * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
 * @param {Object} o the options of the file
 * @return {Object} the new file.
 */
var fileAdd = function(name, data, o) {
    // be sure sub folders exist
    var dataType = utils.getTypeOf(data),
        parent;

    o = prepareFileAttrs(o);

    if (typeof o.unixPermissions === "string") {
        o.unixPermissions = parseInt(o.unixPermissions, 8);
    }

    // UNX_IFDIR  0040000 see zipinfo.c
    if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
        o.dir = true;
    }
    // Bit 4    Directory
    if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
        o.dir = true;
    }

    if (o.dir) {
        name = forceTrailingSlash(name);
    }

    if (o.createFolders && (parent = parentFolder(name))) {
        folderAdd.call(this, parent, true);
    }

    if (o.dir || data === null || typeof data === "undefined") {
        o.base64 = false;
        o.binary = false;
        data = null;
        dataType = null;
    }
    else if (dataType === "string") {
        if (o.binary && !o.base64) {
            // optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
            if (o.optimizedBinaryString !== true) {
                // this is a string, not in a base64 format.
                // Be sure that this is a correct "binary string"
                data = utils.string2binary(data);
            }
        }
    }
    else { // arraybuffer, uint8array, ...
        o.base64 = false;
        o.binary = true;

        if (!dataType && !(data instanceof CompressedObject)) {
            throw new Error("The data of '" + name + "' is in an unsupported format !");
        }

        // special case : it's way easier to work with Uint8Array than with ArrayBuffer
        if (dataType === "arraybuffer") {
            data = utils.transformTo("uint8array", data);
        }
    }

    var object = new ZipObject(name, data, o);
    this.files[name] = object;
    return object;
};

/**
 * Find the parent folder of the path.
 * @private
 * @param {string} path the path to use
 * @return {string} the parent folder, or ""
 */
var parentFolder = function (path) {
    if (path.slice(-1) == '/') {
        path = path.substring(0, path.length - 1);
    }
    var lastSlash = path.lastIndexOf('/');
    return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
};


/**
 * Returns the path with a slash at the end.
 * @private
 * @param {String} path the path to check.
 * @return {String} the path with a trailing slash.
 */
var forceTrailingSlash = function(path) {
    // Check the name ends with a /
    if (path.slice(-1) != "/") {
        path += "/"; // IE doesn't like substr(-1)
    }
    return path;
};
/**
 * Add a (sub) folder in the current folder.
 * @private
 * @param {string} name the folder's name
 * @param {boolean=} [createFolders] If true, automatically create sub
 *  folders. Defaults to false.
 * @return {Object} the new folder.
 */
var folderAdd = function(name, createFolders) {
    createFolders = (typeof createFolders !== 'undefined') ? createFolders : false;

    name = forceTrailingSlash(name);

    // Does this folder already exist?
    if (!this.files[name]) {
        fileAdd.call(this, name, null, {
            dir: true,
            createFolders: createFolders
        });
    }
    return this.files[name];
};

/**
 * Generate a JSZip.CompressedObject for a given zipOject.
 * @param {ZipObject} file the object to read.
 * @param {JSZip.compression} compression the compression to use.
 * @param {Object} compressionOptions the options to use when compressing.
 * @return {JSZip.CompressedObject} the compressed result.
 */
var generateCompressedObjectFrom = function(file, compression, compressionOptions) {
    var result = new CompressedObject(),
        content;

    // the data has not been decompressed, we might reuse things !
    if (file._data instanceof CompressedObject) {
        result.uncompressedSize = file._data.uncompressedSize;
        result.crc32 = file._data.crc32;

        if (result.uncompressedSize === 0 || file.dir) {
            compression = compressions['STORE'];
            result.compressedContent = "";
            result.crc32 = 0;
        }
        else if (file._data.compressionMethod === compression.magic) {
            result.compressedContent = file._data.getCompressedContent();
        }
        else {
            content = file._data.getContent();
            // need to decompress / recompress
            result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
        }
    }
    else {
        // have uncompressed data
        content = getBinaryData(file);
        if (!content || content.length === 0 || file.dir) {
            compression = compressions['STORE'];
            content = "";
        }
        result.uncompressedSize = content.length;
        result.crc32 = crc32(content);
        result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
    }

    result.compressedSize = result.compressedContent.length;
    result.compressionMethod = compression.magic;

    return result;
};




/**
 * Generate the UNIX part of the external file attributes.
 * @param {Object} unixPermissions the unix permissions or null.
 * @param {Boolean} isDir true if the entry is a directory, false otherwise.
 * @return {Number} a 32 bit integer.
 *
 * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :
 *
 * TTTTsstrwxrwxrwx0000000000ADVSHR
 * ^^^^____________________________ file type, see zipinfo.c (UNX_*)
 *     ^^^_________________________ setuid, setgid, sticky
 *        ^^^^^^^^^________________ permissions
 *                 ^^^^^^^^^^______ not used ?
 *                           ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only
 */
var generateUnixExternalFileAttr = function (unixPermissions, isDir) {

    var result = unixPermissions;
    if (!unixPermissions) {
        // I can't use octal values in strict mode, hence the hexa.
        //  040775 => 0x41fd
        // 0100664 => 0x81b4
        result = isDir ? 0x41fd : 0x81b4;
    }

    return (result & 0xFFFF) << 16;
};

/**
 * Generate the DOS part of the external file attributes.
 * @param {Object} dosPermissions the dos permissions or null.
 * @param {Boolean} isDir true if the entry is a directory, false otherwise.
 * @return {Number} a 32 bit integer.
 *
 * Bit 0     Read-Only
 * Bit 1     Hidden
 * Bit 2     System
 * Bit 3     Volume Label
 * Bit 4     Directory
 * Bit 5     Archive
 */
var generateDosExternalFileAttr = function (dosPermissions, isDir) {

    // the dir flag is already set for compatibility

    return (dosPermissions || 0)  & 0x3F;
};

/**
 * Generate the various parts used in the construction of the final zip file.
 * @param {string} name the file name.
 * @param {ZipObject} file the file content.
 * @param {JSZip.CompressedObject} compressedObject the compressed object.
 * @param {number} offset the current offset from the start of the zip file.
 * @param {String} platform let's pretend we are this platform (change platform dependents fields)
 * @param {Function} encodeFileName the function to encode the file name / comment.
 * @return {object} the zip parts.
 */
var generateZipParts = function(name, file, compressedObject, offset, platform, encodeFileName) {
    var data = compressedObject.compressedContent,
        useCustomEncoding = encodeFileName !== utf8.utf8encode,
        encodedFileName = utils.transformTo("string", encodeFileName(file.name)),
        utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
        comment = file.comment || "",
        encodedComment = utils.transformTo("string", encodeFileName(comment)),
        utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
        useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
        useUTF8ForComment = utfEncodedComment.length !== comment.length,
        o = file.options,
        dosTime,
        dosDate,
        extraFields = "",
        unicodePathExtraField = "",
        unicodeCommentExtraField = "",
        dir, date;


    // handle the deprecated options.dir
    if (file._initialMetadata.dir !== file.dir) {
        dir = file.dir;
    } else {
        dir = o.dir;
    }

    // handle the deprecated options.date
    if(file._initialMetadata.date !== file.date) {
        date = file.date;
    } else {
        date = o.date;
    }

    var extFileAttr = 0;
    var versionMadeBy = 0;
    if (dir) {
        // dos or unix, we set the dos dir flag
        extFileAttr |= 0x00010;
    }
    if(platform === "UNIX") {
        versionMadeBy = 0x031E; // UNIX, version 3.0
        extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
    } else { // DOS or other, fallback to DOS
        versionMadeBy = 0x0014; // DOS, version 2.0
        extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir);
    }

    // date
    // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
    // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
    // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html

    dosTime = date.getHours();
    dosTime = dosTime << 6;
    dosTime = dosTime | date.getMinutes();
    dosTime = dosTime << 5;
    dosTime = dosTime | date.getSeconds() / 2;

    dosDate = date.getFullYear() - 1980;
    dosDate = dosDate << 4;
    dosDate = dosDate | (date.getMonth() + 1);
    dosDate = dosDate << 5;
    dosDate = dosDate | date.getDate();

    if (useUTF8ForFileName) {
        // set the unicode path extra field. unzip needs at least one extra
        // field to correctly handle unicode path, so using the path is as good
        // as any other information. This could improve the situation with
        // other archive managers too.
        // This field is usually used without the utf8 flag, with a non
        // unicode path in the header (winrar, winzip). This helps (a bit)
        // with the messy Windows' default compressed folders feature but
        // breaks on p7zip which doesn't seek the unicode path extra field.
        // So for now, UTF-8 everywhere !
        unicodePathExtraField =
            // Version
            decToHex(1, 1) +
            // NameCRC32
            decToHex(crc32(encodedFileName), 4) +
            // UnicodeName
            utfEncodedFileName;

        extraFields +=
            // Info-ZIP Unicode Path Extra Field
            "\x75\x70" +
            // size
            decToHex(unicodePathExtraField.length, 2) +
            // content
            unicodePathExtraField;
    }

    if(useUTF8ForComment) {

        unicodeCommentExtraField =
            // Version
            decToHex(1, 1) +
            // CommentCRC32
            decToHex(this.crc32(encodedComment), 4) +
            // UnicodeName
            utfEncodedComment;

        extraFields +=
            // Info-ZIP Unicode Path Extra Field
            "\x75\x63" +
            // size
            decToHex(unicodeCommentExtraField.length, 2) +
            // content
            unicodeCommentExtraField;
    }

    var header = "";

    // version needed to extract
    header += "\x0A\x00";
    // general purpose bit flag
    // set bit 11 if utf8
    header += !useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00";
    // compression method
    header += compressedObject.compressionMethod;
    // last mod file time
    header += decToHex(dosTime, 2);
    // last mod file date
    header += decToHex(dosDate, 2);
    // crc-32
    header += decToHex(compressedObject.crc32, 4);
    // compressed size
    header += decToHex(compressedObject.compressedSize, 4);
    // uncompressed size
    header += decToHex(compressedObject.uncompressedSize, 4);
    // file name length
    header += decToHex(encodedFileName.length, 2);
    // extra field length
    header += decToHex(extraFields.length, 2);


    var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields;

    var dirRecord = signature.CENTRAL_FILE_HEADER +
    // version made by (00: DOS)
    decToHex(versionMadeBy, 2) +
    // file header (common to file and central directory)
    header +
    // file comment length
    decToHex(encodedComment.length, 2) +
    // disk number start
    "\x00\x00" +
    // internal file attributes TODO
    "\x00\x00" +
    // external file attributes
    decToHex(extFileAttr, 4) +
    // relative offset of local header
    decToHex(offset, 4) +
    // file name
    encodedFileName +
    // extra field
    extraFields +
    // file comment
    encodedComment;

    return {
        fileRecord: fileRecord,
        dirRecord: dirRecord,
        compressedObject: compressedObject
    };
};


// return the actual prototype of JSZip
var out = {
    /**
     * Read an existing zip and merge the data in the current JSZip object.
     * The implementation is in jszip-load.js, don't forget to include it.
     * @param {String|ArrayBuffer|Uint8Array|Buffer} stream  The stream to load
     * @param {Object} options Options for loading the stream.
     *  options.base64 : is the stream in base64 ? default : false
     * @return {JSZip} the current JSZip object
     */
    load: function(stream, options) {
        throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
    },

    /**
     * Filter nested files/folders with the specified function.
     * @param {Function} search the predicate to use :
     * function (relativePath, file) {...}
     * It takes 2 arguments : the relative path and the file.
     * @return {Array} An array of matching elements.
     */
    filter: function(search) {
        var result = [],
            filename, relativePath, file, fileClone;
        for (filename in this.files) {
            if (!this.files.hasOwnProperty(filename)) {
                continue;
            }
            file = this.files[filename];
            // return a new object, don't let the user mess with our internal objects :)
            fileClone = new ZipObject(file.name, file._data, utils.extend(file.options));
            relativePath = filename.slice(this.root.length, filename.length);
            if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
            search(relativePath, fileClone)) { // and the file matches the function
                result.push(fileClone);
            }
        }
        return result;
    },

    /**
     * Add a file to the zip file, or search a file.
     * @param   {string|RegExp} name The name of the file to add (if data is defined),
     * the name of the file to find (if no data) or a regex to match files.
     * @param   {String|ArrayBuffer|Uint8Array|Buffer} data  The file data, either raw or base64 encoded
     * @param   {Object} o     File options
     * @return  {JSZip|Object|Array} this JSZip object (when adding a file),
     * a file (when searching by string) or an array of files (when searching by regex).
     */
    file: function(name, data, o) {
        if (arguments.length === 1) {
            if (utils.isRegExp(name)) {
                var regexp = name;
                return this.filter(function(relativePath, file) {
                    return !file.dir && regexp.test(relativePath);
                });
            }
            else { // text
                return this.filter(function(relativePath, file) {
                    return !file.dir && relativePath === name;
                })[0] || null;
            }
        }
        else { // more than one argument : we have data !
            name = this.root + name;
            fileAdd.call(this, name, data, o);
        }
        return this;
    },

    /**
     * Add a directory to the zip file, or search.
     * @param   {String|RegExp} arg The name of the directory to add, or a regex to search folders.
     * @return  {JSZip} an object with the new directory as the root, or an array containing matching folders.
     */
    folder: function(arg) {
        if (!arg) {
            return this;
        }

        if (utils.isRegExp(arg)) {
            return this.filter(function(relativePath, file) {
                return file.dir && arg.test(relativePath);
            });
        }

        // else, name is a new folder
        var name = this.root + arg;
        var newFolder = folderAdd.call(this, name);

        // Allow chaining by returning a new object with this folder as the root
        var ret = this.clone();
        ret.root = newFolder.name;
        return ret;
    },

    /**
     * Delete a file, or a directory and all sub-files, from the zip
     * @param {string} name the name of the file to delete
     * @return {JSZip} this JSZip object
     */
    remove: function(name) {
        name = this.root + name;
        var file = this.files[name];
        if (!file) {
            // Look for any folders
            if (name.slice(-1) != "/") {
                name += "/";
            }
            file = this.files[name];
        }

        if (file && !file.dir) {
            // file
            delete this.files[name];
        } else {
            // maybe a folder, delete recursively
            var kids = this.filter(function(relativePath, file) {
                return file.name.slice(0, name.length) === name;
            });
            for (var i = 0; i < kids.length; i++) {
                delete this.files[kids[i].name];
            }
        }

        return this;
    },

    /**
     * Generate the complete zip file
     * @param {Object} options the options to generate the zip file :
     * - base64, (deprecated, use type instead) true to generate base64.
     * - compression, "STORE" by default.
     * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
     * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
     */
    generate: function(options) {
        options = utils.extend(options || {}, {
            base64: true,
            compression: "STORE",
            compressionOptions : null,
            type: "base64",
            platform: "DOS",
            comment: null,
            mimeType: 'application/zip',
            encodeFileName: utf8.utf8encode
        });

        utils.checkSupport(options.type);

        // accept nodejs `process.platform`
        if(
          options.platform === 'darwin' ||
          options.platform === 'freebsd' ||
          options.platform === 'linux' ||
          options.platform === 'sunos'
        ) {
          options.platform = "UNIX";
        }
        if (options.platform === 'win32') {
          options.platform = "DOS";
        }

        var zipData = [],
            localDirLength = 0,
            centralDirLength = 0,
            writer, i,
            encodedComment = utils.transformTo("string", options.encodeFileName(options.comment || this.comment || ""));

        // first, generate all the zip parts.
        for (var name in this.files) {
            if (!this.files.hasOwnProperty(name)) {
                continue;
            }
            var file = this.files[name];

            var compressionName = file.options.compression || options.compression.toUpperCase();
            var compression = compressions[compressionName];
            if (!compression) {
                throw new Error(compressionName + " is not a valid compression method !");
            }
            var compressionOptions = file.options.compressionOptions || options.compressionOptions || {};

            var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions);

            var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform, options.encodeFileName);
            localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
            centralDirLength += zipPart.dirRecord.length;
            zipData.push(zipPart);
        }

        var dirEnd = "";

        // end of central dir signature
        dirEnd = signature.CENTRAL_DIRECTORY_END +
        // number of this disk
        "\x00\x00" +
        // number of the disk with the start of the central directory
        "\x00\x00" +
        // total number of entries in the central directory on this disk
        decToHex(zipData.length, 2) +
        // total number of entries in the central directory
        decToHex(zipData.length, 2) +
        // size of the central directory   4 bytes
        decToHex(centralDirLength, 4) +
        // offset of start of central directory with respect to the starting disk number
        decToHex(localDirLength, 4) +
        // .ZIP file comment length
        decToHex(encodedComment.length, 2) +
        // .ZIP file comment
        encodedComment;


        // we have all the parts (and the total length)
        // time to create a writer !
        var typeName = options.type.toLowerCase();
        if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") {
            writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
        }else{
            writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
        }

        for (i = 0; i < zipData.length; i++) {
            writer.append(zipData[i].fileRecord);
            writer.append(zipData[i].compressedObject.compressedContent);
        }
        for (i = 0; i < zipData.length; i++) {
            writer.append(zipData[i].dirRecord);
        }

        writer.append(dirEnd);

        var zip = writer.finalize();



        switch(options.type.toLowerCase()) {
            // case "zip is an Uint8Array"
            case "uint8array" :
            case "arraybuffer" :
            case "nodebuffer" :
               return utils.transformTo(options.type.toLowerCase(), zip);
            case "blob" :
               return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType);
            // case "zip is a string"
            case "base64" :
               return (options.base64) ? base64.encode(zip) : zip;
            default : // case "string" :
               return zip;
         }

    },

    /**
     * @deprecated
     * This method will be removed in a future version without replacement.
     */
    crc32: function (input, crc) {
        return crc32(input, crc);
    },

    /**
     * @deprecated
     * This method will be removed in a future version without replacement.
     */
    utf8encode: function (string) {
        return utils.transformTo("string", utf8.utf8encode(string));
    },

    /**
     * @deprecated
     * This method will be removed in a future version without replacement.
     */
    utf8decode: function (input) {
        return utf8.utf8decode(input);
    }
};
module.exports = out;


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {



exports.STORE = {
    magic: "\x00\x00",
    compress: function(content, compressionOptions) {
        return content; // no compression
    },
    uncompress: function(content) {
        return content; // no compression
    },
    compressInputType: null,
    uncompressInputType: null
};
exports.DEFLATE = __webpack_require__(27);


/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {
  2:      'need dictionary',     /* Z_NEED_DICT       2  */
  1:      'stream end',          /* Z_STREAM_END      1  */
  0:      '',                    /* Z_OK              0  */
  '-1':   'file error',          /* Z_ERRNO         (-1) */
  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
};


/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {


/* WEBPACK VAR INJECTION */(function(Buffer) {
module.exports = function(data, encoding){
    return new Buffer(data, encoding);
};
module.exports.test = function(b){
    return Buffer.isBuffer(b);
};

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8).Buffer))

/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {


/* WEBPACK VAR INJECTION */(function(global) {/*!
 * The buffer module from node.js, for the browser.
 *
 * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
 * @license  MIT
 */
/* eslint-disable no-proto */



var base64 = __webpack_require__(24)
var ieee754 = __webpack_require__(25)
var isArray = __webpack_require__(26)

exports.Buffer = Buffer
exports.SlowBuffer = SlowBuffer
exports.INSPECT_MAX_BYTES = 50

/**
 * If `Buffer.TYPED_ARRAY_SUPPORT`:
 *   === true    Use Uint8Array implementation (fastest)
 *   === false   Use Object implementation (most compatible, even IE6)
 *
 * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
 * Opera 11.6+, iOS 4.2+.
 *
 * Due to various browser bugs, sometimes the Object implementation will be used even
 * when the browser supports typed arrays.
 *
 * Note:
 *
 *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
 *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
 *
 *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
 *
 *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
 *     incorrect length in some situations.

 * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
 * get the Object implementation, which is slower but behaves correctly.
 */
Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
  ? global.TYPED_ARRAY_SUPPORT
  : typedArraySupport()

/*
 * Export kMaxLength after typed array support is determined.
 */
exports.kMaxLength = kMaxLength()

function typedArraySupport () {
  try {
    var arr = new Uint8Array(1)
    arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}
    return arr.foo() === 42 && // typed array instances can be augmented
        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
        arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
  } catch (e) {
    return false
  }
}

function kMaxLength () {
  return Buffer.TYPED_ARRAY_SUPPORT
    ? 0x7fffffff
    : 0x3fffffff
}

function createBuffer (that, length) {
  if (kMaxLength() < length) {
    throw new RangeError('Invalid typed array length')
  }
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    // Return an augmented `Uint8Array` instance, for best performance
    that = new Uint8Array(length)
    that.__proto__ = Buffer.prototype
  } else {
    // Fallback: Return an object instance of the Buffer class
    if (that === null) {
      that = new Buffer(length)
    }
    that.length = length
  }

  return that
}

/**
 * The Buffer constructor returns instances of `Uint8Array` that have their
 * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
 * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
 * and the `Uint8Array` methods. Square bracket notation works as expected -- it
 * returns a single octet.
 *
 * The `Uint8Array` prototype remains unmodified.
 */

function Buffer (arg, encodingOrOffset, length) {
  if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
    return new Buffer(arg, encodingOrOffset, length)
  }

  // Common case.
  if (typeof arg === 'number') {
    if (typeof encodingOrOffset === 'string') {
      throw new Error(
        'If encoding is specified then the first argument must be a string'
      )
    }
    return allocUnsafe(this, arg)
  }
  return from(this, arg, encodingOrOffset, length)
}

Buffer.poolSize = 8192 // not used by this implementation

// TODO: Legacy, not needed anymore. Remove in next major version.
Buffer._augment = function (arr) {
  arr.__proto__ = Buffer.prototype
  return arr
}

function from (that, value, encodingOrOffset, length) {
  if (typeof value === 'number') {
    throw new TypeError('"value" argument must not be a number')
  }

  if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
    return fromArrayBuffer(that, value, encodingOrOffset, length)
  }

  if (typeof value === 'string') {
    return fromString(that, value, encodingOrOffset)
  }

  return fromObject(that, value)
}

/**
 * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
 * if value is a number.
 * Buffer.from(str[, encoding])
 * Buffer.from(array)
 * Buffer.from(buffer)
 * Buffer.from(arrayBuffer[, byteOffset[, length]])
 **/
Buffer.from = function (value, encodingOrOffset, length) {
  return from(null, value, encodingOrOffset, length)
}

if (Buffer.TYPED_ARRAY_SUPPORT) {
  Buffer.prototype.__proto__ = Uint8Array.prototype
  Buffer.__proto__ = Uint8Array
  if (typeof Symbol !== 'undefined' && Symbol.species &&
      Buffer[Symbol.species] === Buffer) {
    // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
    Object.defineProperty(Buffer, Symbol.species, {
      value: null,
      configurable: true
    })
  }
}

function assertSize (size) {
  if (typeof size !== 'number') {
    throw new TypeError('"size" argument must be a number')
  } else if (size < 0) {
    throw new RangeError('"size" argument must not be negative')
  }
}

function alloc (that, size, fill, encoding) {
  assertSize(size)
  if (size <= 0) {
    return createBuffer(that, size)
  }
  if (fill !== undefined) {
    // Only pay attention to encoding if it's a string. This
    // prevents accidentally sending in a number that would
    // be interpretted as a start offset.
    return typeof encoding === 'string'
      ? createBuffer(that, size).fill(fill, encoding)
      : createBuffer(that, size).fill(fill)
  }
  return createBuffer(that, size)
}

/**
 * Creates a new filled Buffer instance.
 * alloc(size[, fill[, encoding]])
 **/
Buffer.alloc = function (size, fill, encoding) {
  return alloc(null, size, fill, encoding)
}

function allocUnsafe (that, size) {
  assertSize(size)
  that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
  if (!Buffer.TYPED_ARRAY_SUPPORT) {
    for (var i = 0; i < size; ++i) {
      that[i] = 0
    }
  }
  return that
}

/**
 * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
 * */
Buffer.allocUnsafe = function (size) {
  return allocUnsafe(null, size)
}
/**
 * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
 */
Buffer.allocUnsafeSlow = function (size) {
  return allocUnsafe(null, size)
}

function fromString (that, string, encoding) {
  if (typeof encoding !== 'string' || encoding === '') {
    encoding = 'utf8'
  }

  if (!Buffer.isEncoding(encoding)) {
    throw new TypeError('"encoding" must be a valid string encoding')
  }

  var length = byteLength(string, encoding) | 0
  that = createBuffer(that, length)

  var actual = that.write(string, encoding)

  if (actual !== length) {
    // Writing a hex string, for example, that contains invalid characters will
    // cause everything after the first invalid character to be ignored. (e.g.
    // 'abxxcd' will be treated as 'ab')
    that = that.slice(0, actual)
  }

  return that
}

function fromArrayLike (that, array) {
  var length = array.length < 0 ? 0 : checked(array.length) | 0
  that = createBuffer(that, length)
  for (var i = 0; i < length; i += 1) {
    that[i] = array[i] & 255
  }
  return that
}

function fromArrayBuffer (that, array, byteOffset, length) {
  array.byteLength // this throws if `array` is not a valid ArrayBuffer

  if (byteOffset < 0 || array.byteLength < byteOffset) {
    throw new RangeError('\'offset\' is out of bounds')
  }

  if (array.byteLength < byteOffset + (length || 0)) {
    throw new RangeError('\'length\' is out of bounds')
  }

  if (byteOffset === undefined && length === undefined) {
    array = new Uint8Array(array)
  } else if (length === undefined) {
    array = new Uint8Array(array, byteOffset)
  } else {
    array = new Uint8Array(array, byteOffset, length)
  }

  if (Buffer.TYPED_ARRAY_SUPPORT) {
    // Return an augmented `Uint8Array` instance, for best performance
    that = array
    that.__proto__ = Buffer.prototype
  } else {
    // Fallback: Return an object instance of the Buffer class
    that = fromArrayLike(that, array)
  }
  return that
}

function fromObject (that, obj) {
  if (Buffer.isBuffer(obj)) {
    var len = checked(obj.length) | 0
    that = createBuffer(that, len)

    if (that.length === 0) {
      return that
    }

    obj.copy(that, 0, 0, len)
    return that
  }

  if (obj) {
    if ((typeof ArrayBuffer !== 'undefined' &&
        obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
      if (typeof obj.length !== 'number' || isnan(obj.length)) {
        return createBuffer(that, 0)
      }
      return fromArrayLike(that, obj)
    }

    if (obj.type === 'Buffer' && isArray(obj.data)) {
      return fromArrayLike(that, obj.data)
    }
  }

  throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
}

function checked (length) {
  // Note: cannot use `length < kMaxLength()` here because that fails when
  // length is NaN (which is otherwise coerced to zero.)
  if (length >= kMaxLength()) {
    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
                         'size: 0x' + kMaxLength().toString(16) + ' bytes')
  }
  return length | 0
}

function SlowBuffer (length) {
  if (+length != length) { // eslint-disable-line eqeqeq
    length = 0
  }
  return Buffer.alloc(+length)
}

Buffer.isBuffer = function isBuffer (b) {
  return !!(b != null && b._isBuffer)
}

Buffer.compare = function compare (a, b) {
  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
    throw new TypeError('Arguments must be Buffers')
  }

  if (a === b) return 0

  var x = a.length
  var y = b.length

  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
    if (a[i] !== b[i]) {
      x = a[i]
      y = b[i]
      break
    }
  }

  if (x < y) return -1
  if (y < x) return 1
  return 0
}

Buffer.isEncoding = function isEncoding (encoding) {
  switch (String(encoding).toLowerCase()) {
    case 'hex':
    case 'utf8':
    case 'utf-8':
    case 'ascii':
    case 'latin1':
    case 'binary':
    case 'base64':
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return true
    default:
      return false
  }
}

Buffer.concat = function concat (list, length) {
  if (!isArray(list)) {
    throw new TypeError('"list" argument must be an Array of Buffers')
  }

  if (list.length === 0) {
    return Buffer.alloc(0)
  }

  var i
  if (length === undefined) {
    length = 0
    for (i = 0; i < list.length; ++i) {
      length += list[i].length
    }
  }

  var buffer = Buffer.allocUnsafe(length)
  var pos = 0
  for (i = 0; i < list.length; ++i) {
    var buf = list[i]
    if (!Buffer.isBuffer(buf)) {
      throw new TypeError('"list" argument must be an Array of Buffers')
    }
    buf.copy(buffer, pos)
    pos += buf.length
  }
  return buffer
}

function byteLength (string, encoding) {
  if (Buffer.isBuffer(string)) {
    return string.length
  }
  if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
      (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
    return string.byteLength
  }
  if (typeof string !== 'string') {
    string = '' + string
  }

  var len = string.length
  if (len === 0) return 0

  // Use a for loop to avoid recursion
  var loweredCase = false
  for (;;) {
    switch (encoding) {
      case 'ascii':
      case 'latin1':
      case 'binary':
        return len
      case 'utf8':
      case 'utf-8':
      case undefined:
        return utf8ToBytes(string).length
      case 'ucs2':
      case 'ucs-2':
      case 'utf16le':
      case 'utf-16le':
        return len * 2
      case 'hex':
        return len >>> 1
      case 'base64':
        return base64ToBytes(string).length
      default:
        if (loweredCase) return utf8ToBytes(string).length // assume utf8
        encoding = ('' + encoding).toLowerCase()
        loweredCase = true
    }
  }
}
Buffer.byteLength = byteLength

function slowToString (encoding, start, end) {
  var loweredCase = false

  // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
  // property of a typed array.

  // This behaves neither like String nor Uint8Array in that we set start/end
  // to their upper/lower bounds if the value passed is out of range.
  // undefined is handled specially as per ECMA-262 6th Edition,
  // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
  if (start === undefined || start < 0) {
    start = 0
  }
  // Return early if start > this.length. Done here to prevent potential uint32
  // coercion fail below.
  if (start > this.length) {
    return ''
  }

  if (end === undefined || end > this.length) {
    end = this.length
  }

  if (end <= 0) {
    return ''
  }

  // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
  end >>>= 0
  start >>>= 0

  if (end <= start) {
    return ''
  }

  if (!encoding) encoding = 'utf8'

  while (true) {
    switch (encoding) {
      case 'hex':
        return hexSlice(this, start, end)

      case 'utf8':
      case 'utf-8':
        return utf8Slice(this, start, end)

      case 'ascii':
        return asciiSlice(this, start, end)

      case 'latin1':
      case 'binary':
        return latin1Slice(this, start, end)

      case 'base64':
        return base64Slice(this, start, end)

      case 'ucs2':
      case 'ucs-2':
      case 'utf16le':
      case 'utf-16le':
        return utf16leSlice(this, start, end)

      default:
        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
        encoding = (encoding + '').toLowerCase()
        loweredCase = true
    }
  }
}

// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
// Buffer instances.
Buffer.prototype._isBuffer = true

function swap (b, n, m) {
  var i = b[n]
  b[n] = b[m]
  b[m] = i
}

Buffer.prototype.swap16 = function swap16 () {
  var len = this.length
  if (len % 2 !== 0) {
    throw new RangeError('Buffer size must be a multiple of 16-bits')
  }
  for (var i = 0; i < len; i += 2) {
    swap(this, i, i + 1)
  }
  return this
}

Buffer.prototype.swap32 = function swap32 () {
  var len = this.length
  if (len % 4 !== 0) {
    throw new RangeError('Buffer size must be a multiple of 32-bits')
  }
  for (var i = 0; i < len; i += 4) {
    swap(this, i, i + 3)
    swap(this, i + 1, i + 2)
  }
  return this
}

Buffer.prototype.swap64 = function swap64 () {
  var len = this.length
  if (len % 8 !== 0) {
    throw new RangeError('Buffer size must be a multiple of 64-bits')
  }
  for (var i = 0; i < len; i += 8) {
    swap(this, i, i + 7)
    swap(this, i + 1, i + 6)
    swap(this, i + 2, i + 5)
    swap(this, i + 3, i + 4)
  }
  return this
}

Buffer.prototype.toString = function toString () {
  var length = this.length | 0
  if (length === 0) return ''
  if (arguments.length === 0) return utf8Slice(this, 0, length)
  return slowToString.apply(this, arguments)
}

Buffer.prototype.equals = function equals (b) {
  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
  if (this === b) return true
  return Buffer.compare(this, b) === 0
}

Buffer.prototype.inspect = function inspect () {
  var str = ''
  var max = exports.INSPECT_MAX_BYTES
  if (this.length > 0) {
    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
    if (this.length > max) str += ' ... '
  }
  return '<Buffer ' + str + '>'
}

Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
  if (!Buffer.isBuffer(target)) {
    throw new TypeError('Argument must be a Buffer')
  }

  if (start === undefined) {
    start = 0
  }
  if (end === undefined) {
    end = target ? target.length : 0
  }
  if (thisStart === undefined) {
    thisStart = 0
  }
  if (thisEnd === undefined) {
    thisEnd = this.length
  }

  if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
    throw new RangeError('out of range index')
  }

  if (thisStart >= thisEnd && start >= end) {
    return 0
  }
  if (thisStart >= thisEnd) {
    return -1
  }
  if (start >= end) {
    return 1
  }

  start >>>= 0
  end >>>= 0
  thisStart >>>= 0
  thisEnd >>>= 0

  if (this === target) return 0

  var x = thisEnd - thisStart
  var y = end - start
  var len = Math.min(x, y)

  var thisCopy = this.slice(thisStart, thisEnd)
  var targetCopy = target.slice(start, end)

  for (var i = 0; i < len; ++i) {
    if (thisCopy[i] !== targetCopy[i]) {
      x = thisCopy[i]
      y = targetCopy[i]
      break
    }
  }

  if (x < y) return -1
  if (y < x) return 1
  return 0
}

// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
//
// Arguments:
// - buffer - a Buffer to search
// - val - a string, Buffer, or number
// - byteOffset - an index into `buffer`; will be clamped to an int32
// - encoding - an optional encoding, relevant is val is a string
// - dir - true for indexOf, false for lastIndexOf
function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
  // Empty buffer means no match
  if (buffer.length === 0) return -1

  // Normalize byteOffset
  if (typeof byteOffset === 'string') {
    encoding = byteOffset
    byteOffset = 0
  } else if (byteOffset > 0x7fffffff) {
    byteOffset = 0x7fffffff
  } else if (byteOffset < -0x80000000) {
    byteOffset = -0x80000000
  }
  byteOffset = +byteOffset  // Coerce to Number.
  if (isNaN(byteOffset)) {
    // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
    byteOffset = dir ? 0 : (buffer.length - 1)
  }

  // Normalize byteOffset: negative offsets start from the end of the buffer
  if (byteOffset < 0) byteOffset = buffer.length + byteOffset
  if (byteOffset >= buffer.length) {
    if (dir) return -1
    else byteOffset = buffer.length - 1
  } else if (byteOffset < 0) {
    if (dir) byteOffset = 0
    else return -1
  }

  // Normalize val
  if (typeof val === 'string') {
    val = Buffer.from(val, encoding)
  }

  // Finally, search either indexOf (if dir is true) or lastIndexOf
  if (Buffer.isBuffer(val)) {
    // Special case: looking for empty string/buffer always fails
    if (val.length === 0) {
      return -1
    }
    return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
  } else if (typeof val === 'number') {
    val = val & 0xFF // Search for a byte value [0-255]
    if (Buffer.TYPED_ARRAY_SUPPORT &&
        typeof Uint8Array.prototype.indexOf === 'function') {
      if (dir) {
        return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
      } else {
        return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
      }
    }
    return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
  }

  throw new TypeError('val must be string, number or Buffer')
}

function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
  var indexSize = 1
  var arrLength = arr.length
  var valLength = val.length

  if (encoding !== undefined) {
    encoding = String(encoding).toLowerCase()
    if (encoding === 'ucs2' || encoding === 'ucs-2' ||
        encoding === 'utf16le' || encoding === 'utf-16le') {
      if (arr.length < 2 || val.length < 2) {
        return -1
      }
      indexSize = 2
      arrLength /= 2
      valLength /= 2
      byteOffset /= 2
    }
  }

  function read (buf, i) {
    if (indexSize === 1) {
      return buf[i]
    } else {
      return buf.readUInt16BE(i * indexSize)
    }
  }

  var i
  if (dir) {
    var foundIndex = -1
    for (i = byteOffset; i < arrLength; i++) {
      if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
        if (foundIndex === -1) foundIndex = i
        if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
      } else {
        if (foundIndex !== -1) i -= i - foundIndex
        foundIndex = -1
      }
    }
  } else {
    if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
    for (i = byteOffset; i >= 0; i--) {
      var found = true
      for (var j = 0; j < valLength; j++) {
        if (read(arr, i + j) !== read(val, j)) {
          found = false
          break
        }
      }
      if (found) return i
    }
  }

  return -1
}

Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
  return this.indexOf(val, byteOffset, encoding) !== -1
}

Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
  return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
}

Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
  return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
}

function hexWrite (buf, string, offset, length) {
  offset = Number(offset) || 0
  var remaining = buf.length - offset
  if (!length) {
    length = remaining
  } else {
    length = Number(length)
    if (length > remaining) {
      length = remaining
    }
  }

  // must be an even number of digits
  var strLen = string.length
  if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')

  if (length > strLen / 2) {
    length = strLen / 2
  }
  for (var i = 0; i < length; ++i) {
    var parsed = parseInt(string.substr(i * 2, 2), 16)
    if (isNaN(parsed)) return i
    buf[offset + i] = parsed
  }
  return i
}

function utf8Write (buf, string, offset, length) {
  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
}

function asciiWrite (buf, string, offset, length) {
  return blitBuffer(asciiToBytes(string), buf, offset, length)
}

function latin1Write (buf, string, offset, length) {
  return asciiWrite(buf, string, offset, length)
}

function base64Write (buf, string, offset, length) {
  return blitBuffer(base64ToBytes(string), buf, offset, length)
}

function ucs2Write (buf, string, offset, length) {
  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
}

Buffer.prototype.write = function write (string, offset, length, encoding) {
  // Buffer#write(string)
  if (offset === undefined) {
    encoding = 'utf8'
    length = this.length
    offset = 0
  // Buffer#write(string, encoding)
  } else if (length === undefined && typeof offset === 'string') {
    encoding = offset
    length = this.length
    offset = 0
  // Buffer#write(string, offset[, length][, encoding])
  } else if (isFinite(offset)) {
    offset = offset | 0
    if (isFinite(length)) {
      length = length | 0
      if (encoding === undefined) encoding = 'utf8'
    } else {
      encoding = length
      length = undefined
    }
  // legacy write(string, encoding, offset, length) - remove in v0.13
  } else {
    throw new Error(
      'Buffer.write(string, encoding, offset[, length]) is no longer supported'
    )
  }

  var remaining = this.length - offset
  if (length === undefined || length > remaining) length = remaining

  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
    throw new RangeError('Attempt to write outside buffer bounds')
  }

  if (!encoding) encoding = 'utf8'

  var loweredCase = false
  for (;;) {
    switch (encoding) {
      case 'hex':
        return hexWrite(this, string, offset, length)

      case 'utf8':
      case 'utf-8':
        return utf8Write(this, string, offset, length)

      case 'ascii':
        return asciiWrite(this, string, offset, length)

      case 'latin1':
      case 'binary':
        return latin1Write(this, string, offset, length)

      case 'base64':
        // Warning: maxLength not taken into account in base64Write
        return base64Write(this, string, offset, length)

      case 'ucs2':
      case 'ucs-2':
      case 'utf16le':
      case 'utf-16le':
        return ucs2Write(this, string, offset, length)

      default:
        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
        encoding = ('' + encoding).toLowerCase()
        loweredCase = true
    }
  }
}

Buffer.prototype.toJSON = function toJSON () {
  return {
    type: 'Buffer',
    data: Array.prototype.slice.call(this._arr || this, 0)
  }
}

function base64Slice (buf, start, end) {
  if (start === 0 && end === buf.length) {
    return base64.fromByteArray(buf)
  } else {
    return base64.fromByteArray(buf.slice(start, end))
  }
}

function utf8Slice (buf, start, end) {
  end = Math.min(buf.length, end)
  var res = []

  var i = start
  while (i < end) {
    var firstByte = buf[i]
    var codePoint = null
    var bytesPerSequence = (firstByte > 0xEF) ? 4
      : (firstByte > 0xDF) ? 3
      : (firstByte > 0xBF) ? 2
      : 1

    if (i + bytesPerSequence <= end) {
      var secondByte, thirdByte, fourthByte, tempCodePoint

      switch (bytesPerSequence) {
        case 1:
          if (firstByte < 0x80) {
            codePoint = firstByte
          }
          break
        case 2:
          secondByte = buf[i + 1]
          if ((secondByte & 0xC0) === 0x80) {
            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
            if (tempCodePoint > 0x7F) {
              codePoint = tempCodePoint
            }
          }
          break
        case 3:
          secondByte = buf[i + 1]
          thirdByte = buf[i + 2]
          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
              codePoint = tempCodePoint
            }
          }
          break
        case 4:
          secondByte = buf[i + 1]
          thirdByte = buf[i + 2]
          fourthByte = buf[i + 3]
          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
              codePoint = tempCodePoint
            }
          }
      }
    }

    if (codePoint === null) {
      // we did not generate a valid codePoint so insert a
      // replacement char (U+FFFD) and advance only 1 byte
      codePoint = 0xFFFD
      bytesPerSequence = 1
    } else if (codePoint > 0xFFFF) {
      // encode to utf16 (surrogate pair dance)
      codePoint -= 0x10000
      res.push(codePoint >>> 10 & 0x3FF | 0xD800)
      codePoint = 0xDC00 | codePoint & 0x3FF
    }

    res.push(codePoint)
    i += bytesPerSequence
  }

  return decodeCodePointsArray(res)
}

// Based on http://stackoverflow.com/a/22747272/680742, the browser with
// the lowest limit is Chrome, with 0x10000 args.
// We go 1 magnitude less, for safety
var MAX_ARGUMENTS_LENGTH = 0x1000

function decodeCodePointsArray (codePoints) {
  var len = codePoints.length
  if (len <= MAX_ARGUMENTS_LENGTH) {
    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
  }

  // Decode in chunks to avoid "call stack size exceeded".
  var res = ''
  var i = 0
  while (i < len) {
    res += String.fromCharCode.apply(
      String,
      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
    )
  }
  return res
}

function asciiSlice (buf, start, end) {
  var ret = ''
  end = Math.min(buf.length, end)

  for (var i = start; i < end; ++i) {
    ret += String.fromCharCode(buf[i] & 0x7F)
  }
  return ret
}

function latin1Slice (buf, start, end) {
  var ret = ''
  end = Math.min(buf.length, end)

  for (var i = start; i < end; ++i) {
    ret += String.fromCharCode(buf[i])
  }
  return ret
}

function hexSlice (buf, start, end) {
  var len = buf.length

  if (!start || start < 0) start = 0
  if (!end || end < 0 || end > len) end = len

  var out = ''
  for (var i = start; i < end; ++i) {
    out += toHex(buf[i])
  }
  return out
}

function utf16leSlice (buf, start, end) {
  var bytes = buf.slice(start, end)
  var res = ''
  for (var i = 0; i < bytes.length; i += 2) {
    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
  }
  return res
}

Buffer.prototype.slice = function slice (start, end) {
  var len = this.length
  start = ~~start
  end = end === undefined ? len : ~~end

  if (start < 0) {
    start += len
    if (start < 0) start = 0
  } else if (start > len) {
    start = len
  }

  if (end < 0) {
    end += len
    if (end < 0) end = 0
  } else if (end > len) {
    end = len
  }

  if (end < start) end = start

  var newBuf
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    newBuf = this.subarray(start, end)
    newBuf.__proto__ = Buffer.prototype
  } else {
    var sliceLen = end - start
    newBuf = new Buffer(sliceLen, undefined)
    for (var i = 0; i < sliceLen; ++i) {
      newBuf[i] = this[i + start]
    }
  }

  return newBuf
}

/*
 * Need to make sure that buffer isn't trying to write out of bounds.
 */
function checkOffset (offset, ext, length) {
  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
}

Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) checkOffset(offset, byteLength, this.length)

  var val = this[offset]
  var mul = 1
  var i = 0
  while (++i < byteLength && (mul *= 0x100)) {
    val += this[offset + i] * mul
  }

  return val
}

Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) {
    checkOffset(offset, byteLength, this.length)
  }

  var val = this[offset + --byteLength]
  var mul = 1
  while (byteLength > 0 && (mul *= 0x100)) {
    val += this[offset + --byteLength] * mul
  }

  return val
}

Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 1, this.length)
  return this[offset]
}

Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 2, this.length)
  return this[offset] | (this[offset + 1] << 8)
}

Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 2, this.length)
  return (this[offset] << 8) | this[offset + 1]
}

Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)

  return ((this[offset]) |
      (this[offset + 1] << 8) |
      (this[offset + 2] << 16)) +
      (this[offset + 3] * 0x1000000)
}

Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)

  return (this[offset] * 0x1000000) +
    ((this[offset + 1] << 16) |
    (this[offset + 2] << 8) |
    this[offset + 3])
}

Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) checkOffset(offset, byteLength, this.length)

  var val = this[offset]
  var mul = 1
  var i = 0
  while (++i < byteLength && (mul *= 0x100)) {
    val += this[offset + i] * mul
  }
  mul *= 0x80

  if (val >= mul) val -= Math.pow(2, 8 * byteLength)

  return val
}

Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) checkOffset(offset, byteLength, this.length)

  var i = byteLength
  var mul = 1
  var val = this[offset + --i]
  while (i > 0 && (mul *= 0x100)) {
    val += this[offset + --i] * mul
  }
  mul *= 0x80

  if (val >= mul) val -= Math.pow(2, 8 * byteLength)

  return val
}

Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 1, this.length)
  if (!(this[offset] & 0x80)) return (this[offset])
  return ((0xff - this[offset] + 1) * -1)
}

Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 2, this.length)
  var val = this[offset] | (this[offset + 1] << 8)
  return (val & 0x8000) ? val | 0xFFFF0000 : val
}

Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 2, this.length)
  var val = this[offset + 1] | (this[offset] << 8)
  return (val & 0x8000) ? val | 0xFFFF0000 : val
}

Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)

  return (this[offset]) |
    (this[offset + 1] << 8) |
    (this[offset + 2] << 16) |
    (this[offset + 3] << 24)
}

Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)

  return (this[offset] << 24) |
    (this[offset + 1] << 16) |
    (this[offset + 2] << 8) |
    (this[offset + 3])
}

Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)
  return ieee754.read(this, offset, true, 23, 4)
}

Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 4, this.length)
  return ieee754.read(this, offset, false, 23, 4)
}

Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 8, this.length)
  return ieee754.read(this, offset, true, 52, 8)
}

Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
  if (!noAssert) checkOffset(offset, 8, this.length)
  return ieee754.read(this, offset, false, 52, 8)
}

function checkInt (buf, value, offset, ext, max, min) {
  if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
  if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
  if (offset + ext > buf.length) throw new RangeError('Index out of range')
}

Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
  value = +value
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) {
    var maxBytes = Math.pow(2, 8 * byteLength) - 1
    checkInt(this, value, offset, byteLength, maxBytes, 0)
  }

  var mul = 1
  var i = 0
  this[offset] = value & 0xFF
  while (++i < byteLength && (mul *= 0x100)) {
    this[offset + i] = (value / mul) & 0xFF
  }

  return offset + byteLength
}

Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
  value = +value
  offset = offset | 0
  byteLength = byteLength | 0
  if (!noAssert) {
    var maxBytes = Math.pow(2, 8 * byteLength) - 1
    checkInt(this, value, offset, byteLength, maxBytes, 0)
  }

  var i = byteLength - 1
  var mul = 1
  this[offset + i] = value & 0xFF
  while (--i >= 0 && (mul *= 0x100)) {
    this[offset + i] = (value / mul) & 0xFF
  }

  return offset + byteLength
}

Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
  this[offset] = (value & 0xff)
  return offset + 1
}

function objectWriteUInt16 (buf, value, offset, littleEndian) {
  if (value < 0) value = 0xffff + value + 1
  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {
    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
      (littleEndian ? i : 1 - i) * 8
  }
}

Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value & 0xff)
    this[offset + 1] = (value >>> 8)
  } else {
    objectWriteUInt16(this, value, offset, true)
  }
  return offset + 2
}

Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value >>> 8)
    this[offset + 1] = (value & 0xff)
  } else {
    objectWriteUInt16(this, value, offset, false)
  }
  return offset + 2
}

function objectWriteUInt32 (buf, value, offset, littleEndian) {
  if (value < 0) value = 0xffffffff + value + 1
  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {
    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
  }
}

Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset + 3] = (value >>> 24)
    this[offset + 2] = (value >>> 16)
    this[offset + 1] = (value >>> 8)
    this[offset] = (value & 0xff)
  } else {
    objectWriteUInt32(this, value, offset, true)
  }
  return offset + 4
}

Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value >>> 24)
    this[offset + 1] = (value >>> 16)
    this[offset + 2] = (value >>> 8)
    this[offset + 3] = (value & 0xff)
  } else {
    objectWriteUInt32(this, value, offset, false)
  }
  return offset + 4
}

Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) {
    var limit = Math.pow(2, 8 * byteLength - 1)

    checkInt(this, value, offset, byteLength, limit - 1, -limit)
  }

  var i = 0
  var mul = 1
  var sub = 0
  this[offset] = value & 0xFF
  while (++i < byteLength && (mul *= 0x100)) {
    if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
      sub = 1
    }
    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
  }

  return offset + byteLength
}

Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) {
    var limit = Math.pow(2, 8 * byteLength - 1)

    checkInt(this, value, offset, byteLength, limit - 1, -limit)
  }

  var i = byteLength - 1
  var mul = 1
  var sub = 0
  this[offset + i] = value & 0xFF
  while (--i >= 0 && (mul *= 0x100)) {
    if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
      sub = 1
    }
    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
  }

  return offset + byteLength
}

Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
  if (value < 0) value = 0xff + value + 1
  this[offset] = (value & 0xff)
  return offset + 1
}

Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value & 0xff)
    this[offset + 1] = (value >>> 8)
  } else {
    objectWriteUInt16(this, value, offset, true)
  }
  return offset + 2
}

Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value >>> 8)
    this[offset + 1] = (value & 0xff)
  } else {
    objectWriteUInt16(this, value, offset, false)
  }
  return offset + 2
}

Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value & 0xff)
    this[offset + 1] = (value >>> 8)
    this[offset + 2] = (value >>> 16)
    this[offset + 3] = (value >>> 24)
  } else {
    objectWriteUInt32(this, value, offset, true)
  }
  return offset + 4
}

Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
  value = +value
  offset = offset | 0
  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
  if (value < 0) value = 0xffffffff + value + 1
  if (Buffer.TYPED_ARRAY_SUPPORT) {
    this[offset] = (value >>> 24)
    this[offset + 1] = (value >>> 16)
    this[offset + 2] = (value >>> 8)
    this[offset + 3] = (value & 0xff)
  } else {
    objectWriteUInt32(this, value, offset, false)
  }
  return offset + 4
}

function checkIEEE754 (buf, value, offset, ext, max, min) {
  if (offset + ext > buf.length) throw new RangeError('Index out of range')
  if (offset < 0) throw new RangeError('Index out of range')
}

function writeFloat (buf, value, offset, littleEndian, noAssert) {
  if (!noAssert) {
    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
  }
  ieee754.write(buf, value, offset, littleEndian, 23, 4)
  return offset + 4
}

Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
  return writeFloat(this, value, offset, true, noAssert)
}

Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
  return writeFloat(this, value, offset, false, noAssert)
}

function writeDouble (buf, value, offset, littleEndian, noAssert) {
  if (!noAssert) {
    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
  }
  ieee754.write(buf, value, offset, littleEndian, 52, 8)
  return offset + 8
}

Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
  return writeDouble(this, value, offset, true, noAssert)
}

Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
  return writeDouble(this, value, offset, false, noAssert)
}

// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
Buffer.prototype.copy = function copy (target, targetStart, start, end) {
  if (!start) start = 0
  if (!end && end !== 0) end = this.length
  if (targetStart >= target.length) targetStart = target.length
  if (!targetStart) targetStart = 0
  if (end > 0 && end < start) end = start

  // Copy 0 bytes; we're done
  if (end === start) return 0
  if (target.length === 0 || this.length === 0) return 0

  // Fatal error conditions
  if (targetStart < 0) {
    throw new RangeError('targetStart out of bounds')
  }
  if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
  if (end < 0) throw new RangeError('sourceEnd out of bounds')

  // Are we oob?
  if (end > this.length) end = this.length
  if (target.length - targetStart < end - start) {
    end = target.length - targetStart + start
  }

  var len = end - start
  var i

  if (this === target && start < targetStart && targetStart < end) {
    // descending copy from end
    for (i = len - 1; i >= 0; --i) {
      target[i + targetStart] = this[i + start]
    }
  } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
    // ascending copy from start
    for (i = 0; i < len; ++i) {
      target[i + targetStart] = this[i + start]
    }
  } else {
    Uint8Array.prototype.set.call(
      target,
      this.subarray(start, start + len),
      targetStart
    )
  }

  return len
}

// Usage:
//    buffer.fill(number[, offset[, end]])
//    buffer.fill(buffer[, offset[, end]])
//    buffer.fill(string[, offset[, end]][, encoding])
Buffer.prototype.fill = function fill (val, start, end, encoding) {
  // Handle string cases:
  if (typeof val === 'string') {
    if (typeof start === 'string') {
      encoding = start
      start = 0
      end = this.length
    } else if (typeof end === 'string') {
      encoding = end
      end = this.length
    }
    if (val.length === 1) {
      var code = val.charCodeAt(0)
      if (code < 256) {
        val = code
      }
    }
    if (encoding !== undefined && typeof encoding !== 'string') {
      throw new TypeError('encoding must be a string')
    }
    if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
      throw new TypeError('Unknown encoding: ' + encoding)
    }
  } else if (typeof val === 'number') {
    val = val & 255
  }

  // Invalid ranges are not set to a default, so can range check early.
  if (start < 0 || this.length < start || this.length < end) {
    throw new RangeError('Out of range index')
  }

  if (end <= start) {
    return this
  }

  start = start >>> 0
  end = end === undefined ? this.length : end >>> 0

  if (!val) val = 0

  var i
  if (typeof val === 'number') {
    for (i = start; i < end; ++i) {
      this[i] = val
    }
  } else {
    var bytes = Buffer.isBuffer(val)
      ? val
      : utf8ToBytes(new Buffer(val, encoding).toString())
    var len = bytes.length
    for (i = 0; i < end - start; ++i) {
      this[i + start] = bytes[i % len]
    }
  }

  return this
}

// HELPER FUNCTIONS
// ================

var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g

function base64clean (str) {
  // Node strips out invalid characters like \n and \t from the string, base64-js does not
  str = stringtrim(str).replace(INVALID_BASE64_RE, '')
  // Node converts strings with length < 2 to ''
  if (str.length < 2) return ''
  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
  while (str.length % 4 !== 0) {
    str = str + '='
  }
  return str
}

function stringtrim (str) {
  if (str.trim) return str.trim()
  return str.replace(/^\s+|\s+$/g, '')
}

function toHex (n) {
  if (n < 16) return '0' + n.toString(16)
  return n.toString(16)
}

function utf8ToBytes (string, units) {
  units = units || Infinity
  var codePoint
  var length = string.length
  var leadSurrogate = null
  var bytes = []

  for (var i = 0; i < length; ++i) {
    codePoint = string.charCodeAt(i)

    // is surrogate component
    if (codePoint > 0xD7FF && codePoint < 0xE000) {
      // last char was a lead
      if (!leadSurrogate) {
        // no lead yet
        if (codePoint > 0xDBFF) {
          // unexpected trail
          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
          continue
        } else if (i + 1 === length) {
          // unpaired lead
          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
          continue
        }

        // valid lead
        leadSurrogate = codePoint

        continue
      }

      // 2 leads in a row
      if (codePoint < 0xDC00) {
        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
        leadSurrogate = codePoint
        continue
      }

      // valid surrogate pair
      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
    } else if (leadSurrogate) {
      // valid bmp char, but last char was a lead
      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
    }

    leadSurrogate = null

    // encode utf8
    if (codePoint < 0x80) {
      if ((units -= 1) < 0) break
      bytes.push(codePoint)
    } else if (codePoint < 0x800) {
      if ((units -= 2) < 0) break
      bytes.push(
        codePoint >> 0x6 | 0xC0,
        codePoint & 0x3F | 0x80
      )
    } else if (codePoint < 0x10000) {
      if ((units -= 3) < 0) break
      bytes.push(
        codePoint >> 0xC | 0xE0,
        codePoint >> 0x6 & 0x3F | 0x80,
        codePoint & 0x3F | 0x80
      )
    } else if (codePoint < 0x110000) {
      if ((units -= 4) < 0) break
      bytes.push(
        codePoint >> 0x12 | 0xF0,
        codePoint >> 0xC & 0x3F | 0x80,
        codePoint >> 0x6 & 0x3F | 0x80,
        codePoint & 0x3F | 0x80
      )
    } else {
      throw new Error('Invalid code point')
    }
  }

  return bytes
}

function asciiToBytes (str) {
  var byteArray = []
  for (var i = 0; i < str.length; ++i) {
    // Node's code seems to be doing this and not & 0x7F..
    byteArray.push(str.charCodeAt(i) & 0xFF)
  }
  return byteArray
}

function utf16leToBytes (str, units) {
  var c, hi, lo
  var byteArray = []
  for (var i = 0; i < str.length; ++i) {
    if ((units -= 2) < 0) break

    c = str.charCodeAt(i)
    hi = c >> 8
    lo = c % 256
    byteArray.push(lo)
    byteArray.push(hi)
  }

  return byteArray
}

function base64ToBytes (str) {
  return base64.toByteArray(base64clean(str))
}

function blitBuffer (src, dst, offset, length) {
  for (var i = 0; i < length; ++i) {
    if ((i + offset >= dst.length) || (i >= src.length)) break
    dst[i + offset] = src[i]
  }
  return i
}

function isnan (val) {
  return val !== val // eslint-disable-line no-self-compare
}

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(23)))

/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {




// Note: adler32 takes 12% for level 0 and 2% for level 6.
// It doesn't worth to make additional optimizationa as in original.
// Small size is preferable.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function adler32(adler, buf, len, pos) {
  var s1 = (adler & 0xffff) |0,
      s2 = ((adler >>> 16) & 0xffff) |0,
      n = 0;

  while (len !== 0) {
    // Set limit ~ twice less than 5552, to keep
    // s2 in 31-bits, because we force signed ints.
    // in other case %= will fail.
    n = len > 2000 ? 2000 : len;
    len -= n;

    do {
      s1 = (s1 + buf[pos++]) |0;
      s2 = (s2 + s1) |0;
    } while (--n);

    s1 %= 65521;
    s2 %= 65521;
  }

  return (s1 | (s2 << 16)) |0;
}


module.exports = adler32;


/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {




// Note: we can't get significant speed boost here.
// So write code to minimize size - no pregenerated tables
// and array tools dependencies.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// Use ordinary array, since untyped makes no boost here
function makeTable() {
  var c, table = [];

  for (var n = 0; n < 256; n++) {
    c = n;
    for (var k = 0; k < 8; k++) {
      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
    }
    table[n] = c;
  }

  return table;
}

// Create table on load. Just 255 signed longs. Not a problem.
var crcTable = makeTable();


function crc32(crc, buf, len, pos) {
  var t = crcTable,
      end = pos + len;

  crc ^= -1;

  for (var i = pos; i < end; i++) {
    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
  }

  return (crc ^ (-1)); // >>> 0;
}


module.exports = crc32;


/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {


// String encode/decode helpers



var utils = __webpack_require__(1);


// Quick check if we can use fast array to bin string conversion
//
// - apply(Array) can fail on Android 2.2
// - apply(Uint8Array) can fail on iOS 5.1 Safary
//
var STR_APPLY_OK = true;
var STR_APPLY_UIA_OK = true;

try { String.fromCharCode.apply(null, [ 0 ]); } catch (__) { STR_APPLY_OK = false; }
try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }


// Table with utf8 lengths (calculated by first byte of sequence)
// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
// because max possible codepoint is 0x10ffff
var _utf8len = new utils.Buf8(256);
for (var q = 0; q < 256; q++) {
  _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
}
_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start


// convert string to array (typed, when possible)
exports.string2buf = function (str) {
  var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;

  // count binary size
  for (m_pos = 0; m_pos < str_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
  }

  // allocate buffer
  buf = new utils.Buf8(buf_len);

  // convert
  for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    if (c < 0x80) {
      /* one byte */
      buf[i++] = c;
    } else if (c < 0x800) {
      /* two bytes */
      buf[i++] = 0xC0 | (c >>> 6);
      buf[i++] = 0x80 | (c & 0x3f);
    } else if (c < 0x10000) {
      /* three bytes */
      buf[i++] = 0xE0 | (c >>> 12);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    } else {
      /* four bytes */
      buf[i++] = 0xf0 | (c >>> 18);
      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    }
  }

  return buf;
};

// Helper (used in 2 places)
function buf2binstring(buf, len) {
  // use fallback for big arrays to avoid stack overflow
  if (len < 65537) {
    if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) {
      return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
    }
  }

  var result = '';
  for (var i = 0; i < len; i++) {
    result += String.fromCharCode(buf[i]);
  }
  return result;
}


// Convert byte array to binary string
exports.buf2binstring = function (buf) {
  return buf2binstring(buf, buf.length);
};


// Convert binary string (typed, when possible)
exports.binstring2buf = function (str) {
  var buf = new utils.Buf8(str.length);
  for (var i = 0, len = buf.length; i < len; i++) {
    buf[i] = str.charCodeAt(i);
  }
  return buf;
};


// convert array to string
exports.buf2string = function (buf, max) {
  var i, out, c, c_len;
  var len = max || buf.length;

  // Reserve max possible length (2 words per char)
  // NB: by unknown reasons, Array is significantly faster for
  //     String.fromCharCode.apply than Uint16Array.
  var utf16buf = new Array(len * 2);

  for (out = 0, i = 0; i < len;) {
    c = buf[i++];
    // quick process ascii
    if (c < 0x80) { utf16buf[out++] = c; continue; }

    c_len = _utf8len[c];
    // skip 5 & 6 byte codes
    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }

    // apply mask on first byte
    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
    // join the rest
    while (c_len > 1 && i < len) {
      c = (c << 6) | (buf[i++] & 0x3f);
      c_len--;
    }

    // terminated by end of string?
    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }

    if (c < 0x10000) {
      utf16buf[out++] = c;
    } else {
      c -= 0x10000;
      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
    }
  }

  return buf2binstring(utf16buf, out);
};


// Calculate max possible position in utf8 buffer,
// that will not break sequence. If that's not possible
// - (very small limits) return max size as is.
//
// buf[] - utf8 bytes array
// max   - length limit (mandatory);
exports.utf8border = function (buf, max) {
  var pos;

  max = max || buf.length;
  if (max > buf.length) { max = buf.length; }

  // go back from last position, until start of sequence found
  pos = max - 1;
  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }

  // Fuckup - very small and broken sequence,
  // return max, because we should return something anyway.
  if (pos < 0) { return max; }

  // If we came to start of buffer - that means vuffer is too small,
  // return max too.
  if (pos === 0) { return max; }

  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};


/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function ZStream() {
  /* next input byte */
  this.input = null; // JS specific, because we have no pointers
  this.next_in = 0;
  /* number of bytes available at input */
  this.avail_in = 0;
  /* total number of input bytes read so far */
  this.total_in = 0;
  /* next output byte should be put there */
  this.output = null; // JS specific, because we have no pointers
  this.next_out = 0;
  /* remaining free space at output */
  this.avail_out = 0;
  /* total number of bytes output so far */
  this.total_out = 0;
  /* last error message, NULL if no error */
  this.msg = ''/*Z_NULL*/;
  /* not visible by applications */
  this.state = null;
  /* best guess about the data type: binary or text */
  this.data_type = 2/*Z_UNKNOWN*/;
  /* adler32 value of the uncompressed data */
  this.adler = 0;
}

module.exports = ZStream;


/***/ }),
/* 13 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {

  /* Allowed flush values; see deflate() and inflate() below for details */
  Z_NO_FLUSH:         0,
  Z_PARTIAL_FLUSH:    1,
  Z_SYNC_FLUSH:       2,
  Z_FULL_FLUSH:       3,
  Z_FINISH:           4,
  Z_BLOCK:            5,
  Z_TREES:            6,

  /* Return codes for the compression/decompression functions. Negative values
  * are errors, positive values are used for special but normal events.
  */
  Z_OK:               0,
  Z_STREAM_END:       1,
  Z_NEED_DICT:        2,
  Z_ERRNO:           -1,
  Z_STREAM_ERROR:    -2,
  Z_DATA_ERROR:      -3,
  //Z_MEM_ERROR:     -4,
  Z_BUF_ERROR:       -5,
  //Z_VERSION_ERROR: -6,

  /* compression levels */
  Z_NO_COMPRESSION:         0,
  Z_BEST_SPEED:             1,
  Z_BEST_COMPRESSION:       9,
  Z_DEFAULT_COMPRESSION:   -1,


  Z_FILTERED:               1,
  Z_HUFFMAN_ONLY:           2,
  Z_RLE:                    3,
  Z_FIXED:                  4,
  Z_DEFAULT_STRATEGY:       0,

  /* Possible values of the data_type field (though see inflate()) */
  Z_BINARY:                 0,
  Z_TEXT:                   1,
  //Z_ASCII:                1, // = Z_TEXT (deprecated)
  Z_UNKNOWN:                2,

  /* The deflate compression method */
  Z_DEFLATED:               8
  //Z_NULL:                 null // Use -1 or null inline, depending on var type
};


/***/ }),
/* 14 */
/***/ (function(module, exports, __webpack_require__) {



exports.LOCAL_FILE_HEADER = "PK\x03\x04";
exports.CENTRAL_FILE_HEADER = "PK\x01\x02";
exports.CENTRAL_DIRECTORY_END = "PK\x05\x06";
exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07";
exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06";
exports.DATA_DESCRIPTOR = "PK\x07\x08";


/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {



exports.base64 = false;
exports.binary = false;
exports.dir = false;
exports.createFolders = false;
exports.date = null;
exports.compression = null;
exports.compressionOptions = null;
exports.comment = null;
exports.unixPermissions = null;
exports.dosPermissions = null;


/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {



function CompressedObject() {
    this.compressedSize = 0;
    this.uncompressedSize = 0;
    this.crc32 = 0;
    this.compressionMethod = null;
    this.compressedContent = null;
}

CompressedObject.prototype = {
    /**
     * Return the decompressed content in an unspecified format.
     * The format will depend on the decompressor.
     * @return {Object} the decompressed content.
     */
    getContent: function() {
        return null; // see implementation
    },
    /**
     * Return the compressed content in an unspecified format.
     * The format will depend on the compressed conten source.
     * @return {Object} the compressed content.
     */
    getCompressedContent: function() {
        return null; // see implementation
    }
};
module.exports = CompressedObject;


/***/ }),
/* 17 */
/***/ (function(module, exports, __webpack_require__) {




var utils = __webpack_require__(0);
var support = __webpack_require__(2);
var nodeBuffer = __webpack_require__(7);

/**
 * The following functions come from pako, from pako/lib/utils/strings
 * released under the MIT license, see pako https://github.com/nodeca/pako/
 */

// Table with utf8 lengths (calculated by first byte of sequence)
// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
// because max possible codepoint is 0x10ffff
var _utf8len = new Array(256);
for (var i=0; i<256; i++) {
  _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1);
}
_utf8len[254]=_utf8len[254]=1; // Invalid sequence start

// convert string to array (typed, when possible)
var string2buf = function (str) {
    var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;

    // count binary size
    for (m_pos = 0; m_pos < str_len; m_pos++) {
        c = str.charCodeAt(m_pos);
        if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
            c2 = str.charCodeAt(m_pos+1);
            if ((c2 & 0xfc00) === 0xdc00) {
                c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
                m_pos++;
            }
        }
        buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
    }

    // allocate buffer
    if (support.uint8array) {
        buf = new Uint8Array(buf_len);
    } else {
        buf = new Array(buf_len);
    }

    // convert
    for (i=0, m_pos = 0; i < buf_len; m_pos++) {
        c = str.charCodeAt(m_pos);
        if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
            c2 = str.charCodeAt(m_pos+1);
            if ((c2 & 0xfc00) === 0xdc00) {
                c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
                m_pos++;
            }
        }
        if (c < 0x80) {
            /* one byte */
            buf[i++] = c;
        } else if (c < 0x800) {
            /* two bytes */
            buf[i++] = 0xC0 | (c >>> 6);
            buf[i++] = 0x80 | (c & 0x3f);
        } else if (c < 0x10000) {
            /* three bytes */
            buf[i++] = 0xE0 | (c >>> 12);
            buf[i++] = 0x80 | (c >>> 6 & 0x3f);
            buf[i++] = 0x80 | (c & 0x3f);
        } else {
            /* four bytes */
            buf[i++] = 0xf0 | (c >>> 18);
            buf[i++] = 0x80 | (c >>> 12 & 0x3f);
            buf[i++] = 0x80 | (c >>> 6 & 0x3f);
            buf[i++] = 0x80 | (c & 0x3f);
        }
    }

    return buf;
};

// Calculate max possible position in utf8 buffer,
// that will not break sequence. If that's not possible
// - (very small limits) return max size as is.
//
// buf[] - utf8 bytes array
// max   - length limit (mandatory);
var utf8border = function(buf, max) {
    var pos;

    max = max || buf.length;
    if (max > buf.length) { max = buf.length; }

    // go back from last position, until start of sequence found
    pos = max-1;
    while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }

    // Fuckup - very small and broken sequence,
    // return max, because we should return something anyway.
    if (pos < 0) { return max; }

    // If we came to start of buffer - that means vuffer is too small,
    // return max too.
    if (pos === 0) { return max; }

    return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};

// convert array to string
var buf2string = function (buf) {
    var str, i, out, c, c_len;
    var len = buf.length;

    // Reserve max possible length (2 words per char)
    // NB: by unknown reasons, Array is significantly faster for
    //     String.fromCharCode.apply than Uint16Array.
    var utf16buf = new Array(len*2);

    for (out=0, i=0; i<len;) {
        c = buf[i++];
        // quick process ascii
        if (c < 0x80) { utf16buf[out++] = c; continue; }

        c_len = _utf8len[c];
        // skip 5 & 6 byte codes
        if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; }

        // apply mask on first byte
        c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
        // join the rest
        while (c_len > 1 && i < len) {
            c = (c << 6) | (buf[i++] & 0x3f);
            c_len--;
        }

        // terminated by end of string?
        if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }

        if (c < 0x10000) {
            utf16buf[out++] = c;
        } else {
            c -= 0x10000;
            utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
            utf16buf[out++] = 0xdc00 | (c & 0x3ff);
        }
    }

    // shrinkBuf(utf16buf, out)
    if (utf16buf.length !== out) {
        if(utf16buf.subarray) {
            utf16buf = utf16buf.subarray(0, out);
        } else {
            utf16buf.length = out;
        }
    }

    // return String.fromCharCode.apply(null, utf16buf);
    return utils.applyFromCharCode(utf16buf);
};


// That's all for the pako functions.


/**
 * Transform a javascript string into an array (typed if possible) of bytes,
 * UTF-8 encoded.
 * @param {String} str the string to encode
 * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string.
 */
exports.utf8encode = function utf8encode(str) {
    if (support.nodebuffer) {
        return nodeBuffer(str, "utf-8");
    }

    return string2buf(str);
};


/**
 * Transform a bytes array (or a representation) representing an UTF-8 encoded
 * string into a javascript string.
 * @param {Array|Uint8Array|Buffer} buf the data de decode
 * @return {String} the decoded string.
 */
exports.utf8decode = function utf8decode(buf) {
    if (support.nodebuffer) {
        return utils.transformTo("nodebuffer", buf).toString("utf-8");
    }

    buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf);

    // return buf2string(buf);
    // Chrome prefers to work with "small" chunks of data
    // for the method buf2string.
    // Firefox and Chrome has their own shortcut, IE doesn't seem to really care.
    var result = [], k = 0, len = buf.length, chunk = 65536;
    while (k < len) {
        var nextBoundary = utf8border(buf, Math.min(k + chunk, len));
        if (support.uint8array) {
            result.push(buf2string(buf.subarray(k, nextBoundary)));
        } else {
            result.push(buf2string(buf.slice(k, nextBoundary)));
        }
        k = nextBoundary;
    }
    return result.join("");

};
// vim: set shiftwidth=4 softtabstop=4:


/***/ }),
/* 18 */
/***/ (function(module, exports, __webpack_require__) {



var DataReader = __webpack_require__(19);
var utils = __webpack_require__(0);

function StringReader(data, optimizedBinaryString) {
    this.data = data;
    if (!optimizedBinaryString) {
        this.data = utils.string2binary(this.data);
    }
    this.length = this.data.length;
    this.index = 0;
    this.zero = 0;
}
StringReader.prototype = new DataReader();
/**
 * @see DataReader.byteAt
 */
StringReader.prototype.byteAt = function(i) {
    return this.data.charCodeAt(this.zero + i);
};
/**
 * @see DataReader.lastIndexOfSignature
 */
StringReader.prototype.lastIndexOfSignature = function(sig) {
    return this.data.lastIndexOf(sig) - this.zero;
};
/**
 * @see DataReader.readData
 */
StringReader.prototype.readData = function(size) {
    this.checkOffset(size);
    // this will work because the constructor applied the "& 0xff" mask.
    var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
    this.index += size;
    return result;
};
module.exports = StringReader;


/***/ }),
/* 19 */
/***/ (function(module, exports, __webpack_require__) {



var utils = __webpack_require__(0);

function DataReader(data) {
    this.data = null; // type : see implementation
    this.length = 0;
    this.index = 0;
    this.zero = 0;
}
DataReader.prototype = {
    /**
     * Check that the offset will not go too far.
     * @param {string} offset the additional offset to check.
     * @throws {Error} an Error if the offset is out of bounds.
     */
    checkOffset: function(offset) {
        this.checkIndex(this.index + offset);
    },
    /**
     * Check that the specifed index will not be too far.
     * @param {string} newIndex the index to check.
     * @throws {Error} an Error if the index is out of bounds.
     */
    checkIndex: function(newIndex) {
        if (this.length < this.zero + newIndex || newIndex < 0) {
            throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?");
        }
    },
    /**
     * Change the index.
     * @param {number} newIndex The new index.
     * @throws {Error} if the new index is out of the data.
     */
    setIndex: function(newIndex) {
        this.checkIndex(newIndex);
        this.index = newIndex;
    },
    /**
     * Skip the next n bytes.
     * @param {number} n the number of bytes to skip.
     * @throws {Error} if the new index is out of the data.
     */
    skip: function(n) {
        this.setIndex(this.index + n);
    },
    /**
     * Get the byte at the specified index.
     * @param {number} i the index to use.
     * @return {number} a byte.
     */
    byteAt: function(i) {
        // see implementations
    },
    /**
     * Get the next number with a given byte size.
     * @param {number} size the number of bytes to read.
     * @return {number} the corresponding number.
     */
    readInt: function(size) {
        var result = 0,
            i;
        this.checkOffset(size);
        for (i = this.index + size - 1; i >= this.index; i--) {
            result = (result << 8) + this.byteAt(i);
        }
        this.index += size;
        return result;
    },
    /**
     * Get the next string with a given byte size.
     * @param {number} size the number of bytes to read.
     * @return {string} the corresponding string.
     */
    readString: function(size) {
        return utils.transformTo("string", this.readData(size));
    },
    /**
     * Get raw data without conversion, <size> bytes.
     * @param {number} size the number of bytes to read.
     * @return {Object} the raw data, implementation specific.
     */
    readData: function(size) {
        // see implementations
    },
    /**
     * Find the last occurence of a zip signature (4 bytes).
     * @param {string} sig the signature to find.
     * @return {number} the index of the last occurence, -1 if not found.
     */
    lastIndexOfSignature: function(sig) {
        // see implementations
    },
    /**
     * Get the next date.
     * @return {Date} the date.
     */
    readDate: function() {
        var dostime = this.readInt(4);
        return new Date(
        ((dostime >> 25) & 0x7f) + 1980, // year
        ((dostime >> 21) & 0x0f) - 1, // month
        (dostime >> 16) & 0x1f, // day
        (dostime >> 11) & 0x1f, // hour
        (dostime >> 5) & 0x3f, // minute
        (dostime & 0x1f) << 1); // second
    }
};
module.exports = DataReader;


/***/ }),
/* 20 */
/***/ (function(module, exports, __webpack_require__) {



var ArrayReader = __webpack_require__(21);

function Uint8ArrayReader(data) {
    if (data) {
        this.data = data;
        this.length = this.data.length;
        this.index = 0;
        this.zero = 0;
    }
}
Uint8ArrayReader.prototype = new ArrayReader();
/**
 * @see DataReader.readData
 */
Uint8ArrayReader.prototype.readData = function(size) {
    this.checkOffset(size);
    if(size === 0) {
        // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of [].
        return new Uint8Array(0);
    }
    var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size);
    this.index += size;
    return result;
};
module.exports = Uint8ArrayReader;


/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {



var DataReader = __webpack_require__(19);

function ArrayReader(data) {
    if (data) {
        this.data = data;
        this.length = this.data.length;
        this.index = 0;
        this.zero = 0;

        for(var i = 0; i < this.data.length; i++) {
            data[i] = data[i] & 0xFF;
        }
    }
}
ArrayReader.prototype = new DataReader();
/**
 * @see DataReader.byteAt
 */
ArrayReader.prototype.byteAt = function(i) {
    return this.data[this.zero + i];
};
/**
 * @see DataReader.lastIndexOfSignature
 */
ArrayReader.prototype.lastIndexOfSignature = function(sig) {
    var sig0 = sig.charCodeAt(0),
        sig1 = sig.charCodeAt(1),
        sig2 = sig.charCodeAt(2),
        sig3 = sig.charCodeAt(3);
    for (var i = this.length - 4; i >= 0; --i) {
        if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) {
            return i - this.zero;
        }
    }

    return -1;
};
/**
 * @see DataReader.readData
 */
ArrayReader.prototype.readData = function(size) {
    this.checkOffset(size);
    if(size === 0) {
        return [];
    }
    var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
    this.index += size;
    return result;
};
module.exports = ArrayReader;


/***/ }),
/* 22 */
/***/ (function(module, exports, __webpack_require__) {




var base64 = __webpack_require__(3);

/**
Usage:
   zip = new JSZip();
   zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing");
   zip.folder("images").file("smile.gif", base64Data, {base64: true});
   zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")});
   zip.remove("tempfile");

   base64zip = zip.generate();

**/

/**
 * Representation a of zip file in js
 * @constructor
 * @param {String=|ArrayBuffer=|Uint8Array=} data the data to load, if any (optional).
 * @param {Object=} options the options for creating this objects (optional).
 */
function JSZip(data, options) {
    // if this constructor is used without `new`, it adds `new` before itself:
    if(!(this instanceof JSZip)) return new JSZip(data, options);

    // object containing the files :
    // {
    //   "folder/" : {...},
    //   "folder/data.txt" : {...}
    // }
    this.files = {};

    this.comment = null;

    // Where we are in the hierarchy
    this.root = "";
    if (data) {
        this.load(data, options);
    }
    this.clone = function() {
        var newObj = new JSZip();
        for (var i in this) {
            if (typeof this[i] !== "function") {
                newObj[i] = this[i];
            }
        }
        return newObj;
    };
}
JSZip.prototype = __webpack_require__(4);
JSZip.prototype.load = __webpack_require__(40);
JSZip.support = __webpack_require__(2);
JSZip.defaults = __webpack_require__(15);

/**
 * @deprecated
 * This namespace will be removed in a future version without replacement.
 */
JSZip.utils = __webpack_require__(44);

JSZip.base64 = {
    /**
     * @deprecated
     * This method will be removed in a future version without replacement.
     */
    encode : function(input) {
        return base64.encode(input);
    },
    /**
     * @deprecated
     * This method will be removed in a future version without replacement.
     */
    decode : function(input) {
        return base64.decode(input);
    }
};
JSZip.compressions = __webpack_require__(5);
module.exports = JSZip;


/***/ }),
/* 23 */
/***/ (function(module, exports) {

var g;

// This works in non-strict mode
g = (function() {
	return this;
})();

try {
	// This works if eval is allowed (see CSP)
	g = g || Function("return this")() || (1,eval)("this");
} catch(e) {
	// This works if the window reference is available
	if(typeof window === "object")
		g = window;
}

// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}

module.exports = g;


/***/ }),
/* 24 */
/***/ (function(module, exports, __webpack_require__) {




exports.byteLength = byteLength
exports.toByteArray = toByteArray
exports.fromByteArray = fromByteArray

var lookup = []
var revLookup = []
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array

var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for (var i = 0, len = code.length; i < len; ++i) {
  lookup[i] = code[i]
  revLookup[code.charCodeAt(i)] = i
}

revLookup['-'.charCodeAt(0)] = 62
revLookup['_'.charCodeAt(0)] = 63

function placeHoldersCount (b64) {
  var len = b64.length
  if (len % 4 > 0) {
    throw new Error('Invalid string. Length must be a multiple of 4')
  }

  // the number of equal signs (place holders)
  // if there are two placeholders, than the two characters before it
  // represent one byte
  // if there is only one, then the three characters before it represent 2 bytes
  // this is just a cheap hack to not do indexOf twice
  return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
}

function byteLength (b64) {
  // base64 is 4/3 + up to two characters of the original data
  return (b64.length * 3 / 4) - placeHoldersCount(b64)
}

function toByteArray (b64) {
  var i, l, tmp, placeHolders, arr
  var len = b64.length
  placeHolders = placeHoldersCount(b64)

  arr = new Arr((len * 3 / 4) - placeHolders)

  // if there are placeholders, only get up to the last complete 4 chars
  l = placeHolders > 0 ? len - 4 : len

  var L = 0

  for (i = 0; i < l; i += 4) {
    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
    arr[L++] = (tmp >> 16) & 0xFF
    arr[L++] = (tmp >> 8) & 0xFF
    arr[L++] = tmp & 0xFF
  }

  if (placeHolders === 2) {
    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
    arr[L++] = tmp & 0xFF
  } else if (placeHolders === 1) {
    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
    arr[L++] = (tmp >> 8) & 0xFF
    arr[L++] = tmp & 0xFF
  }

  return arr
}

function tripletToBase64 (num) {
  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
}

function encodeChunk (uint8, start, end) {
  var tmp
  var output = []
  for (var i = start; i < end; i += 3) {
    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
    output.push(tripletToBase64(tmp))
  }
  return output.join('')
}

function fromByteArray (uint8) {
  var tmp
  var len = uint8.length
  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
  var output = ''
  var parts = []
  var maxChunkLength = 16383 // must be multiple of 3

  // go through the array every three bytes, we'll deal with trailing stuff later
  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
  }

  // pad the end with zeros, but make sure to not forget the extra bytes
  if (extraBytes === 1) {
    tmp = uint8[len - 1]
    output += lookup[tmp >> 2]
    output += lookup[(tmp << 4) & 0x3F]
    output += '=='
  } else if (extraBytes === 2) {
    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
    output += lookup[tmp >> 10]
    output += lookup[(tmp >> 4) & 0x3F]
    output += lookup[(tmp << 2) & 0x3F]
    output += '='
  }

  parts.push(output)

  return parts.join('')
}


/***/ }),
/* 25 */
/***/ (function(module, exports) {

exports.read = function (buffer, offset, isLE, mLen, nBytes) {
  var e, m
  var eLen = nBytes * 8 - mLen - 1
  var eMax = (1 << eLen) - 1
  var eBias = eMax >> 1
  var nBits = -7
  var i = isLE ? (nBytes - 1) : 0
  var d = isLE ? -1 : 1
  var s = buffer[offset + i]

  i += d

  e = s & ((1 << (-nBits)) - 1)
  s >>= (-nBits)
  nBits += eLen
  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}

  m = e & ((1 << (-nBits)) - 1)
  e >>= (-nBits)
  nBits += mLen
  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}

  if (e === 0) {
    e = 1 - eBias
  } else if (e === eMax) {
    return m ? NaN : ((s ? -1 : 1) * Infinity)
  } else {
    m = m + Math.pow(2, mLen)
    e = e - eBias
  }
  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
}

exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
  var e, m, c
  var eLen = nBytes * 8 - mLen - 1
  var eMax = (1 << eLen) - 1
  var eBias = eMax >> 1
  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
  var i = isLE ? 0 : (nBytes - 1)
  var d = isLE ? 1 : -1
  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0

  value = Math.abs(value)

  if (isNaN(value) || value === Infinity) {
    m = isNaN(value) ? 1 : 0
    e = eMax
  } else {
    e = Math.floor(Math.log(value) / Math.LN2)
    if (value * (c = Math.pow(2, -e)) < 1) {
      e--
      c *= 2
    }
    if (e + eBias >= 1) {
      value += rt / c
    } else {
      value += rt * Math.pow(2, 1 - eBias)
    }
    if (value * c >= 2) {
      e++
      c /= 2
    }

    if (e + eBias >= eMax) {
      m = 0
      e = eMax
    } else if (e + eBias >= 1) {
      m = (value * c - 1) * Math.pow(2, mLen)
      e = e + eBias
    } else {
      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
      e = 0
    }
  }

  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}

  e = (e << mLen) | m
  eLen += mLen
  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}

  buffer[offset + i - d] |= s * 128
}


/***/ }),
/* 26 */
/***/ (function(module, exports) {

var toString = {}.toString;

module.exports = Array.isArray || function (arr) {
  return toString.call(arr) == '[object Array]';
};


/***/ }),
/* 27 */
/***/ (function(module, exports, __webpack_require__) {



var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined');

var pako = __webpack_require__(28);
exports.uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array";
exports.compressInputType = USE_TYPEDARRAY ? "uint8array" : "array";

exports.magic = "\x08\x00";
exports.compress = function(input, compressionOptions) {
    return pako.deflateRaw(input, {
        level : compressionOptions.level || -1 // default compression
    });
};
exports.uncompress =  function(input) {
    return pako.inflateRaw(input);
};


/***/ }),
/* 28 */
/***/ (function(module, exports, __webpack_require__) {


// Top level file is just a mixin of submodules & constants


var assign    = __webpack_require__(1).assign;

var deflate   = __webpack_require__(29);
var inflate   = __webpack_require__(32);
var constants = __webpack_require__(13);

var pako = {};

assign(pako, deflate, inflate, constants);

module.exports = pako;


/***/ }),
/* 29 */
/***/ (function(module, exports, __webpack_require__) {





var zlib_deflate = __webpack_require__(30);
var utils        = __webpack_require__(1);
var strings      = __webpack_require__(11);
var msg          = __webpack_require__(6);
var ZStream      = __webpack_require__(12);

var toString = Object.prototype.toString;

/* Public constants ==========================================================*/
/* ===========================================================================*/

var Z_NO_FLUSH      = 0;
var Z_FINISH        = 4;

var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_SYNC_FLUSH    = 2;

var Z_DEFAULT_COMPRESSION = -1;

var Z_DEFAULT_STRATEGY    = 0;

var Z_DEFLATED  = 8;

/* ===========================================================================*/


/**
 * class Deflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[deflate]],
 * [[deflateRaw]] and [[gzip]].
 **/

/* internal
 * Deflate.chunks -> Array
 *
 * Chunks of output data, if [[Deflate#onData]] not overriden.
 **/

/**
 * Deflate.result -> Uint8Array|Array
 *
 * Compressed result, generated by default [[Deflate#onData]]
 * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Deflate#push]] with `Z_FINISH` / `true` param)  or if you
 * push a chunk with explicit flush (call [[Deflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Deflate.err -> Number
 *
 * Error code after deflate finished. 0 (Z_OK) on success.
 * You will not need it in real life, because deflate errors
 * are possible only on wrong options or bad `onData` / `onEnd`
 * custom handlers.
 **/

/**
 * Deflate.msg -> String
 *
 * Error message, if [[Deflate.err]] != 0
 **/


/**
 * new Deflate(options)
 * - options (Object): zlib deflate options.
 *
 * Creates new deflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `level`
 * - `windowBits`
 * - `memLevel`
 * - `strategy`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw deflate
 * - `gzip` (Boolean) - create gzip wrapper
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 * - `header` (Object) - custom header for gzip
 *   - `text` (Boolean) - true if compressed data believed to be text
 *   - `time` (Number) - modification time, unix timestamp
 *   - `os` (Number) - operation system code
 *   - `extra` (Array) - array of bytes with extra data (max 65536)
 *   - `name` (String) - file name (binary string)
 *   - `comment` (String) - comment (binary string)
 *   - `hcrc` (Boolean) - true if header crc should be added
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var deflate = new pako.Deflate({ level: 3});
 *
 * deflate.push(chunk1, false);
 * deflate.push(chunk2, true);  // true -> last chunk
 *
 * if (deflate.err) { throw new Error(deflate.err); }
 *
 * console.log(deflate.result);
 * ```
 **/
function Deflate(options) {
  if (!(this instanceof Deflate)) return new Deflate(options);

  this.options = utils.assign({
    level: Z_DEFAULT_COMPRESSION,
    method: Z_DEFLATED,
    chunkSize: 16384,
    windowBits: 15,
    memLevel: 8,
    strategy: Z_DEFAULT_STRATEGY,
    to: ''
  }, options || {});

  var opt = this.options;

  if (opt.raw && (opt.windowBits > 0)) {
    opt.windowBits = -opt.windowBits;
  }

  else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) {
    opt.windowBits += 16;
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm = new ZStream();
  this.strm.avail_out = 0;

  var status = zlib_deflate.deflateInit2(
    this.strm,
    opt.level,
    opt.method,
    opt.windowBits,
    opt.memLevel,
    opt.strategy
  );

  if (status !== Z_OK) {
    throw new Error(msg[status]);
  }

  if (opt.header) {
    zlib_deflate.deflateSetHeader(this.strm, opt.header);
  }

  if (opt.dictionary) {
    var dict;
    // Convert data if needed
    if (typeof opt.dictionary === 'string') {
      // If we need to compress text, change encoding to utf8.
      dict = strings.string2buf(opt.dictionary);
    } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
      dict = new Uint8Array(opt.dictionary);
    } else {
      dict = opt.dictionary;
    }

    status = zlib_deflate.deflateSetDictionary(this.strm, dict);

    if (status !== Z_OK) {
      throw new Error(msg[status]);
    }

    this._dict_set = true;
  }
}

/**
 * Deflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be
 *   converted to utf8 byte sequence.
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH.
 *
 * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
 * new compressed chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the compression context.
 *
 * On fail call [[Deflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * array format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Deflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var status, _mode;

  if (this.ended) { return false; }

  _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // If we need to compress text, change encoding to utf8.
    strm.input = strings.string2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }
    status = zlib_deflate.deflate(strm, _mode);    /* no bad return value */

    if (status !== Z_STREAM_END && status !== Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }
    if (strm.avail_out === 0 || (strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH))) {
      if (this.options.to === 'string') {
        this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out)));
      } else {
        this.onData(utils.shrinkBuf(strm.output, strm.next_out));
      }
    }
  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END);

  // Finalize on the last chunk.
  if (_mode === Z_FINISH) {
    status = zlib_deflate.deflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === Z_SYNC_FLUSH) {
    this.onEnd(Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Deflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): ouput data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Deflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Deflate#onEnd(status) -> Void
 * - status (Number): deflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called once after you tell deflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Deflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === Z_OK) {
    if (this.options.to === 'string') {
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * deflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * Compress `data` with deflate algorithm and `options`.
 *
 * Supported options are:
 *
 * - level
 * - windowBits
 * - memLevel
 * - strategy
 * - dictionary
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , data = Uint8Array([1,2,3,4,5,6,7,8,9]);
 *
 * console.log(pako.deflate(data));
 * ```
 **/
function deflate(input, options) {
  var deflator = new Deflate(options);

  deflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (deflator.err) { throw deflator.msg || msg[deflator.err]; }

  return deflator.result;
}


/**
 * deflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function deflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return deflate(input, options);
}


/**
 * gzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but create gzip wrapper instead of
 * deflate one.
 **/
function gzip(input, options) {
  options = options || {};
  options.gzip = true;
  return deflate(input, options);
}


exports.Deflate = Deflate;
exports.deflate = deflate;
exports.deflateRaw = deflateRaw;
exports.gzip = gzip;


/***/ }),
/* 30 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils   = __webpack_require__(1);
var trees   = __webpack_require__(31);
var adler32 = __webpack_require__(9);
var crc32   = __webpack_require__(10);
var msg     = __webpack_require__(6);

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
var Z_NO_FLUSH      = 0;
var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
//var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
//var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
//var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;


/* compression levels */
//var Z_NO_COMPRESSION      = 0;
//var Z_BEST_SPEED          = 1;
//var Z_BEST_COMPRESSION    = 9;
var Z_DEFAULT_COMPRESSION = -1;


var Z_FILTERED            = 1;
var Z_HUFFMAN_ONLY        = 2;
var Z_RLE                 = 3;
var Z_FIXED               = 4;
var Z_DEFAULT_STRATEGY    = 0;

/* Possible values of the data_type field (though see inflate()) */
//var Z_BINARY              = 0;
//var Z_TEXT                = 1;
//var Z_ASCII               = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;


/* The deflate compression method */
var Z_DEFLATED  = 8;

/*============================================================================*/


var MAX_MEM_LEVEL = 9;
/* Maximum value for memLevel in deflateInit2 */
var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_MEM_LEVEL = 8;


var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */
var LITERALS      = 256;
/* number of literal bytes 0..255 */
var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */
var D_CODES       = 30;
/* number of distance codes */
var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */
var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */
var MAX_BITS  = 15;
/* All codes must not exceed MAX_BITS bits */

var MIN_MATCH = 3;
var MAX_MATCH = 258;
var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);

var PRESET_DICT = 0x20;

var INIT_STATE = 42;
var EXTRA_STATE = 69;
var NAME_STATE = 73;
var COMMENT_STATE = 91;
var HCRC_STATE = 103;
var BUSY_STATE = 113;
var FINISH_STATE = 666;

var BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
var BS_BLOCK_DONE     = 2; /* block flush performed */
var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
var BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */

var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.

function err(strm, errorCode) {
  strm.msg = msg[errorCode];
  return errorCode;
}

function rank(f) {
  return ((f) << 1) - ((f) > 4 ? 9 : 0);
}

function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }


/* =========================================================================
 * Flush as much pending output as possible. All deflate() output goes
 * through this function so some applications may wish to modify it
 * to avoid allocating a large strm->output buffer and copying into it.
 * (See also read_buf()).
 */
function flush_pending(strm) {
  var s = strm.state;

  //_tr_flush_bits(s);
  var len = s.pending;
  if (len > strm.avail_out) {
    len = strm.avail_out;
  }
  if (len === 0) { return; }

  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
  strm.next_out += len;
  s.pending_out += len;
  strm.total_out += len;
  strm.avail_out -= len;
  s.pending -= len;
  if (s.pending === 0) {
    s.pending_out = 0;
  }
}


function flush_block_only(s, last) {
  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
  s.block_start = s.strstart;
  flush_pending(s.strm);
}


function put_byte(s, b) {
  s.pending_buf[s.pending++] = b;
}


/* =========================================================================
 * Put a short in the pending buffer. The 16-bit value is put in MSB order.
 * IN assertion: the stream state is correct and there is enough room in
 * pending_buf.
 */
function putShortMSB(s, b) {
//  put_byte(s, (Byte)(b >> 8));
//  put_byte(s, (Byte)(b & 0xff));
  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
  s.pending_buf[s.pending++] = b & 0xff;
}


/* ===========================================================================
 * Read a new buffer from the current input stream, update the adler32
 * and total number of bytes read.  All deflate() input goes through
 * this function so some applications may wish to modify it to avoid
 * allocating a large strm->input buffer and copying from it.
 * (See also flush_pending()).
 */
function read_buf(strm, buf, start, size) {
  var len = strm.avail_in;

  if (len > size) { len = size; }
  if (len === 0) { return 0; }

  strm.avail_in -= len;

  // zmemcpy(buf, strm->next_in, len);
  utils.arraySet(buf, strm.input, strm.next_in, len, start);
  if (strm.state.wrap === 1) {
    strm.adler = adler32(strm.adler, buf, len, start);
  }

  else if (strm.state.wrap === 2) {
    strm.adler = crc32(strm.adler, buf, len, start);
  }

  strm.next_in += len;
  strm.total_in += len;

  return len;
}


/* ===========================================================================
 * Set match_start to the longest match starting at the given string and
 * return its length. Matches shorter or equal to prev_length are discarded,
 * in which case the result is equal to prev_length and match_start is
 * garbage.
 * IN assertions: cur_match is the head of the hash chain for the current
 *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
 * OUT assertion: the match length is not greater than s->lookahead.
 */
function longest_match(s, cur_match) {
  var chain_length = s.max_chain_length;      /* max hash chain length */
  var scan = s.strstart; /* current string */
  var match;                       /* matched string */
  var len;                           /* length of current match */
  var best_len = s.prev_length;              /* best match length so far */
  var nice_match = s.nice_match;             /* stop if match long enough */
  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;

  var _win = s.window; // shortcut

  var wmask = s.w_mask;
  var prev  = s.prev;

  /* Stop when cur_match becomes <= limit. To simplify the code,
   * we prevent matches with the string of window index 0.
   */

  var strend = s.strstart + MAX_MATCH;
  var scan_end1  = _win[scan + best_len - 1];
  var scan_end   = _win[scan + best_len];

  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
   * It is easy to get rid of this optimization if necessary.
   */
  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");

  /* Do not waste too much time if we already have a good match: */
  if (s.prev_length >= s.good_match) {
    chain_length >>= 2;
  }
  /* Do not look for matches beyond the end of the input. This is necessary
   * to make deflate deterministic.
   */
  if (nice_match > s.lookahead) { nice_match = s.lookahead; }

  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");

  do {
    // Assert(cur_match < s->strstart, "no future");
    match = cur_match;

    /* Skip to next match if the match length cannot increase
     * or if the match length is less than 2.  Note that the checks below
     * for insufficient lookahead only occur occasionally for performance
     * reasons.  Therefore uninitialized memory will be accessed, and
     * conditional jumps will be made that depend on those values.
     * However the length of the match is limited to the lookahead, so
     * the output of deflate is not affected by the uninitialized values.
     */

    if (_win[match + best_len]     !== scan_end  ||
        _win[match + best_len - 1] !== scan_end1 ||
        _win[match]                !== _win[scan] ||
        _win[++match]              !== _win[scan + 1]) {
      continue;
    }

    /* The check at best_len-1 can be removed because it will be made
     * again later. (This heuristic is not always a win.)
     * It is not necessary to compare scan[2] and match[2] since they
     * are always equal when the other bytes match, given that
     * the hash keys are equal and that HASH_BITS >= 8.
     */
    scan += 2;
    match++;
    // Assert(*scan == *match, "match[2]?");

    /* We check for insufficient lookahead only every 8th comparison;
     * the 256th check will be made at strstart+258.
     */
    do {
      /*jshint noempty:false*/
    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             scan < strend);

    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");

    len = MAX_MATCH - (strend - scan);
    scan = strend - MAX_MATCH;

    if (len > best_len) {
      s.match_start = cur_match;
      best_len = len;
      if (len >= nice_match) {
        break;
      }
      scan_end1  = _win[scan + best_len - 1];
      scan_end   = _win[scan + best_len];
    }
  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);

  if (best_len <= s.lookahead) {
    return best_len;
  }
  return s.lookahead;
}


/* ===========================================================================
 * Fill the window when the lookahead becomes insufficient.
 * Updates strstart and lookahead.
 *
 * IN assertion: lookahead < MIN_LOOKAHEAD
 * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
 *    At least one byte has been read, or avail_in == 0; reads are
 *    performed for at least two bytes (required for the zip translate_eol
 *    option -- not supported here).
 */
function fill_window(s) {
  var _w_size = s.w_size;
  var p, n, m, more, str;

  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");

  do {
    more = s.window_size - s.lookahead - s.strstart;

    // JS ints have 32 bit, block below not needed
    /* Deal with !@#$% 64K limit: */
    //if (sizeof(int) <= 2) {
    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
    //        more = wsize;
    //
    //  } else if (more == (unsigned)(-1)) {
    //        /* Very unlikely, but possible on 16 bit machine if
    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
    //         */
    //        more--;
    //    }
    //}


    /* If the window is almost full and there is insufficient lookahead,
     * move the upper half to the lower one to make room in the upper half.
     */
    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {

      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
      s.match_start -= _w_size;
      s.strstart -= _w_size;
      /* we now have strstart >= MAX_DIST */
      s.block_start -= _w_size;

      /* Slide the hash table (could be avoided with 32 bit values
       at the expense of memory usage). We slide even when level == 0
       to keep the hash table consistent if we switch back to level > 0
       later. (Using level 0 permanently is not an optimal usage of
       zlib, so we don't care about this pathological case.)
       */

      n = s.hash_size;
      p = n;
      do {
        m = s.head[--p];
        s.head[p] = (m >= _w_size ? m - _w_size : 0);
      } while (--n);

      n = _w_size;
      p = n;
      do {
        m = s.prev[--p];
        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
        /* If n is not on any hash chain, prev[n] is garbage but
         * its value will never be used.
         */
      } while (--n);

      more += _w_size;
    }
    if (s.strm.avail_in === 0) {
      break;
    }

    /* If there was no sliding:
     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
     *    more == window_size - lookahead - strstart
     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
     * => more >= window_size - 2*WSIZE + 2
     * In the BIG_MEM or MMAP case (not yet supported),
     *   window_size == input_size + MIN_LOOKAHEAD  &&
     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
     * Otherwise, window_size == 2*WSIZE so more >= 2.
     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
     */
    //Assert(more >= 2, "more < 2");
    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
    s.lookahead += n;

    /* Initialize the hash value now that we have some input: */
    if (s.lookahead + s.insert >= MIN_MATCH) {
      str = s.strstart - s.insert;
      s.ins_h = s.window[str];

      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
//#if MIN_MATCH != 3
//        Call update_hash() MIN_MATCH-3 more times
//#endif
      while (s.insert) {
        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

        s.prev[str & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = str;
        str++;
        s.insert--;
        if (s.lookahead + s.insert < MIN_MATCH) {
          break;
        }
      }
    }
    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
     * but this is not important since only literal bytes will be emitted.
     */

  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);

  /* If the WIN_INIT bytes after the end of the current data have never been
   * written, then zero those bytes in order to avoid memory check reports of
   * the use of uninitialized (or uninitialised as Julian writes) bytes by
   * the longest match routines.  Update the high water mark for the next
   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
   */
//  if (s.high_water < s.window_size) {
//    var curr = s.strstart + s.lookahead;
//    var init = 0;
//
//    if (s.high_water < curr) {
//      /* Previous high water mark below current data -- zero WIN_INIT
//       * bytes or up to end of window, whichever is less.
//       */
//      init = s.window_size - curr;
//      if (init > WIN_INIT)
//        init = WIN_INIT;
//      zmemzero(s->window + curr, (unsigned)init);
//      s->high_water = curr + init;
//    }
//    else if (s->high_water < (ulg)curr + WIN_INIT) {
//      /* High water mark at or above current data, but below current data
//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
//       * to end of window, whichever is less.
//       */
//      init = (ulg)curr + WIN_INIT - s->high_water;
//      if (init > s->window_size - s->high_water)
//        init = s->window_size - s->high_water;
//      zmemzero(s->window + s->high_water, (unsigned)init);
//      s->high_water += init;
//    }
//  }
//
//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
//    "not enough room for search");
}

/* ===========================================================================
 * Copy without compression as much as possible from the input stream, return
 * the current block state.
 * This function does not insert new strings in the dictionary since
 * uncompressible data is probably not useful. This function is used
 * only for the level=0 compression option.
 * NOTE: this function should be optimized to avoid extra copying from
 * window to pending_buf.
 */
function deflate_stored(s, flush) {
  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
   * to pending_buf_size, and each stored block has a 5 byte header:
   */
  var max_block_size = 0xffff;

  if (max_block_size > s.pending_buf_size - 5) {
    max_block_size = s.pending_buf_size - 5;
  }

  /* Copy as much as possible from input to output: */
  for (;;) {
    /* Fill the window as much as possible: */
    if (s.lookahead <= 1) {

      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
      //  s->block_start >= (long)s->w_size, "slide too late");
//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
//        s.block_start >= s.w_size)) {
//        throw  new Error("slide too late");
//      }

      fill_window(s);
      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }

      if (s.lookahead === 0) {
        break;
      }
      /* flush the current block */
    }
    //Assert(s->block_start >= 0L, "block gone");
//    if (s.block_start < 0) throw new Error("block gone");

    s.strstart += s.lookahead;
    s.lookahead = 0;

    /* Emit a stored block if pending_buf will be full: */
    var max_start = s.block_start + max_block_size;

    if (s.strstart === 0 || s.strstart >= max_start) {
      /* strstart == 0 is possible when wraparound on 16-bit machine */
      s.lookahead = s.strstart - max_start;
      s.strstart = max_start;
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/


    }
    /* Flush if we may have to slide, otherwise block_start may become
     * negative and the data will be gone:
     */
    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }

  s.insert = 0;

  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }

  if (s.strstart > s.block_start) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_NEED_MORE;
}

/* ===========================================================================
 * Compress as much as possible from the input stream, return the current
 * block state.
 * This function does not perform lazy evaluation of matches and inserts
 * new strings in the dictionary only for unmatched strings or for short
 * matches. It is used only for the fast compression options.
 */
function deflate_fast(s, flush) {
  var hash_head;        /* head of the hash chain */
  var bflush;           /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) {
        break; /* flush the current block */
      }
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     * At this point we have always match_length < MIN_MATCH
     */
    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */
    }
    if (s.match_length >= MIN_MATCH) {
      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only

      /*** _tr_tally_dist(s, s.strstart - s.match_start,
                     s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;

      /* Insert new strings in the hash table only if the match length
       * is not too large. This saves time but degrades compression.
       */
      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
        s.match_length--; /* string at strstart already in table */
        do {
          s.strstart++;
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
           * always MIN_MATCH bytes ahead.
           */
        } while (--s.match_length !== 0);
        s.strstart++;
      } else
      {
        s.strstart += s.match_length;
        s.match_length = 0;
        s.ins_h = s.window[s.strstart];
        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;

//#if MIN_MATCH != 3
//                Call UPDATE_HASH() MIN_MATCH-3 more times
//#endif
        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
         * matter since it will be recomputed at next deflate call.
         */
      }
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s.window[s.strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * Same as above, but achieves better compression. We use a lazy
 * evaluation for matches: a match is finally adopted only if there is
 * no better match at the next window position.
 */
function deflate_slow(s, flush) {
  var hash_head;          /* head of hash chain */
  var bflush;              /* set if current block must be flushed */

  var max_insert;

  /* Process the input block. */
  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     */
    s.prev_length = s.match_length;
    s.prev_match = s.match_start;
    s.match_length = MIN_MATCH - 1;

    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */

      if (s.match_length <= 5 &&
         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {

        /* If prev_match is also MIN_MATCH, match_start is garbage
         * but we will ignore the current match anyway.
         */
        s.match_length = MIN_MATCH - 1;
      }
    }
    /* If there was a match at the previous step and the current
     * match is not better, output the previous match:
     */
    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
      max_insert = s.strstart + s.lookahead - MIN_MATCH;
      /* Do not insert strings in hash table beyond this. */

      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);

      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
                     s.prev_length - MIN_MATCH, bflush);***/
      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
      /* Insert in hash table all strings up to the end of the match.
       * strstart-1 and strstart are already inserted. If there is not
       * enough lookahead, the last two strings are not inserted in
       * the hash table.
       */
      s.lookahead -= s.prev_length - 1;
      s.prev_length -= 2;
      do {
        if (++s.strstart <= max_insert) {
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
        }
      } while (--s.prev_length !== 0);
      s.match_available = 0;
      s.match_length = MIN_MATCH - 1;
      s.strstart++;

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);
        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/
      }

    } else if (s.match_available) {
      /* If there was no match at the previous position, output a
       * single literal. If there was a match but the current match
       * is longer, truncate the previous match to a single literal.
       */
      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

      if (bflush) {
        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
        flush_block_only(s, false);
        /***/
      }
      s.strstart++;
      s.lookahead--;
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
    } else {
      /* There is no previous match to compare with, wait for
       * the next step to decide.
       */
      s.match_available = 1;
      s.strstart++;
      s.lookahead--;
    }
  }
  //Assert (flush != Z_NO_FLUSH, "no flush?");
  if (s.match_available) {
    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

    s.match_available = 0;
  }
  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_BLOCK_DONE;
}


/* ===========================================================================
 * For Z_RLE, simply look for runs of bytes, generate matches only of distance
 * one.  Do not maintain a hash table.  (It will be regenerated if this run of
 * deflate switches away from Z_RLE.)
 */
function deflate_rle(s, flush) {
  var bflush;            /* set if current block must be flushed */
  var prev;              /* byte at distance one to match */
  var scan, strend;      /* scan goes up to strend for length of run */

  var _win = s.window;

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the longest run, plus one for the unrolled loop.
     */
    if (s.lookahead <= MAX_MATCH) {
      fill_window(s);
      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* See how many times the previous byte repeats */
    s.match_length = 0;
    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
      scan = s.strstart - 1;
      prev = _win[scan];
      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
        strend = s.strstart + MAX_MATCH;
        do {
          /*jshint noempty:false*/
        } while (prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 scan < strend);
        s.match_length = MAX_MATCH - (strend - scan);
        if (s.match_length > s.lookahead) {
          s.match_length = s.lookahead;
        }
      }
      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
    }

    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
    if (s.match_length >= MIN_MATCH) {
      //check_match(s, s.strstart, s.strstart - 1, s.match_length);

      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;
      s.strstart += s.match_length;
      s.match_length = 0;
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s->window[s->strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
 * (It will be regenerated if this run of deflate switches away from Huffman.)
 */
function deflate_huff(s, flush) {
  var bflush;             /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we have a literal to write. */
    if (s.lookahead === 0) {
      fill_window(s);
      if (s.lookahead === 0) {
        if (flush === Z_NO_FLUSH) {
          return BS_NEED_MORE;
        }
        break;      /* flush the current block */
      }
    }

    /* Output a literal byte */
    s.match_length = 0;
    //Tracevv((stderr,"%c", s->window[s->strstart]));
    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
    s.lookahead--;
    s.strstart++;
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* Values for max_lazy_match, good_match and max_chain_length, depending on
 * the desired pack level (0..9). The values given below have been tuned to
 * exclude worst case performance for pathological files. Better values may be
 * found for specific files.
 */
function Config(good_length, max_lazy, nice_length, max_chain, func) {
  this.good_length = good_length;
  this.max_lazy = max_lazy;
  this.nice_length = nice_length;
  this.max_chain = max_chain;
  this.func = func;
}

var configuration_table;

configuration_table = [
  /*      good lazy nice chain */
  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */

  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
];


/* ===========================================================================
 * Initialize the "longest match" routines for a new zlib stream
 */
function lm_init(s) {
  s.window_size = 2 * s.w_size;

  /*** CLEAR_HASH(s); ***/
  zero(s.head); // Fill with NIL (= 0);

  /* Set the default configuration parameters:
   */
  s.max_lazy_match = configuration_table[s.level].max_lazy;
  s.good_match = configuration_table[s.level].good_length;
  s.nice_match = configuration_table[s.level].nice_length;
  s.max_chain_length = configuration_table[s.level].max_chain;

  s.strstart = 0;
  s.block_start = 0;
  s.lookahead = 0;
  s.insert = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  s.ins_h = 0;
}


function DeflateState() {
  this.strm = null;            /* pointer back to this zlib stream */
  this.status = 0;            /* as the name implies */
  this.pending_buf = null;      /* output still pending */
  this.pending_buf_size = 0;  /* size of pending_buf */
  this.pending_out = 0;       /* next pending byte to output to the stream */
  this.pending = 0;           /* nb of bytes in the pending buffer */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.gzhead = null;         /* gzip header information to write */
  this.gzindex = 0;           /* where in extra, name, or comment */
  this.method = Z_DEFLATED; /* can only be DEFLATED */
  this.last_flush = -1;   /* value of flush param for previous deflate call */

  this.w_size = 0;  /* LZ77 window size (32K by default) */
  this.w_bits = 0;  /* log2(w_size)  (8..16) */
  this.w_mask = 0;  /* w_size - 1 */

  this.window = null;
  /* Sliding window. Input bytes are read into the second half of the window,
   * and move to the first half later to keep a dictionary of at least wSize
   * bytes. With this organization, matches are limited to a distance of
   * wSize-MAX_MATCH bytes, but this ensures that IO is always
   * performed with a length multiple of the block size.
   */

  this.window_size = 0;
  /* Actual size of window: 2*wSize, except when the user input buffer
   * is directly used as sliding window.
   */

  this.prev = null;
  /* Link to older string with same hash index. To limit the size of this
   * array to 64K, this link is maintained only for the last 32K strings.
   * An index in this array is thus a window index modulo 32K.
   */

  this.head = null;   /* Heads of the hash chains or NIL. */

  this.ins_h = 0;       /* hash index of string to be inserted */
  this.hash_size = 0;   /* number of elements in hash table */
  this.hash_bits = 0;   /* log2(hash_size) */
  this.hash_mask = 0;   /* hash_size-1 */

  this.hash_shift = 0;
  /* Number of bits by which ins_h must be shifted at each input
   * step. It must be such that after MIN_MATCH steps, the oldest
   * byte no longer takes part in the hash key, that is:
   *   hash_shift * MIN_MATCH >= hash_bits
   */

  this.block_start = 0;
  /* Window position at the beginning of the current output block. Gets
   * negative when the window is moved backwards.
   */

  this.match_length = 0;      /* length of best match */
  this.prev_match = 0;        /* previous match */
  this.match_available = 0;   /* set if previous match exists */
  this.strstart = 0;          /* start of string to insert */
  this.match_start = 0;       /* start of matching string */
  this.lookahead = 0;         /* number of valid bytes ahead in window */

  this.prev_length = 0;
  /* Length of the best match at previous step. Matches not greater than this
   * are discarded. This is used in the lazy match evaluation.
   */

  this.max_chain_length = 0;
  /* To speed up deflation, hash chains are never searched beyond this
   * length.  A higher limit improves compression ratio but degrades the
   * speed.
   */

  this.max_lazy_match = 0;
  /* Attempt to find a better match only when the current match is strictly
   * smaller than this value. This mechanism is used only for compression
   * levels >= 4.
   */
  // That's alias to max_lazy_match, don't use directly
  //this.max_insert_length = 0;
  /* Insert new strings in the hash table only if the match length is not
   * greater than this length. This saves time but degrades compression.
   * max_insert_length is used only for compression levels <= 3.
   */

  this.level = 0;     /* compression level (1..9) */
  this.strategy = 0;  /* favor or force Huffman coding*/

  this.good_match = 0;
  /* Use a faster search when the previous match is longer than this */

  this.nice_match = 0; /* Stop searching when current match exceeds this */

              /* used by trees.c: */

  /* Didn't use ct_data typedef below to suppress compiler warning */

  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */

  // Use flat array of DOUBLE size, with interleaved fata,
  // because JS does not support effective
  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);
  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);
  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);
  zero(this.dyn_ltree);
  zero(this.dyn_dtree);
  zero(this.bl_tree);

  this.l_desc   = null;         /* desc. for literal tree */
  this.d_desc   = null;         /* desc. for distance tree */
  this.bl_desc  = null;         /* desc. for bit length tree */

  //ush bl_count[MAX_BITS+1];
  this.bl_count = new utils.Buf16(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
  zero(this.heap);

  this.heap_len = 0;               /* number of elements in the heap */
  this.heap_max = 0;               /* element of largest frequency */
  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
   * The same heap array is used to build all trees.
   */

  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
  zero(this.depth);
  /* Depth of each subtree used as tie breaker for trees of equal frequency
   */

  this.l_buf = 0;          /* buffer index for literals or lengths */

  this.lit_bufsize = 0;
  /* Size of match buffer for literals/lengths.  There are 4 reasons for
   * limiting lit_bufsize to 64K:
   *   - frequencies can be kept in 16 bit counters
   *   - if compression is not successful for the first block, all input
   *     data is still in the window so we can still emit a stored block even
   *     when input comes from standard input.  (This can also be done for
   *     all blocks if lit_bufsize is not greater than 32K.)
   *   - if compression is not successful for a file smaller than 64K, we can
   *     even emit a stored file instead of a stored block (saving 5 bytes).
   *     This is applicable only for zip (not gzip or zlib).
   *   - creating new Huffman trees less frequently may not provide fast
   *     adaptation to changes in the input data statistics. (Take for
   *     example a binary file with poorly compressible code followed by
   *     a highly compressible string table.) Smaller buffer sizes give
   *     fast adaptation but have of course the overhead of transmitting
   *     trees more frequently.
   *   - I can't count above 4
   */

  this.last_lit = 0;      /* running index in l_buf */

  this.d_buf = 0;
  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
   * the same number of elements. To use different lengths, an extra flag
   * array would be necessary.
   */

  this.opt_len = 0;       /* bit length of current block with optimal trees */
  this.static_len = 0;    /* bit length of current block with static trees */
  this.matches = 0;       /* number of string matches in current block */
  this.insert = 0;        /* bytes at end of window left to insert */


  this.bi_buf = 0;
  /* Output buffer. bits are inserted starting at the bottom (least
   * significant bits).
   */
  this.bi_valid = 0;
  /* Number of valid bits in bi_buf.  All bits above the last valid bit
   * are always zero.
   */

  // Used for window memory init. We safely ignore it for JS. That makes
  // sense only for pointers and memory check tools.
  //this.high_water = 0;
  /* High water mark offset in window for initialized bytes -- bytes above
   * this are set to zero in order to avoid memory check warnings when
   * longest match routines access bytes past the input.  This is then
   * updated to the new high water mark.
   */
}


function deflateResetKeep(strm) {
  var s;

  if (!strm || !strm.state) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.total_in = strm.total_out = 0;
  strm.data_type = Z_UNKNOWN;

  s = strm.state;
  s.pending = 0;
  s.pending_out = 0;

  if (s.wrap < 0) {
    s.wrap = -s.wrap;
    /* was made negative by deflate(..., Z_FINISH); */
  }
  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
  strm.adler = (s.wrap === 2) ?
    0  // crc32(0, Z_NULL, 0)
  :
    1; // adler32(0, Z_NULL, 0)
  s.last_flush = Z_NO_FLUSH;
  trees._tr_init(s);
  return Z_OK;
}


function deflateReset(strm) {
  var ret = deflateResetKeep(strm);
  if (ret === Z_OK) {
    lm_init(strm.state);
  }
  return ret;
}


function deflateSetHeader(strm, head) {
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
  strm.state.gzhead = head;
  return Z_OK;
}


function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
  if (!strm) { // === Z_NULL
    return Z_STREAM_ERROR;
  }
  var wrap = 1;

  if (level === Z_DEFAULT_COMPRESSION) {
    level = 6;
  }

  if (windowBits < 0) { /* suppress zlib wrapper */
    wrap = 0;
    windowBits = -windowBits;
  }

  else if (windowBits > 15) {
    wrap = 2;           /* write gzip wrapper instead */
    windowBits -= 16;
  }


  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
    strategy < 0 || strategy > Z_FIXED) {
    return err(strm, Z_STREAM_ERROR);
  }


  if (windowBits === 8) {
    windowBits = 9;
  }
  /* until 256-byte window bug fixed */

  var s = new DeflateState();

  strm.state = s;
  s.strm = strm;

  s.wrap = wrap;
  s.gzhead = null;
  s.w_bits = windowBits;
  s.w_size = 1 << s.w_bits;
  s.w_mask = s.w_size - 1;

  s.hash_bits = memLevel + 7;
  s.hash_size = 1 << s.hash_bits;
  s.hash_mask = s.hash_size - 1;
  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);

  s.window = new utils.Buf8(s.w_size * 2);
  s.head = new utils.Buf16(s.hash_size);
  s.prev = new utils.Buf16(s.w_size);

  // Don't need mem init magic for JS.
  //s.high_water = 0;  /* nothing written to s->window yet */

  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */

  s.pending_buf_size = s.lit_bufsize * 4;

  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
  //s->pending_buf = (uchf *) overlay;
  s.pending_buf = new utils.Buf8(s.pending_buf_size);

  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
  s.d_buf = 1 * s.lit_bufsize;

  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
  s.l_buf = (1 + 2) * s.lit_bufsize;

  s.level = level;
  s.strategy = strategy;
  s.method = method;

  return deflateReset(strm);
}

function deflateInit(strm, level) {
  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
}


function deflate(strm, flush) {
  var old_flush, s;
  var beg, val; // for gzip header write only

  if (!strm || !strm.state ||
    flush > Z_BLOCK || flush < 0) {
    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
  }

  s = strm.state;

  if (!strm.output ||
      (!strm.input && strm.avail_in !== 0) ||
      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
  }

  s.strm = strm; /* just in case */
  old_flush = s.last_flush;
  s.last_flush = flush;

  /* Write the header */
  if (s.status === INIT_STATE) {

    if (s.wrap === 2) { // GZIP header
      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
      put_byte(s, 31);
      put_byte(s, 139);
      put_byte(s, 8);
      if (!s.gzhead) { // s->gzhead == Z_NULL
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, OS_CODE);
        s.status = BUSY_STATE;
      }
      else {
        put_byte(s, (s.gzhead.text ? 1 : 0) +
                    (s.gzhead.hcrc ? 2 : 0) +
                    (!s.gzhead.extra ? 0 : 4) +
                    (!s.gzhead.name ? 0 : 8) +
                    (!s.gzhead.comment ? 0 : 16)
                );
        put_byte(s, s.gzhead.time & 0xff);
        put_byte(s, (s.gzhead.time >> 8) & 0xff);
        put_byte(s, (s.gzhead.time >> 16) & 0xff);
        put_byte(s, (s.gzhead.time >> 24) & 0xff);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, s.gzhead.os & 0xff);
        if (s.gzhead.extra && s.gzhead.extra.length) {
          put_byte(s, s.gzhead.extra.length & 0xff);
          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
        }
        if (s.gzhead.hcrc) {
          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
        }
        s.gzindex = 0;
        s.status = EXTRA_STATE;
      }
    }
    else // DEFLATE header
    {
      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
      var level_flags = -1;

      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
        level_flags = 0;
      } else if (s.level < 6) {
        level_flags = 1;
      } else if (s.level === 6) {
        level_flags = 2;
      } else {
        level_flags = 3;
      }
      header |= (level_flags << 6);
      if (s.strstart !== 0) { header |= PRESET_DICT; }
      header += 31 - (header % 31);

      s.status = BUSY_STATE;
      putShortMSB(s, header);

      /* Save the adler32 of the preset dictionary: */
      if (s.strstart !== 0) {
        putShortMSB(s, strm.adler >>> 16);
        putShortMSB(s, strm.adler & 0xffff);
      }
      strm.adler = 1; // adler32(0L, Z_NULL, 0);
    }
  }

//#ifdef GZIP
  if (s.status === EXTRA_STATE) {
    if (s.gzhead.extra/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */

      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            break;
          }
        }
        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
        s.gzindex++;
      }
      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (s.gzindex === s.gzhead.extra.length) {
        s.gzindex = 0;
        s.status = NAME_STATE;
      }
    }
    else {
      s.status = NAME_STATE;
    }
  }
  if (s.status === NAME_STATE) {
    if (s.gzhead.name/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.name.length) {
          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.gzindex = 0;
        s.status = COMMENT_STATE;
      }
    }
    else {
      s.status = COMMENT_STATE;
    }
  }
  if (s.status === COMMENT_STATE) {
    if (s.gzhead.comment/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.comment.length) {
          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.status = HCRC_STATE;
      }
    }
    else {
      s.status = HCRC_STATE;
    }
  }
  if (s.status === HCRC_STATE) {
    if (s.gzhead.hcrc) {
      if (s.pending + 2 > s.pending_buf_size) {
        flush_pending(strm);
      }
      if (s.pending + 2 <= s.pending_buf_size) {
        put_byte(s, strm.adler & 0xff);
        put_byte(s, (strm.adler >> 8) & 0xff);
        strm.adler = 0; //crc32(0L, Z_NULL, 0);
        s.status = BUSY_STATE;
      }
    }
    else {
      s.status = BUSY_STATE;
    }
  }
//#endif

  /* Flush as much pending output as possible */
  if (s.pending !== 0) {
    flush_pending(strm);
    if (strm.avail_out === 0) {
      /* Since avail_out is 0, deflate will be called again with
       * more output space, but possibly with both pending and
       * avail_in equal to zero. There won't be anything to do,
       * but this is not an error situation so make sure we
       * return OK instead of BUF_ERROR at next call of deflate:
       */
      s.last_flush = -1;
      return Z_OK;
    }

    /* Make sure there is something to do and avoid duplicate consecutive
     * flushes. For repeated and useless calls with Z_FINISH, we keep
     * returning Z_STREAM_END instead of Z_BUF_ERROR.
     */
  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
    flush !== Z_FINISH) {
    return err(strm, Z_BUF_ERROR);
  }

  /* User must not provide more input after the first FINISH: */
  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
    return err(strm, Z_BUF_ERROR);
  }

  /* Start a new block or continue the current one.
   */
  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
        configuration_table[s.level].func(s, flush));

    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
      s.status = FINISH_STATE;
    }
    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
      if (strm.avail_out === 0) {
        s.last_flush = -1;
        /* avoid BUF_ERROR next call, see above */
      }
      return Z_OK;
      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
       * of deflate should use the same flush parameter to make sure
       * that the flush is complete. So we don't have to output an
       * empty block here, this will be done at next call. This also
       * ensures that for a very small output buffer, we emit at most
       * one empty block.
       */
    }
    if (bstate === BS_BLOCK_DONE) {
      if (flush === Z_PARTIAL_FLUSH) {
        trees._tr_align(s);
      }
      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */

        trees._tr_stored_block(s, 0, 0, false);
        /* For a full flush, this empty block will be recognized
         * as a special marker by inflate_sync().
         */
        if (flush === Z_FULL_FLUSH) {
          /*** CLEAR_HASH(s); ***/             /* forget history */
          zero(s.head); // Fill with NIL (= 0);

          if (s.lookahead === 0) {
            s.strstart = 0;
            s.block_start = 0;
            s.insert = 0;
          }
        }
      }
      flush_pending(strm);
      if (strm.avail_out === 0) {
        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
        return Z_OK;
      }
    }
  }
  //Assert(strm->avail_out > 0, "bug2");
  //if (strm.avail_out <= 0) { throw new Error("bug2");}

  if (flush !== Z_FINISH) { return Z_OK; }
  if (s.wrap <= 0) { return Z_STREAM_END; }

  /* Write the trailer */
  if (s.wrap === 2) {
    put_byte(s, strm.adler & 0xff);
    put_byte(s, (strm.adler >> 8) & 0xff);
    put_byte(s, (strm.adler >> 16) & 0xff);
    put_byte(s, (strm.adler >> 24) & 0xff);
    put_byte(s, strm.total_in & 0xff);
    put_byte(s, (strm.total_in >> 8) & 0xff);
    put_byte(s, (strm.total_in >> 16) & 0xff);
    put_byte(s, (strm.total_in >> 24) & 0xff);
  }
  else
  {
    putShortMSB(s, strm.adler >>> 16);
    putShortMSB(s, strm.adler & 0xffff);
  }

  flush_pending(strm);
  /* If avail_out is zero, the application will call deflate again
   * to flush the rest.
   */
  if (s.wrap > 0) { s.wrap = -s.wrap; }
  /* write the trailer only once! */
  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
}

function deflateEnd(strm) {
  var status;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  status = strm.state.status;
  if (status !== INIT_STATE &&
    status !== EXTRA_STATE &&
    status !== NAME_STATE &&
    status !== COMMENT_STATE &&
    status !== HCRC_STATE &&
    status !== BUSY_STATE &&
    status !== FINISH_STATE
  ) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.state = null;

  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
}


/* =========================================================================
 * Initializes the compression dictionary from the given byte
 * sequence without producing any compressed output.
 */
function deflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var s;
  var str, n;
  var wrap;
  var avail;
  var next;
  var input;
  var tmpDict;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  s = strm.state;
  wrap = s.wrap;

  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
    return Z_STREAM_ERROR;
  }

  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
  if (wrap === 1) {
    /* adler32(strm->adler, dictionary, dictLength); */
    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
  }

  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */

  /* if dictionary would fill window, just replace the history */
  if (dictLength >= s.w_size) {
    if (wrap === 0) {            /* already empty otherwise */
      /*** CLEAR_HASH(s); ***/
      zero(s.head); // Fill with NIL (= 0);
      s.strstart = 0;
      s.block_start = 0;
      s.insert = 0;
    }
    /* use the tail */
    // dictionary = dictionary.slice(dictLength - s.w_size);
    tmpDict = new utils.Buf8(s.w_size);
    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
    dictionary = tmpDict;
    dictLength = s.w_size;
  }
  /* insert dictionary into window and hash */
  avail = strm.avail_in;
  next = strm.next_in;
  input = strm.input;
  strm.avail_in = dictLength;
  strm.next_in = 0;
  strm.input = dictionary;
  fill_window(s);
  while (s.lookahead >= MIN_MATCH) {
    str = s.strstart;
    n = s.lookahead - (MIN_MATCH - 1);
    do {
      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

      s.prev[str & s.w_mask] = s.head[s.ins_h];

      s.head[s.ins_h] = str;
      str++;
    } while (--n);
    s.strstart = str;
    s.lookahead = MIN_MATCH - 1;
    fill_window(s);
  }
  s.strstart += s.lookahead;
  s.block_start = s.strstart;
  s.insert = s.lookahead;
  s.lookahead = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  strm.next_in = next;
  strm.input = input;
  strm.avail_in = avail;
  s.wrap = wrap;
  return Z_OK;
}


exports.deflateInit = deflateInit;
exports.deflateInit2 = deflateInit2;
exports.deflateReset = deflateReset;
exports.deflateResetKeep = deflateResetKeep;
exports.deflateSetHeader = deflateSetHeader;
exports.deflate = deflate;
exports.deflateEnd = deflateEnd;
exports.deflateSetDictionary = deflateSetDictionary;
exports.deflateInfo = 'pako deflate (from Nodeca project)';

/* Not implemented
exports.deflateBound = deflateBound;
exports.deflateCopy = deflateCopy;
exports.deflateParams = deflateParams;
exports.deflatePending = deflatePending;
exports.deflatePrime = deflatePrime;
exports.deflateTune = deflateTune;
*/


/***/ }),
/* 31 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = __webpack_require__(1);

/* Public constants ==========================================================*/
/* ===========================================================================*/


//var Z_FILTERED          = 1;
//var Z_HUFFMAN_ONLY      = 2;
//var Z_RLE               = 3;
var Z_FIXED               = 4;
//var Z_DEFAULT_STRATEGY  = 0;

/* Possible values of the data_type field (though see inflate()) */
var Z_BINARY              = 0;
var Z_TEXT                = 1;
//var Z_ASCII             = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;

/*============================================================================*/


function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }

// From zutil.h

var STORED_BLOCK = 0;
var STATIC_TREES = 1;
var DYN_TREES    = 2;
/* The three kinds of block type */

var MIN_MATCH    = 3;
var MAX_MATCH    = 258;
/* The minimum and maximum match lengths */

// From deflate.h
/* ===========================================================================
 * Internal compression state.
 */

var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */

var LITERALS      = 256;
/* number of literal bytes 0..255 */

var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */

var D_CODES       = 30;
/* number of distance codes */

var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */

var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */

var MAX_BITS      = 15;
/* All codes must not exceed MAX_BITS bits */

var Buf_size      = 16;
/* size of bit buffer in bi_buf */


/* ===========================================================================
 * Constants
 */

var MAX_BL_BITS = 7;
/* Bit length codes must not exceed MAX_BL_BITS bits */

var END_BLOCK   = 256;
/* end of block literal code */

var REP_3_6     = 16;
/* repeat previous bit length 3-6 times (2 bits of repeat count) */

var REPZ_3_10   = 17;
/* repeat a zero length 3-10 times  (3 bits of repeat count) */

var REPZ_11_138 = 18;
/* repeat a zero length 11-138 times  (7 bits of repeat count) */

/* eslint-disable comma-spacing,array-bracket-spacing */
var extra_lbits =   /* extra bits for each length code */
  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];

var extra_dbits =   /* extra bits for each distance code */
  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];

var extra_blbits =  /* extra bits for each bit length code */
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];

var bl_order =
  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
/* eslint-enable comma-spacing,array-bracket-spacing */

/* The lengths of the bit length codes are sent in order of decreasing
 * probability, to avoid transmitting the lengths for unused bit length codes.
 */

/* ===========================================================================
 * Local data. These are initialized only once.
 */

// We pre-fill arrays with 0 to avoid uninitialized gaps

var DIST_CODE_LEN = 512; /* see definition of array dist_code below */

// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1
var static_ltree  = new Array((L_CODES + 2) * 2);
zero(static_ltree);
/* The static literal tree. Since the bit lengths are imposed, there is no
 * need for the L_CODES extra codes used during heap construction. However
 * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
 * below).
 */

var static_dtree  = new Array(D_CODES * 2);
zero(static_dtree);
/* The static distance tree. (Actually a trivial tree since all codes use
 * 5 bits.)
 */

var _dist_code    = new Array(DIST_CODE_LEN);
zero(_dist_code);
/* Distance codes. The first 256 values correspond to the distances
 * 3 .. 258, the last 256 values correspond to the top 8 bits of
 * the 15 bit distances.
 */

var _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
zero(_length_code);
/* length code for each normalized match length (0 == MIN_MATCH) */

var base_length   = new Array(LENGTH_CODES);
zero(base_length);
/* First normalized length for each code (0 = MIN_MATCH) */

var base_dist     = new Array(D_CODES);
zero(base_dist);
/* First normalized distance for each code (0 = distance of 1) */


function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {

  this.static_tree  = static_tree;  /* static tree or NULL */
  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
  this.extra_base   = extra_base;   /* base index for extra_bits */
  this.elems        = elems;        /* max number of elements in the tree */
  this.max_length   = max_length;   /* max bit length for the codes */

  // show if `static_tree` has data or dummy - needed for monomorphic objects
  this.has_stree    = static_tree && static_tree.length;
}


var static_l_desc;
var static_d_desc;
var static_bl_desc;


function TreeDesc(dyn_tree, stat_desc) {
  this.dyn_tree = dyn_tree;     /* the dynamic tree */
  this.max_code = 0;            /* largest code with non zero frequency */
  this.stat_desc = stat_desc;   /* the corresponding static tree */
}



function d_code(dist) {
  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
}


/* ===========================================================================
 * Output a short LSB first on the stream.
 * IN assertion: there is enough room in pendingBuf.
 */
function put_short(s, w) {
//    put_byte(s, (uch)((w) & 0xff));
//    put_byte(s, (uch)((ush)(w) >> 8));
  s.pending_buf[s.pending++] = (w) & 0xff;
  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
}


/* ===========================================================================
 * Send a value on a given number of bits.
 * IN assertion: length <= 16 and value fits in length bits.
 */
function send_bits(s, value, length) {
  if (s.bi_valid > (Buf_size - length)) {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    put_short(s, s.bi_buf);
    s.bi_buf = value >> (Buf_size - s.bi_valid);
    s.bi_valid += length - Buf_size;
  } else {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    s.bi_valid += length;
  }
}


function send_code(s, c, tree) {
  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
}


/* ===========================================================================
 * Reverse the first len bits of a code, using straightforward code (a faster
 * method would use a table)
 * IN assertion: 1 <= len <= 15
 */
function bi_reverse(code, len) {
  var res = 0;
  do {
    res |= code & 1;
    code >>>= 1;
    res <<= 1;
  } while (--len > 0);
  return res >>> 1;
}


/* ===========================================================================
 * Flush the bit buffer, keeping at most 7 bits in it.
 */
function bi_flush(s) {
  if (s.bi_valid === 16) {
    put_short(s, s.bi_buf);
    s.bi_buf = 0;
    s.bi_valid = 0;

  } else if (s.bi_valid >= 8) {
    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
    s.bi_buf >>= 8;
    s.bi_valid -= 8;
  }
}


/* ===========================================================================
 * Compute the optimal bit lengths for a tree and update the total bit length
 * for the current block.
 * IN assertion: the fields freq and dad are set, heap[heap_max] and
 *    above are the tree nodes sorted by increasing frequency.
 * OUT assertions: the field len is set to the optimal bit length, the
 *     array bl_count contains the frequencies for each bit length.
 *     The length opt_len is updated; static_len is also updated if stree is
 *     not null.
 */
function gen_bitlen(s, desc)
//    deflate_state *s;
//    tree_desc *desc;    /* the tree descriptor */
{
  var tree            = desc.dyn_tree;
  var max_code        = desc.max_code;
  var stree           = desc.stat_desc.static_tree;
  var has_stree       = desc.stat_desc.has_stree;
  var extra           = desc.stat_desc.extra_bits;
  var base            = desc.stat_desc.extra_base;
  var max_length      = desc.stat_desc.max_length;
  var h;              /* heap index */
  var n, m;           /* iterate over the tree elements */
  var bits;           /* bit length */
  var xbits;          /* extra bits */
  var f;              /* frequency */
  var overflow = 0;   /* number of elements with bit length too large */

  for (bits = 0; bits <= MAX_BITS; bits++) {
    s.bl_count[bits] = 0;
  }

  /* In a first pass, compute the optimal bit lengths (which may
   * overflow in the case of the bit length tree).
   */
  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */

  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
    n = s.heap[h];
    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
    if (bits > max_length) {
      bits = max_length;
      overflow++;
    }
    tree[n * 2 + 1]/*.Len*/ = bits;
    /* We overwrite tree[n].Dad which is no longer needed */

    if (n > max_code) { continue; } /* not a leaf node */

    s.bl_count[bits]++;
    xbits = 0;
    if (n >= base) {
      xbits = extra[n - base];
    }
    f = tree[n * 2]/*.Freq*/;
    s.opt_len += f * (bits + xbits);
    if (has_stree) {
      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
    }
  }
  if (overflow === 0) { return; }

  // Trace((stderr,"\nbit length overflow\n"));
  /* This happens for example on obj2 and pic of the Calgary corpus */

  /* Find the first bit length which could increase: */
  do {
    bits = max_length - 1;
    while (s.bl_count[bits] === 0) { bits--; }
    s.bl_count[bits]--;      /* move one leaf down the tree */
    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
    s.bl_count[max_length]--;
    /* The brother of the overflow item also moves one step up,
     * but this does not affect bl_count[max_length]
     */
    overflow -= 2;
  } while (overflow > 0);

  /* Now recompute all bit lengths, scanning in increasing frequency.
   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
   * lengths instead of fixing only the wrong ones. This idea is taken
   * from 'ar' written by Haruhiko Okumura.)
   */
  for (bits = max_length; bits !== 0; bits--) {
    n = s.bl_count[bits];
    while (n !== 0) {
      m = s.heap[--h];
      if (m > max_code) { continue; }
      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
        tree[m * 2 + 1]/*.Len*/ = bits;
      }
      n--;
    }
  }
}


/* ===========================================================================
 * Generate the codes for a given tree and bit counts (which need not be
 * optimal).
 * IN assertion: the array bl_count contains the bit length statistics for
 * the given tree and the field len is set for all tree elements.
 * OUT assertion: the field code is set for all tree elements of non
 *     zero code length.
 */
function gen_codes(tree, max_code, bl_count)
//    ct_data *tree;             /* the tree to decorate */
//    int max_code;              /* largest code with non zero frequency */
//    ushf *bl_count;            /* number of codes at each bit length */
{
  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
  var code = 0;              /* running code value */
  var bits;                  /* bit index */
  var n;                     /* code index */

  /* The distribution counts are first used to generate the code values
   * without bit reversal.
   */
  for (bits = 1; bits <= MAX_BITS; bits++) {
    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
  }
  /* Check that the bit counts in bl_count are consistent. The last code
   * must be all ones.
   */
  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
  //        "inconsistent bit counts");
  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));

  for (n = 0;  n <= max_code; n++) {
    var len = tree[n * 2 + 1]/*.Len*/;
    if (len === 0) { continue; }
    /* Now reverse the bits */
    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);

    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
  }
}


/* ===========================================================================
 * Initialize the various 'constant' tables.
 */
function tr_static_init() {
  var n;        /* iterates over tree elements */
  var bits;     /* bit counter */
  var length;   /* length value */
  var code;     /* code value */
  var dist;     /* distance index */
  var bl_count = new Array(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  // do check in _tr_init()
  //if (static_init_done) return;

  /* For some embedded targets, global variables are not initialized: */
/*#ifdef NO_INIT_GLOBAL_POINTERS
  static_l_desc.static_tree = static_ltree;
  static_l_desc.extra_bits = extra_lbits;
  static_d_desc.static_tree = static_dtree;
  static_d_desc.extra_bits = extra_dbits;
  static_bl_desc.extra_bits = extra_blbits;
#endif*/

  /* Initialize the mapping length (0..255) -> length code (0..28) */
  length = 0;
  for (code = 0; code < LENGTH_CODES - 1; code++) {
    base_length[code] = length;
    for (n = 0; n < (1 << extra_lbits[code]); n++) {
      _length_code[length++] = code;
    }
  }
  //Assert (length == 256, "tr_static_init: length != 256");
  /* Note that the length 255 (match length 258) can be represented
   * in two different ways: code 284 + 5 bits or code 285, so we
   * overwrite length_code[255] to use the best encoding:
   */
  _length_code[length - 1] = code;

  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
  dist = 0;
  for (code = 0; code < 16; code++) {
    base_dist[code] = dist;
    for (n = 0; n < (1 << extra_dbits[code]); n++) {
      _dist_code[dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: dist != 256");
  dist >>= 7; /* from now on, all distances are divided by 128 */
  for (; code < D_CODES; code++) {
    base_dist[code] = dist << 7;
    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
      _dist_code[256 + dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: 256+dist != 512");

  /* Construct the codes of the static literal tree */
  for (bits = 0; bits <= MAX_BITS; bits++) {
    bl_count[bits] = 0;
  }

  n = 0;
  while (n <= 143) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  while (n <= 255) {
    static_ltree[n * 2 + 1]/*.Len*/ = 9;
    n++;
    bl_count[9]++;
  }
  while (n <= 279) {
    static_ltree[n * 2 + 1]/*.Len*/ = 7;
    n++;
    bl_count[7]++;
  }
  while (n <= 287) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  /* Codes 286 and 287 do not exist, but we must include them in the
   * tree construction to get a canonical Huffman tree (longest code
   * all ones)
   */
  gen_codes(static_ltree, L_CODES + 1, bl_count);

  /* The static distance tree is trivial: */
  for (n = 0; n < D_CODES; n++) {
    static_dtree[n * 2 + 1]/*.Len*/ = 5;
    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
  }

  // Now data ready and we can init static trees
  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);

  //static_init_done = true;
}


/* ===========================================================================
 * Initialize a new block.
 */
function init_block(s) {
  var n; /* iterates over tree elements */

  /* Initialize the trees. */
  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }

  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
  s.opt_len = s.static_len = 0;
  s.last_lit = s.matches = 0;
}


/* ===========================================================================
 * Flush the bit buffer and align the output on a byte boundary
 */
function bi_windup(s)
{
  if (s.bi_valid > 8) {
    put_short(s, s.bi_buf);
  } else if (s.bi_valid > 0) {
    //put_byte(s, (Byte)s->bi_buf);
    s.pending_buf[s.pending++] = s.bi_buf;
  }
  s.bi_buf = 0;
  s.bi_valid = 0;
}

/* ===========================================================================
 * Copy a stored block, storing first the length and its
 * one's complement if requested.
 */
function copy_block(s, buf, len, header)
//DeflateState *s;
//charf    *buf;    /* the input data */
//unsigned len;     /* its length */
//int      header;  /* true if block header must be written */
{
  bi_windup(s);        /* align on byte boundary */

  if (header) {
    put_short(s, len);
    put_short(s, ~len);
  }
//  while (len--) {
//    put_byte(s, *buf++);
//  }
  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
  s.pending += len;
}

/* ===========================================================================
 * Compares to subtrees, using the tree depth as tie breaker when
 * the subtrees have equal frequency. This minimizes the worst case length.
 */
function smaller(tree, n, m, depth) {
  var _n2 = n * 2;
  var _m2 = m * 2;
  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
}

/* ===========================================================================
 * Restore the heap property by moving down the tree starting at node k,
 * exchanging a node with the smallest of its two sons if necessary, stopping
 * when the heap property is re-established (each father smaller than its
 * two sons).
 */
function pqdownheap(s, tree, k)
//    deflate_state *s;
//    ct_data *tree;  /* the tree to restore */
//    int k;               /* node to move down */
{
  var v = s.heap[k];
  var j = k << 1;  /* left son of k */
  while (j <= s.heap_len) {
    /* Set j to the smallest of the two sons: */
    if (j < s.heap_len &&
      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
      j++;
    }
    /* Exit if v is smaller than both sons */
    if (smaller(tree, v, s.heap[j], s.depth)) { break; }

    /* Exchange v with the smallest son */
    s.heap[k] = s.heap[j];
    k = j;

    /* And continue down the tree, setting j to the left son of k */
    j <<= 1;
  }
  s.heap[k] = v;
}


// inlined manually
// var SMALLEST = 1;

/* ===========================================================================
 * Send the block data compressed using the given Huffman trees
 */
function compress_block(s, ltree, dtree)
//    deflate_state *s;
//    const ct_data *ltree; /* literal tree */
//    const ct_data *dtree; /* distance tree */
{
  var dist;           /* distance of matched string */
  var lc;             /* match length or unmatched char (if dist == 0) */
  var lx = 0;         /* running index in l_buf */
  var code;           /* the code to send */
  var extra;          /* number of extra bits to send */

  if (s.last_lit !== 0) {
    do {
      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
      lc = s.pending_buf[s.l_buf + lx];
      lx++;

      if (dist === 0) {
        send_code(s, lc, ltree); /* send a literal byte */
        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
      } else {
        /* Here, lc is the match length - MIN_MATCH */
        code = _length_code[lc];
        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
        extra = extra_lbits[code];
        if (extra !== 0) {
          lc -= base_length[code];
          send_bits(s, lc, extra);       /* send the extra length bits */
        }
        dist--; /* dist is now the match distance - 1 */
        code = d_code(dist);
        //Assert (code < D_CODES, "bad d_code");

        send_code(s, code, dtree);       /* send the distance code */
        extra = extra_dbits[code];
        if (extra !== 0) {
          dist -= base_dist[code];
          send_bits(s, dist, extra);   /* send the extra distance bits */
        }
      } /* literal or match pair ? */

      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
      //       "pendingBuf overflow");

    } while (lx < s.last_lit);
  }

  send_code(s, END_BLOCK, ltree);
}


/* ===========================================================================
 * Construct one Huffman tree and assigns the code bit strings and lengths.
 * Update the total bit length for the current block.
 * IN assertion: the field freq is set for all tree elements.
 * OUT assertions: the fields len and code are set to the optimal bit length
 *     and corresponding code. The length opt_len is updated; static_len is
 *     also updated if stree is not null. The field max_code is set.
 */
function build_tree(s, desc)
//    deflate_state *s;
//    tree_desc *desc; /* the tree descriptor */
{
  var tree     = desc.dyn_tree;
  var stree    = desc.stat_desc.static_tree;
  var has_stree = desc.stat_desc.has_stree;
  var elems    = desc.stat_desc.elems;
  var n, m;          /* iterate over heap elements */
  var max_code = -1; /* largest code with non zero frequency */
  var node;          /* new node being created */

  /* Construct the initial heap, with least frequent element in
   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
   * heap[0] is not used.
   */
  s.heap_len = 0;
  s.heap_max = HEAP_SIZE;

  for (n = 0; n < elems; n++) {
    if (tree[n * 2]/*.Freq*/ !== 0) {
      s.heap[++s.heap_len] = max_code = n;
      s.depth[n] = 0;

    } else {
      tree[n * 2 + 1]/*.Len*/ = 0;
    }
  }

  /* The pkzip format requires that at least one distance code exists,
   * and that at least one bit should be sent even if there is only one
   * possible code. So to avoid special checks later on we force at least
   * two codes of non zero frequency.
   */
  while (s.heap_len < 2) {
    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
    tree[node * 2]/*.Freq*/ = 1;
    s.depth[node] = 0;
    s.opt_len--;

    if (has_stree) {
      s.static_len -= stree[node * 2 + 1]/*.Len*/;
    }
    /* node is 0 or 1 so it does not have extra bits */
  }
  desc.max_code = max_code;

  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
   * establish sub-heaps of increasing lengths:
   */
  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }

  /* Construct the Huffman tree by repeatedly combining the least two
   * frequent nodes.
   */
  node = elems;              /* next internal node of the tree */
  do {
    //pqremove(s, tree, n);  /* n = node of least frequency */
    /*** pqremove ***/
    n = s.heap[1/*SMALLEST*/];
    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
    pqdownheap(s, tree, 1/*SMALLEST*/);
    /***/

    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */

    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
    s.heap[--s.heap_max] = m;

    /* Create a new node father of n and m */
    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;

    /* and insert the new node in the heap */
    s.heap[1/*SMALLEST*/] = node++;
    pqdownheap(s, tree, 1/*SMALLEST*/);

  } while (s.heap_len >= 2);

  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];

  /* At this point, the fields freq and dad are set. We can now
   * generate the bit lengths.
   */
  gen_bitlen(s, desc);

  /* The field len is now set, we can generate the bit codes */
  gen_codes(tree, max_code, s.bl_count);
}


/* ===========================================================================
 * Scan a literal or distance tree to determine the frequencies of the codes
 * in the bit length tree.
 */
function scan_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree;   /* the tree to be scanned */
//    int max_code;    /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }
  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      s.bl_tree[curlen * 2]/*.Freq*/ += count;

    } else if (curlen !== 0) {

      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;

    } else if (count <= 10) {
      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;

    } else {
      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
    }

    count = 0;
    prevlen = curlen;

    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Send a literal or distance tree in compressed form, using the codes in
 * bl_tree.
 */
function send_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree; /* the tree to be scanned */
//    int max_code;       /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  /* tree[max_code+1].Len = -1; */  /* guard already set */
  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);

    } else if (curlen !== 0) {
      if (curlen !== prevlen) {
        send_code(s, curlen, s.bl_tree);
        count--;
      }
      //Assert(count >= 3 && count <= 6, " 3_6?");
      send_code(s, REP_3_6, s.bl_tree);
      send_bits(s, count - 3, 2);

    } else if (count <= 10) {
      send_code(s, REPZ_3_10, s.bl_tree);
      send_bits(s, count - 3, 3);

    } else {
      send_code(s, REPZ_11_138, s.bl_tree);
      send_bits(s, count - 11, 7);
    }

    count = 0;
    prevlen = curlen;
    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Construct the Huffman tree for the bit lengths and return the index in
 * bl_order of the last bit length code to send.
 */
function build_bl_tree(s) {
  var max_blindex;  /* index of last bit length code of non zero freq */

  /* Determine the bit length frequencies for literal and distance trees */
  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);

  /* Build the bit length tree: */
  build_tree(s, s.bl_desc);
  /* opt_len now includes the length of the tree representations, except
   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
   */

  /* Determine the number of bit length codes to send. The pkzip format
   * requires that at least 4 bit length codes be sent. (appnote.txt says
   * 3 but the actual value used is 4.)
   */
  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
      break;
    }
  }
  /* Update opt_len to include the bit length tree and counts */
  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
  //        s->opt_len, s->static_len));

  return max_blindex;
}


/* ===========================================================================
 * Send the header for a block using dynamic Huffman trees: the counts, the
 * lengths of the bit length codes, the literal tree and the distance tree.
 * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
 */
function send_all_trees(s, lcodes, dcodes, blcodes)
//    deflate_state *s;
//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
{
  var rank;                    /* index in bl_order */

  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
  //        "too many codes");
  //Tracev((stderr, "\nbl counts: "));
  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
  send_bits(s, dcodes - 1,   5);
  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
  for (rank = 0; rank < blcodes; rank++) {
    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
  }
  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
}


/* ===========================================================================
 * Check if the data type is TEXT or BINARY, using the following algorithm:
 * - TEXT if the two conditions below are satisfied:
 *    a) There are no non-portable control characters belonging to the
 *       "black list" (0..6, 14..25, 28..31).
 *    b) There is at least one printable character belonging to the
 *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
 * - BINARY otherwise.
 * - The following partially-portable control characters form a
 *   "gray list" that is ignored in this detection algorithm:
 *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
 * IN assertion: the fields Freq of dyn_ltree are set.
 */
function detect_data_type(s) {
  /* black_mask is the bit mask of black-listed bytes
   * set bits 0..6, 14..25, and 28..31
   * 0xf3ffc07f = binary 11110011111111111100000001111111
   */
  var black_mask = 0xf3ffc07f;
  var n;

  /* Check for non-textual ("black-listed") bytes. */
  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
      return Z_BINARY;
    }
  }

  /* Check for textual ("white-listed") bytes. */
  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
    return Z_TEXT;
  }
  for (n = 32; n < LITERALS; n++) {
    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
      return Z_TEXT;
    }
  }

  /* There are no "black-listed" or "white-listed" bytes:
   * this stream either is empty or has tolerated ("gray-listed") bytes only.
   */
  return Z_BINARY;
}


var static_init_done = false;

/* ===========================================================================
 * Initialize the tree data structures for a new zlib stream.
 */
function _tr_init(s)
{

  if (!static_init_done) {
    tr_static_init();
    static_init_done = true;
  }

  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);

  s.bi_buf = 0;
  s.bi_valid = 0;

  /* Initialize the first block of the first file: */
  init_block(s);
}


/* ===========================================================================
 * Send a stored block
 */
function _tr_stored_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
  copy_block(s, buf, stored_len, true); /* with header */
}


/* ===========================================================================
 * Send one empty static block to give enough lookahead for inflate.
 * This takes 10 bits, of which 7 may remain in the bit buffer.
 */
function _tr_align(s) {
  send_bits(s, STATIC_TREES << 1, 3);
  send_code(s, END_BLOCK, static_ltree);
  bi_flush(s);
}


/* ===========================================================================
 * Determine the best encoding for the current block: dynamic trees, static
 * trees or store, and output the encoded block to the zip file.
 */
function _tr_flush_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block, or NULL if too old */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
  var max_blindex = 0;        /* index of last bit length code of non zero freq */

  /* Build the Huffman trees unless a stored block is forced */
  if (s.level > 0) {

    /* Check if the file is binary or text */
    if (s.strm.data_type === Z_UNKNOWN) {
      s.strm.data_type = detect_data_type(s);
    }

    /* Construct the literal and distance trees */
    build_tree(s, s.l_desc);
    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));

    build_tree(s, s.d_desc);
    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));
    /* At this point, opt_len and static_len are the total bit lengths of
     * the compressed block data, excluding the tree representations.
     */

    /* Build the bit length tree for the above two trees, and get the index
     * in bl_order of the last bit length code to send.
     */
    max_blindex = build_bl_tree(s);

    /* Determine the best encoding. Compute the block lengths in bytes. */
    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
    static_lenb = (s.static_len + 3 + 7) >>> 3;

    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
    //        s->last_lit));

    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }

  } else {
    // Assert(buf != (char*)0, "lost buf");
    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
  }

  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
    /* 4: two words for the lengths */

    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
     * Otherwise we can't have processed more than WSIZE input bytes since
     * the last block flush, because compression would have been
     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
     * transform a block into a stored block.
     */
    _tr_stored_block(s, buf, stored_len, last);

  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {

    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
    compress_block(s, static_ltree, static_dtree);

  } else {
    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
    compress_block(s, s.dyn_ltree, s.dyn_dtree);
  }
  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
  /* The above check is made mod 2^32, for files larger than 512 MB
   * and uLong implemented on 32 bits.
   */
  init_block(s);

  if (last) {
    bi_windup(s);
  }
  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
  //       s->compressed_len-7*last));
}

/* ===========================================================================
 * Save the match info and tally the frequency counts. Return true if
 * the current block must be flushed.
 */
function _tr_tally(s, dist, lc)
//    deflate_state *s;
//    unsigned dist;  /* distance of matched string */
//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
  //var out_length, in_length, dcode;

  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;

  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
  s.last_lit++;

  if (dist === 0) {
    /* lc is the unmatched char */
    s.dyn_ltree[lc * 2]/*.Freq*/++;
  } else {
    s.matches++;
    /* Here, lc is the match length - MIN_MATCH */
    dist--;             /* dist = match distance - 1 */
    //Assert((ush)dist < (ush)MAX_DIST(s) &&
    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");

    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
  }

// (!) This block is disabled in zlib defailts,
// don't enable it for binary compatibility

//#ifdef TRUNCATE_BLOCK
//  /* Try to guess if it is profitable to stop the current block here */
//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
//    /* Compute an upper bound for the compressed length */
//    out_length = s.last_lit*8;
//    in_length = s.strstart - s.block_start;
//
//    for (dcode = 0; dcode < D_CODES; dcode++) {
//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
//    }
//    out_length >>>= 3;
//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
//    //       s->last_lit, in_length, out_length,
//    //       100L - out_length*100L/in_length));
//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
//      return true;
//    }
//  }
//#endif

  return (s.last_lit === s.lit_bufsize - 1);
  /* We avoid equality with lit_bufsize because of wraparound at 64K
   * on 16 bit machines and because stored blocks are restricted to
   * 64K-1 bytes.
   */
}

exports._tr_init  = _tr_init;
exports._tr_stored_block = _tr_stored_block;
exports._tr_flush_block  = _tr_flush_block;
exports._tr_tally = _tr_tally;
exports._tr_align = _tr_align;


/***/ }),
/* 32 */
/***/ (function(module, exports, __webpack_require__) {





var zlib_inflate = __webpack_require__(33);
var utils        = __webpack_require__(1);
var strings      = __webpack_require__(11);
var c            = __webpack_require__(13);
var msg          = __webpack_require__(6);
var ZStream      = __webpack_require__(12);
var GZheader     = __webpack_require__(36);

var toString = Object.prototype.toString;

/**
 * class Inflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[inflate]]
 * and [[inflateRaw]].
 **/

/* internal
 * inflate.chunks -> Array
 *
 * Chunks of output data, if [[Inflate#onData]] not overriden.
 **/

/**
 * Inflate.result -> Uint8Array|Array|String
 *
 * Uncompressed result, generated by default [[Inflate#onData]]
 * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you
 * push a chunk with explicit flush (call [[Inflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Inflate.err -> Number
 *
 * Error code after inflate finished. 0 (Z_OK) on success.
 * Should be checked if broken data possible.
 **/

/**
 * Inflate.msg -> String
 *
 * Error message, if [[Inflate.err]] != 0
 **/


/**
 * new Inflate(options)
 * - options (Object): zlib inflate options.
 *
 * Creates new inflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `windowBits`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw inflate
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 * By default, when no options set, autodetect deflate/gzip data format via
 * wrapper header.
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var inflate = new pako.Inflate({ level: 3});
 *
 * inflate.push(chunk1, false);
 * inflate.push(chunk2, true);  // true -> last chunk
 *
 * if (inflate.err) { throw new Error(inflate.err); }
 *
 * console.log(inflate.result);
 * ```
 **/
function Inflate(options) {
  if (!(this instanceof Inflate)) return new Inflate(options);

  this.options = utils.assign({
    chunkSize: 16384,
    windowBits: 0,
    to: ''
  }, options || {});

  var opt = this.options;

  // Force window size for `raw` data, if not set directly,
  // because we have no header for autodetect.
  if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
    opt.windowBits = -opt.windowBits;
    if (opt.windowBits === 0) { opt.windowBits = -15; }
  }

  // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
  if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
      !(options && options.windowBits)) {
    opt.windowBits += 32;
  }

  // Gzip header has no info about windows size, we can do autodetect only
  // for deflate. So, if window size not set, force it to max when gzip possible
  if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
    // bit 3 (16) -> gzipped data
    // bit 4 (32) -> autodetect gzip/deflate
    if ((opt.windowBits & 15) === 0) {
      opt.windowBits |= 15;
    }
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm   = new ZStream();
  this.strm.avail_out = 0;

  var status  = zlib_inflate.inflateInit2(
    this.strm,
    opt.windowBits
  );

  if (status !== c.Z_OK) {
    throw new Error(msg[status]);
  }

  this.header = new GZheader();

  zlib_inflate.inflateGetHeader(this.strm, this.header);
}

/**
 * Inflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH.
 *
 * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
 * new output chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the decompression context.
 *
 * On fail call [[Inflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Inflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var dictionary = this.options.dictionary;
  var status, _mode;
  var next_out_utf8, tail, utf8str;
  var dict;

  // Flag to properly process Z_BUF_ERROR on testing inflate call
  // when we check that all output data was flushed.
  var allowBufError = false;

  if (this.ended) { return false; }
  _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // Only binary strings can be decompressed on practice
    strm.input = strings.binstring2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }

    status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);    /* no bad return value */

    if (status === c.Z_NEED_DICT && dictionary) {
      // Convert data if needed
      if (typeof dictionary === 'string') {
        dict = strings.string2buf(dictionary);
      } else if (toString.call(dictionary) === '[object ArrayBuffer]') {
        dict = new Uint8Array(dictionary);
      } else {
        dict = dictionary;
      }

      status = zlib_inflate.inflateSetDictionary(this.strm, dict);

    }

    if (status === c.Z_BUF_ERROR && allowBufError === true) {
      status = c.Z_OK;
      allowBufError = false;
    }

    if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }

    if (strm.next_out) {
      if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) {

        if (this.options.to === 'string') {

          next_out_utf8 = strings.utf8border(strm.output, strm.next_out);

          tail = strm.next_out - next_out_utf8;
          utf8str = strings.buf2string(strm.output, next_out_utf8);

          // move tail
          strm.next_out = tail;
          strm.avail_out = chunkSize - tail;
          if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); }

          this.onData(utf8str);

        } else {
          this.onData(utils.shrinkBuf(strm.output, strm.next_out));
        }
      }
    }

    // When no more input data, we should check that internal inflate buffers
    // are flushed. The only way to do it when avail_out = 0 - run one more
    // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR.
    // Here we set flag to process this error properly.
    //
    // NOTE. Deflate does not return error in this case and does not needs such
    // logic.
    if (strm.avail_in === 0 && strm.avail_out === 0) {
      allowBufError = true;
    }

  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END);

  if (status === c.Z_STREAM_END) {
    _mode = c.Z_FINISH;
  }

  // Finalize on the last chunk.
  if (_mode === c.Z_FINISH) {
    status = zlib_inflate.inflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === c.Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === c.Z_SYNC_FLUSH) {
    this.onEnd(c.Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Inflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): ouput data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Inflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Inflate#onEnd(status) -> Void
 * - status (Number): inflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called either after you tell inflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Inflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === c.Z_OK) {
    if (this.options.to === 'string') {
      // Glue & convert here, until we teach pako to send
      // utf8 alligned strings to onData
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * inflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Decompress `data` with inflate/ungzip and `options`. Autodetect
 * format via wrapper header by default. That's why we don't provide
 * separate `ungzip` method.
 *
 * Supported options are:
 *
 * - windowBits
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , input = pako.deflate([1,2,3,4,5,6,7,8,9])
 *   , output;
 *
 * try {
 *   output = pako.inflate(input);
 * } catch (err)
 *   console.log(err);
 * }
 * ```
 **/
function inflate(input, options) {
  var inflator = new Inflate(options);

  inflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (inflator.err) { throw inflator.msg || msg[inflator.err]; }

  return inflator.result;
}


/**
 * inflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * The same as [[inflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function inflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return inflate(input, options);
}


/**
 * ungzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Just shortcut to [[inflate]], because it autodetects format
 * by header.content. Done for convenience.
 **/


exports.Inflate = Inflate;
exports.inflate = inflate;
exports.inflateRaw = inflateRaw;
exports.ungzip  = inflate;


/***/ }),
/* 33 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils         = __webpack_require__(1);
var adler32       = __webpack_require__(9);
var crc32         = __webpack_require__(10);
var inflate_fast  = __webpack_require__(34);
var inflate_table = __webpack_require__(35);

var CODES = 0;
var LENS = 1;
var DISTS = 2;

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
//var Z_NO_FLUSH      = 0;
//var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
//var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;

/* The deflate compression method */
var Z_DEFLATED  = 8;


/* STATES ====================================================================*/
/* ===========================================================================*/


var    HEAD = 1;       /* i: waiting for magic header */
var    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
var    TIME = 3;       /* i: waiting for modification time (gzip) */
var    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
var    EXLEN = 5;      /* i: waiting for extra length (gzip) */
var    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
var    NAME = 7;       /* i: waiting for end of file name (gzip) */
var    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
var    HCRC = 9;       /* i: waiting for header crc (gzip) */
var    DICTID = 10;    /* i: waiting for dictionary check value */
var    DICT = 11;      /* waiting for inflateSetDictionary() call */
var        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
var        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
var        STORED = 14;    /* i: waiting for stored size (length and complement) */
var        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
var        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
var        TABLE = 17;     /* i: waiting for dynamic block table lengths */
var        LENLENS = 18;   /* i: waiting for code length code lengths */
var        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
var            LEN_ = 20;      /* i: same as LEN below, but only first time in */
var            LEN = 21;       /* i: waiting for length/lit/eob code */
var            LENEXT = 22;    /* i: waiting for length extra bits */
var            DIST = 23;      /* i: waiting for distance code */
var            DISTEXT = 24;   /* i: waiting for distance extra bits */
var            MATCH = 25;     /* o: waiting for output space to copy string */
var            LIT = 26;       /* o: waiting for output space to write literal */
var    CHECK = 27;     /* i: waiting for 32-bit check value */
var    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
var    DONE = 29;      /* finished check, done -- remain here until reset */
var    BAD = 30;       /* got a data error -- remain here until reset */
var    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
var    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */

/* ===========================================================================*/



var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);

var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_WBITS = MAX_WBITS;


function zswap32(q) {
  return  (((q >>> 24) & 0xff) +
          ((q >>> 8) & 0xff00) +
          ((q & 0xff00) << 8) +
          ((q & 0xff) << 24));
}


function InflateState() {
  this.mode = 0;             /* current inflate mode */
  this.last = false;          /* true if processing last block */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.havedict = false;      /* true if dictionary provided */
  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
  this.check = 0;             /* protected copy of check value */
  this.total = 0;             /* protected copy of output count */
  // TODO: may be {}
  this.head = null;           /* where to save gzip header information */

  /* sliding window */
  this.wbits = 0;             /* log base 2 of requested window size */
  this.wsize = 0;             /* window size or zero if not using window */
  this.whave = 0;             /* valid bytes in the window */
  this.wnext = 0;             /* window write index */
  this.window = null;         /* allocated sliding window, if needed */

  /* bit accumulator */
  this.hold = 0;              /* input bit accumulator */
  this.bits = 0;              /* number of bits in "in" */

  /* for string and stored block copying */
  this.length = 0;            /* literal or length of data to copy */
  this.offset = 0;            /* distance back to copy string from */

  /* for table and code decoding */
  this.extra = 0;             /* extra bits needed */

  /* fixed and dynamic code tables */
  this.lencode = null;          /* starting table for length/literal codes */
  this.distcode = null;         /* starting table for distance codes */
  this.lenbits = 0;           /* index bits for lencode */
  this.distbits = 0;          /* index bits for distcode */

  /* dynamic table building */
  this.ncode = 0;             /* number of code length code lengths */
  this.nlen = 0;              /* number of length code lengths */
  this.ndist = 0;             /* number of distance code lengths */
  this.have = 0;              /* number of code lengths in lens[] */
  this.next = null;              /* next available space in codes[] */

  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
  this.work = new utils.Buf16(288); /* work area for code table building */

  /*
   because we don't have pointers in js, we use lencode and distcode directly
   as buffers so we don't need codes
  */
  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */
  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
  this.sane = 0;                   /* if false, allow invalid distance too far */
  this.back = 0;                   /* bits back of last unprocessed length/lit */
  this.was = 0;                    /* initial length of match */
}

function inflateResetKeep(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  strm.total_in = strm.total_out = state.total = 0;
  strm.msg = ''; /*Z_NULL*/
  if (state.wrap) {       /* to support ill-conceived Java test suite */
    strm.adler = state.wrap & 1;
  }
  state.mode = HEAD;
  state.last = 0;
  state.havedict = 0;
  state.dmax = 32768;
  state.head = null/*Z_NULL*/;
  state.hold = 0;
  state.bits = 0;
  //state.lencode = state.distcode = state.next = state.codes;
  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);

  state.sane = 1;
  state.back = -1;
  //Tracev((stderr, "inflate: reset\n"));
  return Z_OK;
}

function inflateReset(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  state.wsize = 0;
  state.whave = 0;
  state.wnext = 0;
  return inflateResetKeep(strm);

}

function inflateReset2(strm, windowBits) {
  var wrap;
  var state;

  /* get the state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;

  /* extract wrap request from windowBits parameter */
  if (windowBits < 0) {
    wrap = 0;
    windowBits = -windowBits;
  }
  else {
    wrap = (windowBits >> 4) + 1;
    if (windowBits < 48) {
      windowBits &= 15;
    }
  }

  /* set number of window bits, free window if different */
  if (windowBits && (windowBits < 8 || windowBits > 15)) {
    return Z_STREAM_ERROR;
  }
  if (state.window !== null && state.wbits !== windowBits) {
    state.window = null;
  }

  /* update state and reset the rest of it */
  state.wrap = wrap;
  state.wbits = windowBits;
  return inflateReset(strm);
}

function inflateInit2(strm, windowBits) {
  var ret;
  var state;

  if (!strm) { return Z_STREAM_ERROR; }
  //strm.msg = Z_NULL;                 /* in case we return an error */

  state = new InflateState();

  //if (state === Z_NULL) return Z_MEM_ERROR;
  //Tracev((stderr, "inflate: allocated\n"));
  strm.state = state;
  state.window = null/*Z_NULL*/;
  ret = inflateReset2(strm, windowBits);
  if (ret !== Z_OK) {
    strm.state = null/*Z_NULL*/;
  }
  return ret;
}

function inflateInit(strm) {
  return inflateInit2(strm, DEF_WBITS);
}


/*
 Return state with length and distance decoding tables and index sizes set to
 fixed code decoding.  Normally this returns fixed tables from inffixed.h.
 If BUILDFIXED is defined, then instead this routine builds the tables the
 first time it's called, and returns those tables the first time and
 thereafter.  This reduces the size of the code by about 2K bytes, in
 exchange for a little execution time.  However, BUILDFIXED should not be
 used for threaded applications, since the rewriting of the tables and virgin
 may not be thread-safe.
 */
var virgin = true;

var lenfix, distfix; // We have no pointers in JS, so keep tables separate

function fixedtables(state) {
  /* build fixed huffman tables if first call (may not be thread safe) */
  if (virgin) {
    var sym;

    lenfix = new utils.Buf32(512);
    distfix = new utils.Buf32(32);

    /* literal/length table */
    sym = 0;
    while (sym < 144) { state.lens[sym++] = 8; }
    while (sym < 256) { state.lens[sym++] = 9; }
    while (sym < 280) { state.lens[sym++] = 7; }
    while (sym < 288) { state.lens[sym++] = 8; }

    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });

    /* distance table */
    sym = 0;
    while (sym < 32) { state.lens[sym++] = 5; }

    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });

    /* do this just once */
    virgin = false;
  }

  state.lencode = lenfix;
  state.lenbits = 9;
  state.distcode = distfix;
  state.distbits = 5;
}


/*
 Update the window with the last wsize (normally 32K) bytes written before
 returning.  If window does not exist yet, create it.  This is only called
 when a window is already in use, or when output has been written during this
 inflate call, but the end of the deflate stream has not been reached yet.
 It is also called to create a window for dictionary data when a dictionary
 is loaded.

 Providing output buffers larger than 32K to inflate() should provide a speed
 advantage, since only the last 32K of output is copied to the sliding window
 upon return from inflate(), and since all distances after the first 32K of
 output will fall in the output data, making match copies simpler and faster.
 The advantage may be dependent on the size of the processor's data caches.
 */
function updatewindow(strm, src, end, copy) {
  var dist;
  var state = strm.state;

  /* if it hasn't been done already, allocate space for the window */
  if (state.window === null) {
    state.wsize = 1 << state.wbits;
    state.wnext = 0;
    state.whave = 0;

    state.window = new utils.Buf8(state.wsize);
  }

  /* copy state->wsize or less output bytes into the circular window */
  if (copy >= state.wsize) {
    utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
    state.wnext = 0;
    state.whave = state.wsize;
  }
  else {
    dist = state.wsize - state.wnext;
    if (dist > copy) {
      dist = copy;
    }
    //zmemcpy(state->window + state->wnext, end - copy, dist);
    utils.arraySet(state.window, src, end - copy, dist, state.wnext);
    copy -= dist;
    if (copy) {
      //zmemcpy(state->window, end - copy, copy);
      utils.arraySet(state.window, src, end - copy, copy, 0);
      state.wnext = copy;
      state.whave = state.wsize;
    }
    else {
      state.wnext += dist;
      if (state.wnext === state.wsize) { state.wnext = 0; }
      if (state.whave < state.wsize) { state.whave += dist; }
    }
  }
  return 0;
}

function inflate(strm, flush) {
  var state;
  var input, output;          // input/output buffers
  var next;                   /* next input INDEX */
  var put;                    /* next output INDEX */
  var have, left;             /* available input and output */
  var hold;                   /* bit buffer */
  var bits;                   /* bits in bit buffer */
  var _in, _out;              /* save starting available input and output */
  var copy;                   /* number of stored or match bytes to copy */
  var from;                   /* where to copy match bytes from */
  var from_source;
  var here = 0;               /* current decoding table entry */
  var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
  //var last;                   /* parent table entry */
  var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
  var len;                    /* length to copy for repeats, bits to drop */
  var ret;                    /* return code */
  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */
  var opts;

  var n; // temporary var for NEED_BITS

  var order = /* permutation of code lengths */
    [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];


  if (!strm || !strm.state || !strm.output ||
      (!strm.input && strm.avail_in !== 0)) {
    return Z_STREAM_ERROR;
  }

  state = strm.state;
  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */


  //--- LOAD() ---
  put = strm.next_out;
  output = strm.output;
  left = strm.avail_out;
  next = strm.next_in;
  input = strm.input;
  have = strm.avail_in;
  hold = state.hold;
  bits = state.bits;
  //---

  _in = have;
  _out = left;
  ret = Z_OK;

  inf_leave: // goto emulation
  for (;;) {
    switch (state.mode) {
    case HEAD:
      if (state.wrap === 0) {
        state.mode = TYPEDO;
        break;
      }
      //=== NEEDBITS(16);
      while (bits < 16) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
        state.check = 0/*crc32(0L, Z_NULL, 0)*/;
        //=== CRC2(state.check, hold);
        hbuf[0] = hold & 0xff;
        hbuf[1] = (hold >>> 8) & 0xff;
        state.check = crc32(state.check, hbuf, 2, 0);
        //===//

        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = FLAGS;
        break;
      }
      state.flags = 0;           /* expect zlib header */
      if (state.head) {
        state.head.done = false;
      }
      if (!(state.wrap & 1) ||   /* check if zlib header allowed */
        (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
        strm.msg = 'incorrect header check';
        state.mode = BAD;
        break;
      }
      if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
        strm.msg = 'unknown compression method';
        state.mode = BAD;
        break;
      }
      //--- DROPBITS(4) ---//
      hold >>>= 4;
      bits -= 4;
      //---//
      len = (hold & 0x0f)/*BITS(4)*/ + 8;
      if (state.wbits === 0) {
        state.wbits = len;
      }
      else if (len > state.wbits) {
        strm.msg = 'invalid window size';
        state.mode = BAD;
        break;
      }
      state.dmax = 1 << len;
      //Tracev((stderr, "inflate:   zlib header ok\n"));
      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
      state.mode = hold & 0x200 ? DICTID : TYPE;
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      break;
    case FLAGS:
      //=== NEEDBITS(16); */
      while (bits < 16) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      state.flags = hold;
      if ((state.flags & 0xff) !== Z_DEFLATED) {
        strm.msg = 'unknown compression method';
        state.mode = BAD;
        break;
      }
      if (state.flags & 0xe000) {
        strm.msg = 'unknown header flags set';
        state.mode = BAD;
        break;
      }
      if (state.head) {
        state.head.text = ((hold >> 8) & 1);
      }
      if (state.flags & 0x0200) {
        //=== CRC2(state.check, hold);
        hbuf[0] = hold & 0xff;
        hbuf[1] = (hold >>> 8) & 0xff;
        state.check = crc32(state.check, hbuf, 2, 0);
        //===//
      }
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      state.mode = TIME;
      /* falls through */
    case TIME:
      //=== NEEDBITS(32); */
      while (bits < 32) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      if (state.head) {
        state.head.time = hold;
      }
      if (state.flags & 0x0200) {
        //=== CRC4(state.check, hold)
        hbuf[0] = hold & 0xff;
        hbuf[1] = (hold >>> 8) & 0xff;
        hbuf[2] = (hold >>> 16) & 0xff;
        hbuf[3] = (hold >>> 24) & 0xff;
        state.check = crc32(state.check, hbuf, 4, 0);
        //===
      }
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      state.mode = OS;
      /* falls through */
    case OS:
      //=== NEEDBITS(16); */
      while (bits < 16) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      if (state.head) {
        state.head.xflags = (hold & 0xff);
        state.head.os = (hold >> 8);
      }
      if (state.flags & 0x0200) {
        //=== CRC2(state.check, hold);
        hbuf[0] = hold & 0xff;
        hbuf[1] = (hold >>> 8) & 0xff;
        state.check = crc32(state.check, hbuf, 2, 0);
        //===//
      }
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      state.mode = EXLEN;
      /* falls through */
    case EXLEN:
      if (state.flags & 0x0400) {
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.length = hold;
        if (state.head) {
          state.head.extra_len = hold;
        }
        if (state.flags & 0x0200) {
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
      }
      else if (state.head) {
        state.head.extra = null/*Z_NULL*/;
      }
      state.mode = EXTRA;
      /* falls through */
    case EXTRA:
      if (state.flags & 0x0400) {
        copy = state.length;
        if (copy > have) { copy = have; }
        if (copy) {
          if (state.head) {
            len = state.head.extra_len - state.length;
            if (!state.head.extra) {
              // Use untyped array for more conveniend processing later
              state.head.extra = new Array(state.head.extra_len);
            }
            utils.arraySet(
              state.head.extra,
              input,
              next,
              // extra field is limited to 65536 bytes
              // - no need for additional size check
              copy,
              /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
              len
            );
            //zmemcpy(state.head.extra + len, next,
            //        len + copy > state.head.extra_max ?
            //        state.head.extra_max - len : copy);
          }
          if (state.flags & 0x0200) {
            state.check = crc32(state.check, input, copy, next);
          }
          have -= copy;
          next += copy;
          state.length -= copy;
        }
        if (state.length) { break inf_leave; }
      }
      state.length = 0;
      state.mode = NAME;
      /* falls through */
    case NAME:
      if (state.flags & 0x0800) {
        if (have === 0) { break inf_leave; }
        copy = 0;
        do {
          // TODO: 2 or 1 bytes?
          len = input[next + copy++];
          /* use constant limit because in js we should not preallocate memory */
          if (state.head && len &&
              (state.length < 65536 /*state.head.name_max*/)) {
            state.head.name += String.fromCharCode(len);
          }
        } while (len && copy < have);

        if (state.flags & 0x0200) {
          state.check = crc32(state.check, input, copy, next);
        }
        have -= copy;
        next += copy;
        if (len) { break inf_leave; }
      }
      else if (state.head) {
        state.head.name = null;
      }
      state.length = 0;
      state.mode = COMMENT;
      /* falls through */
    case COMMENT:
      if (state.flags & 0x1000) {
        if (have === 0) { break inf_leave; }
        copy = 0;
        do {
          len = input[next + copy++];
          /* use constant limit because in js we should not preallocate memory */
          if (state.head && len &&
              (state.length < 65536 /*state.head.comm_max*/)) {
            state.head.comment += String.fromCharCode(len);
          }
        } while (len && copy < have);
        if (state.flags & 0x0200) {
          state.check = crc32(state.check, input, copy, next);
        }
        have -= copy;
        next += copy;
        if (len) { break inf_leave; }
      }
      else if (state.head) {
        state.head.comment = null;
      }
      state.mode = HCRC;
      /* falls through */
    case HCRC:
      if (state.flags & 0x0200) {
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (hold !== (state.check & 0xffff)) {
          strm.msg = 'header crc mismatch';
          state.mode = BAD;
          break;
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
      }
      if (state.head) {
        state.head.hcrc = ((state.flags >> 9) & 1);
        state.head.done = true;
      }
      strm.adler = state.check = 0;
      state.mode = TYPE;
      break;
    case DICTID:
      //=== NEEDBITS(32); */
      while (bits < 32) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      strm.adler = state.check = zswap32(hold);
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      state.mode = DICT;
      /* falls through */
    case DICT:
      if (state.havedict === 0) {
        //--- RESTORE() ---
        strm.next_out = put;
        strm.avail_out = left;
        strm.next_in = next;
        strm.avail_in = have;
        state.hold = hold;
        state.bits = bits;
        //---
        return Z_NEED_DICT;
      }
      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
      state.mode = TYPE;
      /* falls through */
    case TYPE:
      if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
      /* falls through */
    case TYPEDO:
      if (state.last) {
        //--- BYTEBITS() ---//
        hold >>>= bits & 7;
        bits -= bits & 7;
        //---//
        state.mode = CHECK;
        break;
      }
      //=== NEEDBITS(3); */
      while (bits < 3) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      state.last = (hold & 0x01)/*BITS(1)*/;
      //--- DROPBITS(1) ---//
      hold >>>= 1;
      bits -= 1;
      //---//

      switch ((hold & 0x03)/*BITS(2)*/) {
      case 0:                             /* stored block */
        //Tracev((stderr, "inflate:     stored block%s\n",
        //        state.last ? " (last)" : ""));
        state.mode = STORED;
        break;
      case 1:                             /* fixed block */
        fixedtables(state);
        //Tracev((stderr, "inflate:     fixed codes block%s\n",
        //        state.last ? " (last)" : ""));
        state.mode = LEN_;             /* decode codes */
        if (flush === Z_TREES) {
          //--- DROPBITS(2) ---//
          hold >>>= 2;
          bits -= 2;
          //---//
          break inf_leave;
        }
        break;
      case 2:                             /* dynamic block */
        //Tracev((stderr, "inflate:     dynamic codes block%s\n",
        //        state.last ? " (last)" : ""));
        state.mode = TABLE;
        break;
      case 3:
        strm.msg = 'invalid block type';
        state.mode = BAD;
      }
      //--- DROPBITS(2) ---//
      hold >>>= 2;
      bits -= 2;
      //---//
      break;
    case STORED:
      //--- BYTEBITS() ---// /* go to byte boundary */
      hold >>>= bits & 7;
      bits -= bits & 7;
      //---//
      //=== NEEDBITS(32); */
      while (bits < 32) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
        strm.msg = 'invalid stored block lengths';
        state.mode = BAD;
        break;
      }
      state.length = hold & 0xffff;
      //Tracev((stderr, "inflate:       stored length %u\n",
      //        state.length));
      //=== INITBITS();
      hold = 0;
      bits = 0;
      //===//
      state.mode = COPY_;
      if (flush === Z_TREES) { break inf_leave; }
      /* falls through */
    case COPY_:
      state.mode = COPY;
      /* falls through */
    case COPY:
      copy = state.length;
      if (copy) {
        if (copy > have) { copy = have; }
        if (copy > left) { copy = left; }
        if (copy === 0) { break inf_leave; }
        //--- zmemcpy(put, next, copy); ---
        utils.arraySet(output, input, next, copy, put);
        //---//
        have -= copy;
        next += copy;
        left -= copy;
        put += copy;
        state.length -= copy;
        break;
      }
      //Tracev((stderr, "inflate:       stored end\n"));
      state.mode = TYPE;
      break;
    case TABLE:
      //=== NEEDBITS(14); */
      while (bits < 14) {
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
      }
      //===//
      state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
      //--- DROPBITS(5) ---//
      hold >>>= 5;
      bits -= 5;
      //---//
      state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
      //--- DROPBITS(5) ---//
      hold >>>= 5;
      bits -= 5;
      //---//
      state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
      //--- DROPBITS(4) ---//
      hold >>>= 4;
      bits -= 4;
      //---//
//#ifndef PKZIP_BUG_WORKAROUND
      if (state.nlen > 286 || state.ndist > 30) {
        strm.msg = 'too many length or distance symbols';
        state.mode = BAD;
        break;
      }
//#endif
      //Tracev((stderr, "inflate:       table sizes ok\n"));
      state.have = 0;
      state.mode = LENLENS;
      /* falls through */
    case LENLENS:
      while (state.have < state.ncode) {
        //=== NEEDBITS(3);
        while (bits < 3) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
        //--- DROPBITS(3) ---//
        hold >>>= 3;
        bits -= 3;
        //---//
      }
      while (state.have < 19) {
        state.lens[order[state.have++]] = 0;
      }
      // We have separate tables & no pointers. 2 commented lines below not needed.
      //state.next = state.codes;
      //state.lencode = state.next;
      // Switch to use dynamic table
      state.lencode = state.lendyn;
      state.lenbits = 7;

      opts = { bits: state.lenbits };
      ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
      state.lenbits = opts.bits;

      if (ret) {
        strm.msg = 'invalid code lengths set';
        state.mode = BAD;
        break;
      }
      //Tracev((stderr, "inflate:       code lengths ok\n"));
      state.have = 0;
      state.mode = CODELENS;
      /* falls through */
    case CODELENS:
      while (state.have < state.nlen + state.ndist) {
        for (;;) {
          here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if ((here_bits) <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        if (here_val < 16) {
          //--- DROPBITS(here.bits) ---//
          hold >>>= here_bits;
          bits -= here_bits;
          //---//
          state.lens[state.have++] = here_val;
        }
        else {
          if (here_val === 16) {
            //=== NEEDBITS(here.bits + 2);
            n = here_bits + 2;
            while (bits < n) {
              if (have === 0) { break inf_leave; }
              have--;
              hold += input[next++] << bits;
              bits += 8;
            }
            //===//
            //--- DROPBITS(here.bits) ---//
            hold >>>= here_bits;
            bits -= here_bits;
            //---//
            if (state.have === 0) {
              strm.msg = 'invalid bit length repeat';
              state.mode = BAD;
              break;
            }
            len = state.lens[state.have - 1];
            copy = 3 + (hold & 0x03);//BITS(2);
            //--- DROPBITS(2) ---//
            hold >>>= 2;
            bits -= 2;
            //---//
          }
          else if (here_val === 17) {
            //=== NEEDBITS(here.bits + 3);
            n = here_bits + 3;
            while (bits < n) {
              if (have === 0) { break inf_leave; }
              have--;
              hold += input[next++] << bits;
              bits += 8;
            }
            //===//
            //--- DROPBITS(here.bits) ---//
            hold >>>= here_bits;
            bits -= here_bits;
            //---//
            len = 0;
            copy = 3 + (hold & 0x07);//BITS(3);
            //--- DROPBITS(3) ---//
            hold >>>= 3;
            bits -= 3;
            //---//
          }
          else {
            //=== NEEDBITS(here.bits + 7);
            n = here_bits + 7;
            while (bits < n) {
              if (have === 0) { break inf_leave; }
              have--;
              hold += input[next++] << bits;
              bits += 8;
            }
            //===//
            //--- DROPBITS(here.bits) ---//
            hold >>>= here_bits;
            bits -= here_bits;
            //---//
            len = 0;
            copy = 11 + (hold & 0x7f);//BITS(7);
            //--- DROPBITS(7) ---//
            hold >>>= 7;
            bits -= 7;
            //---//
          }
          if (state.have + copy > state.nlen + state.ndist) {
            strm.msg = 'invalid bit length repeat';
            state.mode = BAD;
            break;
          }
          while (copy--) {
            state.lens[state.have++] = len;
          }
        }
      }

      /* handle error breaks in while */
      if (state.mode === BAD) { break; }

      /* check for end-of-block code (better have one) */
      if (state.lens[256] === 0) {
        strm.msg = 'invalid code -- missing end-of-block';
        state.mode = BAD;
        break;
      }

      /* build code tables -- note: do not change the lenbits or distbits
         values here (9 and 6) without reading the comments in inftrees.h
         concerning the ENOUGH constants, which depend on those values */
      state.lenbits = 9;

      opts = { bits: state.lenbits };
      ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
      // We have separate tables & no pointers. 2 commented lines below not needed.
      // state.next_index = opts.table_index;
      state.lenbits = opts.bits;
      // state.lencode = state.next;

      if (ret) {
        strm.msg = 'invalid literal/lengths set';
        state.mode = BAD;
        break;
      }

      state.distbits = 6;
      //state.distcode.copy(state.codes);
      // Switch to use dynamic table
      state.distcode = state.distdyn;
      opts = { bits: state.distbits };
      ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
      // We have separate tables & no pointers. 2 commented lines below not needed.
      // state.next_index = opts.table_index;
      state.distbits = opts.bits;
      // state.distcode = state.next;

      if (ret) {
        strm.msg = 'invalid distances set';
        state.mode = BAD;
        break;
      }
      //Tracev((stderr, 'inflate:       codes ok\n'));
      state.mode = LEN_;
      if (flush === Z_TREES) { break inf_leave; }
      /* falls through */
    case LEN_:
      state.mode = LEN;
      /* falls through */
    case LEN:
      if (have >= 6 && left >= 258) {
        //--- RESTORE() ---
        strm.next_out = put;
        strm.avail_out = left;
        strm.next_in = next;
        strm.avail_in = have;
        state.hold = hold;
        state.bits = bits;
        //---
        inflate_fast(strm, _out);
        //--- LOAD() ---
        put = strm.next_out;
        output = strm.output;
        left = strm.avail_out;
        next = strm.next_in;
        input = strm.input;
        have = strm.avail_in;
        hold = state.hold;
        bits = state.bits;
        //---

        if (state.mode === TYPE) {
          state.back = -1;
        }
        break;
      }
      state.back = 0;
      for (;;) {
        here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/
        here_bits = here >>> 24;
        here_op = (here >>> 16) & 0xff;
        here_val = here & 0xffff;

        if (here_bits <= bits) { break; }
        //--- PULLBYTE() ---//
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
        //---//
      }
      if (here_op && (here_op & 0xf0) === 0) {
        last_bits = here_bits;
        last_op = here_op;
        last_val = here_val;
        for (;;) {
          here = state.lencode[last_val +
                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if ((last_bits + here_bits) <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        //--- DROPBITS(last.bits) ---//
        hold >>>= last_bits;
        bits -= last_bits;
        //---//
        state.back += last_bits;
      }
      //--- DROPBITS(here.bits) ---//
      hold >>>= here_bits;
      bits -= here_bits;
      //---//
      state.back += here_bits;
      state.length = here_val;
      if (here_op === 0) {
        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
        //        "inflate:         literal '%c'\n" :
        //        "inflate:         literal 0x%02x\n", here.val));
        state.mode = LIT;
        break;
      }
      if (here_op & 32) {
        //Tracevv((stderr, "inflate:         end of block\n"));
        state.back = -1;
        state.mode = TYPE;
        break;
      }
      if (here_op & 64) {
        strm.msg = 'invalid literal/length code';
        state.mode = BAD;
        break;
      }
      state.extra = here_op & 15;
      state.mode = LENEXT;
      /* falls through */
    case LENEXT:
      if (state.extra) {
        //=== NEEDBITS(state.extra);
        n = state.extra;
        while (bits < n) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
        //--- DROPBITS(state.extra) ---//
        hold >>>= state.extra;
        bits -= state.extra;
        //---//
        state.back += state.extra;
      }
      //Tracevv((stderr, "inflate:         length %u\n", state.length));
      state.was = state.length;
      state.mode = DIST;
      /* falls through */
    case DIST:
      for (;;) {
        here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
        here_bits = here >>> 24;
        here_op = (here >>> 16) & 0xff;
        here_val = here & 0xffff;

        if ((here_bits) <= bits) { break; }
        //--- PULLBYTE() ---//
        if (have === 0) { break inf_leave; }
        have--;
        hold += input[next++] << bits;
        bits += 8;
        //---//
      }
      if ((here_op & 0xf0) === 0) {
        last_bits = here_bits;
        last_op = here_op;
        last_val = here_val;
        for (;;) {
          here = state.distcode[last_val +
                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if ((last_bits + here_bits) <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        //--- DROPBITS(last.bits) ---//
        hold >>>= last_bits;
        bits -= last_bits;
        //---//
        state.back += last_bits;
      }
      //--- DROPBITS(here.bits) ---//
      hold >>>= here_bits;
      bits -= here_bits;
      //---//
      state.back += here_bits;
      if (here_op & 64) {
        strm.msg = 'invalid distance code';
        state.mode = BAD;
        break;
      }
      state.offset = here_val;
      state.extra = (here_op) & 15;
      state.mode = DISTEXT;
      /* falls through */
    case DISTEXT:
      if (state.extra) {
        //=== NEEDBITS(state.extra);
        n = state.extra;
        while (bits < n) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
        //--- DROPBITS(state.extra) ---//
        hold >>>= state.extra;
        bits -= state.extra;
        //---//
        state.back += state.extra;
      }
//#ifdef INFLATE_STRICT
      if (state.offset > state.dmax) {
        strm.msg = 'invalid distance too far back';
        state.mode = BAD;
        break;
      }
//#endif
      //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
      state.mode = MATCH;
      /* falls through */
    case MATCH:
      if (left === 0) { break inf_leave; }
      copy = _out - left;
      if (state.offset > copy) {         /* copy from window */
        copy = state.offset - copy;
        if (copy > state.whave) {
          if (state.sane) {
            strm.msg = 'invalid distance too far back';
            state.mode = BAD;
            break;
          }
// (!) This block is disabled in zlib defailts,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//          Trace((stderr, "inflate.c too far\n"));
//          copy -= state.whave;
//          if (copy > state.length) { copy = state.length; }
//          if (copy > left) { copy = left; }
//          left -= copy;
//          state.length -= copy;
//          do {
//            output[put++] = 0;
//          } while (--copy);
//          if (state.length === 0) { state.mode = LEN; }
//          break;
//#endif
        }
        if (copy > state.wnext) {
          copy -= state.wnext;
          from = state.wsize - copy;
        }
        else {
          from = state.wnext - copy;
        }
        if (copy > state.length) { copy = state.length; }
        from_source = state.window;
      }
      else {                              /* copy from output */
        from_source = output;
        from = put - state.offset;
        copy = state.length;
      }
      if (copy > left) { copy = left; }
      left -= copy;
      state.length -= copy;
      do {
        output[put++] = from_source[from++];
      } while (--copy);
      if (state.length === 0) { state.mode = LEN; }
      break;
    case LIT:
      if (left === 0) { break inf_leave; }
      output[put++] = state.length;
      left--;
      state.mode = LEN;
      break;
    case CHECK:
      if (state.wrap) {
        //=== NEEDBITS(32);
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          // Use '|' insdead of '+' to make sure that result is signed
          hold |= input[next++] << bits;
          bits += 8;
        }
        //===//
        _out -= left;
        strm.total_out += _out;
        state.total += _out;
        if (_out) {
          strm.adler = state.check =
              /*UPDATE(state.check, put - _out, _out);*/
              (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));

        }
        _out = left;
        // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
        if ((state.flags ? hold : zswap32(hold)) !== state.check) {
          strm.msg = 'incorrect data check';
          state.mode = BAD;
          break;
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        //Tracev((stderr, "inflate:   check matches trailer\n"));
      }
      state.mode = LENGTH;
      /* falls through */
    case LENGTH:
      if (state.wrap && state.flags) {
        //=== NEEDBITS(32);
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (hold !== (state.total & 0xffffffff)) {
          strm.msg = 'incorrect length check';
          state.mode = BAD;
          break;
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        //Tracev((stderr, "inflate:   length matches trailer\n"));
      }
      state.mode = DONE;
      /* falls through */
    case DONE:
      ret = Z_STREAM_END;
      break inf_leave;
    case BAD:
      ret = Z_DATA_ERROR;
      break inf_leave;
    case MEM:
      return Z_MEM_ERROR;
    case SYNC:
      /* falls through */
    default:
      return Z_STREAM_ERROR;
    }
  }

  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"

  /*
     Return from inflate(), updating the total counts and the check value.
     If there was no progress during the inflate() call, return a buffer
     error.  Call updatewindow() to create and/or update the window state.
     Note: a memory error from inflate() is non-recoverable.
   */

  //--- RESTORE() ---
  strm.next_out = put;
  strm.avail_out = left;
  strm.next_in = next;
  strm.avail_in = have;
  state.hold = hold;
  state.bits = bits;
  //---

  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
                      (state.mode < CHECK || flush !== Z_FINISH))) {
    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
      state.mode = MEM;
      return Z_MEM_ERROR;
    }
  }
  _in -= strm.avail_in;
  _out -= strm.avail_out;
  strm.total_in += _in;
  strm.total_out += _out;
  state.total += _out;
  if (state.wrap && _out) {
    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
  }
  strm.data_type = state.bits + (state.last ? 64 : 0) +
                    (state.mode === TYPE ? 128 : 0) +
                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
    ret = Z_BUF_ERROR;
  }
  return ret;
}

function inflateEnd(strm) {

  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
    return Z_STREAM_ERROR;
  }

  var state = strm.state;
  if (state.window) {
    state.window = null;
  }
  strm.state = null;
  return Z_OK;
}

function inflateGetHeader(strm, head) {
  var state;

  /* check state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }

  /* save header structure */
  state.head = head;
  head.done = false;
  return Z_OK;
}

function inflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var state;
  var dictid;
  var ret;

  /* check state */
  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }
  state = strm.state;

  if (state.wrap !== 0 && state.mode !== DICT) {
    return Z_STREAM_ERROR;
  }

  /* check for correct dictionary identifier */
  if (state.mode === DICT) {
    dictid = 1; /* adler32(0, null, 0)*/
    /* dictid = adler32(dictid, dictionary, dictLength); */
    dictid = adler32(dictid, dictionary, dictLength, 0);
    if (dictid !== state.check) {
      return Z_DATA_ERROR;
    }
  }
  /* copy dictionary to window using updatewindow(), which will amend the
   existing dictionary if appropriate */
  ret = updatewindow(strm, dictionary, dictLength, dictLength);
  if (ret) {
    state.mode = MEM;
    return Z_MEM_ERROR;
  }
  state.havedict = 1;
  // Tracev((stderr, "inflate:   dictionary set\n"));
  return Z_OK;
}

exports.inflateReset = inflateReset;
exports.inflateReset2 = inflateReset2;
exports.inflateResetKeep = inflateResetKeep;
exports.inflateInit = inflateInit;
exports.inflateInit2 = inflateInit2;
exports.inflate = inflate;
exports.inflateEnd = inflateEnd;
exports.inflateGetHeader = inflateGetHeader;
exports.inflateSetDictionary = inflateSetDictionary;
exports.inflateInfo = 'pako inflate (from Nodeca project)';

/* Not implemented
exports.inflateCopy = inflateCopy;
exports.inflateGetDictionary = inflateGetDictionary;
exports.inflateMark = inflateMark;
exports.inflatePrime = inflatePrime;
exports.inflateSync = inflateSync;
exports.inflateSyncPoint = inflateSyncPoint;
exports.inflateUndermine = inflateUndermine;
*/


/***/ }),
/* 34 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// See state defs from inflate.js
var BAD = 30;       /* got a data error -- remain here until reset */
var TYPE = 12;      /* i: waiting for type bits, including last-flag bit */

/*
   Decode literal, length, and distance codes and write out the resulting
   literal and match bytes until either not enough input or output is
   available, an end-of-block is encountered, or a data error is encountered.
   When large enough input and output buffers are supplied to inflate(), for
   example, a 16K input buffer and a 64K output buffer, more than 95% of the
   inflate execution time is spent in this routine.

   Entry assumptions:

        state.mode === LEN
        strm.avail_in >= 6
        strm.avail_out >= 258
        start >= strm.avail_out
        state.bits < 8

   On return, state.mode is one of:

        LEN -- ran out of enough output space or enough available input
        TYPE -- reached end of block code, inflate() to interpret next block
        BAD -- error in block data

   Notes:

    - The maximum input bits used by a length/distance pair is 15 bits for the
      length code, 5 bits for the length extra, 15 bits for the distance code,
      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
      Therefore if strm.avail_in >= 6, then there is enough input to avoid
      checking for available input while decoding.

    - The maximum bytes that a single length/distance pair can output is 258
      bytes, which is the maximum length that can be coded.  inflate_fast()
      requires strm.avail_out >= 258 for each loop to avoid checking for
      output space.
 */
module.exports = function inflate_fast(strm, start) {
  var state;
  var _in;                    /* local strm.input */
  var last;                   /* have enough input while in < last */
  var _out;                   /* local strm.output */
  var beg;                    /* inflate()'s initial strm.output */
  var end;                    /* while out < end, enough space available */
//#ifdef INFLATE_STRICT
  var dmax;                   /* maximum distance from zlib header */
//#endif
  var wsize;                  /* window size or zero if not using window */
  var whave;                  /* valid bytes in the window */
  var wnext;                  /* window write index */
  // Use `s_window` instead `window`, avoid conflict with instrumentation tools
  var s_window;               /* allocated sliding window, if wsize != 0 */
  var hold;                   /* local strm.hold */
  var bits;                   /* local strm.bits */
  var lcode;                  /* local strm.lencode */
  var dcode;                  /* local strm.distcode */
  var lmask;                  /* mask for first level of length codes */
  var dmask;                  /* mask for first level of distance codes */
  var here;                   /* retrieved table entry */
  var op;                     /* code bits, operation, extra bits, or */
                              /*  window position, window bytes to copy */
  var len;                    /* match length, unused bytes */
  var dist;                   /* match distance */
  var from;                   /* where to copy match from */
  var from_source;


  var input, output; // JS specific, because we have no pointers

  /* copy state to local variables */
  state = strm.state;
  //here = state.here;
  _in = strm.next_in;
  input = strm.input;
  last = _in + (strm.avail_in - 5);
  _out = strm.next_out;
  output = strm.output;
  beg = _out - (start - strm.avail_out);
  end = _out + (strm.avail_out - 257);
//#ifdef INFLATE_STRICT
  dmax = state.dmax;
//#endif
  wsize = state.wsize;
  whave = state.whave;
  wnext = state.wnext;
  s_window = state.window;
  hold = state.hold;
  bits = state.bits;
  lcode = state.lencode;
  dcode = state.distcode;
  lmask = (1 << state.lenbits) - 1;
  dmask = (1 << state.distbits) - 1;


  /* decode literals and length/distances until end-of-block or not enough
     input data or output space */

  top:
  do {
    if (bits < 15) {
      hold += input[_in++] << bits;
      bits += 8;
      hold += input[_in++] << bits;
      bits += 8;
    }

    here = lcode[hold & lmask];

    dolen:
    for (;;) { // Goto emulation
      op = here >>> 24/*here.bits*/;
      hold >>>= op;
      bits -= op;
      op = (here >>> 16) & 0xff/*here.op*/;
      if (op === 0) {                          /* literal */
        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
        //        "inflate:         literal '%c'\n" :
        //        "inflate:         literal 0x%02x\n", here.val));
        output[_out++] = here & 0xffff/*here.val*/;
      }
      else if (op & 16) {                     /* length base */
        len = here & 0xffff/*here.val*/;
        op &= 15;                           /* number of extra bits */
        if (op) {
          if (bits < op) {
            hold += input[_in++] << bits;
            bits += 8;
          }
          len += hold & ((1 << op) - 1);
          hold >>>= op;
          bits -= op;
        }
        //Tracevv((stderr, "inflate:         length %u\n", len));
        if (bits < 15) {
          hold += input[_in++] << bits;
          bits += 8;
          hold += input[_in++] << bits;
          bits += 8;
        }
        here = dcode[hold & dmask];

        dodist:
        for (;;) { // goto emulation
          op = here >>> 24/*here.bits*/;
          hold >>>= op;
          bits -= op;
          op = (here >>> 16) & 0xff/*here.op*/;

          if (op & 16) {                      /* distance base */
            dist = here & 0xffff/*here.val*/;
            op &= 15;                       /* number of extra bits */
            if (bits < op) {
              hold += input[_in++] << bits;
              bits += 8;
              if (bits < op) {
                hold += input[_in++] << bits;
                bits += 8;
              }
            }
            dist += hold & ((1 << op) - 1);
//#ifdef INFLATE_STRICT
            if (dist > dmax) {
              strm.msg = 'invalid distance too far back';
              state.mode = BAD;
              break top;
            }
//#endif
            hold >>>= op;
            bits -= op;
            //Tracevv((stderr, "inflate:         distance %u\n", dist));
            op = _out - beg;                /* max distance in output */
            if (dist > op) {                /* see if copy from window */
              op = dist - op;               /* distance back in window */
              if (op > whave) {
                if (state.sane) {
                  strm.msg = 'invalid distance too far back';
                  state.mode = BAD;
                  break top;
                }

// (!) This block is disabled in zlib defailts,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//                if (len <= op - whave) {
//                  do {
//                    output[_out++] = 0;
//                  } while (--len);
//                  continue top;
//                }
//                len -= op - whave;
//                do {
//                  output[_out++] = 0;
//                } while (--op > whave);
//                if (op === 0) {
//                  from = _out - dist;
//                  do {
//                    output[_out++] = output[from++];
//                  } while (--len);
//                  continue top;
//                }
//#endif
              }
              from = 0; // window index
              from_source = s_window;
              if (wnext === 0) {           /* very common case */
                from += wsize - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              else if (wnext < op) {      /* wrap around window */
                from += wsize + wnext - op;
                op -= wnext;
                if (op < len) {         /* some from end of window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = 0;
                  if (wnext < len) {  /* some from start of window */
                    op = wnext;
                    len -= op;
                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);
                    from = _out - dist;      /* rest from output */
                    from_source = output;
                  }
                }
              }
              else {                      /* contiguous in window */
                from += wnext - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              while (len > 2) {
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                len -= 3;
              }
              if (len) {
                output[_out++] = from_source[from++];
                if (len > 1) {
                  output[_out++] = from_source[from++];
                }
              }
            }
            else {
              from = _out - dist;          /* copy direct from output */
              do {                        /* minimum length is three */
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                len -= 3;
              } while (len > 2);
              if (len) {
                output[_out++] = output[from++];
                if (len > 1) {
                  output[_out++] = output[from++];
                }
              }
            }
          }
          else if ((op & 64) === 0) {          /* 2nd level distance code */
            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
            continue dodist;
          }
          else {
            strm.msg = 'invalid distance code';
            state.mode = BAD;
            break top;
          }

          break; // need to emulate goto via "continue"
        }
      }
      else if ((op & 64) === 0) {              /* 2nd level length code */
        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
        continue dolen;
      }
      else if (op & 32) {                     /* end-of-block */
        //Tracevv((stderr, "inflate:         end of block\n"));
        state.mode = TYPE;
        break top;
      }
      else {
        strm.msg = 'invalid literal/length code';
        state.mode = BAD;
        break top;
      }

      break; // need to emulate goto via "continue"
    }
  } while (_in < last && _out < end);

  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
  len = bits >> 3;
  _in -= len;
  bits -= len << 3;
  hold &= (1 << bits) - 1;

  /* update state and return */
  strm.next_in = _in;
  strm.next_out = _out;
  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
  state.hold = hold;
  state.bits = bits;
  return;
};


/***/ }),
/* 35 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = __webpack_require__(1);

var MAXBITS = 15;
var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);

var CODES = 0;
var LENS = 1;
var DISTS = 2;

var lbase = [ /* Length codes 257..285 base */
  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
];

var lext = [ /* Length codes 257..285 extra */
  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
];

var dbase = [ /* Distance codes 0..29 base */
  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
  8193, 12289, 16385, 24577, 0, 0
];

var dext = [ /* Distance codes 0..29 extra */
  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
  28, 28, 29, 29, 64, 64
];

module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
{
  var bits = opts.bits;
      //here = opts.here; /* table entry for duplication */

  var len = 0;               /* a code's length in bits */
  var sym = 0;               /* index of code symbols */
  var min = 0, max = 0;          /* minimum and maximum code lengths */
  var root = 0;              /* number of index bits for root table */
  var curr = 0;              /* number of index bits for current table */
  var drop = 0;              /* code bits to drop for sub-table */
  var left = 0;                   /* number of prefix codes available */
  var used = 0;              /* code entries in table used */
  var huff = 0;              /* Huffman code */
  var incr;              /* for incrementing code, index */
  var fill;              /* index for replicating entries */
  var low;               /* low bits for current root entry */
  var mask;              /* mask for low root bits */
  var next;             /* next available space in table */
  var base = null;     /* base value table to use */
  var base_index = 0;
//  var shoextra;    /* extra bits table to use */
  var end;                    /* use base and extra for symbol > end */
  var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */
  var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */
  var extra = null;
  var extra_index = 0;

  var here_bits, here_op, here_val;

  /*
   Process a set of code lengths to create a canonical Huffman code.  The
   code lengths are lens[0..codes-1].  Each length corresponds to the
   symbols 0..codes-1.  The Huffman code is generated by first sorting the
   symbols by length from short to long, and retaining the symbol order
   for codes with equal lengths.  Then the code starts with all zero bits
   for the first code of the shortest length, and the codes are integer
   increments for the same length, and zeros are appended as the length
   increases.  For the deflate format, these bits are stored backwards
   from their more natural integer increment ordering, and so when the
   decoding tables are built in the large loop below, the integer codes
   are incremented backwards.

   This routine assumes, but does not check, that all of the entries in
   lens[] are in the range 0..MAXBITS.  The caller must assure this.
   1..MAXBITS is interpreted as that code length.  zero means that that
   symbol does not occur in this code.

   The codes are sorted by computing a count of codes for each length,
   creating from that a table of starting indices for each length in the
   sorted table, and then entering the symbols in order in the sorted
   table.  The sorted table is work[], with that space being provided by
   the caller.

   The length counts are used for other purposes as well, i.e. finding
   the minimum and maximum length codes, determining if there are any
   codes at all, checking for a valid set of lengths, and looking ahead
   at length counts to determine sub-table sizes when building the
   decoding tables.
   */

  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
  for (len = 0; len <= MAXBITS; len++) {
    count[len] = 0;
  }
  for (sym = 0; sym < codes; sym++) {
    count[lens[lens_index + sym]]++;
  }

  /* bound code lengths, force root to be within code lengths */
  root = bits;
  for (max = MAXBITS; max >= 1; max--) {
    if (count[max] !== 0) { break; }
  }
  if (root > max) {
    root = max;
  }
  if (max === 0) {                     /* no symbols to code at all */
    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;


    //table.op[opts.table_index] = 64;
    //table.bits[opts.table_index] = 1;
    //table.val[opts.table_index++] = 0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;

    opts.bits = 1;
    return 0;     /* no symbols, but wait for decoding to report error */
  }
  for (min = 1; min < max; min++) {
    if (count[min] !== 0) { break; }
  }
  if (root < min) {
    root = min;
  }

  /* check for an over-subscribed or incomplete set of lengths */
  left = 1;
  for (len = 1; len <= MAXBITS; len++) {
    left <<= 1;
    left -= count[len];
    if (left < 0) {
      return -1;
    }        /* over-subscribed */
  }
  if (left > 0 && (type === CODES || max !== 1)) {
    return -1;                      /* incomplete set */
  }

  /* generate offsets into symbol table for each length for sorting */
  offs[1] = 0;
  for (len = 1; len < MAXBITS; len++) {
    offs[len + 1] = offs[len] + count[len];
  }

  /* sort symbols by length, by symbol order within each length */
  for (sym = 0; sym < codes; sym++) {
    if (lens[lens_index + sym] !== 0) {
      work[offs[lens[lens_index + sym]]++] = sym;
    }
  }

  /*
   Create and fill in decoding tables.  In this loop, the table being
   filled is at next and has curr index bits.  The code being used is huff
   with length len.  That code is converted to an index by dropping drop
   bits off of the bottom.  For codes where len is less than drop + curr,
   those top drop + curr - len bits are incremented through all values to
   fill the table with replicated entries.

   root is the number of index bits for the root table.  When len exceeds
   root, sub-tables are created pointed to by the root entry with an index
   of the low root bits of huff.  This is saved in low to check for when a
   new sub-table should be started.  drop is zero when the root table is
   being filled, and drop is root when sub-tables are being filled.

   When a new sub-table is needed, it is necessary to look ahead in the
   code lengths to determine what size sub-table is needed.  The length
   counts are used for this, and so count[] is decremented as codes are
   entered in the tables.

   used keeps track of how many table entries have been allocated from the
   provided *table space.  It is checked for LENS and DIST tables against
   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
   the initial root table size constants.  See the comments in inftrees.h
   for more information.

   sym increments through all symbols, and the loop terminates when
   all codes of length max, i.e. all codes, have been processed.  This
   routine permits incomplete codes, so another loop after this one fills
   in the rest of the decoding tables with invalid code markers.
   */

  /* set up for code type */
  // poor man optimization - use if-else instead of switch,
  // to avoid deopts in old v8
  if (type === CODES) {
    base = extra = work;    /* dummy value--not used */
    end = 19;

  } else if (type === LENS) {
    base = lbase;
    base_index -= 257;
    extra = lext;
    extra_index -= 257;
    end = 256;

  } else {                    /* DISTS */
    base = dbase;
    extra = dext;
    end = -1;
  }

  /* initialize opts for loop */
  huff = 0;                   /* starting code */
  sym = 0;                    /* starting code symbol */
  len = min;                  /* starting code length */
  next = table_index;              /* current table to fill in */
  curr = root;                /* current table index bits */
  drop = 0;                   /* current bits to drop from code for index */
  low = -1;                   /* trigger new sub-table when len > root */
  used = 1 << root;          /* use root table entries */
  mask = used - 1;            /* mask for comparing low */

  /* check available table space */
  if ((type === LENS && used > ENOUGH_LENS) ||
    (type === DISTS && used > ENOUGH_DISTS)) {
    return 1;
  }

  /* process all codes and make table entries */
  for (;;) {
    /* create table entry */
    here_bits = len - drop;
    if (work[sym] < end) {
      here_op = 0;
      here_val = work[sym];
    }
    else if (work[sym] > end) {
      here_op = extra[extra_index + work[sym]];
      here_val = base[base_index + work[sym]];
    }
    else {
      here_op = 32 + 64;         /* end of block */
      here_val = 0;
    }

    /* replicate for those indices with low len bits equal to huff */
    incr = 1 << (len - drop);
    fill = 1 << curr;
    min = fill;                 /* save offset to next table */
    do {
      fill -= incr;
      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
    } while (fill !== 0);

    /* backwards increment the len-bit code huff */
    incr = 1 << (len - 1);
    while (huff & incr) {
      incr >>= 1;
    }
    if (incr !== 0) {
      huff &= incr - 1;
      huff += incr;
    } else {
      huff = 0;
    }

    /* go to next symbol, update count, len */
    sym++;
    if (--count[len] === 0) {
      if (len === max) { break; }
      len = lens[lens_index + work[sym]];
    }

    /* create new sub-table if needed */
    if (len > root && (huff & mask) !== low) {
      /* if first time, transition to sub-tables */
      if (drop === 0) {
        drop = root;
      }

      /* increment past last table */
      next += min;            /* here min is 1 << curr */

      /* determine length of next table */
      curr = len - drop;
      left = 1 << curr;
      while (curr + drop < max) {
        left -= count[curr + drop];
        if (left <= 0) { break; }
        curr++;
        left <<= 1;
      }

      /* check for enough space */
      used += 1 << curr;
      if ((type === LENS && used > ENOUGH_LENS) ||
        (type === DISTS && used > ENOUGH_DISTS)) {
        return 1;
      }

      /* point entry in root table to sub-table */
      low = huff & mask;
      /*table.op[low] = curr;
      table.bits[low] = root;
      table.val[low] = next - opts.table_index;*/
      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
    }
  }

  /* fill in remaining table entry if code is incomplete (guaranteed to have
   at most one remaining entry, since if the code is incomplete, the
   maximum code length that was allowed to get this far is one bit) */
  if (huff !== 0) {
    //table.op[next + huff] = 64;            /* invalid code marker */
    //table.bits[next + huff] = len - drop;
    //table.val[next + huff] = 0;
    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
  }

  /* set return parameters */
  //opts.table_index += used;
  opts.bits = root;
  return 0;
};


/***/ }),
/* 36 */
/***/ (function(module, exports, __webpack_require__) {




// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function GZheader() {
  /* true if compressed data believed to be text */
  this.text       = 0;
  /* modification time */
  this.time       = 0;
  /* extra flags (not used when writing a gzip file) */
  this.xflags     = 0;
  /* operating system */
  this.os         = 0;
  /* pointer to extra field or Z_NULL if none */
  this.extra      = null;
  /* extra field length (valid if extra != Z_NULL) */
  this.extra_len  = 0; // Actually, we don't need it in JS,
                       // but leave for few code modifications

  //
  // Setup limits is not necessary because in js we should not preallocate memory
  // for inflate use constant limit in 65536 bytes
  //

  /* space at extra (only when reading header) */
  // this.extra_max  = 0;
  /* pointer to zero-terminated file name or Z_NULL */
  this.name       = '';
  /* space at name (only when reading header) */
  // this.name_max   = 0;
  /* pointer to zero-terminated comment or Z_NULL */
  this.comment    = '';
  /* space at comment (only when reading header) */
  // this.comm_max   = 0;
  /* true if there was or will be a header crc */
  this.hcrc       = 0;
  /* true when done reading gzip header (not used when writing a gzip file) */
  this.done       = false;
}

module.exports = GZheader;


/***/ }),
/* 37 */
/***/ (function(module, exports, __webpack_require__) {




var utils = __webpack_require__(0);

var table = [
    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
    0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
    0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
    0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
    0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
    0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
    0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
    0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
    0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
    0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
    0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
    0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
    0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
    0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
    0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
    0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
    0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
    0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
    0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
    0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
    0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
    0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
    0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
    0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
    0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
    0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
    0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
    0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
    0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
    0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
    0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
    0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
    0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
];

/**
 *
 *  Javascript crc32
 *  http://www.webtoolkit.info/
 *
 */
module.exports = function crc32(input, crc) {
    if (typeof input === "undefined" || !input.length) {
        return 0;
    }

    var isArray = utils.getTypeOf(input) !== "string";

    if (typeof(crc) == "undefined") {
        crc = 0;
    }
    var x = 0;
    var y = 0;
    var b = 0;

    crc = crc ^ (-1);
    for (var i = 0, iTop = input.length; i < iTop; i++) {
        b = isArray ? input[i] : input.charCodeAt(i);
        y = (crc ^ b) & 0xFF;
        x = table[y];
        crc = (crc >>> 8) ^ x;
    }

    return crc ^ (-1);
};
// vim: set shiftwidth=4 softtabstop=4:


/***/ }),
/* 38 */
/***/ (function(module, exports, __webpack_require__) {




var utils = __webpack_require__(0);

/**
 * An object to write any content to a string.
 * @constructor
 */
var StringWriter = function() {
    this.data = [];
};
StringWriter.prototype = {
    /**
     * Append any content to the current string.
     * @param {Object} input the content to add.
     */
    append: function(input) {
        input = utils.transformTo("string", input);
        this.data.push(input);
    },
    /**
     * Finalize the construction an return the result.
     * @return {string} the generated string.
     */
    finalize: function() {
        return this.data.join("");
    }
};

module.exports = StringWriter;


/***/ }),
/* 39 */
/***/ (function(module, exports, __webpack_require__) {




var utils = __webpack_require__(0);

/**
 * An object to write any content to an Uint8Array.
 * @constructor
 * @param {number} length The length of the array.
 */
var Uint8ArrayWriter = function(length) {
    this.data = new Uint8Array(length);
    this.index = 0;
};
Uint8ArrayWriter.prototype = {
    /**
     * Append any content to the current array.
     * @param {Object} input the content to add.
     */
    append: function(input) {
        if (input.length !== 0) {
            // with an empty Uint8Array, Opera fails with a "Offset larger than array size"
            input = utils.transformTo("uint8array", input);
            this.data.set(input, this.index);
            this.index += input.length;
        }
    },
    /**
     * Finalize the construction an return the result.
     * @return {Uint8Array} the generated array.
     */
    finalize: function() {
        return this.data;
    }
};

module.exports = Uint8ArrayWriter;


/***/ }),
/* 40 */
/***/ (function(module, exports, __webpack_require__) {



var base64 = __webpack_require__(3);
var utf8 = __webpack_require__(17);
var utils = __webpack_require__(0);
var ZipEntries = __webpack_require__(41);
module.exports = function(data, options) {
    var files, zipEntries, i, input;
    options = utils.extend(options || {}, {
        base64: false,
        checkCRC32: false,
        optimizedBinaryString : false,
        createFolders: false,
        decodeFileName: utf8.utf8decode
    });
    if (options.base64) {
        data = base64.decode(data);
    }

    zipEntries = new ZipEntries(data, options);
    files = zipEntries.files;
    for (i = 0; i < files.length; i++) {
        input = files[i];
        this.file(input.fileNameStr, input.decompressed, {
            binary: true,
            optimizedBinaryString: true,
            date: input.date,
            dir: input.dir,
            comment : input.fileCommentStr.length ? input.fileCommentStr : null,
            unixPermissions : input.unixPermissions,
            dosPermissions : input.dosPermissions,
            createFolders: options.createFolders
        });
    }
    if (zipEntries.zipComment.length) {
        this.comment = zipEntries.zipComment;
    }

    return this;
};


/***/ }),
/* 41 */
/***/ (function(module, exports, __webpack_require__) {



var StringReader = __webpack_require__(18);
var NodeBufferReader = __webpack_require__(42);
var Uint8ArrayReader = __webpack_require__(20);
var ArrayReader = __webpack_require__(21);
var utils = __webpack_require__(0);
var sig = __webpack_require__(14);
var ZipEntry = __webpack_require__(43);
var support = __webpack_require__(2);
var jszipProto = __webpack_require__(4);
//  class ZipEntries {{{
/**
 * All the entries in the zip file.
 * @constructor
 * @param {String|ArrayBuffer|Uint8Array} data the binary stream to load.
 * @param {Object} loadOptions Options for loading the stream.
 */
function ZipEntries(data, loadOptions) {
    this.files = [];
    this.loadOptions = loadOptions;
    if (data) {
        this.load(data);
    }
}
ZipEntries.prototype = {
    /**
     * Check that the reader is on the speficied signature.
     * @param {string} expectedSignature the expected signature.
     * @throws {Error} if it is an other signature.
     */
    checkSignature: function(expectedSignature) {
        var signature = this.reader.readString(4);
        if (signature !== expectedSignature) {
            throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")");
        }
    },
    /**
     * Check if the given signature is at the given index.
     * @param {number} askedIndex the index to check.
     * @param {string} expectedSignature the signature to expect.
     * @return {boolean} true if the signature is here, false otherwise.
     */
    isSignature: function(askedIndex, expectedSignature) {
        var currentIndex = this.reader.index;
        this.reader.setIndex(askedIndex);
        var signature = this.reader.readString(4);
        var result = signature === expectedSignature;
        this.reader.setIndex(currentIndex);
        return result;
    },
    /**
     * Read the end of the central directory.
     */
    readBlockEndOfCentral: function() {
        this.diskNumber = this.reader.readInt(2);
        this.diskWithCentralDirStart = this.reader.readInt(2);
        this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
        this.centralDirRecords = this.reader.readInt(2);
        this.centralDirSize = this.reader.readInt(4);
        this.centralDirOffset = this.reader.readInt(4);

        this.zipCommentLength = this.reader.readInt(2);
        // warning : the encoding depends of the system locale
        // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded.
        // On a windows machine, this field is encoded with the localized windows code page.
        var zipComment = this.reader.readData(this.zipCommentLength);
        var decodeParamType = support.uint8array ? "uint8array" : "array";
        // To get consistent behavior with the generation part, we will assume that
        // this is utf8 encoded unless specified otherwise.
        var decodeContent = utils.transformTo(decodeParamType, zipComment);
        this.zipComment = this.loadOptions.decodeFileName(decodeContent);
    },
    /**
     * Read the end of the Zip 64 central directory.
     * Not merged with the method readEndOfCentral :
     * The end of central can coexist with its Zip64 brother,
     * I don't want to read the wrong number of bytes !
     */
    readBlockZip64EndOfCentral: function() {
        this.zip64EndOfCentralSize = this.reader.readInt(8);
        this.versionMadeBy = this.reader.readString(2);
        this.versionNeeded = this.reader.readInt(2);
        this.diskNumber = this.reader.readInt(4);
        this.diskWithCentralDirStart = this.reader.readInt(4);
        this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
        this.centralDirRecords = this.reader.readInt(8);
        this.centralDirSize = this.reader.readInt(8);
        this.centralDirOffset = this.reader.readInt(8);

        this.zip64ExtensibleData = {};
        var extraDataSize = this.zip64EndOfCentralSize - 44,
            index = 0,
            extraFieldId,
            extraFieldLength,
            extraFieldValue;
        while (index < extraDataSize) {
            extraFieldId = this.reader.readInt(2);
            extraFieldLength = this.reader.readInt(4);
            extraFieldValue = this.reader.readString(extraFieldLength);
            this.zip64ExtensibleData[extraFieldId] = {
                id: extraFieldId,
                length: extraFieldLength,
                value: extraFieldValue
            };
        }
    },
    /**
     * Read the end of the Zip 64 central directory locator.
     */
    readBlockZip64EndOfCentralLocator: function() {
        this.diskWithZip64CentralDirStart = this.reader.readInt(4);
        this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
        this.disksCount = this.reader.readInt(4);
        if (this.disksCount > 1) {
            throw new Error("Multi-volumes zip are not supported");
        }
    },
    /**
     * Read the local files, based on the offset read in the central part.
     */
    readLocalFiles: function() {
        var i, file;
        for (i = 0; i < this.files.length; i++) {
            file = this.files[i];
            this.reader.setIndex(file.localHeaderOffset);
            this.checkSignature(sig.LOCAL_FILE_HEADER);
            file.readLocalPart(this.reader);
            file.handleUTF8();
            file.processAttributes();
        }
    },
    /**
     * Read the central directory.
     */
    readCentralDir: function() {
        var file;

        this.reader.setIndex(this.centralDirOffset);
        while (this.reader.readString(4) === sig.CENTRAL_FILE_HEADER) {
            file = new ZipEntry({
                zip64: this.zip64
            }, this.loadOptions);
            file.readCentralPart(this.reader);
            this.files.push(file);
        }

        if (this.centralDirRecords !== this.files.length) {
            if (this.centralDirRecords !== 0 && this.files.length === 0) {
                // We expected some records but couldn't find ANY.
                // This is really suspicious, as if something went wrong.
                throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
            } else {
                // We found some records but not all.
                // Something is wrong but we got something for the user: no error here.
                // console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length);
            }
        }
    },
    /**
     * Read the end of central directory.
     */
    readEndOfCentral: function() {
        var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
        if (offset < 0) {
            // Check if the content is a truncated zip or complete garbage.
            // A "LOCAL_FILE_HEADER" is not required at the beginning (auto
            // extractible zip for example) but it can give a good hint.
            // If an ajax request was used without responseType, we will also
            // get unreadable data.
            var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER);

            if (isGarbage) {
                throw new Error("Can't find end of central directory : is this a zip file ? " +
                                "If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html");
            } else {
                throw new Error("Corrupted zip : can't find end of central directory");
            }
        }
        this.reader.setIndex(offset);
        var endOfCentralDirOffset = offset;
        this.checkSignature(sig.CENTRAL_DIRECTORY_END);
        this.readBlockEndOfCentral();


        /* extract from the zip spec :
            4)  If one of the fields in the end of central directory
                record is too small to hold required data, the field
                should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
                ZIP64 format record should be created.
            5)  The end of central directory record and the
                Zip64 end of central directory locator record must
                reside on the same disk when splitting or spanning
                an archive.
         */
        if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
            this.zip64 = true;

            /*
            Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from
            the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents
            all numbers as 64-bit double precision IEEE 754 floating point numbers.
            So, we have 53bits for integers and bitwise operations treat everything as 32bits.
            see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
            and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5
            */

            // should look for a zip64 EOCD locator
            offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
            if (offset < 0) {
                throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");
            }
            this.reader.setIndex(offset);
            this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
            this.readBlockZip64EndOfCentralLocator();

            // now the zip64 EOCD record
            if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
                // console.warn("ZIP64 end of central directory not where expected.");
                this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
                if (this.relativeOffsetEndOfZip64CentralDir < 0) {
                    throw new Error("Corrupted zip : can't find the ZIP64 end of central directory");
                }
            }
            this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
            this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
            this.readBlockZip64EndOfCentral();
        }

        var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;
        if (this.zip64) {
            expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator
            expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize;
        }

        var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;

        if (extraBytes > 0) {
            // console.warn(extraBytes, "extra bytes at beginning or within zipfile");
            if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) {
                // The offsets seem wrong, but we have something at the specified offset.
                // So… we keep it.
            } else {
                // the offset is wrong, update the "zero" of the reader
                // this happens if data has been prepended (crx files for example)
                this.reader.zero = extraBytes;
            }
        } else if (extraBytes < 0) {
            throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
        }
    },
    prepareReader: function(data) {
        var type = utils.getTypeOf(data);
        utils.checkSupport(type);
        if (type === "string" && !support.uint8array) {
            this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString);
        }
        else if (type === "nodebuffer") {
            this.reader = new NodeBufferReader(data);
        }
        else if (support.uint8array) {
            this.reader = new Uint8ArrayReader(utils.transformTo("uint8array", data));
        } else if (support.array) {
            this.reader = new ArrayReader(utils.transformTo("array", data));
        } else {
            throw new Error("Unexpected error: unsupported type '" + type + "'");
        }
    },
    /**
     * Read a zip file and create ZipEntries.
     * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
     */
    load: function(data) {
        this.prepareReader(data);
        this.readEndOfCentral();
        this.readCentralDir();
        this.readLocalFiles();
    }
};
// }}} end of ZipEntries
module.exports = ZipEntries;


/***/ }),
/* 42 */
/***/ (function(module, exports, __webpack_require__) {



var Uint8ArrayReader = __webpack_require__(20);

function NodeBufferReader(data) {
    this.data = data;
    this.length = this.data.length;
    this.index = 0;
    this.zero = 0;
}
NodeBufferReader.prototype = new Uint8ArrayReader();

/**
 * @see DataReader.readData
 */
NodeBufferReader.prototype.readData = function(size) {
    this.checkOffset(size);
    var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
    this.index += size;
    return result;
};
module.exports = NodeBufferReader;


/***/ }),
/* 43 */
/***/ (function(module, exports, __webpack_require__) {



var StringReader = __webpack_require__(18);
var utils = __webpack_require__(0);
var CompressedObject = __webpack_require__(16);
var jszipProto = __webpack_require__(4);
var support = __webpack_require__(2);

var MADE_BY_DOS = 0x00;
var MADE_BY_UNIX = 0x03;

// class ZipEntry {{{
/**
 * An entry in the zip file.
 * @constructor
 * @param {Object} options Options of the current file.
 * @param {Object} loadOptions Options for loading the stream.
 */
function ZipEntry(options, loadOptions) {
    this.options = options;
    this.loadOptions = loadOptions;
}
ZipEntry.prototype = {
    /**
     * say if the file is encrypted.
     * @return {boolean} true if the file is encrypted, false otherwise.
     */
    isEncrypted: function() {
        // bit 1 is set
        return (this.bitFlag & 0x0001) === 0x0001;
    },
    /**
     * say if the file has utf-8 filename/comment.
     * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
     */
    useUTF8: function() {
        // bit 11 is set
        return (this.bitFlag & 0x0800) === 0x0800;
    },
    /**
     * Prepare the function used to generate the compressed content from this ZipFile.
     * @param {DataReader} reader the reader to use.
     * @param {number} from the offset from where we should read the data.
     * @param {number} length the length of the data to read.
     * @return {Function} the callback to get the compressed content (the type depends of the DataReader class).
     */
    prepareCompressedContent: function(reader, from, length) {
        return function() {
            var previousIndex = reader.index;
            reader.setIndex(from);
            var compressedFileData = reader.readData(length);
            reader.setIndex(previousIndex);

            return compressedFileData;
        };
    },
    /**
     * Prepare the function used to generate the uncompressed content from this ZipFile.
     * @param {DataReader} reader the reader to use.
     * @param {number} from the offset from where we should read the data.
     * @param {number} length the length of the data to read.
     * @param {JSZip.compression} compression the compression used on this file.
     * @param {number} uncompressedSize the uncompressed size to expect.
     * @return {Function} the callback to get the uncompressed content (the type depends of the DataReader class).
     */
    prepareContent: function(reader, from, length, compression, uncompressedSize) {
        return function() {

            var compressedFileData = utils.transformTo(compression.uncompressInputType, this.getCompressedContent());
            var uncompressedFileData = compression.uncompress(compressedFileData);

            if (uncompressedFileData.length !== uncompressedSize) {
                throw new Error("Bug : uncompressed data size mismatch");
            }

            return uncompressedFileData;
        };
    },
    /**
     * Read the local part of a zip file and add the info in this object.
     * @param {DataReader} reader the reader to use.
     */
    readLocalPart: function(reader) {
        var compression, localExtraFieldsLength;

        // we already know everything from the central dir !
        // If the central dir data are false, we are doomed.
        // On the bright side, the local part is scary  : zip64, data descriptors, both, etc.
        // The less data we get here, the more reliable this should be.
        // Let's skip the whole header and dash to the data !
        reader.skip(22);
        // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
        // Strangely, the filename here is OK.
        // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
        // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
        // Search "unzip mismatching "local" filename continuing with "central" filename version" on
        // the internet.
        //
        // I think I see the logic here : the central directory is used to display
        // content and the local directory is used to extract the files. Mixing / and \
        // may be used to display \ to windows users and use / when extracting the files.
        // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
        this.fileNameLength = reader.readInt(2);
        localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
        this.fileName = reader.readData(this.fileNameLength);
        reader.skip(localExtraFieldsLength);

        if (this.compressedSize == -1 || this.uncompressedSize == -1) {
            throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize == -1 || uncompressedSize == -1)");
        }

        compression = utils.findCompression(this.compressionMethod);
        if (compression === null) { // no compression found
            throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " +  utils.transformTo("string", this.fileName) + ")");
        }
        this.decompressed = new CompressedObject();
        this.decompressed.compressedSize = this.compressedSize;
        this.decompressed.uncompressedSize = this.uncompressedSize;
        this.decompressed.crc32 = this.crc32;
        this.decompressed.compressionMethod = this.compressionMethod;
        this.decompressed.getCompressedContent = this.prepareCompressedContent(reader, reader.index, this.compressedSize, compression);
        this.decompressed.getContent = this.prepareContent(reader, reader.index, this.compressedSize, compression, this.uncompressedSize);

        // we need to compute the crc32...
        if (this.loadOptions.checkCRC32) {
            this.decompressed = utils.transformTo("string", this.decompressed.getContent());
            if (jszipProto.crc32(this.decompressed) !== this.crc32) {
                throw new Error("Corrupted zip : CRC32 mismatch");
            }
        }
    },

    /**
     * Read the central part of a zip file and add the info in this object.
     * @param {DataReader} reader the reader to use.
     */
    readCentralPart: function(reader) {
        this.versionMadeBy = reader.readInt(2);
        this.versionNeeded = reader.readInt(2);
        this.bitFlag = reader.readInt(2);
        this.compressionMethod = reader.readString(2);
        this.date = reader.readDate();
        this.crc32 = reader.readInt(4);
        this.compressedSize = reader.readInt(4);
        this.uncompressedSize = reader.readInt(4);
        this.fileNameLength = reader.readInt(2);
        this.extraFieldsLength = reader.readInt(2);
        this.fileCommentLength = reader.readInt(2);
        this.diskNumberStart = reader.readInt(2);
        this.internalFileAttributes = reader.readInt(2);
        this.externalFileAttributes = reader.readInt(4);
        this.localHeaderOffset = reader.readInt(4);

        if (this.isEncrypted()) {
            throw new Error("Encrypted zip are not supported");
        }

        this.fileName = reader.readData(this.fileNameLength);
        this.readExtraFields(reader);
        this.parseZIP64ExtraField(reader);
        this.fileComment = reader.readData(this.fileCommentLength);
    },

    /**
     * Parse the external file attributes and get the unix/dos permissions.
     */
    processAttributes: function () {
        this.unixPermissions = null;
        this.dosPermissions = null;
        var madeBy = this.versionMadeBy >> 8;

        // Check if we have the DOS directory flag set.
        // We look for it in the DOS and UNIX permissions
        // but some unknown platform could set it as a compatibility flag.
        this.dir = this.externalFileAttributes & 0x0010 ? true : false;

        if(madeBy === MADE_BY_DOS) {
            // first 6 bits (0 to 5)
            this.dosPermissions = this.externalFileAttributes & 0x3F;
        }

        if(madeBy === MADE_BY_UNIX) {
            this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF;
            // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8);
        }

        // fail safe : if the name ends with a / it probably means a folder
        if (!this.dir && this.fileNameStr.slice(-1) === '/') {
            this.dir = true;
        }
    },

    /**
     * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
     * @param {DataReader} reader the reader to use.
     */
    parseZIP64ExtraField: function(reader) {

        if (!this.extraFields[0x0001]) {
            return;
        }

        // should be something, preparing the extra reader
        var extraReader = new StringReader(this.extraFields[0x0001].value);

        // I really hope that these 64bits integer can fit in 32 bits integer, because js
        // won't let us have more.
        if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
            this.uncompressedSize = extraReader.readInt(8);
        }
        if (this.compressedSize === utils.MAX_VALUE_32BITS) {
            this.compressedSize = extraReader.readInt(8);
        }
        if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
            this.localHeaderOffset = extraReader.readInt(8);
        }
        if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
            this.diskNumberStart = extraReader.readInt(4);
        }
    },
    /**
     * Read the central part of a zip file and add the info in this object.
     * @param {DataReader} reader the reader to use.
     */
    readExtraFields: function(reader) {
        var start = reader.index,
            extraFieldId,
            extraFieldLength,
            extraFieldValue;

        this.extraFields = this.extraFields || {};

        while (reader.index < start + this.extraFieldsLength) {
            extraFieldId = reader.readInt(2);
            extraFieldLength = reader.readInt(2);
            extraFieldValue = reader.readString(extraFieldLength);

            this.extraFields[extraFieldId] = {
                id: extraFieldId,
                length: extraFieldLength,
                value: extraFieldValue
            };
        }
    },
    /**
     * Apply an UTF8 transformation if needed.
     */
    handleUTF8: function() {
        var decodeParamType = support.uint8array ? "uint8array" : "array";
        if (this.useUTF8()) {
            this.fileNameStr = jszipProto.utf8decode(this.fileName);
            this.fileCommentStr = jszipProto.utf8decode(this.fileComment);
        } else {
            var upath = this.findExtraFieldUnicodePath();
            if (upath !== null) {
                this.fileNameStr = upath;
            } else {
                var fileNameByteArray =  utils.transformTo(decodeParamType, this.fileName);
                this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray);
            }

            var ucomment = this.findExtraFieldUnicodeComment();
            if (ucomment !== null) {
                this.fileCommentStr = ucomment;
            } else {
                var commentByteArray =  utils.transformTo(decodeParamType, this.fileComment);
                this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray);
            }
        }
    },

    /**
     * Find the unicode path declared in the extra field, if any.
     * @return {String} the unicode path, null otherwise.
     */
    findExtraFieldUnicodePath: function() {
        var upathField = this.extraFields[0x7075];
        if (upathField) {
            var extraReader = new StringReader(upathField.value);

            // wrong version
            if (extraReader.readInt(1) !== 1) {
                return null;
            }

            // the crc of the filename changed, this field is out of date.
            if (jszipProto.crc32(this.fileName) !== extraReader.readInt(4)) {
                return null;
            }

            return jszipProto.utf8decode(extraReader.readString(upathField.length - 5));
        }
        return null;
    },

    /**
     * Find the unicode comment declared in the extra field, if any.
     * @return {String} the unicode comment, null otherwise.
     */
    findExtraFieldUnicodeComment: function() {
        var ucommentField = this.extraFields[0x6375];
        if (ucommentField) {
            var extraReader = new StringReader(ucommentField.value);

            // wrong version
            if (extraReader.readInt(1) !== 1) {
                return null;
            }

            // the crc of the comment changed, this field is out of date.
            if (jszipProto.crc32(this.fileComment) !== extraReader.readInt(4)) {
                return null;
            }

            return jszipProto.utf8decode(extraReader.readString(ucommentField.length - 5));
        }
        return null;
    }
};
module.exports = ZipEntry;


/***/ }),
/* 44 */
/***/ (function(module, exports, __webpack_require__) {



var utils = __webpack_require__(0);

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.string2binary = function(str) {
    return utils.string2binary(str);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.string2Uint8Array = function(str) {
    return utils.transformTo("uint8array", str);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.uint8Array2String = function(array) {
    return utils.transformTo("string", array);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.string2Blob = function(str) {
    var buffer = utils.transformTo("arraybuffer", str);
    return utils.arrayBuffer2Blob(buffer);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.arrayBuffer2Blob = function(buffer) {
    return utils.arrayBuffer2Blob(buffer);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.transformTo = function(outputType, input) {
    return utils.transformTo(outputType, input);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.getTypeOf = function(input) {
    return utils.getTypeOf(input);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.checkSupport = function(type) {
    return utils.checkSupport(type);
};

/**
 * @deprecated
 * This value will be removed in a future version without replacement.
 */
exports.MAX_VALUE_16BITS = utils.MAX_VALUE_16BITS;

/**
 * @deprecated
 * This value will be removed in a future version without replacement.
 */
exports.MAX_VALUE_32BITS = utils.MAX_VALUE_32BITS;


/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.pretty = function(str) {
    return utils.pretty(str);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.findCompression = function(compressionMethod) {
    return utils.findCompression(compressionMethod);
};

/**
 * @deprecated
 * This function will be removed in a future version without replacement.
 */
exports.isRegExp = function (object) {
    return utils.isRegExp(object);
};



/***/ })
/******/ ])});;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlFileCache',[], function () {
    "use strict";

    // Contains all files that were already retrieved as a promises.
    /**
     * Provides Cache for Promises representing KmlFiles in current KmlDocument.
     * @exports KmlFileCache
     */
    var KmlFileCache = function () {
        this._rootFile = null;
        this._map = {};
    };

    /**
     * Retrieve relevant KmlFile from the cache representing this Document.
     * @param url {String} Url of the file to retrieve from this cache.
     * @returns {Promise|null}
     */
    KmlFileCache.prototype.retrieve = function (url) {
        if (url.indexOf('#') == 0 || url == null || url.indexOf('http') != 0) {
            return this._rootFile;
        } else {
            var urlNormalized = url;
            if (url.indexOf('#') != -1) {
                urlNormalized = url.substr(0, url.indexOf('#') - 1);
            }
            // Start of the URL use to store it in the map.
            if (this._map[urlNormalized]) {
                return this._map[urlNormalized];
            }
        }

        return null;
    };

    /**
     * Adds new KmlFile to the KmlDocument represented by this Cache.
     * @param url {String} Url of the file for internal mapping
     * @param filePromise {Promise} Promise of the file to be stored.
     */
    KmlFileCache.prototype.add = function (url, filePromise) {
        if (!this._rootFile) {
            this._rootFile = filePromise;
        } else {
            this._map[url] = filePromise;
        }
    };

    return KmlFileCache; // Return actually object. This is singleton used throughout the whole application.
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlStyleSelector',[
    './../KmlObject'
], function (KmlObject) {
    "use strict";
    /**
     * Constructs an KmlStyleSelector. Application usually don't call this constructor. It is called by {@link KmlFile}
     * as Objects from KmlFile are read.
     * @alias KmlStyleSelector
     * @constructor
     * @classdesc Contains the data associated with Kml style selector
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml style selector.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#styleselector
     * @augments KmlObject
     */
    var KmlStyleSelector = function (options) {
        KmlObject.call(this, options);
    };

    KmlStyleSelector.prototype = Object.create(KmlObject.prototype);

    /**
     * @inheritDoc
     */
    KmlStyleSelector.prototype.getTagNames = function () {
        return ['Style', 'StyleMap'];
    };

    return KmlStyleSelector;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlPolyStyle',[
    '../../../util/Color',
    './KmlColorStyle',
    './../KmlElements',
    '../util/NodeTransformers'
], function (
    Color,
    KmlColorStyle,
    KmlElements,
    NodeTransformers
) {
    "use strict";

    /**
     * Constructs an KmlPolyStyle. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias KmlPolyStyle
     * @constructor
     * @classdesc Contains the data associated with Kml poly style
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml poly style.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#polystyle
     * @augments KmlColorStyle
     */
    var KmlPolyStyle = function (options) {
        KmlColorStyle.call(this, options);
    };

    KmlPolyStyle.prototype = Object.create(KmlColorStyle.prototype);

    Object.defineProperties(KmlPolyStyle.prototype, {
        /**
         * If true the polygon's surface will be filled with color
         * @memberof KmlPolyStyle.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlFill: {
            get: function(){
                return this._factory.specific(this, {name: 'fill', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * Specifies whether outline polygon. Outline style is defined by line style if present.
         * @memberof KmlPolyStyle.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlOutline: {
            get: function(){
                return this._factory.specific(this, {name: 'outline', transformer: NodeTransformers.boolean});
            }
        }
    });


    KmlPolyStyle.update = function (style, options) {
        style = style || {};
        var shapeOptions = options || {};
        shapeOptions._drawInterior = style.kmlFill || true;
        shapeOptions._drawOutline = style.kmlOutline || false;
        shapeOptions._outlineColor = options._outlineColor || Color.WHITE;
        shapeOptions._interiorColor = style.kmlColor && Color.colorFromKmlHex(style.kmlColor) || Color.WHITE;
        shapeOptions._colorMode = style.kmlColorMode || 'normal'; // TODO Not yet supported.

        return shapeOptions;
    };

    /**
     * @inheritDoc
     */
    KmlPolyStyle.prototype.getTagNames = function () {
        return ['PolyStyle'];
    };

    KmlElements.addKey(KmlPolyStyle.prototype.getTagNames()[0], KmlPolyStyle);

    return KmlPolyStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLink',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlLink. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLink
     * @classdesc Contains the data associated with Link node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing link in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#link
     * @augments KmlObject
     */
    var KmlLink = function (options) {
        KmlObject.call(this, options);

        this.onChangeListeners = [];
    };

    KmlLink.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLink.prototype, {
        /**
         * A URL (either an HTTP address or a local file specification). When the parent of &lt;Link&gt; is a
         * NetworkLink,
         * &lt;href&gt; is a KML file. When the parent of &lt;Link&gt; is a Model, &lt;href&gt; is a COLLADA file. When the parent of
         * &lt;Icon&gt; (same fields as &lt;Link&gt;) is an Overlay, &lt;href&gt; is an image. Relative URLs can be used in this tag
         * and are evaluated relative to the enclosing KML file. See KMZ Files for details on constructing relative
         * references in KML and KMZ files.
         * @memberof KmlLink.prototype
         * @readonly
         * @type {String}
         */
        kmlHref: {
            get: function () {
                return this._factory.specific(this, {name: 'href', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies a time-based refresh mode, which can be one of the following:
         * onChange - refresh when the file is loaded and whenever the Link parameters change (the default).
         * onInterval - refresh every n seconds (specified in &lt;refreshInterval&gt;).
         * onExpire - refresh the file when the expiration time is reached. If a fetched file has a
         *  NetworkLinkControl, the &lt;expires&gt; time takes precedence over expiration times specified in HTTP
         * headers. If no &lt;expires&gt; time is specified, the HTTP max-age header is used (if present). If max-age is
         * not present, the Expires HTTP header is used (if present). (See Section RFC261b of the Hypertext
         * Transfer Protocol - HTTP 1.1 for details on HTTP header fields.)
         * @memberof KmlLink.prototype
         * @readonly
         * @type {String}
         */
        kmlRefreshMode: {
            get: function () {
                return this._factory.specific(this, {name: 'refreshMode', transformer: NodeTransformers.string});
            }
        },

        /**
         * Indicates to refresh the file every n seconds.
         * @memberof KmlLink.prototype
         * @readonly
         * @type {Number}
         */
        kmlRefreshInterval: {
            get: function () {
                return this._factory.specific(this, {name: 'refreshInterval', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies how the link is refreshed when the "camera" changes.
         * Can be one of the following:
         * never (default) - Ignore changes in the view. Also ignore &lt;viewFormat&gt; parameters, if any.
         * onStop - Refresh the file n seconds after movement stops, where n is specified in &lt;viewRefreshTime&gt;.
         * onRequest - Refresh the file only when the user explicitly requests it. (For example, in Google Earth,
         * the user right-clicks and selects Refresh in the Context menu.)
         * onRegion - Refresh the file when the Region becomes active. See &lt;Region&gt;.
         * @memberof KmlLink.prototype
         * @readonly
         * @type {String}
         */
        kmlViewRefreshMode: {
            get: function () {
                return this._factory.specific(this, {name: 'viewRefreshMode', transformer: NodeTransformers.string});
            }
        },

        /**
         * After camera movement stops, specifies the number of seconds to wait before refreshing the view. (See
         * &lt;viewRefreshMode&gt; and onStop above.)
         * @memberof KmlLink.prototype
         * @readonly
         * @type {Number}
         */
        kmlViewRefreshTime: {
            get: function () {
                return this._factory.specific(this, {name: 'viewRefreshTime', transformer: NodeTransformers.number});
            }
        },

        /**
         * Scales the BBOX parameters before sending them to the server. A value less than 1 specifies to use less
         * than the full view (screen). A value greater than 1 specifies to fetch an area that extends beyond the
         * edges of the current view.
         * @memberof KmlLink.prototype
         * @readonly
         * @type {Number}
         */
        kmlViewBoundScale: {
            get: function () {
                return this._factory.specific(this, {name: 'viewBoundScale', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the format of the query string that is appended to the Link's &lt;href&gt; before the file is
         * fetched.(If the &lt;href&gt; specifies a local file, this element is ignored.) If you specify a
         * &lt;viewRefreshMode&gt; of onStop and do not include the &lt;viewFormat&gt; tag in the file, the following
         * information is automatically appended to the query string:
         * BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth] This information matches the Web Map Service (WMS)
         * bounding box specification. If you specify an empty &lt;viewFormat&gt; tag, no information is appended to the
         * query string. You can also specify a custom set of viewing parameters to add to the query string. If you
         * supply a format string, it is used instead of the BBOX information. If you also want the BBOX
         * information, you need to add those parameters along with the custom parameters. You can use any of the
         * following parameters in your format string (and Google Earth will substitute the appropriate current
         * value at the time it creates the query string):
         * [lookatLon], [lookatLat] - longitude and latitude of the point that &lt;LookAt&gt; is viewing
         * [lookatRange], [lookatTilt], [lookatHeading] - values used by the &lt;LookAt&gt; element (see descriptions of
         *  &lt;range&gt;, &lt;tilt&gt;, and &lt;heading&gt; in &lt;LookAt&gt;)
         * [lookatTerrainLon], [lookatTerrainLat], [lookatTerrainAlt] - point on the terrain in degrees/meters that
         *  &lt;LookAt&gt; is viewing
         * [cameraLon], [cameraLat], [cameraAlt] - degrees/meters of the eyepoint for the camera
         * [horizFov], [vertFov] - horizontal, vertical field of view for the camera
         * [horizPixels], [vertPixels] - size in pixels of the 3D viewer
         * [terrainEnabled] - indicates whether the 3D viewer is showing terrain
         * @memberof KmlLink.prototype
         * @readonly
         * @type {String}
         */
        kmlViewFormat: {
            get: function () {
                return this._factory.specific(this, {name: 'viewFormat', transformer: NodeTransformers.string});
            }
        },

        /**
         * Appends information to the query string, based on the parameters specified. (Google Earth substitutes
         * the
         * appropriate current value at the time it creates the query string.) The following parameters are
         * supported:
         * [clientVersion]
         * [kmlVersion]
         * [clientName]
         * [language]
         * @memberof KmlLink.prototype
         * @readonly
         * @type {String}
         */
        kmlHttpQuery: {
            get: function () {
                return this._factory.specific(this, {name: 'httpQuery', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlLink.prototype.getTagNames = function () {
        return ['Link'];
    };

	KmlElements.addKey(KmlLink.prototype.getTagNames()[0], KmlLink);

    return KmlLink;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlIcon',[
    './KmlLink',
    './KmlElements',
    './util/NodeTransformers'
], function (
    KmlLink,
    KmlElements,
    NodeTransformers
) {
    "use strict";
    /**
     * Constructs an KmlIcon. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlIcon
     * @classdesc Contains the data associated with Icon node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing icon in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#icon
     * @augments KmlLink
     */
    var KmlIcon = function (options) {
        KmlLink.call(this, options);
    };

    KmlIcon.prototype = Object.create(KmlLink.prototype);

    Object.defineProperties(KmlIcon.prototype, {
        /**
         * The href can contain a pallet of icons. In this case this is offset from left border.
         * @memberof KmlIcon.prototype
         * @readonly
         * @type {Number}
         */
        kmlX: {
            get: function(){
                return this._factory.specific(this, {name: 'gx:x', transformer: NodeTransformers.number});
            }
        },

        /**
         * The href can contain a pallet of icons. In this case this is offset from top border.
         * @memberof KmlIcon.prototype
         * @readonly
         * @type {Number}
         */
        kmlY: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:y', transformer: NodeTransformers.number});
            }
        },

        /**
         * The href can contain a pallet of icons. In this case this is width of the icon on the pallete.
         * @memberof KmlIcon.prototype
         * @readonly
         * @type {Number}
         */
        kmlW: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:w', transformer: NodeTransformers.number});
            }
        },

        /**
         * The href can contain a pallet of icons. In this case this is height of the icon on the palette.
         * @memberof KmlIcon.prototype
         * @readonly
         * @type {Number}
         */
        kmlH: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:h', transformer: NodeTransformers.number});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlIcon.prototype.getTagNames = function () {
        return ['Icon'];
    };

    KmlElements.addKey(KmlIcon.prototype.getTagNames()[0], KmlIcon);

    return KmlIcon;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlIconStyle',[
    './KmlColorStyle',
    './../KmlElements',
    '../KmlIcon',
    '../util/NodeTransformers'
], function (KmlColorStyle,
             KmlElements,
             KmlIcon,
             NodeTransformers) {
    "use strict";
    /**
     * Constructs an KmlIconStyle. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from KmlFile are read. This object is already concrete implementation.
     * @alias KmlIconStyle
     * @classdesc Contains the data associated with IconStyle node
     * @param options {Object}
     * @param options.objectNode {Node} Node representing IconStyle in the document.
     * @returns {KmlIconStyle}
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined
     * @see https://developers.google.com/kml/documentation/kmlreference#iconstyle
     * @augments KmlColorStyle
     */
    var KmlIconStyle = function (options) {
        KmlColorStyle.call(this, options);
    };

    KmlIconStyle.prototype = Object.create(KmlColorStyle.prototype);

    Object.defineProperties(KmlIconStyle.prototype, {
        /**
         * Scale in which to resize the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlScale: {
            get: function () {
                return this._factory.specific(this, {name: 'scale', transformer: NodeTransformers.number});
            }
        },

        /**
         * Direction in degrees of the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlHeading: {
            get: function () {
                return this._factory.specific(this, {name: 'heading', transformer: NodeTransformers.number});
            }
        },

        /**
         * Custom Icon. If the icon is part of the IconStyle, only href is allowed for the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {KmlIcon}
         */
        kmlIcon: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlIcon.prototype.getTagNames()
                });
            }
        },

        /**
         * Either the number of pixels, a fractional component of the icon, or a pixel inset indicating the x
         * component of a point on the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlHotSpotX: {
            get: function () {
                return this._factory.specific(this, {name: 'hotSpot', transformer: NodeTransformers.attribute('x')});
            }
        },

        /**
         * Either the number of pixels, a fractional component of the icon, or a pixel inset indicating the y
         * component of a point on the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlHotSpotY: {
            get: function () {
                return this._factory.specific(this, {name: 'hotSpot', transformer: NodeTransformers.attribute('y')});
            }
        },

        /**
         * Units in which the x value is specified. A value of fraction indicates the x value is a fraction of the
         * icon. A value of pixels indicates the x value in pixels. A value of insetPixels indicates the indent from
         * the right edge of the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlHotSpotXUnits: {
            get: function () {
                return this._factory.specific(this, {name: 'hotSpot', transformer: NodeTransformers.attribute('xunits')});
            }
        },

        /**
         * Units in which the y value is specified. A value of fraction indicates the y value is a fraction of the
         * icon. A value of pixels indicates the y value in pixels. A value of insetPixels indicates the indent from
         * the top edge of the icon.
         * @memberof KmlIconStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlHotSpotYUnits: {
            get: function () {
                return this._factory.specific(this, {name: 'hotSpot', transformer: NodeTransformers.attribute('yunits')});
            }
        }
    });

    KmlIconStyle.update = function(style, options) {
        style = style || {};
        var shapeOptions = options || {};

        shapeOptions._imageScale = style.kmlScale || 1;
        shapeOptions._imageSource = style.kmlIcon && style.kmlIcon.kmlHref || null;

        return shapeOptions;
    };

    /**
     * @inheritDoc
     */
    KmlIconStyle.prototype.getTagNames = function () {
        return ['IconStyle'];
    };

    KmlElements.addKey(KmlIconStyle.prototype.getTagNames()[0], KmlIconStyle);

    return KmlIconStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlLabelStyle',[
    './KmlColorStyle',
    '../KmlElements',
    '../util/NodeTransformers'
], function (
    KmlColorStyle,
    KmlElements,
    NodeTransformers
) {
    "use strict";
    /**
     * Constructs an KmlLabelStyle. Applications don't usually call this constructor. It is called by {@link KmlFile} as
     * objects from KmlFile are read. This object is already concrete implementation.
     * @alias KmlLabelStyle
     * @classdesc Contains the data associated with LabelStyle
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the LabelStyle in the document.
     * @constructor
     * @throws {ArgumentError} If node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#labelstyle
     * @augments KmlColorStyle
     */
    var KmlLabelStyle = function (options) {
        KmlColorStyle.call(this, options);
    };

    KmlLabelStyle.prototype = Object.create(KmlColorStyle.prototype);

    Object.defineProperties(KmlLabelStyle.prototype, {
        /**
         * Scale in which to resize the icon.
         * @memberof KmlLabelStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlScale: {
            get: function() {
                return this._factory.specific(this, {name: 'scale', transformer: NodeTransformers.number});
            }
        }
    });


    KmlLabelStyle.update = function () {

    };

    /**
     * @inheritDoc
     */
    KmlLabelStyle.prototype.getTagNames = function () {
        return ['LabelStyle'];
    };

    KmlElements.addKey(KmlLabelStyle.prototype.getTagNames()[0], KmlLabelStyle);

    return KmlLabelStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlLineStyle',[
    '../../../util/Color',
    './KmlColorStyle',
    './../KmlElements',
    '../util/NodeTransformers'
], function (
    Color,
    KmlColorStyle,
    KmlElements,
    NodeTransformers
) {
    "use strict";

    /**
     * Constructs an KmlLineStyle object.  Applications shouldn't use this constructor. It is used by
     * {@link KmlFile}. KmlLineStyle represents one line style.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing this line style.
     * @constructor
     * @alias KmlLineStyle
     * @classdesc Class representing LineStyle element of KmlFile
     * @see https://developers.google.com/kml/documentation/kmlreference#linestyle
     * @augments KmlColorStyle
     */
    var KmlLineStyle = function (options) {
        KmlColorStyle.call(this, options);
    };

    KmlLineStyle.prototype = Object.create(KmlColorStyle.prototype);

    Object.defineProperties(KmlLineStyle.prototype, {
        /**
         * Width of the line in pixels.
         * @memberof KmlLineStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlWidth: {
            get: function() {
                return this._factory.specific(this, {name: 'width', transformer: NodeTransformers.number});
            }
        },

        /**
         * Color applied to outer width. Ignored by Polygon and LinearRing.
         * @memberof KmlLineStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlOuterColor: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:outerColor', transformer: NodeTransformers.string});
            }
        },

        /**
         * Value between 0.0 and 1.0 specifies the proportion of the line used by outerColor. Only applies to line
         * setting width with physical width.
         * @memberof KmlLineStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlOuterWidth: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:outerWidth', transformer: NodeTransformers.number});
            }
        },

        /**
         * Physical width of the line in meters.
         * @memberof KmlLineStyle.prototype
         * @readonly
         * @type {Number}
         */
        kmlPhysicalWidth: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:physicalWidth', transformer: NodeTransformers.number});
            }
        },

        /**
         * A boolean defining whether or not to display a text label on a LineString. A LineString's label is
         * contained in the &lt;name&gt; element that is a sibling of &lt;LineString&gt; (i.e. contained within the same
         * &lt;Placemark&gt; element).
         * @memberof KmlLineStyle.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlLabelVisibility: {
            get: function() {
                return this._factory.specific(this, {name: 'gx:labelVisibility', transformer: NodeTransformers.boolean});
            }
        }
    });

    KmlLineStyle.update = function (style, options) {
        var shapeOptions = options || {};
        style = style || {};

        shapeOptions._outlineColor = style.kmlColor && Color.colorFromKmlHex(style.kmlColor) || Color.WHITE;
        shapeOptions._outlineWidth = style.kmlWidth || 10.0;

        return shapeOptions;
    };

    /**
     * @inheritDoc
     */
    KmlLineStyle.prototype.getTagNames = function () {
        return ['LineStyle'];
    };

    KmlElements.addKey(KmlLineStyle.prototype.getTagNames()[0], KmlLineStyle);

    return KmlLineStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlListStyle',[
    '../util/ItemIcon',
    '../KmlElements',
    './KmlSubStyle',
    '../util/NodeTransformers'
], function (ItemIcon,
             KmlElements,
             KmlSubStyle, 
             NodeTransformers) {
    "use strict";
    /**
     * Constructs an KmlListStyle. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlListStyle
     * @classdesc Contains the data associated with ListStyle node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing list style in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#liststyle
     * @augments KmlSubStyle
     */
    var KmlListStyle = function (options) {
        KmlSubStyle.call(this, options);
    };

    KmlListStyle.prototype = Object.create(KmlSubStyle.prototype);

    Object.defineProperties(KmlListStyle.prototype, {
        /**
         * Background color for the Snippet. Color and opacity values are expressed in hexadecimal notation. The
         * range of values for any one color is 0 to 255 (00 to ff). For alpha, 00 is fully transparent and ff is
         * fully opaque. The order of expression is aabbggrr, where aa=alpha (00 to ff); bb=blue (00 to ff);
         * gg=green (00 to ff); rr=red (00 to ff). For example, if you want to apply a blue color with 50 percent
         * opacity to an overlay, you would specify the following: &lt;color&gt;7fff0000&lt;/color&gt;, where alpha=0x7f,
         * blue=0xff, green=0x00, and red=0x00.
         * @memberof KmlListStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlBgColor: {
            get: function () {
                return this._factory.specific(this, {name: 'bgColor', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies how a Feature is displayed in the list view. Possible values are:
         * check (default) - The Feature's visibility is tied to its item's checkbox.
         * radioFolder - When specified for a Container, only one of the Container's items is visible at a time
         * checkOffOnly - When specified for a Container or Network Link, prevents all items from being made
         * visible
         *  at once that is, the user can turn everything in the Container or Network Link off but cannot turn
         * everything on at the same time. This setting is useful for Containers or Network Links containing large
         * amounts of data. checkHideChildren - Use a normal checkbox for visibility but do not display the
         * Container or Network Link's children in the list view. A checkbox allows the user to toggle visibility
         * of the child objects in the viewer.
         * @memberof KmlListStyle.prototype
         * @readonly
         * @type {String}
         */
        kmlListItemType: {
            get: function () {
                return this._factory.specific(this, {name: 'listItemType', transformer: NodeTransformers.string});
            }
        },

        /**
         * Icon used in the List view that reflects the state of a Folder or Link fetch. Icons associated with the
         * open and closed modes are used for Folders and Network Links. Icons associated with the error and
         * fetching0, fetching1, and fetching2 modes are used for Network Links.
         * @memberof KmlListStyle.prototype
         * @readonly
         * @type {ItemIcon}
         */
        kmlItemIcon: {
            get: function () {
                return this._factory.any(this, {
                    name: ItemIcon.prototype.getTagNames()
                });
            }
        }
    });

    KmlListStyle.update = function(style) {

    };

    /**
     * @inheritDoc
     */
    KmlListStyle.prototype.getTagNames = function () {
        return ['ListStyle'];
    };

    KmlElements.addKey(KmlListStyle.prototype.getTagNames()[0], KmlListStyle);

    return KmlListStyle;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlStyle',[
    '../../../util/Color',
    '../../../util/Font',
    './KmlStyleSelector',
    './../KmlElements',
    './KmlPolyStyle',
    './KmlIconStyle',
    './KmlLabelStyle',
    './KmlLineStyle',
    './KmlListStyle',
    './KmlBalloonStyle',
    '../../../util/Offset',
    '../../../util/Promise',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/TextAttributes'
], function (
    Color,
    Font,
    KmlStyleSelector,
    KmlElements,
    KmlPolyStyle,
    KmlIconStyle,
    KmlLabelStyle,
    KmlLineStyle,
    KmlListStyle,
    KmlBalloonStyle,
    Offset,
    Promise,
    ShapeAttributes,
    TextAttributes
) {
    "use strict";

    /**
     * Constructs an KmlStyle. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * Style can contain any amount of different styles. At most one from each of these styles.
     * Possible children styles: IconStyle, LabelStyle, LineStyle, PolyStyle, BalloonStyle
     * @alias KmlStyle
     * @constructor
     * @classdesc Contains the data associated with Kml style
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml style.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#style
     * @augments KmlStyleSelector
     */
    var KmlStyle = function (options) {
        KmlStyleSelector.call(this, options);
    };

    KmlStyle.prototype = Object.create(KmlStyleSelector.prototype);

    Object.defineProperties(KmlStyle.prototype, {
        /**
         * Style used for icons in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlIconStyle|null}
         */
        kmlIconStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlIconStyle.prototype.getTagNames()
                });
            }
        },

        /**
         * Style used for labels in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlLabelStyle|null}
         */
        kmlLabelStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlLabelStyle.prototype.getTagNames()
                });
            }
        },

        /**
         * Style used for line in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlLineStyle|null}
         */
        kmlLineStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlLineStyle.prototype.getTagNames()
                });
            }
        },

        /**
         * Style used for polygon in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlPolyStyle|null}
         */
        kmlPolyStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlPolyStyle.prototype.getTagNames()
                });
            }
        },

        /**
         * Style used for balloons in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlBalloonStyle|null}
         */
        kmlBalloonStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlBalloonStyle.prototype.getTagNames()
                });
            }
        },

        /**
         * Style used for lists in current node and all children nodes.
         * @memberof KmlStyle.prototype
         * @readonly
         * @type {KmlListStyle|null}
         */
        kmlListStyle: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlListStyle.prototype.getTagNames()
                });
            }
        }
    });

    KmlStyle.prototype.generate = function(options) {
        options = options || {};
        var style = this || {};

        if(style.kmlIconStyle) {
            KmlIconStyle.update(style.kmlIconStyle, options);
        }
        if(style.kmlListStyle) {
            KmlListStyle.update(style.kmlListStyle, options);
        }
        if(style.kmlBalloonStyle) {
            KmlBalloonStyle.update(style.kmlBalloonStyle, options);
        }
        if(style.kmlLabelStyle) {
            KmlLabelStyle.update(style.kmlLabelStyle, options);
        }
        if(style.kmlPolyStyle) {
            KmlPolyStyle.update(style.kmlPolyStyle, options);
        }
        if(style.kmlLineStyle) {
            KmlLineStyle.update(style.kmlLineStyle, options);
        }

        return options;
    };

    /**
     * @inheritDoc
     */
    KmlStyle.prototype.getStyle = function() {
        var self = this;
        return new Promise(function(resolve){
            window.setTimeout(function(){
                resolve(self);
            }, 0);
        });
    };

    /**
     * @inheritDoc
     */
    KmlStyle.prototype.getTagNames = function () {
        return ['Style'];
    };

    KmlElements.addKey(KmlStyle.prototype.getTagNames()[0], KmlStyle);

    /**
     * Prepare default values for the placemark Attributes.
     * @param attributes
     * @returns {Object}
     */
    KmlStyle.placemarkAttributes = function(attributes) {
        attributes = attributes || {};
        // These are all documented with their property accessors below.
        attributes._imageColor = attributes._imageColor || new Color(1, 1, 1, 1);
        attributes._imageOffset = attributes._imageOffset||
            new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5);
        attributes._imageScale = attributes._imageScale || 1;
        attributes._imageSource = attributes._imageSource || null;
        attributes._depthTest = attributes._depthTest || true;
        attributes._labelAttributes = attributes._labelAttributes || new TextAttributes(KmlStyle.textAttributes());
        attributes._drawLeaderLine = attributes._drawLeaderLine || false;
        attributes._leaderLineAttributes = attributes._leaderLineAttributes || new ShapeAttributes(KmlStyle.shapeAttributes());

        return attributes;
    };

    /**
     * Prepare default values for text attributes
     * @param attributes
     * @returns {Object}
     */
    KmlStyle.textAttributes = function(attributes) {
        attributes = attributes || {};
        attributes._color = attributes._color || new Color(1, 1, 1, 1);
        attributes._font = attributes._font || new Font(14);
        attributes._offset = attributes._offset || new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.0);
        attributes._scale = attributes._scale || 1;
        attributes._depthTest = attributes._depthTest || false;

        return attributes;
    };

    /**
     * Prepare default values for shape attributes
     * @param attributes
     * @returns {*|{}}
     */
    KmlStyle.shapeAttributes = function(attributes) {
        attributes = attributes || {};
        // All these are documented with their property accessors below.
        attributes._drawInterior = attributes._drawInterior || true;
        attributes._drawOutline = attributes._drawOutline || true;
        attributes._enableLighting = attributes._enableLighting || false;
        attributes._interiorColor = attributes._interiorColor || Color.WHITE;
        attributes._outlineColor = attributes._outlineColor || Color.RED;
        attributes._outlineWidth = attributes._outlineWidth || 1.0;
        attributes._outlineStippleFactor = attributes._outlineStippleFactor || 0;
        attributes._outlineStipplePattern = attributes._outlineStipplePattern || 0xF0F0;
        attributes._imageSource = attributes._imageSource || null;
        attributes._depthTest = attributes._depthTest || true;
        attributes._drawVerticals = attributes._drawVerticals || false;
        attributes._applyLighting = attributes._applyLighting || false;

        return attributes;
    };

    /**
     * It returns default KmlStyle, which doesn't contain any custom information.
     * @returns {KmlStyle}
     */
    KmlStyle.default = function() {
        return new KmlStyle({
            objectNode: document.createElement('Style')
        });
    };

    return KmlStyle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/StyleResolver',[
    '../KmlFile',
    '../styles/KmlStyle',
    '../../../util/Logger'
], function (KmlFile,
             KmlStyle,
             Logger) {
    "use strict";

    /**
     * Provide functions for handling styles.
     * @exports StyleResolver
     */
    var StyleResolver = function (fileCache) {
        this._fileCache = fileCache;
    };

    /**
     * This function externalizes handling of the remote style based on the type of the style
     * @param styleUrl {String} Url of the style. Optional.
     * @param styleSelector {KmlStyleSelector} Style to be applied. Optional.
     * @param resolve {function} Function for resolving dependant promise
     * @param reject {function} Function for rejecting dependant promise
     * @param filePromise {Promise} Promise of the file. It is applied in the case of style url.
     */
    StyleResolver.prototype.handleRemoteStyle = function (styleUrl, styleSelector, resolve, reject, filePromise) {
        if (styleUrl) {
            this.handleStyleUrl(styleUrl, resolve, reject, filePromise);
        } else if (styleSelector) {
            this.handleStyleSelector(styleSelector, resolve, reject);
        } else {
            Logger.logMessage(Logger.LEVEL_WARNING, "StyleResolver", "handleRemoteStyle", "Style was null.");
            resolve(KmlStyle.default());
        }
    };

    // Intentionally undocumented. For internal use only
    StyleResolver.prototype.handleStyleUrl = function (styleUrl, resolve, reject, filePromise) {
        var self = this;
        filePromise = this.handlePromiseOfFile(styleUrl, filePromise);
        filePromise.then(function (kmlFile) {
            kmlFile.resolveStyle(styleUrl).then(function (style) {
                if (style.isMap) {
                    style.resolve(resolve, self);
                } else {
                    resolve({normal: style, highlight: null});
                }
            });
        });
    };

    // Intentionally undocumented. For internal use only
    StyleResolver.prototype.handlePromiseOfFile = function (styleUrl, filePromise) {
        if (!filePromise) {
            filePromise = this._fileCache.retrieve(styleUrl);
            if (!filePromise) {
                // This is an issue of circular dependency again.
                filePromise = new WorldWind.KmlFile({url: styleUrl});
                this._fileCache.add(filePromise);
            }
        }
        return filePromise;
    };

    // Intentionally undocumented. For internal use only
    StyleResolver.prototype.handleStyleSelector = function (styleSelector, resolve, reject) {
        if (styleSelector.isMap) {
            styleSelector.resolve(resolve, this);
        } else {
            // Move this resolve to the end of the stack to prevent recursion.
            window.setTimeout(function () {
                resolve({normal: styleSelector, highlight: null});
            }, 0);
        }
    };

    return StyleResolver;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/Pair',[
    './../KmlElements',
    '../KmlObject',
    '../styles/KmlStyleSelector',
    './NodeTransformers',
    '../../../util/Promise',
    '../util/StyleResolver'
], function (
    KmlElements,
    KmlObject,
    KmlStyleSelector,
    NodeTransformers,
    Promise,
    StyleResolver
) {
    "use strict";

    /**
     * Constructs a Pair. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias Pair
     * @constructor
     * @classdesc Contains the data associated with Kml Pair
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml Pair.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#pair
     * @augments KmlObject
     */
    var Pair = function (options) {
        KmlObject.call(this, options);
    };

    Pair.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(Pair.prototype, {
        /**
         * Identifies the key
         * @memberof Pair.prototype
         * @readonly
         * @type {String}
         */
        kmlKey: {
            get: function() {
                return this._factory.specific(this, {name: 'key', transformer: NodeTransformers.string});
            }
        },

        /**
         * References the style using Url. If part of the same document start with the prefix #
         * @memberof Pair.prototype
         * @readonly
         * @type {String}
         */
        kmlStyleUrl: {
            get: function() {
                return this._factory.specific(this, {name: 'styleUrl', transformer: NodeTransformers.string});
            }
        },

        /**
         * Definition of styles applied to this Pair.
         * @memberof Pair.prototype
         * @readonly
         * @type {KmlStyle}
         */
        kmlStyleSelector: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlStyleSelector.prototype.getTagNames()
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    Pair.prototype.getTagNames = function () {
        return ['Pair'];
    };

    /**
     * @inheritDoc
     */
    Pair.prototype.getStyle = function(styleResolver) {
        var self = this;
        return new Promise(function (resolve, reject) {
            window.setTimeout(function(){
                styleResolver.handleRemoteStyle(self.kmlStyleUrl, self.kmlStyleSelector, resolve, reject);
            },0);
        });
    };

    KmlElements.addKey(Pair.prototype.getTagNames()[0], Pair);

    return Pair;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/styles/KmlStyleMap',[
    '../KmlElements',
    './KmlSubStyle',
    '../util/Pair',
    '../../../util/Promise'
], function (KmlElements,
             KmlSubStyle,
             Pair,
             Promise) {
    "use strict";
    /**
     * Constructs an KmlStyleMap. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlStyleMap
     * @classdesc Contains the data associated with StyleMap node.
     * @param node {Node} Node representing style map in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#stylemap
     * @augments KmlSubStyle
     */
    var KmlStyleMap = function (node) {
        KmlSubStyle.call(this, node);
    };

    KmlStyleMap.prototype = Object.create(KmlSubStyle.prototype);

    Object.defineProperties(KmlStyleMap.prototype, {
        /**
         * Defines a key/value pair that maps a mode (normal or highlight) to the predefined &lt;styleUrl&gt;.
         * &lt;Pair&gt;
         * contains two elements (both are required):
         * &lt;key&gt;, which identifies the key
         * &lt;styleUrl&gt; or &lt;Style&gt;, which references the style. In &lt;styleUrl&gt;, for referenced style elements that are
         *  local to the KML document, a simple # referencing is used. For styles that are contained in external
         * files, use a full URL along with # referencing.
         * @memberof KmlStyleMap.prototype
         * @readonly
         * @type {Pair[]}
         */
        kmlPairs: {
            get: function () {
                return this._factory.all(this);
            }
        },

        isMap: {
            get: function() {
                return true;
            }
        }
    });

    /**
     * Resolve the information from style map and create the options with normal and highlight.
     * @param resolve Callback to be called when all promises are resolved with correct style.
     */
    KmlStyleMap.prototype.resolve = function(resolve, styleResolver) {
        // Create promise which resolves, when all styles are resolved.
        var self = this;
        var results = {};
        var promises = [];
        var pairs = self.kmlPairs;
        pairs.forEach(function(pair) {
            var key = pair.kmlKey;
            var style = pair.getStyle(styleResolver);
            promises.push(style);
            style.then(function(pStyle){
                results[key] = pStyle.normal;
            });
        });

        var compoundPromise = Promise.all(promises);
        compoundPromise.then(function(){
            if(!results['normal']){
                results['normal'] = null;
            }

            if(!results['highlight']){
                results['highlight'] =  null;
            }

            resolve(results);
        });
    };

    /**
     * @inheritDoc
     */
    KmlStyleMap.prototype.getTagNames = function() {
        return ['StyleMap'];
    };

    KmlElements.addKey(KmlStyleMap.prototype.getTagNames()[0], KmlStyleMap);

    return KmlStyleMap;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlTimeSpan
 */
define('formats/kml/KmlTimeSpan',[
    './KmlElements',
    './KmlTimePrimitive',
    './util/NodeTransformers'
], function(
    KmlElements,
    KmlTimePrimitive,
    NodeTransformers
){
    "use strict";
    /**
     * Constructs an KmlTimeSpan. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from KmlFile are read.
     * @alias KmlTimeSpan
     * @classdesc Contains the data associated with Kml TimeSpan
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml TimeSpan
     * @constructor
     * @throws {ArgumentError} If the content of the node contains invalid elements.
     * @see https://developers.google.com/kml/documentation/kmlreference#timespan
     * @augments KmlTimePrimitive
     */
    var KmlTimeSpan = function (options) {
        //noinspection JSUndefinedPropertyAssignment
        options.isTimeSpan = true;

        KmlTimePrimitive.call(this, options);
    };

    KmlTimeSpan.prototype = Object.create(KmlTimePrimitive.prototype);

    Object.defineProperties(KmlTimeSpan.prototype, {
        /**
         * Time from which is the event valid.
         * @memberof KmlTimeSpan.prototype
         * @type {Date}
         * @readonly
         */
        kmlBegin: {
            get: function() {
                return this._factory.specific(this, {name: 'begin', transformer: NodeTransformers.date});
            }
        },

        /**
         * Time to which is the event valid.
         * @memberof KmlTimeSpan.prototype
         * @type {Date}
         * @readonly
         */
        kmlEnd: {
            get: function() {
                return this._factory.specific(this, {name: 'end', transformer: NodeTransformers.date});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlTimeSpan.prototype.getTagNames = function () {
        return ['TimeSpan'];
    };

    KmlElements.addKey(KmlTimeSpan.prototype.getTagNames()[0], KmlTimeSpan);

    return KmlTimeSpan;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlTimeStamp
 */
define('formats/kml/KmlTimeStamp',[
    './KmlElements',
    './KmlTimePrimitive',
    './util/NodeTransformers'
], function (KmlElements,
             KmlTimePrimitive,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlTimeStamp. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from KmlFile are read.
     * @alias KmlTimeStamp
     * @classdesc Contains the data associated with Kml TimeStamp
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml TimeStamp
     * @constructor
     * @throws {ArgumentError} If the content of the node contains invalid elements.
     * @see https://developers.google.com/kml/documentation/kmlreference#timestamp
     * @augments KmlTimePrimitive
     */
    var KmlTimeStamp = function (options) {
        //noinspection JSUndefinedPropertyAssignment
        options.isTimeStamp = true;
        KmlTimePrimitive.call(this, options);
    };

    KmlTimeStamp.prototype = Object.create(KmlTimePrimitive.prototype);

    Object.defineProperties(KmlTimeStamp.prototype, {
        /**
         * This property specifies when exactly the event happen.
         * @memberof KmlTimeStamp.prototype
         * @type {Date}
         * @readonly
         */
        kmlWhen: {
            get: function () {
                return this._factory.specific(this, {name: 'when', transformer: NodeTransformers.date});
            }
        }
    });


    /**
     * @inheritDoc
     */
    KmlTimeStamp.prototype.getTagNames = function () {
        return ['TimeStamp'];
    };

    KmlElements.addKey(KmlTimeStamp.prototype.getTagNames()[0], KmlTimeStamp);

    return KmlTimeStamp;
});
define('formats/kml/util/RefreshListener',[], function(){
	/**
	 * Utility class which is associated with every Kml file. It allows parts of the KML to ask for the refresh which will happen in the correct time.
	 * Main usage is in the different modes of refresh in the NetworkLink / Link.
	 * The refresh listener works in this fashion:
	 * 	User, which is some of the classes supporting refreshes adds event to the Refresh listener. Event has some content and
	 * @constructor
	 * @alias RefreshListener
	 */
	var RefreshListener = function(){
		this.currentActiveEvents = [];
	};

	/**
	 * It adds event which should be scheduled later on. It is necessary to store it in a structure, which will return
	 * what is to be scheduled in a fast manner.
	 * @param event {RefreshListener.Event} Event which should be part of the Refresh listeners internals.
	 */
	RefreshListener.prototype.addEvent = function(event) {
		var self = this;
		setTimeout(function(){
			self.currentActiveEvents.push(event);
		}, event.time);
	};

	/**
	 * All events, which weren't scheduled and should still be.
	 * @return {RefreshListener.Event[]} It returns all events which should have been scheduled by now.
	 */
	RefreshListener.prototype.getActiveEvents = function() {
		var activeEvents = this.currentActiveEvents.slice();
		this.currentActiveEvents = [];
		return activeEvents;
	};

	/**
	 * It represents simple Event used inside of the RefreshListener.
	 * @alias RefreshListener.Event
	 * @constructor
	 * @param type {String} type of the event. The consumers decides whether the event is relevant for them based on this type.
	 * @param payload {Object} Object representing payload of the event. It is possible to schedule event with some additional information
	 * @param time {Number} Number of milliseconds before the event should occur.
	 */
	RefreshListener.Event = function(type, time, payload) {
		this.type = type;
		this.payload = payload;
		this.time = time;
	};

	return RefreshListener;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/RemoteFile',[
    '../../../error/ArgumentError',
    '../../../util/Logger',
    '../../../util/Promise'
], function (
    ArgumentError,
    Logger,
    Promise
) {
    "use strict";
    /**
     * Creates representation of RemoteFile. In order to load an object it is necessary to run get function on created object.
     * @param options {Object}
     * @param options.ajax {Boolean} If we should use plain AJAX
     * @param options.zip {Boolean} If we are downloading kmz
     * @param options.responseType {String} Optional responseType applied in specific circumstances for the kmz
     * @constructor
     * @alias RemoteFile
     */
    var RemoteFile = function(options) {
        if(!options.ajax && !options.zip) {
            throw new ArgumentError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "RemoteDocument", "constructor",
                    "Invalid option for retrieval specified. Use either ajax or zip option.")
            );
        }

        this.options = options;
    };

	/**
     * It retrieves the current file. Usually it is used only once, but it can be used multiple times.
     * @returns {Promise}
     */
    RemoteFile.prototype.get = function() {
        var options = this.options;
        if(options.ajax) {
            return this.ajax(options.url, options);
        } else if(options.zip) {
            options.responseType = options.responseType || "arraybuffer";
            return this.ajax(options.url, options);
        } else {
            // This branch should never happen.
            throw new ArgumentError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "RemoteDocument", "constructor",
                    "Invalid option for retrieval specified. Use either ajax or zip option.")
            );
        }
    };

    /**
     * Retrieves the data from remote server.
     * @param url {String} Url to query for data
     * @param options {Object}
     * @param options.responseType {String} If set, rewrites default responseType.
     * @returns {Promise} Promise of the data.
     */
    RemoteFile.prototype.ajax = function(url, options) {
        // Return promise.
        return new Promise(function (resolve, reject) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            if (options.responseType) {
                xhr.responseType = options.responseType;
            }
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var text;
                        if(options.responseType == 'arraybuffer') {
                            text = this.response;
                        } else {
                            text = this.responseText;
                        }
                        resolve({text: text, headers: xhr.getAllResponseHeaders()});
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "KmlFile retrieval failed (" + xhr.statusText + "): " + url);
                    }
                }
            });

            xhr.onerror = (function () {
                Logger.log(Logger.LEVEL_WARNING, "Remote file retrieval failed: " + url);

                reject();
            }).bind(this);

            xhr.ontimeout = (function () {
                Logger.log(Logger.LEVEL_WARNING, "Remote file retrieval timed out: " + url);

                reject();
            }).bind(this);

            xhr.send(null);
        });
    };

    return RemoteFile;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
// It simply adds XmlParser, which encapsulates the fact that, there are different implementations
define('util/XmlDocument',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function(
        ArgumentError,
        Logger
    ){
        /**
         * Constructor function responsible for abstracting away the complexities in parsing XmlDocuments.
         * @param document String representation of the xml document.
         * @constructor
         */
        var XmlDocument = function(document) {
            /**
             * Retrieved textual representation of the document.
             */
            this._document = document;
        };

        /**
         * This method abstracts parsing of XmlDocument away form users of this class. It should work in all browsers
         * since IE5
         * @returns {Document} Parsed dom.
         */
        XmlDocument.prototype.dom = function() {
            if(DOMParser) {
                var parser = new DOMParser();
                var parsedDocument = parser.parseFromString(this._document, "text/xml");
                if(parsedDocument.getElementsByTagName("parsererror").length || !parsedDocument) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "XmlDocument", "dom", "Invalid XML document. " +
                            parsedDocument.getElementsByTagName("parsererror")[0].innerHTML)
                    );
                }
                return parsedDocument;
            } else {
                // Support for IE6
                var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
                xmlDoc.async=false;
                xmlDoc.loadXML(text);
                return xmlDoc;
            }
        };

        XmlDocument.isValid = function(document) {
            // TODO refactor.
            try {
                new XmlDocument(document).dom();
                return true;
            } catch(e) {
                return false;
            }
        };

        return XmlDocument;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * It is basically a collection of KmlRecords.
 * @exports KmlParser
 */
define('formats/kml/KmlFile',[
    '../../error/ArgumentError',
    '../../util/jszip',
    './KmlElements',
    './KmlFileCache',
    './KmlObject',
    './styles/KmlStyle',
    './styles/KmlStyleMap',
    './KmlTimeSpan',
    './KmlTimeStamp',
    '../../util/Logger',
    '../../util/Promise',
    './util/RefreshListener',
    './util/RemoteFile',
    './util/StyleResolver',
    '../../util/XmlDocument',
    '../../util/WWUtil'
], function (ArgumentError,
             JsZip,
             KmlElements,
             KmlFileCache,
             KmlObject,
             KmlStyle,
             KmlStyleMap,
             KmlTimeSpan,
             KmlTimeStamp,
             Logger,
             Promise,
             RefreshListener,
             RemoteFile,
             StyleResolver,
             XmlDocument,
             WWUtil) {
    "use strict";

    // TODO: Make sure that the KmlFile is also rendered as a part of this hierarchy and not added to the layer.
    /**
     * Constructs an object for Kml file. Applications usually don't call this constructor.
     * Parses associated KmlFile and allows user to draw the whole KmlFile in passed layer. The whole file is
     * rendered in one Layer.
     * @constructor
     * @param url {String} Url of the remote document.
     * @param controls {KmlControls[]} List of controls applied to this File.
     * @alias KmlFile
     * @classdesc Support for Kml File parsing and display.
     * @augments KmlObject
     */
    var KmlFile = function (url, controls) {
        var self = this;
        if (!url) {
            throw new ArgumentError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "KmlFile", "constructor", "invalidDocumentPassed")
            );
        }

        // Default values.
        this._controls = controls || null;
        this._fileCache = new KmlFileCache();
        this._styleResolver = new StyleResolver(this._fileCache);
        this._listener = new RefreshListener();
        this._headers = null;
        
        var filePromise;
        // Load the document
        filePromise = new Promise(function (resolve) {
            var promise = self.requestRemote(url);
            promise.then(function (options) {
                var rootDocument = null;
                var loadedDocument = options.text;
                self._headers = options.headers;
                if (!self.hasExtension("kmz", url)) {
                    rootDocument = loadedDocument;
                } else {
                    var kmzFile = new JsZip();
                    kmzFile.load(loadedDocument);
                    for(var key in kmzFile.files) {
                        var file = kmzFile.files[key];
                        if (rootDocument == null && self.hasExtension("kml", file.name)) {
                            rootDocument = file.asText();
                        }
                    }
                }

                self._document = new XmlDocument(rootDocument).dom();
                KmlObject.call(self, {objectNode: self._document.documentElement, controls: controls});

                window.setTimeout(function () {
                    resolve(self);
                }, 0);
            });
        });
        this._fileCache.add(url, filePromise);
        return filePromise;
    };

    KmlFile.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlFile.prototype, {
        /**
         * Contains shapes present in the document. Cache so that we don't need to parse the document every time
         * it is passed through.
         * @type {KmlObject[]}
         * @memberof KmlFile.prototype
         * @readonly
         */
        shapes: {
            get: function () {
                return this._factory.all(this);
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlFile.prototype.render = function (dc, kmlOptions) {
        var self = this;
        kmlOptions = kmlOptions || {};
        this.shapes.forEach(function (shape) {
            shape.render(dc, {
                lastStyle: kmlOptions.lastStyle || null,
                lastVisibility: kmlOptions.lastVisibility || null,
                currentTimeInterval: kmlOptions.currentTimeInterval || null,
                regionInvisible: kmlOptions.regionInvisible || null,
                fileCache: self._fileCache,
                styleResolver: self._styleResolver,
                listener: self._listener,
                activeEvents: self._listener.getActiveEvents()
            });
        });
    };

    /**
     * FOR INTERNAL USE ONLY.
     * Returns a value indicating whether the URL ends with the given extension.
     * @param url {String} Url to a file
     * @returns {boolean} true if the extension matches otherwise false
     */
    KmlFile.prototype.hasExtension = function (extension, url) {
        return WWUtil.endsWith(url, "." + extension);
    };

    /**
     * FOR INTERNAL USE ONLY.
     * Based on the information from the URL, return correct Remote object.
     * @param url {String} Url of the document to retrieve.
     * @returns {Promise} Promise of Remote.
     */
    KmlFile.prototype.requestRemote = function (url) {
        var options = {};
        options.url = url;
        if (this.hasExtension("kmz", url)) {
            options.zip = true;
        } else {
            options.ajax = true;
        }

        return new RemoteFile(options).get();
    };

	/**
     * It finds the style in the document.
     * @param pId {String} Id of the style.
     */
    KmlFile.prototype.resolveStyle = function (pId) {
        var self = this;
        var id = pId.substring(pId.indexOf('#') + 1, pId.length);
        // It returns promise of the Style.
        return new Promise(function (resolve, reject) {
            var style;
            if (self._document.querySelector) {
                style = self._document.querySelector("*[id='" + id + "']");
            } else {
                style = self._document.getElementById(id);
            }
            if (!style || style == null) {
                reject();
            }

            if (style.nodeName == KmlStyle.prototype.getTagNames()[0]) {
                resolve(new KmlStyle({objectNode: style}, {styleResolver: self._styleResolver}));
            } else if (style.nodeName == KmlStyleMap.prototype.getTagNames()[0]) {
                resolve(new KmlStyleMap({objectNode: style}, {styleResolver: self._styleResolver}));
            } else {
                Logger.logMessage(Logger.LEVEL_WARNING, "KmlFile", "resolveStyle", "Style must contain either" +
                    " Style node or StyleMap node.");
            }
        });
    };

	/**
     * This function returns expire time of this file in miliseconds.
     * @returns {Number} miliseconds for this file to expire.
     */
    KmlFile.prototype.getExpired = function() {
        var expireDate = new Date(this._headers.getRequestHeader("Expires"));
        var currentDate = new Date();
        return currentDate.getTime - expireDate.getTime();
    };

    return KmlFile;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLatLonAltBox',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject, 
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlLatLonAltBox. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLatLonAltBox
     * @classdesc Contains the data associated with LatLonAltBox node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing alternative lat lon box in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#latlonaltbox
     * @aguments KmlObject
     */
    var KmlLatLonAltBox = function (options) {
        KmlObject.call(this, options);
    };

    KmlLatLonAltBox.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLatLonAltBox.prototype, {
        /**
         * Specifies the latitude of the north edge of the bounding box, in decimal degrees from 0 to +-90.
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlNorth: {
            get: function () {
                return this._factory.specific(this, {name: 'north', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the latitude of the south edge of the bounding box, in decimal degrees from 0 to +-90.
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlSouth: {
            get: function () {
                return this._factory.specific(this, {name: 'south', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the longitude of the east edge of the bounding box, in decimal degrees from 0 to +-180.
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlEast: {
            get: function () {
                return this._factory.specific(this, {name: 'east', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the longitude of the west edge of the bounding box, in decimal degrees from 0 to +-180.
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlWest: {
            get: function () {
                return this._factory.specific(this, {name: 'west', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specified in meters (and is affected by the altitude mode specification).
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlMinAltitude: {
            get: function () {
                return this._factory.specific(this, {name: 'minAltitude', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specified in meters (and is affected by the altitude mode specification).
         * @memberof KmlLatLonAltBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlMaxAltitude: {
            get: function () {
                return this._factory.specific(this, {name: 'maxAltitude', transformer: NodeTransformers.number});
            }
        }
    });


    /**
     * @inheritDoc
     */
    KmlLatLonAltBox.prototype.getTagNames = function () {
        return ['LatLonAltBox'];
    };

    KmlElements.addKey(KmlLatLonAltBox.prototype.getTagNames()[0], KmlLatLonAltBox);

    return KmlLatLonAltBox;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLod',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject, 
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlLod. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLod
     * @classdesc Contains the data associated with Lod node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing lod in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#lod
     * @augments KmlObject
     */
    var KmlLod = function (options) {
        KmlObject.call(this, options);
    };

    KmlLod.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLod.prototype, {
        /**
         * Defines a square in screen space, with sides of the specified value in pixels. For example, 128 defines
         * a
         * square of 128 x 128 pixels. The region's bounding box must be larger than this square (and smaller than
         * the maxLodPixels square) in order for the Region to be active.
         *
         * More details are available in the Working with Regions chapter of the Developer's Guide, as well as the
         *  Google Earth Outreach documentation's Avoiding Overload with Regions tutorial.
         * @memberof KmlLod.prototype
         * @readonly
         * @type {Number}
         */
        kmlMinLodPixels: {
            get: function () {
                return this._factory.specific(this, {name: 'minLodPixels', transformer: NodeTransformers.number});
            }
        },

        /**
         * Measurement in screen pixels that represents the maximum limit of the visibility range for a given
         * Region. A value of -1, the default, indicates "active to infinite size."
         * @memberof KmlLod.prototype
         * @readonly
         * @type {Number}
         */
        kmlMaxLodPixels: {
            get: function () {
                return this._factory.specific(this, {name: 'maxLodPixels', transformer: NodeTransformers.number});
            }
        },

        /**
         * Distance over which the geometry fades, from fully opaque to fully transparent. This ramp value,
         * expressed in screen pixels, is applied at the minimum end of the LOD (visibility) limits.
         * @memberof KmlLod.prototype
         * @readonly
         * @type {Number}
         */
        kmlMinFadeExtent: {
            get: function () {
                return this._factory.specific(this, {name: 'minFadeExtent', transformer: NodeTransformers.number});
            }
        },

        /**
         * Distance over which the geometry fades, from fully transparent to fully opaque. This ramp value,
         * expressed in screen pixels, is applied at the maximum end of the LOD (visibility) limits.
         * @memberof KmlLod.prototype
         * @readonly
         * @type {Number}
         */
        kmlMaxFadeExtent: {
            get: function () {
                return this._factory.specific(this, {name: 'maxFadeExtent', transformer: NodeTransformers.number});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlLod.prototype.getTagNames = function () {
        return ['Lod'];
    };

    KmlElements.addKey(KmlLod.prototype.getTagNames()[0], KmlLod);

    return KmlLod;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlRegion',[
    '../../geom/BoundingBox',
    '../../util/Color',
    './KmlElements',
    './KmlLatLonAltBox',
    './KmlLod',
    './KmlObject',
    './styles/KmlStyle',
    './util/NodeTransformers',
    '../../geom/Sector'
], function (BoundingBox,
             Color,
             KmlElements,
             KmlLatLonAltBox,
             KmlLod,
             KmlObject,
             KmlStyle,
             NodeTransformers,
             Sector) {
    "use strict";

    /**
     * Constructs an KmlRegion. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlRegion
     * @classdesc Contains the data associated with Region node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing region in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#region
     */
    var KmlRegion = function (options) {
        KmlObject.call(this, options);
    };

    KmlRegion.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlRegion.prototype, {
        /**
         * A bounding box that describes an area of interest defined by geographic coordinates and altitudes.
         * Default values and required fields are as follows:
         * @memberof KmlRegion.prototype
         * @readonly
         * @type {KmlLatLonBox}
         */
        kmlLatLonAltBox: {
            get: function () {
                return this._factory.specific(this, {name: KmlLatLonAltBox.prototype.getTagNames(), transformer: NodeTransformers.kmlObject});
            }
        },

        /**
         * Lod is an abbreviation for Level of Detail. &lt;Lod&gt; describes the size of the projected region on the
         * screen that is required in order for the region to be considered "active." Also specifies the size of
         * the pixel ramp used for fading in (from transparent to opaque) and fading out (from opaque to
         * transparent). See diagram below for a visual representation of these parameters.
         * @memberof KmlRegion.prototype
         * @readonly
         * @type {KmlLod}
         */
        kmlLod: {
            get: function () {
                return this._factory.specific(this, {name: KmlLod.prototype.getTagNames(), transformer: NodeTransformers.kmlObject});
            }
        }
    });

	/**
	 * It tests whether the region intersects the visible area.
     * @param dc {DrawContext} Frustum to test for intersection.
     */
    KmlRegion.prototype.intersectsVisible = function(dc) {
        var box = this.kmlLatLonAltBox;

        var boundingBoxForRegion = new BoundingBox();
        boundingBoxForRegion.setToSector(new Sector(box.kmlSouth, box.kmlNorth, box.kmlWest, box.kmlEast), dc.globe, box.kmlMinAltitude, box.kmlMaxAltitude);

        return boundingBoxForRegion.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates)&&
            (!box.kmlMinAltitude || dc.eyePosition.altitude > box.kmlMinAltitude) &&
            (!box.kmlMaxAltitude || dc.eyePosition.altitude < box.kmlMaxAltitude);
    };

    /**
     * @inheritDoc
     */
    KmlRegion.prototype.getTagNames = function () {
        return ['Region'];
    };

    KmlElements.addKey(KmlRegion.prototype.getTagNames()[0], KmlRegion);

    return KmlRegion;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlFeature',[
    './../KmlObject',
    '../KmlAbstractView',
    '../KmlFile',
    '../styles/KmlStyleMap',
    '../styles/KmlStyleSelector',
    '../KmlRegion',
    '../KmlTimePrimitive',
    '../util/NodeTransformers',
    '../../../util/Promise'
], function (KmlObject,
             KmlAbstractView,
             KmlFile,
             KmlStyleMap,
             KmlStyleSelector,
             KmlRegion,
             KmlTimePrimitive,
             NodeTransformers,
             Promise) {
    "use strict";
    /**
     * Constructs an KmlFeature. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read
     * @alias KmlFeature
     * @classdesc Contains the data associated with KmlFeature.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Feature
     * @constructor
     * @throws {ArgumentError} If the node is null.
     * @see https://developers.google.com/kml/documentation/kmlreference#feature
     * @augments KmlObject
     */
    var KmlFeature = function (options) {
        //noinspection JSUndefinedPropertyAssignment
        this.isFeature = options.isFeature = true;

        KmlObject.call(this, options);

        this._pStyle = null;
        this.controlledVisibility = null;
    };

    KmlFeature.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlFeature.prototype, {
		/**
         * Style of this feature. Every feature should have a style. If there is no Style, null is returned.
         */
        style: {
            get: function() {
                return this._pStyle;
            }
        },

        /**
         * Name of this feature. Every feature should have name.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlName: {
            get: function () {
                return this._factory.specific(this, {name: 'name', transformer: NodeTransformers.string});
            }
        },

        /**
         * Visibility of current feature. It is possible for some features to be invisible.
         * @memberof KmlFeature.prototype
         * @type {Boolean}
         * @readonly
         */
        kmlVisibility: {
            get: function () {
                return this._factory.specific(this, {name: 'visibility', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * It is applied only to Document, Folder and NetworkLink and represents whether they should be rendered
         * collapsed or expanded.
         * @memberof KmlFeature.prototype
         * @type {Boolean}
         * @readonly
         */
        kmlOpen: {
            get: function () {
                return this._factory.specific(this, {name: 'open', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * It represents unstructured address associated with the Feature.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlAddress: {
            get: function () {
                return this._factory.specific(this, {name: 'address', transformer: NodeTransformers.string});
            }
        },

        /**
         * It represents phone number associated with current feature. Quite probably irrelevant information.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlPhoneNumber: {
            get: function () {
                return this._factory.specific(this, {name: 'phoneNumber', transformer: NodeTransformers.string});
            }
        },

        /**
         * It represents description of this feature. It can be displayed as a part of the feature.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlDescription: {
            get: function () {
                return this._factory.specific(this, {name: 'description', transformer: NodeTransformers.string});
            }
        },

        /**
         * URL of a &lt;Style&gt; or &lt;StyleMap&gt; defined in a Document. If the style is in the same file, use
         * a # reference. If the style is defined in an external file, use a full URL along with # referencing. If
         * it references remote URL, this server must support CORS for us to be able to download it.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlStyleUrl: {
            get: function () {
                return this._factory.specific(this, {name: 'styleUrl', transformer: NodeTransformers.string});
            }
        },

        /**
         * A short description of the feature. In Google Earth, this description is displayed in the Places panel
         * under the name of the feature. If a Snippet is not supplied, the first two lines of the
         * &lt;description&gt; are used. In Google Earth, if a Placemark contains both a description and a Snippet,
         * the &lt;Snippet&gt; appears beneath the Placemark in the Places panel, and the &lt;description&gt;
         * appears in the Placemark's description balloon. This tag does not support HTML markup. &lt;Snippet&gt;
         * has a maxLines attribute, an integer that specifies the maximum number of lines to display.
         * @memberof KmlFeature.prototype
         * @type {String}
         * @readonly
         */
        kmlSnippet: {
            get: function () {
                return this._factory.specific(this, {name: 'Snippet', transformer: NodeTransformers.string});
            }
        },

        /**
         * It represents one of the AbstractViews associated with current Feature. Specific implementation of
         * AbstractView will be returned.
         * @memberof KmlFeature.prototype
         * @type {KmlAbstractView}
         * @readonly
         */
        kmlAbstractView: {
            get: function () {
                return this._factory.any(this, {name: KmlAbstractView.prototype.getTagNames()});
            }
        },

        /**
         * It represents one of the TimePrimitives associated with current Feature. Specific implementation of
         * TimePrimitive will be returned.
         * @memberof KmlFeature.prototype
         * @type {KmlTimePrimitive}
         * @readonly
         */
        kmlTimePrimitive: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlTimePrimitive.prototype.getTagNames()
                });
            }
        },

        /**
         * One style element per Feature, with possible children of different substyles.
         * @memberof KmlFeature.prototype
         * @type {KmlStyle}
         * @readonly
         */
        kmlStyleSelector: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlStyleSelector.prototype.getTagNames()
                });
            }
        },

        /**
         * Features and geometry associated with a Region are drawn only when the Region is active. See
         * &lt;Region&gt;.
         * @memberof KmlFeature.prototype
         * @type {KmlRegion}
         * @readonly
         */
        kmlRegion: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlRegion.prototype.getTagNames()
                });
            }
        }
    });

	/**
	 * @inheritDoc
     */
    KmlFeature.prototype.render = function(dc, kmlOptions) {
        KmlObject.prototype.render.call(this, dc, kmlOptions);

        this.solveStyle(dc, kmlOptions);
        this.solveVisibility(dc, kmlOptions);
    };

	/**
     * Internal use only
     * It solves style which should be applied to current feature.
     * @param dc {DrawContext} DrawContext associated with current processing.
     * @param kmlOptions {Object}
     * @param kmlOptions.lastStyle {KmlStyle} Style representing the one to apply to current information.
     */
    KmlFeature.prototype.solveStyle = function(dc, kmlOptions) {
        this.getStyle(dc, kmlOptions);
        if(this.style != null) {
            kmlOptions.lastStyle = this.style;
        }
    };

	/**
     * Internal use only
     * It solves whether the feature should be visible based on the Region.
     * @param dc {DrawContext} Draw context associated with current processing.
     * @returns {boolean} true if there is no region or the feature is in the region.
     */
    KmlFeature.prototype.solveRegion = function(dc) {
        if(this.kmlRegion) {
            return this.kmlRegion.intersectsVisible(dc);
        } else {
            return true;
        }
    };

	/**
     * Internal use only
     * It solves whether current feature should be visible. It takes into account the visibility of parent elements, Time constraints, region, visibility.
     * @param dc {DrawContext} Draw context associated with current processing.
     * @param kmlOptions {Object}
     */
    KmlFeature.prototype.solveVisibility = function(dc, kmlOptions) {
        var parentVisibility = kmlOptions.lastVisibility !== false;
        var timeBasedVisibility = this.solveTimeVisibility(dc);
        var regionVisibility = this.solveRegion(dc);
        var myVisibility = this.kmlVisibility !== false;
        var controlledVisibility = this.controlledVisibility !== false;

        this.enabled = parentVisibility && timeBasedVisibility && regionVisibility && myVisibility && controlledVisibility;

        kmlOptions.lastVisibility = this.enabled;
        if(this._renderable) {
            this._renderable.enabled = this.enabled;
        }
    };

    /**
     * Internal function for solving the time visibility. The element is visible when its whole range is inside the
     * time range chosen by user.
     */
    KmlFeature.prototype.solveTimeVisibility = function (dc) {
        var timeRangeOfFeature = this.kmlTimePrimitive && this.kmlTimePrimitive.timeRange();

        if (dc.currentLayer.currentTimeInterval && timeRangeOfFeature) {
            var from = dc.currentLayer.currentTimeInterval[0];
            var to = dc.currentLayer.currentTimeInterval[1];

            if (
                timeRangeOfFeature &&
                (
                    timeRangeOfFeature.from < from ||
                    timeRangeOfFeature.from > to ||
                    timeRangeOfFeature.to > to
                )
            ) {
                return false;
            }
        }

        return true;
    };

    /**
     * It retrieves the style for current element, regardless of whether it is local or remote.
     * @params dc {DrawContext} All contextual information for rendering.
     * @params kmlOptions {Object}
     * @params kmlOptions.styleResolver {StyleResolver} Instance of StyleResolver used in this file.
     * @return {Promise|undefined} Promise of the file to deliver
     */
    KmlFeature.prototype.getStyle = function (dc, kmlOptions) {
        if (this._pStyle) {
            return;
        }

        var self = this;
        new Promise(function (resolve, reject) {
            window.setTimeout(function () {
                // TODO: Refactor handle Remote Style.
                kmlOptions.styleResolver.handleRemoteStyle(self.kmlStyleUrl, self.kmlStyleSelector, resolve, reject);
            }, 0);
        }).then(function(styles){
            self._pStyle = styles;
            dc.redrawRequested = true;
        });
    };

    /**
     * @inheritDoc
     */
    KmlFeature.prototype.getTagNames = function () {
        return ['NetworkLink', 'Placemark', 'PhotoOverlay', 'ScreenOverlay', 'GroundOverlay', 'Folder',
            'Document'];
    };

    return KmlFeature;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlContainer',[
    './KmlFeature'
], function (KmlFeature) {
    "use strict";
    /**
     * Constructs an KmlContainer. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlContainer
     * @classdesc Contains the data associated with Container options.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the container.
     * @constructor
     * @throws {ArgumentError} If the options is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#container
     * @augments KmlFeature
     */
    var KmlContainer = function (options) {
        KmlFeature.call(this, options);
    };

    KmlContainer.prototype = Object.create(KmlFeature.prototype);

    Object.defineProperties(KmlContainer.prototype, {
        /**
         * Specifies any amount of features, which are part of this document.
         * @memberof KmlDocument.prototype
         * @readonly
         * @type {Node[]}
         * @see {KmlFeature}
         */
        kmlShapes: {
            get: function(){
                var allElements = this._factory.all(this);
                return allElements.filter(function (element) {
                    // For now display only features.
                    return element.isFeature;
                });
            }
        }
    });

	/**
     * @inheritDoc
     */
    KmlContainer.prototype.render = function(dc, kmlOptions) {
        KmlFeature.prototype.render.call(this, dc, kmlOptions);

        var self = this;
        this.kmlShapes.forEach(function(shape) {
            shape.render(dc, {
                lastStyle: kmlOptions.lastStyle,
                lastVisibility: self.enabled,
                currentTimeInterval: kmlOptions.currentTimeInterval,
                regionInvisible: kmlOptions.regionInvisible,
                fileCache: kmlOptions.fileCache,
                styleResolver: kmlOptions.styleResolver,
                listener: kmlOptions.listener,
                activeEvents: kmlOptions.activeEvents
            });
        });
    };


    /**
     * @inheritDoc
     */
    KmlContainer.prototype.getTagNames = function () {
        return ['Folder', 'Document'];
    };

    return KmlContainer;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/controls/KmlControls',['../../../util/Logger'], function (Logger) {
    "use strict";
    /**
     * Every control used by the KML should inherit from this class. It contains common functionality and basically
     * serves as a reference to what needs to be implemented in the descendants.
     * @alias KmlControls
     * @constructor
     */
    var KmlControls = function() {

    };

    /**
     * Controls added to the KML document will be notified by the update of the Kml document. Hook is method which is
     * called once, when the element is updated. It is necessary to be careful and hook the element only once. The
     * other solution is to make sure the ids will be used correctly.
     */
    KmlControls.prototype.hook = function() {
        Logger.logMessage(Logger.LEVEL_WARNING, "KmlControls", "hook", "Every KML controls should override hook" +
            " method.");
    };

    return KmlControls;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/Schema',[
    './../KmlElements',
    '../KmlObject'
], function (KmlElements,
             KmlObject) {
    "use strict";
    /**
     * Constructs an Schema. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias Schema
     * @constructor
     * @classdesc Contains the data associated with Kml Schema
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml Schema.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#itemicon
     * @augments KmlObject
     */
    var Schema = function (options) {
        KmlObject.call(this, options);
    };

    Schema.prototype = Object.create(KmlObject.prototype);

    /**
     * @inheritDoc
     */
    Schema.prototype.getTagNames = function () {
        return ['Schema'];
    };

    KmlElements.addKey(Schema.prototype.getTagNames()[0], Schema);

    return Schema;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlDocument',[
    './KmlContainer',
    '../KmlElements',
    './KmlFeature',
    '../util/Schema'
], function (
    KmlContainer,
    KmlElements,
    KmlFeature,
    Schema
) {
    "use strict";
    /**
     * Constructs an KmlDocument. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlDocument
     * @classdesc Contains the data associated with Document options.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the document.
     * @constructor
     * @throws {ArgumentError} If the options is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#document
     * @augments KmlContainer
     */
    var KmlDocument = function (options) {
        KmlContainer.call(this, options);
    };

    KmlDocument.prototype = Object.create(KmlContainer.prototype);

    Object.defineProperties(KmlDocument.prototype, {
        /**
         * Specifies a custom KML schema that is used to add custom data to KML Features. The "id" attribute is
         * required and must be unique within the KML file. &lt;Schema&gt; is always a child of &lt;Document&gt;.
         * This is array of all Schemas in current document
         * @memberof KmlDocument.prototype
         * @readonly
         * @type {Schema[]}
         * @see {Schema}
         */
        kmlSchemas: {
            get: function(){
                var allElements = this._factory.all(this);
                return allElements.filter(function (element) {
                    return element instanceof Schema;
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlDocument.prototype.getTagNames = function () {
        return ['Document'];
    };

    KmlElements.addKey(KmlDocument.prototype.getTagNames()[0], KmlDocument);

    return KmlDocument;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlFolder',[
    './KmlContainer',
    './../KmlElements'
], function (
    KmlContainer,
    KmlElements
) {
    "use strict";
    /**
     * Constructs an KmlFolder. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlFolder
     * @classdesc Contains the data associated with Folder options.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing this Folder
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#folder
     * @augments KmlContainer
     */
    var KmlFolder = function (options) {
        KmlContainer.call(this, options);
    };

    KmlFolder.prototype = Object.create(KmlContainer.prototype);

    /**
     * @inheritDoc
     */
    KmlFolder.prototype.getTagNames = function () {
        return ['Folder'];
    };

    KmlElements.addKey(KmlFolder.prototype.getTagNames()[0], KmlFolder);

    return KmlFolder;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlGeometry
 */
define('formats/kml/geom/KmlGeometry',[
    '../KmlObject'
], function (KmlObject) {
    "use strict";
    /**
     * Constructs an KmlGeometry. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read.
     * @alias KmlGeometry
     * @constructor
     * @classdesc Contains the data associated with Kml geometry
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Geometry
     * @throws {ArgumentError} If either the node is null or the content of the Kml point contains invalid elements.
     * @see https://developers.google.com/kml/documentation/kmlreference#geometry
     * @augments KmlObject
     */
    var KmlGeometry = function (options) {
        KmlObject.call(this, options);

        this._renderable = null;
    };

    KmlGeometry.prototype = Object.create(KmlObject.prototype);

	/**
     * @inheritDoc
     */
    KmlGeometry.prototype.render = function(dc, kmlOptions) {
        KmlObject.prototype.render.call(this, dc, kmlOptions);

        this.enabled = kmlOptions.lastVisibility;
    };

    /**
     * @inheritDoc
     */
    KmlGeometry.prototype.getTagNames = KmlGeometry.getTagNames = function () {
        return ['Point', 'LinearRing', 'LineString', 'MultiGeometry', 'Polygon'];
    };

    return KmlGeometry;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLatLonBox',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlLatLonBox. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLatLonBox
     * @classdesc Contains the data associated with LatLonBox node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing box lat lon in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#latlonbox
     * @augments KmlObject
     */
    var KmlLatLonBox = function (options) {
        KmlObject.call(this, options);
    };

    KmlLatLonBox.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLatLonBox.prototype, {
        /**
         * Specifies the latitude of the north edge of the bounding box, in decimal degrees from 0 to +-90.
         * @memberof KmlLatLonBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlNorth: {
            get: function () {
                return this._factory.specific(this, {name: 'north', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the latitude of the south edge of the bounding box, in decimal degrees from 0 to +-90.
         * @memberof KmlLatLonBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlSouth: {
            get: function () {
                return this._factory.specific(this, {name: 'south', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the longitude of the east edge of the bounding box, in decimal degrees from 0 to +-180.
         * @memberof KmlLatLonBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlEast: {
            get: function () {
                return this._factory.specific(this, {name: 'east', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies the longitude of the west edge of the bounding box, in decimal degrees from 0 to +-180.
         * @memberof KmlLatLonBox.prototype
         * @readonly
         * @type {Number}
         */
        kmlWest: {
            get: function () {
                return this._factory.specific(this, {name: 'west', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies a rotation of the overlay about its center, in degrees. Values can be +-180. The default is 0
         * (north). Rotations are specified in a counterclockwise direction.
         * @memberof KmlLatLonBox.prototype
         * @readonly
         * @type {String}
         */
        kmlRotation: {
            get: function () {
                return this._factory.specific(this, {name: 'rotation', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlLatLonBox.prototype.getTagNames = function () {
        return ['LatLonBox'];
    };

    KmlElements.addKey(KmlLatLonBox.prototype.getTagNames()[0], KmlLatLonBox);

    return KmlLatLonBox;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLatLonQuad',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlLatLonQuad. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLatLonQuad
     * @classdesc Contains the data associated with LatLonQuad node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing lat lon quadruple in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#gxlatlonquad
     * @augments KmlObject
     */
    var KmlLatLonQuad = function (options) {
        KmlObject.call(this, options);
    };

    KmlLatLonQuad.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLatLonQuad.prototype, {
        /**
         * Specifies the coordinates of the four corner points of a quadrilateral defining the overlay area.
         * Exactly
         * four coordinate tuples have to be provided, each consisting of floating point values for longitude and
         * latitude. Insert a space between tuples. Do not include spaces within a tuple. The coordinates must be
         * specified in counter-clockwise order with the first coordinate corresponding to the lower-left corner of
         * the overlayed image. The shape described by these corners must be convex.
         * @memberof KmlLatLonQuad.prototype
         * @readonly
         * @type {String}
         */
        kmlCoordinates: {
            get: function () {
                return this._factory.specific(this, {name: 'coordinates', transformer: NodeTransformers.string});
            }
        }
    });


    /**
     * @inheritDoc
     */
    KmlLatLonQuad.prototype.getTagNames = function () {
        return ['gx:LatLonQuad'];
    };

    KmlElements.addKey(KmlLatLonQuad.prototype.getTagNames()[0], KmlLatLonQuad);

    return KmlLatLonQuad;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlOverlay',[
    './KmlFeature',
    './../KmlIcon',
    '../util/NodeTransformers'
], function (KmlFeature,
             KmlIcon,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlOverlay. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlOverlay
     * @classdesc Contains the data associated with Overlay node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Overlay
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#overlay
     * @augments KmlFeature
     */
    var KmlOverlay = function (options) {
        KmlFeature.call(this, options);
    };

    KmlOverlay.prototype = Object.create(KmlFeature.prototype);

    Object.defineProperties(KmlOverlay.prototype, {
        /**
         * Color values are expressed in hexadecimal notation, including opacity (alpha) values. The order of
         * expression is alpha, blue, green, red (aabbggrr). The range of values for any one color is 0 to 255 (00
         * to ff). For opacity, 00 is fully transparent and ff is fully opaque. For example, if you want to apply a
         * blue color with
         * 50 percent opacity to an overlay, you would specify the following: &lt;color&gt;7fff0000&lt;/color&gt;
         * @memberof KmlOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlColor: {
            get: function() {
                return this._factory.specific(this, {name: 'color', transformer: NodeTransformers.string});
            }
        },

        /**
         * This element defines the stacking order for the images in overlapping overlays. Overlays with higher
         * &lt;drawOrder&gt; values are drawn on top of overlays with lower &lt;drawOrder&gt; values.
         * @memberof KmlOverlay.prototype
         * @readonly
         * @type {Number}
         */
        kmlDrawOrder: {
            get: function() {
                return this._factory.specific(this, {name: 'drawOrder', transformer: NodeTransformers.string});
            }
        },

        /**
         * Defines the image associated with the Overlay. The &lt;href&gt; element defines the location of the image to
         * be
         * used as the Overlay. This location can be either on a local file system or on a web server. If this
         * element is omitted or contains no &lt;href&gt;, a rectangle is drawn using the color and size defined by the
         * ground or screen overlay.
         * @memberof KmlOverlay.prototype
         * @readonly
         * @type {KmlIcon}
         */
        kmlIcon: {
            get: function(){
                return this._factory.any(this, {
                    name: KmlIcon.prototype.getTagNames()
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlOverlay.prototype.getTagNames = function () {
        return ['PhotoOverlay', 'ScreenOverlay', 'GroundOverlay'];
    };

    return KmlOverlay;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlGroundOverlay',[
    './../KmlElements',
    './KmlFeature',
    '../KmlLatLonBox',
    '../KmlLatLonQuad',
    './KmlOverlay',
    '../util/NodeTransformers',
    '../../../geom/Sector',
    '../../../shapes/SurfaceImage'
], function (
    KmlElements,
    KmlFeature,
    KmlLatLonBox,
    KmlLatLonQuad,
    KmlOverlay,
    NodeTransformers,
    Sector,
    SurfaceImage
) {
    "use strict";

    /**
     * Constructs an KmlGroundOverlay. Applications usually don't call this constructor. It is called by {@link
     * KmlFile} as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlGroundOverlay
     * @classdesc Contains the data associated with GroundOverlay node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing GroundOverlay
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#groundoverlay
     * @augments KmlOverlay
     */
    var KmlGroundOverlay = function (options) {
        this.isGroundOverlay = true;

        KmlOverlay.call(this, options);
    };

    KmlGroundOverlay.prototype = Object.create(KmlOverlay.prototype);

    Object.defineProperties(KmlGroundOverlay.prototype, {
        /**
         * Specifies the distance above the earth's surface, in meters, and is interpreted according to the altitude
         * mode.
         * @memberof KmlGroundOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitude: {
            get: function() {
                return this._factory.specific(this, {name: 'altitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies how the &lt;altitude&gt;is interpreted.
         * @memberof KmlGroundOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitudeMode: {
            get: function() {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string});
            }
        },

        /**
         * Specifies where the top, bottom, right, and left sides of a bounding box for the ground overlay are
         * aligned.
         * @memberof KmlGroundOverlay.prototype
         * @readonly
         * @type {KmlLatLonBox}
         */
        kmlLatLonBox: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlLatLonBox.prototype.getTagNames()
                });
            }
        },

        /**
         * Used for nonrectangular quadrilateral ground overlays.
         * @memberof KmlGroundOverlay.prototype
         * @readonly
         * @type {KmlLatLonQuad}
         */
        kmlLatLonQuad: {
            get: function() {
                return this._factory.any(this, {
                    name: KmlLatLonQuad.prototype.getTagNames()
                });
            }
        }
    });

	/**
     * @inheritDoc
     */
    KmlGroundOverlay.prototype.render = function(dc, kmlOptions) {
        KmlFeature.prototype.render.call(this, dc, kmlOptions);

        if(!this._renderable && this.enabled) {
            if(this.kmlIcon && this.kmlLatLonBox) {
                this._renderable = new SurfaceImage(
                    new Sector(
                        this.kmlLatLonBox.kmlSouth,
                        this.kmlLatLonBox.kmlNorth,
                        this.kmlLatLonBox.kmlWest,
                        this.kmlLatLonBox.kmlEast
                    ),
                    this.kmlIcon.kmlHref
                );
                dc.redrawRequested = true;
            }
        }
        
        if(this._renderable) {
            this._renderable.render(dc);
        }
    };

    /**
     * @inheritDoc
     */
    KmlGroundOverlay.prototype.getTagNames = function () {
        return ['GroundOverlay'];
    };

    KmlElements.addKey(KmlGroundOverlay.prototype.getTagNames()[0], KmlGroundOverlay);

    return KmlGroundOverlay;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Path
 */
define('shapes/Path',[
        '../shapes/AbstractShape',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../geom/BoundingBox',
        '../util/Color',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../geom/Position',
        '../shapes/ShapeAttributes',
        '../shapes/SurfacePolyline',
        '../geom/Vec2',
        '../geom/Vec3'
    ],
    function (AbstractShape,
              ArgumentError,
              BasicTextureProgram,
              BoundingBox,
              Color,
              Location,
              Logger,
              Matrix,
              PickedObject,
              Position,
              ShapeAttributes,
              SurfacePolyline,
              Vec2,
              Vec3) {
        "use strict";

        /**
         * Constructs a path.
         * @alias Path
         * @constructor
         * @augments AbstractShape
         * @classdesc Represents a line, curve or curtain between specified positions. The path is drawn between input
         * positions to achieve a specified path type, which can be one of the following:
         * <ul>
         *     <li>[WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE}</li>
         *     <li>[WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE}</li>
         *     <li>[WorldWind.LINEAR]{@link WorldWind#LINEAR}</li>
         * </ul>
         * <p>
         *     Paths conform to the terrain if the path's [followTerrain]{@link Path#followTerrain} property is true.
         * <p>
         *     Altitudes within the path's positions are interpreted according to the path's altitude mode, which
         *     can be one of the following:
         * <ul>
         *     <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
         *     <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
         *     <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
         * </ul>
         * If the latter, the path positions' altitudes are ignored.
         * <p>
         *     Paths have separate attributes for normal display and highlighted display. They use the interior and
         *     outline attributes of {@link ShapeAttributes} but do not use the image attributes.
         * <p>
         *     A path displays as a curtain if its [extrude]{@link Path#extrude} property is true. A curtain extends
         *     from the line formed by the path positions to the ground.
         * <p>
         *     This shape uses a {@link SurfacePolyline} when drawing on 2D globes and this shape's
         *     [useSurfaceShapeFor2D]{@link AbstractShape#useSurfaceShapeFor2D} is true.
         *
         * @param {Position[]} positions An array containing the path positions.
         * @param {ShapeAttributes} attributes The attributes to associate with this path. May be null, in which case
         * default attributes are associated.
         * @throws {ArgumentError} If the specified positions array is null or undefined.
         */
        var Path = function (positions, attributes) {
            if (!positions) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Path", "constructor", "missingPositions"));
            }

            AbstractShape.call(this, attributes);

            // Private. Documentation is with the defined property below.
            this._positions = positions;

            // Private. Documentation is with the defined property below.
            this._pathType = WorldWind.GREAT_CIRCLE;

            // Private. Documentation is with the defined property below.
            this._terrainConformance = 10;

            // Private. Documentation is with the defined property below.
            this._numSubSegments = 10;

            this.referencePosition = this.determineReferencePosition(this._positions);

            this.scratchPoint = new Vec3(0, 0, 0); // scratch variable
        };

        Path.prototype = Object.create(AbstractShape.prototype);

        Object.defineProperties(Path.prototype, {
            /**
             * This path's positions.
             * @type {Position[]}
             * @memberof Path.prototype
             */
            positions: {
                get: function () {
                    return this._positions;
                },
                set: function (positions) {
                    if (!positions) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "Path", "constructor", "missingPositions"));
                    }

                    this._positions = positions;
                    this.referencePosition = this.determineReferencePosition(this._positions);
                    this.reset();
                }
            },

            /**
             * Indicates whether this path should conform to the terrain.
             * @type {Boolean}
             * @default false
             * @memberof Path.prototype
             */
            followTerrain: {
                get: function () {
                    return this._followTerrain;
                },
                set: function (followTerrain) {
                    this._followTerrain = followTerrain;
                    this.reset();
                }
            },

            /**
             * Specifies how accurately this path must adhere to the terrain when the path is terrain following. The value
             * specifies the maximum number of pixels between tessellation points. Lower values increase accuracy but decrease
             * performance.
             * @type {Number}
             * @default 10
             * @memberof Path.prototype
             */
            terrainConformance: {
                get: function () {
                    return this._terrainConformance;
                },
                set: function (terrainConformance) {
                    this._terrainConformance = terrainConformance;
                    this.reset();
                }
            },

            /**
             * Specifies the number of segments used between specified positions to achieve this path's path type. Higher values
             * cause the path to conform more closely to the path type but decrease performance.
             * <p/>
             * Note: The sub-segments number is ignored when the path follows terrain or when the path type is
             * WorldWind.LINEAR.
             * @type {Number}
             * @default 10
             * @memberof Path.prototype
             */
            numSubSegments: {
                get: function () {
                    return this._numSubSegments;
                },
                set: function (numSubSegments) {
                    this._numSubSegments = numSubSegments >= 0 ? numSubSegments : 0;
                    this.reset();
                }
            },

            /**
             * The type of path to follow when drawing the path. Recognized values are:
             * <ul>
             * <li>[WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE}</li>
             * <li>[WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE}</li>
             * <li>[WorldWind.LINEAR]{@link WorldWind#LINEAR}</li>
             * </ul>
             * @type {String}
             * @default WorldWind.GREAT_CIRCLE
             * @memberof Path.prototype
             */
            pathType: {
                get: function () {
                    return this._pathType;
                },
                set: function (pathType) {
                    this._pathType = pathType;
                    this.reset();
                }
            },

            /**
             * Specifies whether to extrude this path to the ground by drawing a filled interior from the path to the
             * terrain. The filled interior uses this path's interior attributes.
             * @type {Boolean}
             * @default false
             * @memberof Path.prototype
             */
            extrude: {
                get: function () {
                    return this._extrude;
                },
                set: function (extrude) {
                    this._extrude = extrude;
                    this.reset();
                }
            }
        });

        // Intentionally not documented.
        Path.prototype.determineReferencePosition = function (positions) {
            // Assign the first position as the reference position.
            return (positions.length > 0) ? positions[0] : null;
        };

        // Internal. Determines whether this shape's geometry must be re-computed.
        Path.prototype.mustGenerateGeometry = function (dc) {
            if (!this.currentData.tessellatedPoints) {
                return true;
            }

            if (this.currentData.drawInterior !== this.activeAttributes.drawInterior
                || this.currentData.drawVerticals !== this.activeAttributes.drawVerticals) {
                return true;
            }

            if (!this.followTerrain && this.currentData.numSubSegments !== this.numSubSegments) {
                return true;
            }

            if (this.followTerrain && this.currentData.terrainConformance !== this.terrainConformance) {
                return true;
            }

            if (this.altitudeMode === WorldWind.ABSOLUTE) {
                return false;
            }

            return this.currentData.isExpired
        };

        Path.prototype.createSurfaceShape = function () {
            return new SurfacePolyline(this.positions, null);
        };

        // Overridden from AbstractShape base class.
        Path.prototype.doMakeOrderedRenderable = function (dc) {
            // A null reference position is a signal that there are no positions to render.
            if (!this.referencePosition) {
                return null;
            }

            // See if the current shape data can be re-used.
            if (!this.mustGenerateGeometry(dc)) {
                return this;
            }

            // Set the transformation matrix to correspond to the reference position.
            var refPt = this.currentData.referencePoint;
            dc.surfacePointForMode(this.referencePosition.latitude, this.referencePosition.longitude,
                this.referencePosition.altitude, this._altitudeMode, refPt);
            this.currentData.transformationMatrix.setToTranslation(refPt[0], refPt[1], refPt[2]);

            // Tessellate the path in geographic coordinates.
            var tessellatedPositions = this.makeTessellatedPositions(dc);
            if (tessellatedPositions.length < 2) {
                return null;
            }

            // Convert the tessellated geographic coordinates to the Cartesian coordinates that will be rendered.
            var tessellatedPoints = this.computeRenderedPath(dc, tessellatedPositions);

            this.currentData.tessellatedPoints = tessellatedPoints;
            this.currentData.drawInterior = this.activeAttributes.drawInterior;
            this.currentData.drawVerticals = this.activeAttributes.drawVerticals;
            this.currentData.numSubSegments = this.numSubSegments;
            this.currentData.terrainConformance = this.terrainConformance;
            this.resetExpiration(this.currentData);
            this.currentData.fillVbo = true;

            // Create the extent from the Cartesian points. Those points are relative to this path's reference point, so
            // translate the computed extent to the reference point.
            if (!this.currentData.extent) {
                this.currentData.extent = new BoundingBox();
            }
            this.currentData.extent.setToPoints(tessellatedPoints);
            this.currentData.extent.translate(this.currentData.referencePoint);

            return this;
        };

        // Private. Intentionally not documented.
        Path.prototype.makeTessellatedPositions = function (dc) {
            var tessellatedPositions = [],
                navState = dc.navigatorState,
                showVerticals = this.mustDrawVerticals(dc),
                ptA = new Vec3(0, 0, 0),
                ptB = new Vec3(0, 0, 0),
                posA = this._positions[0],
                posB, eyeDistance, pixelSize;

            if (showVerticals) {
                this.currentData.verticalIndices = new Int16Array(this.positions.length * 2);
                this.currentData.verticalIndices[0] = 0;
                this.currentData.verticalIndices[1] = 1;
            }

            tessellatedPositions.push(posA);

            dc.surfacePointForMode(posA.latitude, posA.longitude, posA.altitude, this._altitudeMode, ptA);

            for (var i = 1, len = this._positions.length; i < len; i++) {
                posB = this._positions[i];
                dc.surfacePointForMode(posB.latitude, posB.longitude, posB.altitude, this._altitudeMode, ptB);
                eyeDistance = navState.eyePoint.distanceTo(ptA);
                pixelSize = navState.pixelSizeAtDistance(eyeDistance);
                if (ptA.distanceTo(ptB) < pixelSize * 8 && this.altitudeMode !== WorldWind.ABSOLUTE) {
                    tessellatedPositions.push(posB); // distance is short so no need for sub-segments
                } else {
                    this.makeSegment(dc, posA, posB, ptA, ptB, tessellatedPositions);
                }

                posA = posB;
                ptA.copy(ptB);

                if (showVerticals) {
                    var k = 2 * (tessellatedPositions.length - 1);
                    this.currentData.verticalIndices[i * 2] = k;
                    this.currentData.verticalIndices[i * 2 + 1] = k + 1;
                }
            }

            return tessellatedPositions;
        };

        // Private. Intentionally not documented.
        Path.prototype.makeSegment = function (dc, posA, posB, ptA, ptB, tessellatedPositions) {
            var navState = dc.navigatorState,
                eyePoint = navState.eyePoint,
                pos = new Location(0, 0),
                height = 0,
                arcLength, segmentAzimuth, segmentDistance, s, p, distance;

            // If it's just a straight line and not terrain following, then the segment is just two points.
            if (this._pathType === WorldWind.LINEAR && !this._followTerrain) {
                if (!ptA.equals(ptB)) {
                    tessellatedPositions.push(posB);
                }
                return;
            }

            // Compute the segment length.

            if (this._pathType === WorldWind.LINEAR) {
                segmentDistance = Location.linearDistance(posA, posB);
            } else if (this._pathType === WorldWind.RHUMB_LINE) {
                segmentDistance = Location.rhumbDistance(posA, posB);
            } else {
                segmentDistance = Location.greatCircleDistance(posA, posB);
            }

            if (this._altitudeMode !== WorldWind.CLAMP_TO_GROUND) {
                height = 0.5 * (posA.altitude + posB.altitude);
            }

            arcLength = segmentDistance * (dc.globe.equatorialRadius + height * dc.verticalExaggeration);

            if (arcLength <= 0) { // segment is 0 length
                return;
            }

            // Compute the azimuth to apply while tessellating the segment.

            if (this._pathType === WorldWind.LINEAR) {
                segmentAzimuth = Location.linearAzimuth(posA, posB);
            } else if (this._pathType === WorldWind.RHUMB_LINE) {
                segmentAzimuth = Location.rhumbAzimuth(posA, posB);
            } else {
                segmentAzimuth = Location.greatCircleAzimuth(posA, posB);
            }

            this.scratchPoint.copy(ptA);
            for (s = 0, p = 0; s < 1;) {
                if (this._followTerrain) {
                    p += this._terrainConformance * navState.pixelSizeAtDistance(this.scratchPoint.distanceTo(eyePoint));
                } else {
                    p += arcLength / this._numSubSegments;
                }

                // Stop adding intermediate positions when we reach the arc length, or the remaining distance is in
                // millimeters on Earth.
                if (arcLength < p || arcLength - p < 1e-9)
                    break;

                s = p / arcLength;
                distance = s * segmentDistance;

                if (this._pathType === WorldWind.LINEAR) {
                    Location.linearLocation(posA, segmentAzimuth, distance, pos);
                } else if (this._pathType === WorldWind.RHUMB_LINE) {
                    Location.rhumbLocation(posA, segmentAzimuth, distance, pos);
                } else {
                    Location.greatCircleLocation(posA, segmentAzimuth, distance, pos);
                }

                pos.altitude = (1 - s) * posA.altitude + s * posB.altitude;
                tessellatedPositions.push(new Position(pos.latitude, pos.longitude, pos.altitude));

                if (this._followTerrain) {
                    // Compute a new reference point for eye distance.
                    dc.surfacePointForMode(pos.latitude, pos.longitude, pos.altitude,
                        WorldWind.CLAMP_TO_GROUND, this.scratchPoint);
                }
            }

            tessellatedPositions.push(posB);
        };

        // Private. Intentionally not documented.
        Path.prototype.computeRenderedPath = function (dc, tessellatedPositions) {
            var capturePoles = this.mustDrawInterior(dc) || this.mustDrawVerticals(dc),
                eyeDistSquared = Number.MAX_VALUE,
                eyePoint = dc.navigatorState.eyePoint,
                numPoints = (capturePoles ? 2 : 1) * tessellatedPositions.length,
                tessellatedPoints = new Float32Array(numPoints * 3),
                stride = capturePoles ? 6 : 3,
                pt = new Vec3(0, 0, 0),
                altitudeMode, pos, k, dSquared;

            if (this._followTerrain && this.altitudeMode !== WorldWind.CLAMP_TO_GROUND) {
                altitudeMode = WorldWind.RELATIVE_TO_GROUND;
            } else {
                altitudeMode = this.altitudeMode;
            }

            for (var i = 0, len = tessellatedPositions.length; i < len; i++) {
                pos = tessellatedPositions[i];

                dc.surfacePointForMode(pos.latitude, pos.longitude, pos.altitude, altitudeMode, pt);

                dSquared = pt.distanceToSquared(eyePoint);
                if (dSquared < eyeDistSquared) {
                    eyeDistSquared = dSquared;
                }

                pt.subtract(this.currentData.referencePoint);

                k = stride * i;
                tessellatedPoints[k] = pt[0];
                tessellatedPoints[k + 1] = pt[1];
                tessellatedPoints[k + 2] = pt[2];

                if (capturePoles) {
                    dc.surfacePointForMode(pos.latitude, pos.longitude, 0, WorldWind.CLAMP_TO_GROUND, pt);

                    dSquared = pt.distanceToSquared(eyePoint);
                    if (dSquared < eyeDistSquared) {
                        eyeDistSquared = dSquared;
                    }

                    pt.subtract(this.currentData.referencePoint);

                    tessellatedPoints[k + 3] = pt[0];
                    tessellatedPoints[k + 4] = pt[1];
                    tessellatedPoints[k + 5] = pt[2];
                }
            }

            this.currentData.pointBufferHasExtrusionPoints = capturePoles;
            this.currentData.eyeDistance = Math.sqrt(eyeDistSquared);

            return tessellatedPoints;
        };

        // Private. Intentionally not documented.
        Path.prototype.mustDrawInterior = function (dc) {
            return this.activeAttributes.drawInterior
                && this._extrude
                && this._altitudeMode !== WorldWind.CLAMP_TO_GROUND;
        };

        // Private. Intentionally not documented.
        Path.prototype.mustDrawVerticals = function (dc) {
            return this.activeAttributes.drawOutline && this.activeAttributes.drawVerticals
                && this.altitudeMode !== WorldWind.CLAMP_TO_GROUND;
        };

        // Overridden from AbstractShape base class.
        Path.prototype.doRenderOrdered = function (dc) {
            var gl = dc.currentGlContext,
                program = dc.currentProgram,
                currentData = this.currentData,
                numPoints = currentData.tessellatedPoints.length / 3,
                vboId, opacity, color, pickColor, stride, nPts;

            this.applyMvpMatrix(dc);

            if (!currentData.vboCacheKey) {
                currentData.vboCacheKey = dc.gpuResourceCache.generateCacheKey();
            }

            vboId = dc.gpuResourceCache.resourceForKey(currentData.vboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                dc.gpuResourceCache.putResource(this.currentData.vboCacheKey, vboId,
                    currentData.tessellatedPoints.length * 4);
                currentData.fillVbo = true;
            }

            // Bind and if necessary fill the VBO. We fill the VBO here rather than in doMakeOrderedRenderable so that
            // there's no possibility of the VBO being ejected from the cache between the time it's filled and
            // the time it's used.
            gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            if (currentData.fillVbo) {
                gl.bufferData(gl.ARRAY_BUFFER, currentData.tessellatedPoints,
                    gl.STATIC_DRAW);
                dc.frameStatistics.incrementVboLoadCount(1);
            }

            program.loadTextureEnabled(gl, false);

            if (dc.pickingMode) {
                pickColor = dc.uniquePickColor();
            }

            if (this.mustDrawInterior(dc)) {
                color = this.activeAttributes.interiorColor;
                opacity = color.alpha * dc.currentLayer.opacity;
                // Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
                if (opacity < 1 && !dc.pickingMode) {
                    gl.depthMask(false);
                }
                program.loadColor(gl, dc.pickingMode ? pickColor : color);
                program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);

                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, numPoints);
            }

            if (this.activeAttributes.drawOutline) {
                if ((this.mustDrawVerticals(dc) && this.mustDrawInterior(dc))
                    || this.altitudeMode === WorldWind.CLAMP_TO_GROUND) {
                    // Make the verticals stand out from the interior, or the outline stand out from the terrain.
                    this.applyMvpMatrixForOutline(dc);
                }

                color = this.activeAttributes.outlineColor;
                opacity = color.alpha * dc.currentLayer.opacity;
                // Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
                if (opacity < 1 && !dc.pickingMode) {
                    gl.depthMask(false);
                }
                program.loadColor(gl, dc.pickingMode ? pickColor : color);
                program.loadOpacity(gl, dc.pickingMode ? 1 : opacity);

                gl.lineWidth(this.activeAttributes.outlineWidth);

                if (this.currentData.pointBufferHasExtrusionPoints) {
                    stride = 24;
                    nPts = numPoints / 2;
                } else {
                    stride = 12;
                    nPts = numPoints;
                }

                gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
                gl.drawArrays(gl.LINE_STRIP, 0, nPts);

                if (this.mustDrawVerticals(dc)) {
                    if (!currentData.verticalIndicesVboCacheKey) {
                        currentData.verticalIndicesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
                    }

                    vboId = dc.gpuResourceCache.resourceForKey(currentData.verticalIndicesVboCacheKey);
                    if (!vboId) {
                        vboId = gl.createBuffer();
                        dc.gpuResourceCache.putResource(currentData.verticalIndicesVboCacheKey, vboId,
                            currentData.verticalIndices.length * 4);
                        currentData.fillVbo = true;
                    }

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboId);
                    if (currentData.fillVbo) {
                        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, currentData.verticalIndices,
                            gl.STATIC_DRAW);
                        dc.frameStatistics.incrementVboLoadCount(1);
                    }

                    gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
                    gl.drawElements(gl.LINES, currentData.verticalIndices.length,
                        gl.UNSIGNED_SHORT, 0);
                }
            }
            currentData.fillVbo = false;

            if (dc.pickingMode) {
                var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, null, dc.currentLayer,
                    false);
                dc.resolvePick(po);
            }
        };

        // Overridden from AbstractShape base class.
        Path.prototype.beginDrawing = function (dc) {
            var gl = dc.currentGlContext;

            if (this.mustDrawInterior(dc)) {
                gl.disable(gl.CULL_FACE);
            }

            dc.findAndBindProgram(BasicTextureProgram);
            gl.enableVertexAttribArray(dc.currentProgram.vertexPointLocation);
        };

        // Overridden from AbstractShape base class.
        Path.prototype.endDrawing = function (dc) {
            var gl = dc.currentGlContext;

            gl.disableVertexAttribArray(dc.currentProgram.vertexPointLocation);
            gl.depthMask(true);
            gl.lineWidth(1);
            gl.enable(gl.CULL_FACE);
        };

        return Path;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/geom/KmlLineString',[
    '../../../util/Color',
    '../KmlElements',
    './KmlGeometry',
    '../styles/KmlStyle',
    '../../../geom/Location',
    '../util/NodeTransformers',
    '../../../shapes/Path',
    '../../../geom/Position',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolyline',
    '../../../util/WWUtil'
], function (Color,
             KmlElements,
             KmlGeometry,
             KmlStyle,
             Location,
             NodeTransformers,
             Path,
             Position,
             ShapeAttributes,
             SurfacePolyline,
             WWUtil) {
    "use strict";

    /**
     * Constructs an KmlLineString object.  Applications shouldn't use this constructor. It is used by
     * {@link KmlFile}. KmlLineString represents one line string.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing LineString.
     * @constructor
     * @alias KmlLineString
     * @classdesc Class representing LineString element of KmlFile
     * @see https://developers.google.com/kml/documentation/kmlreference#linestring
     * @augments KmlGeometry
     */
    var KmlLineString = function (options) {
        KmlGeometry.call(this, options);

        this._style = null;
    };

    KmlLineString.prototype = Object.create(KmlGeometry.prototype);

    Object.defineProperties(KmlLineString.prototype, {
        /**
         * Whether current shape should be extruded.
         * @memberof KmlLineString.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlExtrude: {
            get: function () {
                return this._factory.specific(this, {name: 'extrude', transformer: NodeTransformers.boolean}) || false;
            }
        },

        /**
         * Whether tessellation should be used for current node.
         * @memberof KmlLineString.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlTessellate: {
            get: function () {
                return this._factory.specific(this, {name: 'tessellate', transformer: NodeTransformers.boolean}) || false;
            }
        },

        /**
         * It represents different modes to count absolute altitude. Possible choices are explained in:
         * https://developers.google.com/kml/documentation/kmlreference#point
         * @memberof KmlLineString.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitudeMode: {
            get: function () {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string}) || WorldWind.ABSOLUTE;
            }
        },

        /**
         * Positions representing points used by the LineString.
         * @memberof KmlLineString.prototype
         * @readonly
         * @type {Position[]}
         */
        kmlPositions: {
            get: function () {
                return this._factory.specific(this, {name: 'coordinates', transformer: NodeTransformers.positions});
            }
        },

        /**
         * Returns average of the positions, which are part of the LineString. It averages also the altitudes.
         * @memberof KmlLineString.prototype
         * @readonly
         * @type {Position}
         */
        kmlCenter: {
            get: function () {
                // TODO choose better approximation than just plain average.
                var positions = this.kmlPositions;
                var midLatitude = 0;
                var midLongitude = 0;
                var midAltitude = 0;
                positions.forEach(function (position) {
                    midLatitude += position.latitude;
                    midLongitude += position.longitude;
                    midAltitude += position.altitude;
                });
                return new Position(
                    midLatitude / this.kmlPositions.length,
                    midLongitude / this.kmlPositions.length,
                    midAltitude / this.kmlPositions.length
                );
            }
        }
    });

    /**
     * It creates Path representing this LineString unless already initialized.
     * @param styles {Object|null}
     * @param styles.normal {KmlStyle} Style applied when item not highlighted
     * @param styles.highlight {KmlStyle} Style applied when item is highlighted
     */
    KmlLineString.prototype.createPath = function (styles) {
        if(this.kmlAltitudeMode == WorldWind.CLAMP_TO_GROUND) {
            this._renderable = new SurfacePolyline(this.prepareLocations(), this.prepareAttributes(styles.normal));
        } else {
            this._renderable = new Path(this.prepareLocations(), this.prepareAttributes(styles.normal));
        }
        if(styles.highlight) {
            this._renderable.highlightAttributes = this.prepareAttributes(styles.highlight);
        }
        this.moveValidProperties();
    };

    KmlLineString.prototype.render = function(dc, kmlOptions) {
        KmlGeometry.prototype.render.call(this, dc, kmlOptions);

        if(kmlOptions.lastStyle && !this._renderable) {
            this.createPath(kmlOptions.lastStyle);
            dc.redrawRequested = true;
        }

        if(this._renderable) {
            this._renderable.enabled = this.enabled;
            this._renderable.render(dc);
        }
    };

    /**
     * @inheritDoc
     */
    KmlLineString.prototype.prepareAttributes = function (style) {
        var shapeOptions = style && style.generate() || {};

        shapeOptions._applyLighting = true;
        shapeOptions._drawOutline = true;
        shapeOptions._drawInterior = true;
        shapeOptions._drawVerticals = this.kmlExtrude || false;
        shapeOptions._outlineStippleFactor = 0;
        shapeOptions._outlineStipplePattern = 61680;
        shapeOptions._enableLighting = true;

        return new ShapeAttributes(KmlStyle.shapeAttributes(shapeOptions));
    };

    /**
     * Prepare locations representing current Line String.
     * @returns {Position[]} Positions representing this LineString.
     */
    KmlLineString.prototype.prepareLocations = function () {
        return this.kmlPositions;
    };

    /**
     * Moves KML properties from current object into the internal shape representation.
     */
    KmlLineString.prototype.moveValidProperties = function () {
        this._renderable.extrude = this.kmlExtrude || false;
        this._renderable.altitudeMode = this.kmlAltitudeMode || WorldWind.ABSOLUTE;
        //noinspection JSUnusedGlobalSymbols
        this._renderable.tesselate = this.kmlTesselate || false;
    };

    /**
     * Two line strings are equal when the properties and positions are equal.
     * @param toCompare {KmlLineString} LineString to compare to.
     * @returns {Boolean} True if the LineStrings are equal.
     */
    KmlLineString.prototype.equals = function (toCompare) {
        if (!toCompare) {
            return false;
        }
        var positionsEquals = WWUtil.arrayEquals(toCompare.kmlPositions, this.kmlPositions);
        return positionsEquals && toCompare.kmlExtrude == this.kmlExtrude && toCompare.kmlTessellate == this.kmlTessellate &&
            toCompare.kmlAltitudeMode == this.kmlAltitudeMode;
    };

    /**
     * @inheritDoc
     */
    KmlLineString.prototype.getTagNames = function () {
        return ['LineString'];
    };

    KmlElements.addKey(KmlLineString.prototype.getTagNames()[0], KmlLineString);

    return KmlLineString;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/geom/KmlLinearRing',[
    './KmlLineString',
    '../KmlElements'
], function (KmlLineString,
             KmlElements) {
    "use strict";
    /**
     * Constructs an KmlLinearRing element. Applications don't usually call this constructor. It is called by objects in
     * the hierarchy of KmlObject.
     * @alias KmlLinearRing
     * @classdesc Contains the data associated with LinerRing
     * @param options {Object}
     * @param options.objectNode {Node} Node representing LinearRing.
     * @param options.style {Promise} Promise of style to be applied to current geometry
     * @constructor
     * @see https://developers.google.com/kml/documentation/kmlreference#linearring
     * @augments KmlLineString
     */
    var KmlLinearRing = function (options) {
        KmlLineString.call(this, options);
    };

    KmlLinearRing.prototype = Object.create(KmlLineString.prototype);

    /**
     * @inheritDoc
     */
    KmlLinearRing.prototype.getTagNames = function () {
        return ['LinearRing'];
    };

    KmlElements.addKey(KmlLinearRing.prototype.getTagNames()[0], KmlLinearRing);

    return KmlLinearRing;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLocation',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (
    KmlElements,
    KmlObject, 
    NodeTransformers
) {
    "use strict";

    /**
     * Constructs an KmlLocation. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLocation
     * @classdesc Contains the data associated with Location node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing location in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#location
     * @augments KmlObject
     */
    var KmlLocation = function (options) {
        KmlObject.call(this, options);
    };

    KmlLocation.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlLocation.prototype, {
        /**
         * Longitude of the location.
         * @memberof KmlLocation.prototype
         * @readonly
         * @type {String}
         */
        kmlLongitude: {
            get: function() {
                return this._factory.specific(this, {name: 'longitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Latitude of the location.
         * @memberof KmlLocation.prototype
         * @readonly
         * @type {String}
         */
        kmlLatitude: {
            get: function() {
                return this._factory.specific(this, {name: 'latitude', transformer: NodeTransformers.string});
            }
        },

        /**
         * Altitude of the location.
         * @memberof KmlLocation.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitude: {
            get: function() {
                return this._factory.specific(this, {name: 'altitude', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlLocation.prototype.getTagNames = function () {
        return ['Location'];
    };

    KmlElements.addKey(KmlLocation.prototype.getTagNames()[0], KmlLocation);

    return KmlLocation;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlLookAt',[
    './KmlAbstractView',
    './KmlElements',
    './util/NodeTransformers',
    '../../geom/Position'
], function (KmlAbstractView,
             KmlElements,
             NodeTransformers,
             Position
) {
    "use strict";

    /**
     * Constructs an KmlLookAt. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlLookAt
     * @classdesc Contains the data associated with LookAt node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing looking at something in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#lookat
     * @augments KmlAbstractView
     */
    var KmlLookAt = function (options) {
        KmlAbstractView.call(this, options);
    };

    KmlLookAt.prototype = Object.create(KmlAbstractView.prototype);

    Object.defineProperties(KmlLookAt.prototype, {
        /**
         * Longitude of the point the camera is looking at. Angular distance in degrees, relative to the Prime
         * Meridian. Values west of the Meridian range from -180 to 0 degrees. Values east of the Meridian range
         * from 0 to 180 degrees.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlLongitude: {
            get: function () {
                return this._factory.specific(this, {name: 'longitude', transformer: NodeTransformers.number});
            }
        },

        /**
         * Latitude of the point the camera is looking at. Degrees north or south of the Equator (0 degrees). Values
         * range from -90 degrees to 90 degrees.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlLatitude: {
            get: function () {
                return this._factory.specific(this, {name: 'latitude', transformer: NodeTransformers.number});
            }
        },

        /**
         * Distance from the earth's surface, in meters. Interpreted according to the LookAt's altitude mode.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlAltitude: {
            get: function () {
                return this._factory.specific(this, {name: 'altitude', transformer: NodeTransformers.number});
            }
        },

        /**
         * Direction (that is, North, South, East, West), in degrees. Default=0 (North). (See diagram below.) Values
         * range from 0 to 360 degrees.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlHeading: {
            get: function () {
                return this._factory.specific(this, {name: 'heading', transformer: NodeTransformers.number});
            }
        },

        /**
         * Angle between the direction of the LookAt position and the normal to the surface of the earth. (See
         * diagram below.) Values range from 0 to 90 degrees. Values for &lt;tilt&gt; cannot be negative. A &lt;tilt&gt; value
         * of 0 degrees indicates viewing from directly above. A &lt;tilt&gt; value of 90 degrees indicates viewing along
         * the horizon.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlTilt: {
            get: function () {
                return this._factory.specific(this, {name: 'tilt', transformer: NodeTransformers.number});
            }
        },

        /**
         * Distance in meters from the point specified by &lt;longitude&gt;, &lt;latitude&gt;, and &lt;altitude&gt; to the LookAt
         * position. (See diagram below.)
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {Number}
         */
        kmlRange: {
            get: function () {
                return this._factory.specific(this, {name: 'range', transformer: NodeTransformers.number});
            }
        },

        /**
         * Specifies how the &lt;altitude&gt; specified for the LookAt point is interpreted. Possible values are as
         * follows: clampToGround - (default) Indicates to ignore the &lt;altitude&gt; specification and place the LookAt
         * position on the ground. relativeToGround - Interprets the &lt;altitude&gt; as a value in meters above the
         * ground. absolute - Interprets the &lt;altitude&gt; as a value in meters above sea level.
         * @memberof KmlLookAt.prototype
         * @readonly
         * @type {String}
         */
        kmlAltitudeMode: {
            get: function () {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * Go to the look at location.
     */
    KmlLookAt.prototype.update = function(options) {
        if(options.wwd) {
            var altitude = this.kmlAltitude || 4000;
            // TODO: Respect altitude mode.
            options.wwd.goTo(new Position(this.kmlLatitude, this.kmlLongitude, altitude));
        }
    };

    /**
     * @inheritDoc
     */
    KmlLookAt.prototype.getTagNames = function () {
        return ['LookAt'];
    };

    KmlElements.addKey(KmlLookAt.prototype.getTagNames()[0], KmlLookAt);

    return KmlLookAt;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/geom/KmlMultiGeometry',[
    './../KmlElements',
    './KmlGeometry',
    '../../../geom/Position'
], function (KmlElements,
             KmlGeometry,
             Position) {
    "use strict";

    /**
     * Constructs an KmlMultiGeometry object. KmlMultiGeometry is object, which contains other geometry objects. This
     * class isn't intended to be used outside of the KmlObject hierarchy. It is already concrete implementation.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing current geometry
     * @param options.style {Promise} Promise of style to be applied to current geometry
     * @constructor
     * @classdesc Class representing MultiGeometry Element of Kml Document.
     * @alias KmlMultiGeometry
     * @see https://developers.google.com/kml/documentation/kmlreference#multigeometry
     * @augments KmlGeometry
     */
    var KmlMultiGeometry = function (options) {
        KmlGeometry.call(this, options);
        this._style = options.style;
    };

    KmlMultiGeometry.prototype = Object.create(KmlGeometry.prototype);

    Object.defineProperties(KmlMultiGeometry.prototype, {
        /**
         * It returns all shapes currently present in this node.
         * @memberof KmlMultiGeometry.prototype
         * @type {KmlObject[]}
         * @readonly
         */
        kmlShapes: {
            get: function () {
                return this._factory.all(this);
            }
        },

        /**
         * Center of all the geometries implemented as average of centers of all shapes.
         * @memberof KmlMultiGeometry.prototype
         * @type {Position}
         * @readonly
         */
        kmlCenter: {
            get: function () {
                var positions = this.kmlShapes.map(function (shape) {
                    return shape.kmlCenter;
                });
                var midLatitude = 0;
                var midLongitude = 0;
                var midAltitude = 0;
                positions.forEach(function (position) {
                    midLatitude += position.latitude;
                    midLongitude += position.longitude;
                    midAltitude += position.altitude;
                });
                return new Position(
                    midLatitude / positions.length,
                    midLongitude / positions.length,
                    midAltitude / positions.length
                );
            }
        }
    });

	/**
     * @inheritDoc
     */
    KmlMultiGeometry.prototype.render = function(dc, kmlOptions) {
        KmlGeometry.prototype.render.call(this, dc, kmlOptions);

        this.kmlShapes.forEach(function(shape) {
            shape.render(dc, kmlOptions);
        });
    };

    /**
     * @inheritDoc
     */
    KmlMultiGeometry.prototype.getTagNames = function () {
        return ["MultiGeometry"];
    };

    KmlElements.addKey(KmlMultiGeometry.prototype.getTagNames()[0], KmlMultiGeometry);

    return KmlMultiGeometry;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlNetworkLink',[
    './../KmlElements',
    './KmlFeature',
    '../KmlFile',
    '../KmlLink',
    '../util/NodeTransformers',
    '../util/RefreshListener'
], function (KmlElements,
             KmlFeature,
             KmlFile,
             KmlLink,
             NodeTransformers,
             RefreshListener) {
    "use strict";

    var REFRESH_NETWORK_LINK_EVENT = "refreshNetworkLinkEvent";

    /**
     * Constructs an KmlNetworkLink. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlNetworkLink
     * @classdesc Contains the data associated with NetworkLink node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing NetworkLink
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#networklink
     * @augments KmlFeature
     */
    var KmlNetworkLink = function (options) {
        KmlFeature.call(this, options);

        this.isFeature = true;

        this.resolvedFile = null;
        this.displayed = false;
        this.isDownloading = false;
    };

    KmlNetworkLink.prototype = Object.create(KmlFeature.prototype);

    Object.defineProperties(KmlNetworkLink.prototype, {
        /**
         * Boolean value. A value of 0 leaves the visibility of features within the control of the Google Earth
         * user. Set the value to 1 to reset the visibility of features each time the NetworkLink is refreshed. For
         * example, suppose a Placemark within the linked KML file has &lt;visibility&gt; set to 1 and the NetworkLink
         * has
         * &lt;refreshVisibility&gt; set to 1. When the file is first loaded into Google Earth, the user can clear the
         * check box next to the item to turn off display in the 3D viewer. However, when the NetworkLink is
         * refreshed, the Placemark will be made visible again, since its original visibility state was TRUE.
         * @memberof KmlNetworkLink.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlRefreshVisibility: {
            get: function () {
                return this._factory.specific(this, {name: 'refreshVisibility', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * Boolean value. A value of 1 causes Google Earth to fly to the view of the LookAt or Camera in the
         * NetworkLinkControl (if it exists). If the NetworkLinkControl does not contain an AbstractView element,
         * Google Earth flies to the LookAt or Camera element in the Feature child within the &lt;kml&gt; element in the
         * refreshed file. If the &lt;kml&gt; element does not have a LookAt or Camera specified, the view is unchanged.
         * For example, Google Earth would fly to the &lt;LookAt&gt; view of the parent Document, not the &lt;LookAt&gt; of the
         * Placemarks contained within the Document.
         * @memberof KmlNetworkLink.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlFlyToView: {
            get: function () {
                return this._factory.specific(this, {name: 'flyToView', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * @memberof KmlNetworkLink.prototype
         * @readonly
         * @type {KmlLink}
         * @see {KmlLink}
         */
        kmlLink: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlLink.prototype.getTagNames()
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlNetworkLink.prototype.getTagNames = function () {
        return ['NetworkLink'];
    };

	/**
     * @inheritDoc
     */
    KmlNetworkLink.prototype.render = function(dc, kmlOptions) {
        KmlFeature.prototype.render.call(this, dc, kmlOptions);

        // Not visible and wasn't displayed yet.
        if(!kmlOptions.lastVisibility && !this.displayed) {
            return;
        }

        if(!this.isDownloading && !this.resolvedFile) {
            this.isDownloading = true;
            var self = this;

            new KmlFile(self.buildUrl()).then(function (kmlFile) {
                self.resolvedFile = kmlFile;
                self.isDownloading = false;

                self.fireEvent(kmlOptions);
            });
        }


        if(this.resolvedFile && !this.displayed) {
            this.resolvedFile.render(dc, kmlOptions);

            this.handleRefresh(kmlOptions); // This one happens always
        }
    };

    KmlNetworkLink.prototype.buildUrl = function() {
        return this.kmlLink.kmlHref;
    };

	/**
     * It handles refreshing strategy of the NetworkLink.
     * @param kmlOptions {Object}
     * @param kmlOptions.activeEvents {RefreshListener.Event[]} Events which should be processed in this round of render.
     */
    KmlNetworkLink.prototype.handleRefresh = function(kmlOptions) {
        var activeEvents = kmlOptions.activeEvents;
        activeEvents = activeEvents.filter(function(event){
            return event.type == REFRESH_NETWORK_LINK_EVENT;
        });
        if(activeEvents.length > 0) {
            var self = this;
            new KmlFile(self.buildUrl()).then(function (kmlFile) {
                self.resolvedFile = kmlFile;

                self.fireEvent(kmlOptions);
            });
        }
    };

	/**
     * It fires event when the kmlLink refreshMode contains refreshMode.
     * @param kmlOptions {Object}
     * @param kmlOptions.listener {RefreshListener} Object which allows you to schedule events, which will be triggered at some point in future. It doesn't have to be exactly that time.
     */
    KmlNetworkLink.prototype.fireEvent = function(kmlOptions) {
        var time = 0;
        if(this.kmlLink.kmlRefreshMode == "onInterval") {
            time = this.kmlLink.kmlRefreshInterval * 1000;
        } else if(this.kmlLink.kmlRefreshMode == "onExpire") {
            // Test whether the file is expired
            if(!this.resolvedFile) {
                return;
            } else {
                time = this.resolvedFile.getExpired();
            }
        } else {
            // No refresh mode was selected, therefore ignore this method;
            return;
        }

        kmlOptions.listener.addEvent(new RefreshListener.Event(REFRESH_NETWORK_LINK_EVENT, time, null));
    };

    KmlElements.addKey(KmlNetworkLink.prototype.getTagNames()[0], KmlNetworkLink);

    return KmlNetworkLink;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/KmlOrientation',[
    './KmlElements',
    './KmlObject',
    './util/NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";

    /**
     * Constructs an KmlOrientation. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlOrientation
     * @classdesc Contains the data associated with Orientation node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing orientation in the document.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#orientation
     * @augments KmlObject
     */
    var KmlOrientation = function (options) {
        KmlObject.call(this, options);
    };

    KmlOrientation.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(KmlOrientation.prototype, {
        /**
         * Rotation about the z axis (normal to the Earth's surface). A value of 0 (the default) equals North. A
         * positive rotation is clockwise around the z axis and specified in degrees from 0 to 360.
         * @memberof KmlOrientation.prototype
         * @readonly
         * @type {Number}
         */
        kmlHeading: {
            get: function () {
                return this._factory.specific(this, {name: 'heading', transformer: NodeTransformers.number});
            }
        },

        /**
         * Rotation about the x axis. A positive rotation is clockwise around the x axis and specified in degrees
         * from 0 to 180.
         * @memberof KmlOrientation.prototype
         * @readonly
         * @type {Number}
         */
        kmlTilt: {
            get: function () {
                return this._factory.specific(this, {name: 'tilt', transformer: NodeTransformers.number});
            }
        },

        /**
         * Rotation about the y axis. A positive rotation is clockwise around the y axis and specified in degrees
         * from 0 to 180.
         * @memberof KmlOrientation.prototype
         * @readonly
         * @type {Number}
         */
        kmlRoll: {
            get: function () {
                return this._factory.specific(this, {name: 'roll', transformer: NodeTransformers.number});
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlOrientation.prototype.getTagNames = function () {
        return ['Orientation'];
    };

    KmlElements.addKey(KmlOrientation.prototype.getTagNames()[0], KmlOrientation);

    return KmlOrientation;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports KmlPoint
 */
define('formats/kml/geom/KmlPoint',[
    '../../../util/Color',
    '../KmlElements',
    './KmlGeometry',
    '../../../geom/Location',
    '../util/NodeTransformers',
    '../../../shapes/Polygon',
    '../../../geom/Position'
], function(
    Color,
    KmlElements,
    KmlGeometry,
    Location,
    NodeTransformers,
    Polygon,
    Position
){
    "use strict";
    /**
     * Constructs an KmlPoint. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read.
     * @alias KmlPoint
     * @constructor
     * @classdesc Contains the data associated with Kml point
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Point.
     * @throws {ArgumentError} If either the node is null or the content of the Kml point contains invalid elements.
     * @see https://developers.google.com/kml/documentation/kmlreference#point
     * @augments KmlGeometry
     */
    var KmlPoint = function (options) {
        KmlGeometry.call(this, options);

        this._shape = null;
    };

    KmlPoint.prototype = Object.create(KmlGeometry.prototype);

    Object.defineProperties(KmlPoint.prototype, {
        /**
         * Position of the whole geometry.
         * @memberof KmlPoint.prototype
         * @type {Position}
         * @readonly
         */
        kmlPosition: {
            get: function() {
                // TODO Add Position transformer.
                var coordinates = this._factory.specific(this, {name: 'coordinates', transformer: NodeTransformers.string}).split(',');
                return new Position(coordinates[1], coordinates[0], coordinates[2] || 0);
            }
        },

        /**
         * In case that the point is above ground, this property decides whether there is going to be a line to the
         * ground.
         * @memberof KmlPoint.prototype
         * @type {Boolean}
         * @readonly
         */
        kmlExtrude: {
            get: function() {
                return this._factory.specific(this, {name: 'extrude', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * It explains how we should treat the altitude of the point. Possible choices are explained in:
         * https://developers.google.com/kml/documentation/kmlreference#point
         * @memberof KmlPoint.prototype
         * @type {String}
         * @readonly
         */
        kmlAltitudeMode: {
            get: function() {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string});
            }
        },

        /**
         * It returns center of the point. In case of point it means the position of the point.
         * @memberof KmlPoint.prototype
         * @type {Position}
         * @readonly
         */
        kmlCenter: {
            get: function() {
                return this.kmlPosition;
            }
        }
    });
    
    /**
     * @inheritDoc
     */
    KmlPoint.prototype.getTagNames = function () {
        return ['Point'];
    };

    KmlElements.addKey(KmlPoint.prototype.getTagNames()[0], KmlPoint);

    return KmlPoint;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/ViewVolume',[
    '../KmlElements',
    '../KmlObject',
    './NodeTransformers'
], function (KmlElements,
             KmlObject,
             NodeTransformers) {
    "use strict";
    /**
     * Constructs a ViewVolume. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias ViewVolume
     * @constructor
     * @classdesc Contains the data associated with Kml View Volume
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml View Volume.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#viewvolume
     * @augments KmlObject
     */
    var ViewVolume = function (options) {
        KmlObject.call(this, options);
    };

    ViewVolume.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(ViewVolume.prototype, {
        /**
         * Angle, in degrees, between the camera's viewing direction and the left side of the view volume.
         * @memberof ViewVolume.prototype
         * @readonly
         * @type {Number}
         */
        kmlLeftFov: {
            get: function () {
                return this._factory.specific(this, {name: 'leftFov', transformer: NodeTransformers.number});
            }
        },

        /**
         * Angle, in degrees, between the camera's viewing direction and the right side of the view volume.
         * @memberof ViewVolume.prototype
         * @readonly
         * @type {Number}
         */
        kmlRightFov: {
            get: function () {
                return this._factory.specific(this, {name: 'rightFov', transformer: NodeTransformers.number});
            }
        },

        /**
         * Angle, in degrees, between the camera's viewing direction and the bottom side of the view volume.
         * @memberof ViewVolume.prototype
         * @readonly
         * @type {Number}
         */
        kmlBottomFov: {
            get: function () {
                return this._factory.specific(this, {name: 'bottomFov', transformer: NodeTransformers.number});
            }
        },

        /**
         * Angle, in degrees, between the camera's viewing direction and the top side of the view volume.
         * @memberof ViewVolume.prototype
         * @readonly
         * @type {Number}
         */
        kmlTopFov: {
            get: function () {
                return this._factory.specific(this, {name: 'topFov', transformer: NodeTransformers.number});
            }
        },

        /**
         * Measurement in meters along the viewing direction from the camera viewpoint to the PhotoOverlay shape.
         * The field of view for a PhotoOverlay is defined by four planes, each of which is specified by an angle
         * relative to the view vector. These four planes define the top, bottom, left, and right sides of the field
         *  of view, which has the shape of a truncated pyramid, as shown here:
         * @memberof ViewVolume.prototype
         * @readonly
         * @type {String}
         */
        kmlNear: {
            get: function () {
                return this._factory.specific(this, {name: 'near', transformer: NodeTransformers.string});
            }
        }
    });

    /**
     * @inheritDoc
     */
    ViewVolume.prototype.getTagNames = function () {
        return ['ViewVolume'];
    };

    KmlElements.addKey(ViewVolume.prototype.getTagNames()[0], ViewVolume);

    return ViewVolume;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlPhotoOverlay',[
    '../util/ImagePyramid',
    './../KmlElements',
    './KmlOverlay',
    '../geom/KmlPoint',
    '../util/NodeTransformers',
    '../util/ViewVolume'
], function (ImagePyramid,
             KmlElements,
             KmlOverlay,
             KmlPoint,
             NodeTransformers,
             ViewVolume) {
    "use strict";

    /**
     * Constructs an KmlPhotoOverlay. Applications usually don't call this constructor. It is called by {@link KmlFile}
     * as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlPhotoOverlay
     * @classdesc Contains the data associated with PhotoOverlay node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Photo Overlay.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#photooverlay
     * @augments KmlOverlay
     */
    var KmlPhotoOverlay = function (options) {
        KmlOverlay.call(this, options);
    };

    KmlPhotoOverlay.prototype = Object.create(KmlOverlay.prototype);

    Object.defineProperties(KmlPhotoOverlay.prototype, {
        /**
         * Adjusts how the photo is placed inside the field of view. This element is useful if your photo has been
         * rotated and deviates slightly from a desired horizontal view.
         * @memberof KmlPhotoOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlRotation: {
            get: function () {
                return this._factory.specific(this, {name: 'rotation', transformer: NodeTransformers.string});
            }
        },

        /**
         * The PhotoOverlay is projected onto the &lt;shape&gt;. The &lt;shape&gt; can be one of the following:
         * rectangle (default) - for an ordinary photo
         * @memberof KmlPhotoOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlShape: {
            get: function () {
                return this._factory.specific(this, {name: 'shape', transformer: NodeTransformers.string});
            }
        },

        /**
         * The &lt;Point&gt; element acts as a &lt;Point&gt; inside a &lt;Placemark&gt; element. It draws an icon to mark the
         * position of the PhotoOverlay. The icon drawn is specified by the &lt;styleUrl&gt; and &lt;StyleSelector&gt; fields,
         * just as it is for
         * &lt;Placemark&gt;.
         * @memberof KmlPhotoOverlay.prototype
         * @readonly
         * @type {KmlPoint}
         */
        kmlPoint: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlPoint.prototype.getTagNames()
                });
            }
        },

        /**
         * Defines how much of the current scene is visible. Specifying the field of view is analogous to
         * specifying the lens opening in a physical camera. A small field of view, like a telephoto lens, focuses
         * on a small part of the scene. A large field of view, like a wide-angle lens, focuses on a large part of
         * the scene.
         * @memberof KmlPhotoOverlay.prototype
         * @readonly
         * @type {ViewVolume}
         */
        kmlViewVolume: {
            get: function () {
                return this._factory.any(this, {
                    name: ViewVolume.prototype.getTagNames()
                });
            }
        },

        /**
         * For very large images, you'll need to construct an image pyramid, which is a hierarchical set of images,
         * each of which is an increasingly lower resolution version of the original image. Each image in the
         * pyramid is subdivided into tiles, so that only the portions in view need to be loaded. Google Earth
         * calculates the current viewpoint and loads the tiles that are appropriate to the user's distance from
         * the image. As the viewpoint moves closer to the PhotoOverlay, Google Earth loads higher resolution
         * tiles. Since all the pixels in the original image can't be viewed on the screen at once, this
         * preprocessing allows Google Earth to achieve maximum performance because it loads only the portions of
         * the image that are in view, and only the pixel details that can be discerned by the user at the current
         * viewpoint. When you specify an image pyramid, you also modify the &lt;href&gt; in the &lt;Icon&gt; element to
         * include specifications for which tiles to load.
         * @memberof KmlPhotoOverlay.prototype
         * @readonly
         * @type {ImagePyramid}
         */
        kmlImagePyramid: {
            get: function () {
                return this._factory.any(this, {
                    name: ImagePyramid.prototype.getTagNames()
                });
            }
        }
    });

    /**
     * @inheritDoc
     */
    KmlPhotoOverlay.prototype.getTagNames = function () {
        return ['PhotoOverlay'];
    };

    KmlElements.addKey(KmlPhotoOverlay.prototype.getTagNames[0], KmlPhotoOverlay);

    return KmlPhotoOverlay;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @export KmlPlacemark
 */
define('formats/kml/features/KmlPlacemark',[
    './../KmlElements',
    './KmlFeature',
    '../geom/KmlGeometry',
    '../styles/KmlStyle',
    '../KmlTimeSpan',
    '../KmlTimeStamp',
    '../../../shapes/PlacemarkAttributes',
    '../../../shapes/Placemark',
    '../../../util/Color',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/TextAttributes',
    '../../../util/Offset',
    '../../../util/WWUtil'
], function (KmlElements,
             KmlFeature,
             KmlGeometry,
             KmlStyle,
             KmlTimeSpan,
             KmlTimeStamp,
             PlacemarkAttributes,
             Placemark,
             Color,
             ShapeAttributes,
             TextAttributes,
             Offset,
             WWUtil) {
    "use strict";
    /**
     * Constructs an KmlPlacemark. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from Kml file are read
     * @alias KmlPlacemark
     * @classdesc Contains the data associated with KmlPlacemark.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Placemark.
     * @constructor
     * @throws {ArgumentError} If the node is null.
     * @see https://developers.google.com/kml/documentation/kmlreference#placemark
     * @augments KmlFeature
     */
    var KmlPlacemark = function (options) {
        KmlFeature.call(this, options);
    };

    KmlPlacemark.prototype = Object.create(KmlFeature.prototype);

    Object.defineProperties(KmlPlacemark.prototype, {
        /**
         * It contains geometry associated with this placemark. The geometry is cached.
         * @memberof KmlPlacemark.prototype
         * @type {KmlGeometry}
         * @readonly
         */
        kmlGeometry: {
            get: function () {
                return this._factory.any(this, {
                    name: KmlGeometry.prototype.getTagNames()
                });
            }
        }
    });

    KmlPlacemark.prototype.render = function(dc, kmlOptions) {
        KmlFeature.prototype.render.call(this, dc, kmlOptions);

        kmlOptions = WWUtil.clone(kmlOptions);

        if(kmlOptions.lastStyle && !this._renderable) {
            // TODO: render placemarks without geometry.
            if (this.kmlGeometry) {
                this._renderable = new Placemark(
                    this.kmlGeometry.kmlCenter,
                    false,
                    this.prepareAttributes(kmlOptions.lastStyle.normal)
                );
                if(kmlOptions.lastStyle.highlight) {
                    this._renderable.highlightAttributes = this.prepareAttributes(kmlOptions.lastStyle.highlight);
                }
                this.moveValidProperties();
                dc.redrawRequested = true;
            }
        }
        
        if(this._renderable) {
            if (this.kmlGeometry) {
                this.kmlGeometry.render(dc, kmlOptions);
                this._renderable.render(dc);
            }
        }
    };

    /**
     * Prepare attributes for displaying the Placemark.
     * @param style {KmlStyle} Style altering the defaults.
     * @returns {PlacemarkAttributes} Attributes representing the current Placemark.
     */
    KmlPlacemark.prototype.prepareAttributes = function (style) {
        var options = style && style.generate() || {normal: {}, highlight:{}};
        var placemarkAttributes = new PlacemarkAttributes(KmlStyle.placemarkAttributes(options));

        placemarkAttributes.imageOffset = new Offset(
            WorldWind.OFFSET_FRACTION, 0.3,
            WorldWind.OFFSET_FRACTION, 0.0);
        placemarkAttributes.imageColor = Color.WHITE;
        placemarkAttributes.labelAttributes = new TextAttributes(KmlStyle.textAttributes({
            _offset: new Offset(
                WorldWind.OFFSET_FRACTION, 0.5,
                WorldWind.OFFSET_FRACTION, 1.0),
            _color: Color.YELLOW
        }));
        placemarkAttributes.drawLeaderLine = true;
        placemarkAttributes.leaderLineAttributes = new ShapeAttributes(KmlStyle.shapeAttributes({
            outlineColor: Color.RED
        }));

        return placemarkAttributes;
    };

    /**
     * It takes properties from the KML definition and move them into the internal objects.
     */
    KmlPlacemark.prototype.moveValidProperties = function () {
        this._renderable.label = this.kmlName || '';
        this._renderable.altitudeMode = this.kmlAltitudeMode || WorldWind.RELATIVE_TO_GROUND;
        this._renderable.enableLeaderLinePicking = true;
    };

    /**
     * Returns tag name of this Node.
     * @returns {String[]}
     */
    KmlPlacemark.prototype.getTagNames = function () {
        return ['Placemark'];
    };

    KmlElements.addKey(KmlPlacemark.prototype.getTagNames()[0], KmlPlacemark);

    return KmlPlacemark;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/geom/KmlPolygon',[
    '../../../util/Color',
    '../KmlElements',
    './KmlGeometry',
    './KmlLinearRing',
    '../styles/KmlStyle',
    '../../../geom/Location',
    '../util/NodeTransformers',
    '../../../shapes/Polygon',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolygon'
], function (Color,
             KmlElements,
             KmlGeometry,
             KmlLinearRing,
             KmlStyle,
             Location,
             NodeTransformers,
             Polygon,
             ShapeAttributes,
             SurfacePolygon) {
    "use strict";
    /**
     * Constructs an KmlPolygon. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * It is Polygon and KmlGeometry.
     * @alias KmlPolygon
     * @constructor
     * @classdesc Contains the data associated with Kml polygon
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Polygon
     * @param options.style {Promise} Promise of styles to be applied to this Polygon.
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#polygon
     */
    var KmlPolygon = function (options) {
        KmlGeometry.call(this, options);

        this.initialized = false;
    };

    KmlPolygon.prototype = Object.create(KmlGeometry.prototype);

    Object.defineProperties(KmlPolygon.prototype, {
        /**
         * In case that the polygon is above ground, this property decides whether there is going to be a line to
         * the ground.
         * @memberof KmlPolygon.prototype
         * @type {Boolean}
         * @readonly
         */
        kmlExtrude: {
            get: function () {
                return this._factory.specific(this, {name: 'extrude', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * Whether tessellation should be used for current node.
         * @memberof KmlPolygon.prototype
         * @readonly
         * @type {Boolean}
         */
        kmlTessellate: {
            get: function () {
                return this._factory.specific(this, {name: 'tessellate', transformer: NodeTransformers.boolean});
            }
        },

        /**
         * It explains how we should treat the altitude of the polygon. Possible choices are explained in:
         * https://developers.google.com/kml/documentation/kmlreference#point
         * @memberof KmlPolygon.prototype
         * @type {String}
         * @readonly
         */
        kmlAltitudeMode: {
            get: function () {
                return this._factory.specific(this, {name: 'altitudeMode', transformer: NodeTransformers.string});
            }
        },

        /**
         * Outer boundary of this polygon represented as a LinearRing.
         * @memberof KmlPolygon.prototype
         * @type {KmlLinearRing}
         * @readonly
         */
        kmlOuterBoundary: {
            get: function () {
                return this._factory.specific(this, {name: 'outerBoundaryIs', transformer: NodeTransformers.linearRing});
            }
        },

        /**
         * Inner boundary of this polygon represented as a LinearRing. Optional property
         * @memberof KmlPolygon.prototype.
         * @type {KmlLinearRing}
         * @readonly
         */
        kmlInnerBoundary: {
            get: function () {
                return this._factory.specific(this, {name: 'innerBoundaryIs', transformer: NodeTransformers.linearRing});
            }
        },

        /**
         * It returns center of outer boundaries of the polygon.
         * @memberof KmlPolygon.prototype
         * @readonly
         * @type {Position}
         */
        kmlCenter: {
            get: function () {
                return this.kmlOuterBoundary.kmlCenter;
            }
        }
    });

    /**
     * Internal use only. Once create the instance of actual polygon.
     * @param styles {Object|null}
     * @param styles.normal {KmlStyle} Style to apply when not highlighted
     * @param styles.highlight {KmlStyle} Style to apply when item is highlighted. Currently ignored.
     */
    KmlPolygon.prototype.createPolygon = function(styles) {
        if(this.kmlAltitudeMode == WorldWind.CLAMP_TO_GROUND) {
            this._renderable = new SurfacePolygon(this.prepareLocations(), this.prepareAttributes(styles.normal));
        } else {
            this._renderable = new Polygon(this.prepareLocations(), this.prepareAttributes(styles.normal));
        }
        if(styles.highlight) {
            this._renderable.highlightAttributes = this.prepareAttributes(styles.highlight);
        }
        this.moveValidProperties();
    };

	/**
     * @inheritDoc
     */
    KmlPolygon.prototype.render = function(dc, kmlOptions) {
        KmlGeometry.prototype.render.call(this, dc, kmlOptions);

        if(kmlOptions.lastStyle && !this._renderable) {
            this.createPolygon(kmlOptions.lastStyle);
            dc.redrawRequested = true;
        }

        if(this._renderable) {
            this._renderable.enabled = this.enabled;
            this._renderable.render(dc);
        }
    };

    // For internal use only. Intentionally left undocumented.
    KmlPolygon.prototype.moveValidProperties = function () {
        this._renderable.extrude = this.kmlExtrude || true;
        this._renderable.altitudeMode = this.kmlAltitudeMode || WorldWind.CLAMP_TO_GROUND;
    };

    /**
     * @inheritDoc
     */
    KmlPolygon.prototype.prepareAttributes = function (style) {
        var shapeOptions = style && style.generate() || {};

        shapeOptions._drawVerticals = this.kmlExtrude || false;
        shapeOptions._applyLighting = true;
        shapeOptions._depthTest = true;
        shapeOptions._outlineStippleFactor = 0;
        shapeOptions._outlineStipplePattern = 61680;
        shapeOptions._enableLighting = true;

        return new ShapeAttributes(KmlStyle.shapeAttributes(shapeOptions));
    };

    /**
     * @inheritDoc
     */
    KmlPolygon.prototype.prepareLocations = function () {
        var locations = [];
        if (this.kmlInnerBoundary != null) {
            locations[0] = this.kmlInnerBoundary.kmlPositions;
            locations[1] = this.kmlOuterBoundary.kmlPositions;
        } else {
            locations = this.kmlOuterBoundary.kmlPositions;
        }
        return locations;
    };

    /**
     * @inheritDoc
     */
    KmlPolygon.prototype.getStyle = function () {
        return this._style;
    };

    /**
     * @inheritDoc
     */
    KmlPolygon.prototype.getTagNames = function () {
        return ['Polygon'];
    };

    KmlElements.addKey(KmlPolygon.prototype.getTagNames()[0], KmlPolygon);

    return KmlPolygon;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlScreenOverlay',[
    './../KmlElements',
    './KmlFeature',
    './KmlOverlay',
    '../util/NodeTransformers',
    '../../../util/Offset',
    '../../../shapes/ScreenImage',
    '../../../util/WWUtil'
], function (KmlElements,
             KmlFeature,
             KmlOverlay,
             NodeTransformers,
             Offset,
             ScreenImage,
             WWUtil) {
    "use strict";

    /**
     * Constructs an KmlScreenOverlay. Applications usually don't call this constructor. It is called by {@link
        * KmlFile} as objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlScreenOverlay
     * @classdesc Contains the data associated with ScreenOverlay node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing ScreenOverlay
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#screenoverlay
     * @augments KmlOverlay
     */
    var KmlScreenOverlay = function (options) {
        KmlOverlay.call(this, options);

        console.log("Create Screen Overlay", this);
    };

    KmlScreenOverlay.prototype = Object.create(KmlOverlay.prototype);

    Object.defineProperties(KmlScreenOverlay.prototype, {
        /**
         * Indicates the angle of rotation of the parent object. A value of 0 means no rotation. The value is an
         * angle in degrees counterclockwise starting from north. Use +-180 to indicate the rotation of the parent
         * object from
         * 0. The center of the &lt;rotation&gt;, if not (.5,.5), is specified in &lt;rotationXY&gt;.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {Number}
         */
        kmlRotation: {
            get: function () {
                return this._factory.specific(this, {name: 'rotation', transformer: NodeTransformers.number});
            }
        },

        /**
         * Either the number of pixels, a fractional component of the image, or a pixel inset indicating the x
         * component of a point on the overlay image.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlOverlayXYx: {
            get: function () {
                return this._factory.specific(this, {name: 'overlayXY', transformer: NodeTransformers.attribute('x'), attribute: 'kmlOverlayXYx'});
            }
        },

        /**
         * Either the number of pixels, a fractional component of the image, or a pixel inset indicating the y
         * component of a point on the overlay image.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlOverlayXYy: {
            get: function () {
                return this._factory.specific(this, {name: 'overlayXY', transformer: NodeTransformers.attribute('y'), attribute: 'kmlOverlayXYy'});
            }
        },

        /**
         * Units in which the x value is specified. A value of "fraction" indicates the x value is a fraction of the
         * image. A value of "pixels" indicates the x value in pixels. A value of "insetPixels" indicates the indent
         * from the right edge of the image.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlOverlayXYxunits: {
            get: function () {
                return this._factory.specific(this, {name: 'overlayXY', transformer: NodeTransformers.attribute('xunits'), attribute: 'kmlOverlayXYxunits'});
            }
        },

        /**
         * Units in which the y value is specified. A value of "fraction" indicates the y value is a fraction of the
         * image. A value of "pixels" indicates the y value in pixels. A value of "insetPixels" indicates the indent
         * from the top edge of the image.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlOverlayXYyunits: {
            get: function () {
                return this._factory.specific(this, {name: 'overlayXY', transformer: NodeTransformers.attribute('yunits'), attribute: 'kmlOverlayXYyunits'});
            }
        },

        /**
         * Either the number of pixels, a fractional component of the screen, or a pixel inset indicating the x
         * component of a point on the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlScreenXYx: {
            get: function () {
                return this._factory.specific(this, {name: 'screenXY', transformer: NodeTransformers.attribute('x'), attribute: 'kmlScreenXYx'});
            }
        },

        /**
         * Either the number of pixels, a fractional component of the screen, or a pixel inset indicating the y
         * component of a point on the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlScreenXYy: {
            get: function () {
                return this._factory.specific(this, {name: 'screenXY', transformer: NodeTransformers.attribute('y'), attribute: 'kmlScreenXYy'});
            }
        },

        /**
         * Units in which the x value is specified. A value of "fraction" indicates the x value is a fraction of
         * the
         * screen. A value of "pixels" indicates the x value in pixels. A value of "insetPixels" indicates the
         * indent from the right edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlScreenXYxunits: {
            get: function () {
                return this._factory.specific(this, {name: 'screenXY', transformer: NodeTransformers.attribute('xunits'), attribute: 'kmlScreenXYxunits'});
            }
        },

        /**
         * Units in which the y value is specified. A value of fraction indicates the y value is a fraction of the
         * screen. A value of "pixels" indicates the y value in pixels. A value of "insetPixels" indicates the
         * indent from the top edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlScreenXYyunits: {
            get: function () {
                return this._factory.specific(this, {name: 'screenXY', transformer: NodeTransformers.attribute('yunits'), attribute: 'kmlScreenXYyunits'});
            }
        },

        /**
         * It decides by how much will be the screen overlay rotated in x direction
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlRotationXYx: {
            get: function () {
                return this._factory.specific(this, {name: 'rotationXY', transformer: NodeTransformers.attribute('x'), attribute: 'kmlRotationXYx'});
            }
        },

        /**
         * It decides by how much will be the screen overlay rotated in y direction
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlRotationXYy: {
            get: function () {
                return this._factory.specific(this, {name: 'rotationXY', transformer: NodeTransformers.attribute('y'), attribute: 'kmlRotationXYy'});
            }
        },

        /**
         * Units in which the x value is specified. A value of "fraction" indicates the x value is a fraction of
         * the
         * screen. A value of "pixels" indicates the x value in pixels. A value of "insetPixels" indicates the
         * indent from the right edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlRotationXYxunits: {
            get: function () {
                return this._factory.specific(this, {name: 'rotationXY', transformer: NodeTransformers.attribute('xunits'), attribute: 'kmlRotationXYxunits'});
            }
        },

        /**
         * Units in which the y value is specified. A value of fraction indicates the y value is a fraction of the
         * screen. A value of "pixels" indicates the y value in pixels. A value of "insetPixels" indicates the
         * indent from the top edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlRotationXYyunits: {
            get: function () {
                return this._factory.specific(this, {name: 'rotationXY', transformer: NodeTransformers.attribute('yunits'), attribute: 'kmlRotationXYyunits'});
            }
        },

        /**
         * A value of +-1 indicates to use the native dimension
         * A value of 0 indicates to maintain the aspect ratio
         * A value of n sets the value of the dimension
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlSizex: {
            get: function () {
                return this._factory.specific(this, {name: 'size', transformer: NodeTransformers.attribute('x'), attribute: 'kmlSizex'});
            }
        },

        /**
         * A value of +-1 indicates to use the native dimension
         * A value of 0 indicates to maintain the aspect ratio
         * A value of n sets the value of the dimension
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlSizey: {
            get: function () {
                return this._factory.specific(this, {name: 'size', transformer: NodeTransformers.attribute('y'), attribute: 'kmlSizey'});
            }
        },

        /**
         * Units in which the x value is specified. A value of "fraction" indicates the x value is a fraction of
         * the
         * screen. A value of "pixels" indicates the x value in pixels. A value of "insetPixels" indicates the
         * indent from the right edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlSizexunits: {
            get: function () {
                return this._factory.specific(this, {name: 'size', transformer: NodeTransformers.attribute('xunits'), attribute: 'kmlSizexunits'});
            }
        },

        /**
         * Units in which the y value is specified. A value of fraction indicates the y value is a fraction of the
         * screen. A value of "pixels" indicates the y value in pixels. A value of "insetPixels" indicates the
         * indent from the top edge of the screen.
         * @memberof KmlScreenOverlay.prototype
         * @readonly
         * @type {String}
         */
        kmlSizeyunits: {
            get: function () {
                return this._factory.specific(this, {name: 'size', transformer: NodeTransformers.attribute('yunits'), attribute: 'kmlSizeyunits'});
            }
        }
    });

	/**
     * @inheritDoc
     */
    KmlScreenOverlay.prototype.render = function(dc, kmlOptions) {
        KmlFeature.prototype.render.call(this, dc, kmlOptions);

        if(!this._renderable) {
            if(this.kmlIcon) {
                this._renderable = new ScreenImage(
                    new Offset(
                        this.kmlScreenXYxunits,
                        this.kmlScreenXYx,
                        this.kmlScreenXYyunits,
                        this.kmlScreenXYy
                    ),
                    this.kmlIcon.kmlHref
                );
                this._renderable.imageOffset = new Offset(
                    this.kmlOverlayXYxunits,
                    this.kmlOverlayXYx,
                    this.kmlOverlayXYyunits,
                    this.kmlOverlayXYy
                );
                dc.redrawRequested = true;
            }
        }

        if(this._renderable) {
            this._renderable.render(dc);
        }
    };

    /**
     * @inheritDoc
     */
    KmlScreenOverlay.prototype.getTagNames = function () {
        return ['ScreenOverlay'];
    };

    KmlElements.addKey(KmlScreenOverlay.prototype.getTagNames()[0], KmlScreenOverlay);

    return KmlScreenOverlay;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/features/KmlTour',[
    './../KmlElements',
    './KmlFeature'
], function (KmlElements,
             KmlFeature) {
    "use strict";

    /**
     * Constructs an KmlTour. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlTour
     * @classdesc Contains the data associated with Tour node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing Tour.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#gxtour
     * @augments KmlFeature
     */
    var KmlTour = function (options) {
        KmlFeature.call(this, options);
    };

    KmlTour.prototype = Object.create(KmlFeature.prototype);

    /**
     * @inheritDoc
     */
    KmlTour.prototype.getTagNames = function () {
        return ['gx:Tour'];
    };

    KmlElements.addKey(KmlTour.prototype.getTagNames()[0], KmlTour);

    return KmlTour;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/geom/KmlTrack',[
    './../KmlElements',
    './KmlGeometry'
], function (KmlElements,
             KmlGeometry) {
    "use strict";

    /**
     * Constructs an KmlTrack. Applications usually don't call this constructor. It is called by {@link KmlFile} as
     * objects from Kml file are read. This object is already concrete implementation.
     * @alias KmlTrack
     * @classdesc Contains the data associated with Track node.
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Track.
     * @constructor
     * @throws {ArgumentError} If the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#gxtrack
     * @augments KmlGeometry
     */
    var KmlTrack = function (options) {
        KmlGeometry.call(this, options);
    };

    KmlTrack.prototype = Object.create(KmlGeometry.prototype);

    /**
     * @inheritDoc
     */
    KmlTrack.prototype.getTagNames = function () {
        return ['gx:Track'];
    };

    KmlElements.addKey(KmlTrack.prototype.getTagNames()[0], KmlTrack);

    return KmlTrack;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/controls/KmlTreeVisibility',[
    '../../../util/WWUtil',
    './KmlControls'
], function (WWUtil,
             KmlControls
    ) {
    "use strict";

    /**
     * This class represents the structure of Documents, Folders and Features in the document. It renders them into
     * some of the outside area with defined classes, so that user can specify the look and feel.
     * Important part of this effort is to allow user show/hide subset of the Features present in the document.
     * Implementing this functionality also simplifies the manual testing.
     * @param visualElementId {String} Id of the element into which this will be rendered.
     * @param wwd {WorldWindow} WorldWindow instance necessary to control the redraw in the framework.
     * @constructor
     * @augments KmlControls
     * @alias KmlTreeVisibility
     * @classdesc Class for controlling the visibility of features.
     */
    var KmlTreeVisibility = function (visualElementId, wwd) {
        KmlControls.apply(this);

        this._visualElementId = visualElementId;
        this._wwd = wwd;
    };

    KmlTreeVisibility.prototype = Object.create(KmlControls.prototype);

    /**
     * @inheritDoc
     */
    KmlTreeVisibility.prototype.hook = function (node, options) {
        if(options.isFeature) {
            this.createControls(node);
        }
    };

    // For internal use only.
    KmlTreeVisibility.prototype.createControls = function (node) {
        var name = node.kmlName || node.id || WWUtil.guid();
        var enabled = node.enabled || node.kmlVisibility === true;

        var controlsForSingleElement = document.createElement("div");
        var toggleVisibility = document.createElement("input");
        toggleVisibility.setAttribute("type", "checkbox");
        if (enabled) {
            toggleVisibility.setAttribute("checked", "checked");
        }
        toggleVisibility.addEventListener("click", toggleVisibilityOfElement, true);

        controlsForSingleElement.appendChild(toggleVisibility);

        var lookAtName = document.createElement("span");
        lookAtName.appendChild(document.createTextNode(name));
        lookAtName.addEventListener("click", lookAt, true);
        controlsForSingleElement.appendChild(lookAtName);

        document.getElementById(this._visualElementId).appendChild(controlsForSingleElement);

        var self = this;

        function toggleVisibilityOfElement() {
            enabled = !enabled;
            self.updateDescendants(node, enabled);
        }

        function lookAt() {
            if (node.kmlAbstractView) {
                node.kmlAbstractView.update({wwd: self._wwd});
            }
        }
    };

    // Internal use only. Updates all descendants of given Feature.
    KmlTreeVisibility.prototype.updateDescendants = function (node, enabled) {
        node.controlledVisibility = enabled;
        this._wwd.redraw();
    };

    return KmlTreeVisibility;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports LandsatRestLayer
 */
define('layer/LandsatRestLayer',[
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/LevelRowColumnUrlBuilder',
        '../util/WWUtil'
    ],
    function (ArgumentError,
              Location,
              Logger,
              Sector,
              TiledImageLayer,
              LevelRowColumnUrlBuilder,
              WWUtil) {
        "use strict";

        /**
         * Constructs a LandSat image layer that uses a REST interface to retrieve its imagery.
         * @alias LandsatRestLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a LandSat image layer that spans the entire globe. The imagery is obtained from a
         * specified REST tile service.
         * See [LevelRowColumnUrlBuilder]{@link LevelRowColumnUrlBuilder} for a description of the REST interface.
         * @param {String} serverAddress The server address of the tile service. May be null, in which case the
         * current origin is used (see window.location).
         * @param {String} pathToData The path to the data directory relative to the specified server address.
         * May be null, in which case the server address is assumed to be the full path to the data directory.
         * @param {String} displayName The display name to associate with this layer.
         */
        var LandsatRestLayer = function (serverAddress, pathToData, displayName) {
            var cachePath = WWUtil.urlPath(serverAddress + "/" + pathToData);

            TiledImageLayer.call(this, Sector.FULL_SPHERE, new Location(36, 36), 10, "image/png", cachePath, 512, 512);

            this.displayName = displayName;
            this.pickEnabled = false;
            this.urlBuilder = new LevelRowColumnUrlBuilder(serverAddress, pathToData);
        };

        LandsatRestLayer.prototype = Object.create(TiledImageLayer.prototype);

        return LandsatRestLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports LengthMeasurer
 */

define('util/measure/LengthMeasurer',[
        '../../error/ArgumentError',
        '../../geom/Location',
        '../Logger',
        './MeasurerUtils',
        '../../geom/Position',
        '../../geom/Vec3'
    ],
    function (ArgumentError,
              Location,
              Logger,
              MeasurerUtils,
              Position,
              Vec3) {

        /**
         * Utility class to measure length along a path on a globe. <p/> <p>Segments which are longer then the current
         * maxSegmentLength will be subdivided along lines following the current pathType - WorldWind.LINEAR,
         * WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE.</p> <p/> <p>For follow terrain, the computed length will
         * account for terrain deformations as if someone was walking along that path. Otherwise the length is the sum
         * of the cartesian distance between the positions.</p>
         * <p/>
         * <p>When following terrain the measurer will sample terrain elevations at regular intervals along the path.
         * The minimum number of samples used for the whole length can be set with lengthTerrainSamplingSteps.
         * However, the minimum sampling interval is 30 meters.
         * @alias LengthMeasurer
         * @constructor
         * @param {WorldWindow} wwd The WorldWindow associated with LengthMeasurer.
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var LengthMeasurer = function (wwd) {

            if (!wwd) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LengthMeasurer", "constructor", "missingWorldWindow"));
            }

            this.wwd = wwd;

            // Private. The minimum length of a terrain following subdivision.
            this.DEFAULT_MIN_SEGMENT_LENGTH = 30;

            // Private. Documentation is with the defined property below.
            this._maxSegmentLength = 100e3;

            // Private. Documentation is with the defined property below.
            this._lengthTerrainSamplingSteps = 128;

            // Private. A list of positions with no segment longer then maxLength and elevations following terrain or not.
            this.subdividedPositions = null;
        };

        Object.defineProperties(LengthMeasurer.prototype, {
            /**
             * The maximum length a segment can have before being subdivided along a line following the current pathType.
             * @type {Number}
             * @memberof LengthMeasurer.prototype
             */
            maxSegmentLength: {
                get: function () {
                    return this._maxSegmentLength;
                },
                set: function (value) {
                    this._maxSegmentLength = value;
                }
            },

            /**
             * The number of terrain elevation samples used along the path to approximate it's terrain following length.
             * @type {Number}
             * @memberof LengthMeasurer.prototype
             */
            lengthTerrainSamplingSteps: {
                get: function () {
                    return this._lengthTerrainSamplingSteps;
                },
                set: function (value) {
                    this._lengthTerrainSamplingSteps = value;
                }
            }
        });

        /**
         * Get the path length in meter. <p/> <p>If followTerrain is true, the computed length will account
         * for terrain deformations as if someone was walking along that path. Otherwise the length is the sum of the
         * cartesian distance between each positions.</p>
         *
         * @param {Position[]} positions
         * @param {Boolean} followTerrain
         * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
         *
         * @return the current path length or -1 if the position list is too short.
         */
        LengthMeasurer.prototype.getLength = function (positions, followTerrain, pathType) {
            pathType = pathType || WorldWind.GREAT_CIRCLE;
            this.subdividedPositions = null;
            return this.computeLength(positions, followTerrain, pathType);
        };

        /**
         * Get the path length in meter of a Path. <p/> <p>If the path's followTerrain is true, the computed length
         * will account for terrain deformations as if someone was walking along that path. Otherwise the length is the
         * sum of the cartesian distance between each positions.</p>
         *
         * @param {Path} path
         *
         * @return the current path length or -1 if the position list is too short.
         */
        LengthMeasurer.prototype.getPathLength = function (path) {
            this.subdividedPositions = null;
            return this.computeLength(path.positions, path.followTerrain, path.pathType);
        };

        /**
         * Get the great circle, rhumb or linear distance, in meter, of a Path or an array of Positions.
         *
         * @param {Path|Position[]} path A Path or an array of Positions
         * @param {String} pathType Optional argument used when path is an array of Positions.
         * Defaults to WorldWind.GREAT_CIRCLE.
         * Recognized values are:
         * <ul>
         * <li>[WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE}</li>
         * <li>[WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE}</li>
         * <li>[WorldWind.LINEAR]{@link WorldWind#LINEAR}</li>
         * </ul>
         *
         * @return {Number} the current path length or -1 if the position list is too short.
         */
        LengthMeasurer.prototype.getGeographicDistance = function (path, pathType) {
            if (path instanceof WorldWind.Path) {
                var positions = path.positions;
                var _pathType = path.pathType;
            }
            else if (Array.isArray(path)) {
                positions = path;
                _pathType = pathType || WorldWind.GREAT_CIRCLE;
            }

            if (!positions || positions.length < 2) {
                return -1;
            }

            var fn = Location.greatCircleDistance;
            if (_pathType === WorldWind.RHUMB_LINE) {
                fn = Location.rhumbDistance;
            }
            else if (_pathType === WorldWind.LINEAR) {
                fn = Location.linearDistance;
            }

            var distance = 0;
            for (var i = 0, len = positions.length - 1; i < len; i++) {
                var pos1 = positions[i];
                var pos2 = positions[i + 1];
                distance += fn(pos1, pos2);
            }

            return distance * this.wwd.globe.equatorialRadius;
        };

        /**
         * Computes the length.
         * @param {Position[]} positions
         * @param {Boolean} followTerrain
         * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
         */
        LengthMeasurer.prototype.computeLength = function (positions, followTerrain, pathType) {
            if (!positions || positions.length < 2) {
                return -1;
            }

            var globe = this.wwd.globe;

            if (this.subdividedPositions == null) {
                // Subdivide path so as to have at least segments smaller then maxSegmentLength. If follow terrain,
                // subdivide so as to have at least lengthTerrainSamplingSteps segments, but no segments shorter then
                // DEFAULT_MIN_SEGMENT_LENGTH either.
                var maxLength = this._maxSegmentLength;
                if (followTerrain) {
                    // Recurse to compute overall path length not following terrain
                    var pathLength = this.computeLength(positions, false, pathType);
                    // Determine segment length to have enough sampling points
                    maxLength = pathLength / this._lengthTerrainSamplingSteps;
                    maxLength = Math.min(Math.max(maxLength, this.DEFAULT_MIN_SEGMENT_LENGTH), this._maxSegmentLength);
                }
                this.subdividedPositions = MeasurerUtils.subdividePositions(globe, positions, followTerrain, pathType,
                    maxLength);
            }

            var distance = 0;
            var pos0 = this.subdividedPositions[0];
            var p1 = new Vec3(0, 0, 0);
            var p2 = new Vec3(0, 0, 0);
            p1 = globe.computePointFromPosition(pos0.latitude, pos0.longitude, pos0.altitude, p1);
            for (var i = 1, len = this.subdividedPositions.length; i < len; i++) {
                var pos = this.subdividedPositions[i];
                p2 = globe.computePointFromPosition(pos.latitude, pos.longitude, pos.altitude, p2);
                distance += p1.distanceTo(p2);
                p1.copy(p2);
            }

            return distance;
        };

        return LengthMeasurer;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Navigator
 */
define('navigate/Navigator',[
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Matrix',
        '../navigate/NavigatorState',
        '../geom/Position',
        '../error/UnsupportedOperationError',
        '../geom/Vec3',
        '../util/WWMath'],
    function (ArgumentError,
              Logger,
              Matrix,
              NavigatorState,
              Position,
              UnsupportedOperationError,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a base navigator.
         * @alias Navigator
         * @constructor
         * @classdesc Provides an abstract base class for navigators. This class is not meant to be instantiated
         * directly. See {@Link LookAtNavigator} for a concrete navigator.
         * @param {WorldWindow} worldWindow The WorldWindow to associate with this navigator.
         */
        var Navigator = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Navigator", "constructor", "missingWorldWindow"));
            }

            /**
             * The {@link WorldWindow} associated with this navigator.
             * @type {WorldWindow}
             * @readonly
             */
            this.worldWindow = worldWindow;

            /**
             * This navigator's heading, in degrees clockwise from north.
             * @type {Number}
             * @default 0
             */
            this.heading = 0;

            /**
             * This navigator's tilt, in degrees.
             * @type {Number}
             * @default 0
             */
            this.tilt = 0;

            /**
             * This navigator's roll, in degrees.
             * @type {Number}
             * @default 0
             */
            this.roll = 0;

            // Intentionally not documented.
            this.nearDistance = 1;

            // Intentionally not documented.
            this.farDistance = 10e6;
        };

        /**
         * Returns the current state of this navigator. Subclasses must override this method.
         * @returns {NavigatorState} The current state of this navigator.
         */
        Navigator.prototype.currentState = function () {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "Navigator", "currentState", "abstractInvocation"));
        };

        /**
         * Returns the current navigator state for a specified model-view matrix.
         * This method is meant to be called only by subclasses;
         * applications should not call this method.
         * @protected
         * @param {Matrix} modelviewMatrix The modelview matrix.
         * @returns {NavigatorState} The current navigator state.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        Navigator.prototype.currentStateForModelview = function (modelviewMatrix) {
            if (!modelviewMatrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Navigator", "currentStateForModelview", "missingMatrix"));
            }

            var globe = this.worldWindow.globe,
                globeRadius = WWMath.max(globe.equatorialRadius, globe.polarRadius),
                eyePoint = modelviewMatrix.extractEyePoint(new Vec3(0, 0, 0)),
                eyePos = globe.computePositionFromPoint(eyePoint[0], eyePoint[1], eyePoint[2], new Position(0, 0, 0)),
                eyeHorizon = WWMath.horizonDistanceForGlobeRadius(globeRadius, eyePos.altitude),
                atmosphereHorizon = WWMath.horizonDistanceForGlobeRadius(globeRadius, 160000),
                viewport = this.worldWindow.viewport,
                viewDepthBits = this.worldWindow.depthBits,
                distanceToSurface,
                maxNearDistance,
                projectionMatrix = Matrix.fromIdentity();

            // Set the far clip distance to the smallest value that does not clip the atmosphere.
            // TODO adjust the clip plane distances based on the navigator's orientation - shorter distances when the
            // TODO horizon is not in view
            // TODO parameterize the object altitude for horizon distance
            this.farDistance = eyeHorizon + atmosphereHorizon;
            if (this.farDistance < 1e3)
                this.farDistance = 1e3;

            // Compute the near clip distance in order to achieve a desired depth resolution at the far clip distance.
            // This computed distance is limited such that it does not intersect the terrain when possible and is never
            // less than a predetermined minimum (usually one). The computed near distance automatically scales with the
            // resolution of the WebGL depth buffer.
            this.nearDistance = WWMath.perspectiveNearDistanceForFarDistance(this.farDistance, 10, viewDepthBits);

            // Prevent the near clip plane from intersecting the terrain.
            distanceToSurface = eyePos.altitude - globe.elevationAtLocation(eyePos.latitude, eyePos.longitude);
            if (distanceToSurface > 0) {
                maxNearDistance = WWMath.perspectiveNearDistance(viewport.width, viewport.height, distanceToSurface);
                if (this.nearDistance > maxNearDistance)
                    this.nearDistance = maxNearDistance;
            }

            if (this.nearDistance < 1)
                this.nearDistance = 1;

            // Compute the current projection matrix based on this navigator's perspective properties and the current
            // WebGL viewport.
            projectionMatrix.setToPerspectiveProjection(viewport.width, viewport.height, this.nearDistance, this.farDistance);

            return new NavigatorState(modelviewMatrix, projectionMatrix, viewport, this.heading, this.tilt);
        };

        return Navigator;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PanRecognizer
 */
define('gesture/PanRecognizer',['../gesture/GestureRecognizer'],
    function (GestureRecognizer) {
        "use strict";

        /**
         * Constructs a pan gesture recognizer.
         * @alias PanRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for touch panning gestures.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var PanRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            /**
             *
             * @type {Number}
             */
            this.minNumberOfTouches = 1;

            /**
             *
             * @type {Number}
             */
            this.maxNumberOfTouches = Number.MAX_VALUE;

            // Intentionally not documented.
            this.interpretDistance = 20;
        };

        PanRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        // Documented in superclass.
        PanRecognizer.prototype.mouseDown = function (event) {
            if (this.state == WorldWind.POSSIBLE) {
                this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
            }
        };

        // Documented in superclass.
        PanRecognizer.prototype.touchMove = function (touch) {
            if (this.state == WorldWind.POSSIBLE) {
                if (this.shouldInterpret()) {
                    if (this.shouldRecognize()) {
                        this.state = WorldWind.BEGAN;
                    } else {
                        this.state = WorldWind.FAILED;
                    }
                }
            } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                this.state = WorldWind.CHANGED;
            }
        };

        // Documented in superclass.
        PanRecognizer.prototype.touchEnd = function (touch) {
            if (this.touchCount == 0) { // last touch ended
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.ENDED;
                }
            }
        };

        // Documented in superclass.
        PanRecognizer.prototype.touchCancel = function (touch) {
            if (this.touchCount == 0) { // last touch cancelled
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.CANCELLED;
                }
            }
        };

        // Documented in superclass.
        PanRecognizer.prototype.prepareToRecognize = function () {
            // set translation to zero when the pan begins
            this.translationX = 0;
            this.translationY = 0;
        };

        /**
         *
         * @returns {boolean}
         * @protected
         */
        PanRecognizer.prototype.shouldInterpret = function () {
            var dx = this.translationX,
                dy = this.translationY,
                distance = Math.sqrt(dx * dx + dy * dy);
            return distance > this.interpretDistance; // interpret touches when the touch centroid moves far enough
        };

        /**
         *
         * @returns {boolean}
         * @protected
         */
        PanRecognizer.prototype.shouldRecognize = function () {
            var touchCount = this.touchCount;
            return touchCount != 0
                && touchCount >= this.minNumberOfTouches
                && touchCount <= this.maxNumberOfTouches
        };

        return PanRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PinchRecognizer
 */
define('gesture/PinchRecognizer',['../gesture/GestureRecognizer'],
    function (GestureRecognizer) {
        "use strict";

        /**
         * Constructs a pinch gesture recognizer.
         * @alias PinchRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for two finger pinch gestures.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var PinchRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            // Intentionally not documented.
            this._scale = 1;

            // Intentionally not documented.
            this._offsetScale = 1;

            // Intentionally not documented.
            this.referenceDistance = 0;

            // Intentionally not documented.
            this.interpretThreshold = 20;

            // Intentionally not documented.
            this.weight = 0.4;

            // Intentionally not documented.
            this.pinchTouches = [];
        };

        PinchRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        Object.defineProperties(PinchRecognizer.prototype, {
            scale: {
                get: function () {
                    return this._scale * this._offsetScale;
                }
            }
        });

        // Documented in superclass.
        PinchRecognizer.prototype.reset = function () {
            GestureRecognizer.prototype.reset.call(this);

            this._scale = 1;
            this._offsetScale = 1;
            this.referenceDistance = 0;
            this.pinchTouches = [];
        };

        // Documented in superclass.
        PinchRecognizer.prototype.mouseDown = function (event) {
            if (this.state == WorldWind.POSSIBLE) {
                this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
            }
        };

        // Documented in superclass.
        PinchRecognizer.prototype.touchStart = function (touch) {
            if (this.pinchTouches.length < 2) {
                if (this.pinchTouches.push(touch) == 2) {
                    this.referenceDistance = this.currentPinchDistance();
                    this._offsetScale *= this._scale;
                    this._scale = 1;
                }
            }
        };

        // Documented in superclass.
        PinchRecognizer.prototype.touchMove = function (touch) {
            if (this.pinchTouches.length == 2) {
                if (this.state == WorldWind.POSSIBLE) {
                    if (this.shouldRecognize()) {
                        this.state = WorldWind.BEGAN;
                    }
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    var distance = this.currentPinchDistance(),
                        newScale = Math.abs(distance / this.referenceDistance),
                        w = this.weight;
                    this._scale = this._scale * (1 - w) + newScale * w;
                    this.state = WorldWind.CHANGED;
                }
            }
        };

        // Documented in superclass.
        PinchRecognizer.prototype.touchEnd = function (touch) {
            var index = this.pinchTouches.indexOf(touch);
            if (index != -1) {
                this.pinchTouches.splice(index, 1);
            }

            // Transition to the ended state if this was the last touch.
            if (this.touchCount == 0) { // last touch ended
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.ENDED;
                }
            }
        };

        // Documented in superclass.
        PinchRecognizer.prototype.touchCancel = function (touch) {
            var index = this.pinchTouches.indexOf(touch);
            if (index != -1) {
                this.pinchTouches.splice(index, 1);
            }

            // Transition to the cancelled state if this was the last touch.
            if (this.touchCount == 0) {
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.CANCELLED;
                }
            }
        };

        // Documented in superclass.
        PinchRecognizer.prototype.prepareToRecognize = function () {
            this.referenceDistance = this.currentPinchDistance();
            this._scale = 1;
        };

        // Intentionally not documented.
        PinchRecognizer.prototype.shouldRecognize = function () {
            var distance = this.currentPinchDistance();

            return Math.abs(distance - this.referenceDistance) > this.interpretThreshold
        };

        // Intentionally not documented.
        PinchRecognizer.prototype.currentPinchDistance = function () {
            var touch0 = this.pinchTouches[0],
                touch1 = this.pinchTouches[1],
                dx = touch0.clientX - touch1.clientX,
                dy = touch0.clientY - touch1.clientY;

            return Math.sqrt(dx * dx + dy * dy);
        };

        return PinchRecognizer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports RotationRecognizer
 */
define('gesture/RotationRecognizer',[
        '../geom/Angle',
        '../gesture/GestureRecognizer'
    ],
    function (Angle,
              GestureRecognizer) {
        "use strict";

        /**
         * Constructs a rotation gesture recognizer.
         * @alias RotationRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for two finger rotation gestures.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var RotationRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            // Intentionally not documented.
            this._rotation = 0;

            // Intentionally not documented.
            this._offsetRotation = 0;

            // Intentionally not documented.
            this.referenceAngle = 0;

            // Intentionally not documented.
            this.interpretThreshold = 20;

            // Intentionally not documented.
            this.weight = 0.4;

            // Intentionally not documented.
            this.rotationTouches = [];
        };

        RotationRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        Object.defineProperties(RotationRecognizer.prototype, {
            rotation: {
                get: function () {
                    return this._rotation + this._offsetRotation;
                }
            }
        });

        // Documented in superclass.
        RotationRecognizer.prototype.reset = function () {
            GestureRecognizer.prototype.reset.call(this);

            this._rotation = 0;
            this._offsetRotation = 0;
            this.referenceAngle = 0;
            this.rotationTouches = [];
        };

        // Documented in superclass.
        RotationRecognizer.prototype.mouseDown = function (event) {
            if (this.state == WorldWind.POSSIBLE) {
                this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
            }
        };

        // Documented in superclass.
        RotationRecognizer.prototype.touchStart = function (touch) {
            if (this.rotationTouches.length < 2) {
                if (this.rotationTouches.push(touch) == 2) {
                    this.referenceAngle = this.currentTouchAngle();
                    this._offsetRotation += this._rotation;
                    this._rotation = 0;
                }
            }
        };

        // Documented in superclass.
        RotationRecognizer.prototype.touchMove = function (touch) {
            if (this.rotationTouches.length == 2) {
                if (this.state == WorldWind.POSSIBLE) {
                    if (this.shouldRecognize()) {
                        this.state = WorldWind.BEGAN;
                    }
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    var angle = this.currentTouchAngle(),
                        newRotation = Angle.normalizedDegrees(angle - this.referenceAngle),
                        w = this.weight;
                    this._rotation = this._rotation * (1 - w) + newRotation * w;
                    this.state = WorldWind.CHANGED;
                }
            }
        };

        // Documented in superclass.
        RotationRecognizer.prototype.touchEnd = function (touch) {
            var index = this.rotationTouches.indexOf(touch);
            if (index != -1) {
                this.rotationTouches.splice(index, 1);
            }

            // Transition to the ended state if this was the last touch.
            if (this.touchCount == 0) { // last touch ended
                if (this.state == WorldWind.POSSIBLE) {
                    this.state = WorldWind.FAILED;
                } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                    this.state = WorldWind.ENDED;
                }
            }
        };

        // Documented in superclass.
        RotationRecognizer.prototype.touchCancel = function (touch) {
            var index = this.rotationTouches.indexOf(touch);
            if (index != -1) {
                this.rotationTouches.splice(index, 1);

                // Transition to the cancelled state if this was the last touch.
                if (this.touchCount == 0) {
                    if (this.state == WorldWind.POSSIBLE) {
                        this.state = WorldWind.FAILED;
                    } else if (this.state == WorldWind.BEGAN || this.state == WorldWind.CHANGED) {
                        this.state = WorldWind.CANCELLED;
                    }
                }
            }
        };

        // Documented in superclass.
        RotationRecognizer.prototype.prepareToRecognize = function () {
            this.referenceAngle = this.currentTouchAngle();
            this._rotation = 0;
        };

        // Intentionally not documented.
        RotationRecognizer.prototype.shouldRecognize = function () {
            var angle = this.currentTouchAngle(),
                rotation = Angle.normalizedDegrees(angle - this.referenceAngle);

            return Math.abs(rotation) > this.interpretThreshold;
        };

        // Intentionally not documented.
        RotationRecognizer.prototype.currentTouchAngle = function () {
            var touch0 = this.rotationTouches[0],
                touch1 = this.rotationTouches[1],
                dx = touch0.clientX - touch1.clientX,
                dy = touch0.clientY - touch1.clientY;

            return Math.atan2(dy, dx) * Angle.RADIANS_TO_DEGREES;
        };

        return RotationRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TiltRecognizer
 */
define('gesture/TiltRecognizer',['../gesture/PanRecognizer'],
    function (PanRecognizer) {
        "use strict";

        /**
         * Constructs a tilt gesture recognizer.
         * @alias TiltRecognizer
         * @constructor
         * @augments PanRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for two finger tilt gestures.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var TiltRecognizer = function (target, callback) {
            PanRecognizer.call(this, target, callback);

            // Intentionally not documented.
            this.maxTouchDistance = 250;

            // Intentionally not documented.
            this.maxTouchDivergence = 50;
        };

        // Intentionally not documented.
        TiltRecognizer.LEFT = (1 << 0);

        // Intentionally not documented.
        TiltRecognizer.RIGHT = (1 << 1);

        // Intentionally not documented.
        TiltRecognizer.UP = (1 << 2);

        // Intentionally not documented.
        TiltRecognizer.DOWN = (1 << 3);

        TiltRecognizer.prototype = Object.create(PanRecognizer.prototype);

        // Documented in superclass.
        TiltRecognizer.prototype.shouldInterpret = function () {
            for (var i = 0, count = this.touchCount; i < count; i++) {
                var touch = this.touch(i),
                    dx = touch.translationX,
                    dy = touch.translationY,
                    distance = Math.sqrt(dx * dx + dy * dy);
                if (distance > this.interpretDistance) {
                    return true; // interpret touches when any touch moves far enough
                }
            }

            return false;
        };

        // Documented in superclass.
        TiltRecognizer.prototype.shouldRecognize = function () {
            var touchCount = this.touchCount;
            if (touchCount < 2) {
                return false;
            }

            var touch0 = this.touch(0),
                touch1 = this.touch(1),
                dx = touch0.clientX - touch1.clientX,
                dy = touch0.clientY - touch1.clientY,
                distance = Math.sqrt(dx * dx + dy * dy);
            if (distance > this.maxTouchDistance) {
                return false; // touches must be close together
            }

            var tx = touch0.translationX - touch1.translationX,
                ty = touch0.translationY - touch1.translationY,
                divergence = Math.sqrt(tx * tx + ty * ty);
            if (divergence > this.maxTouchDivergence) {
                return false; // touches must be moving in a mostly parallel direction
            }

            var verticalMask = TiltRecognizer.UP | TiltRecognizer.DOWN,
                dirMask0 = this.touchDirection(touch0) & verticalMask,
                dirMask1 = this.touchDirection(touch1) & verticalMask;
            return (dirMask0 & dirMask1) != 0; // touches must move in the same vertical direction
        };

        // Intentionally not documented.
        TiltRecognizer.prototype.touchDirection = function (touch) {
            var dx = touch.translationX,
                dy = touch.translationY,
                dirMask = 0;

            if (Math.abs(dx) > Math.abs(dy)) {
                dirMask |= (dx < 0 ? TiltRecognizer.LEFT : 0);
                dirMask |= (dx > 0 ? TiltRecognizer.RIGHT : 0);
            } else {
                dirMask |= (dy < 0 ? TiltRecognizer.UP : 0);
                dirMask |= (dy > 0 ? TiltRecognizer.DOWN : 0);
            }

            return dirMask;
        };

        return TiltRecognizer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports LookAtNavigator
 */
define('navigate/LookAtNavigator',[
        '../geom/Angle',
        '../gesture/DragRecognizer',
        '../geom/Frustum',
        '../gesture/GestureRecognizer',
        '../geom/Line',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../navigate/Navigator',
        '../gesture/PanRecognizer',
        '../gesture/PinchRecognizer',
        '../geom/Position',
        '../gesture/RotationRecognizer',
        '../gesture/TiltRecognizer',
        '../geom/Vec2',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              DragRecognizer,
              Frustum,
              GestureRecognizer,
              Line,
              Location,
              Logger,
              Matrix,
              Navigator,
              PanRecognizer,
              PinchRecognizer,
              Position,
              RotationRecognizer,
              TiltRecognizer,
              Vec2,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a look-at navigator.
         * @alias LookAtNavigator
         * @constructor
         * @augments Navigator
         * @classdesc Represents a navigator that enables the user to pan, zoom and tilt the globe.
         * This navigator automatically responds to user-input events and gestures.
         * @param {WorldWindow} worldWindow The WorldWindow to associate with this navigator.
         */
        var LookAtNavigator = function (worldWindow) {
            Navigator.call(this, worldWindow);

            // Prevent the browser's default actions in response to mouse and touch events, which interfere with
            // navigation. Register these event listeners  before any others to ensure that they're called last.
            function preventDefaultListener(event) {
                event.preventDefault();
            }
            worldWindow.addEventListener("mousedown", preventDefaultListener);
            worldWindow.addEventListener("touchstart", preventDefaultListener);
            worldWindow.addEventListener("contextmenu", preventDefaultListener);
            worldWindow.addEventListener("wheel", preventDefaultListener);

            // Prevent the browser's default actions in response to to pointer events, which interfere with navigation.
            // This CSS style property is configured here to ensure that it's set for all applications.
            if (window.PointerEvent) {
                worldWindow.canvas.style.setProperty("touch-action", "none");
            }

            /**
             * The geographic location at the center of the viewport.
             * @type {Location}
             */
            this.lookAtLocation = new Location(30, -110);

            /**
             * The distance from this navigator's eye point to its look-at location.
             * @type {Number}
             * @default 10,000 kilometers
             */
            this.range = 10e6; // TODO: Compute initial range to fit globe in viewport.

            // Development testing only. Set this to false to suppress default navigator limits on 2D globes.
            this.enable2DLimits = true;

            var thisNavigator = this;

            // Intentionally not documented.
            this.primaryDragRecognizer = new DragRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handlePanOrDrag(recognizer);
            });

            // Intentionally not documented.
            this.secondaryDragRecognizer = new DragRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handleSecondaryDrag(recognizer);
            });
            this.secondaryDragRecognizer.button = 2; // secondary mouse button

            // Intentionally not documented.
            this.panRecognizer = new PanRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handlePanOrDrag(recognizer);
            });

            // Intentionally not documented.
            this.pinchRecognizer = new PinchRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handlePinch(recognizer);
            });

            // Intentionally not documented.
            this.rotationRecognizer = new RotationRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handleRotation(recognizer);
            });

            // Intentionally not documented.
            this.tiltRecognizer = new TiltRecognizer(worldWindow, function (recognizer) {
                thisNavigator.handleTilt(recognizer);
            });

            // Register wheel event listeners on the WorldWindow's canvas.
            worldWindow.addEventListener("wheel", function (event) {
                thisNavigator.handleWheelEvent(event);
            });

            // Establish the dependencies between gesture recognizers. The pan, pinch and rotate gesture may recognize
            // simultaneously with each other.
            this.panRecognizer.recognizeSimultaneouslyWith(this.pinchRecognizer);
            this.panRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
            this.pinchRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);

            // Since the tilt gesture is a subset of the pan gesture, pan will typically recognize before tilt,
            // effectively suppressing tilt. Establish a dependency between the other touch gestures and tilt to provide
            // tilt an opportunity to recognize.
            this.panRecognizer.requireRecognizerToFail(this.tiltRecognizer);
            this.pinchRecognizer.requireRecognizerToFail(this.tiltRecognizer);
            this.rotationRecognizer.requireRecognizerToFail(this.tiltRecognizer);

            // Intentionally not documented.
            this.beginPoint = new Vec2(0, 0);
            this.lastPoint = new Vec2(0, 0);
            this.beginHeading = 0;
            this.beginTilt = 0;
            this.beginRange = 0;
            this.lastRotation = 0;
        };

        LookAtNavigator.prototype = Object.create(Navigator.prototype);

        // Documented in superclass.
        LookAtNavigator.prototype.currentState = function () {
            this.applyLimits();

            var globe = this.worldWindow.globe,
                lookAtPosition = new Position(this.lookAtLocation.latitude, this.lookAtLocation.longitude, 0),
                modelview = Matrix.fromIdentity();
            modelview.multiplyByLookAtModelview(lookAtPosition, this.range, this.heading, this.tilt, this.roll, globe);

            return this.currentStateForModelview(modelview);
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handlePanOrDrag = function (recognizer) {
            if (this.worldWindow.globe.is2D()) {
                this.handlePanOrDrag2D(recognizer);
            } else {
                this.handlePanOrDrag3D(recognizer);
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handlePanOrDrag3D = function (recognizer) {
            var state = recognizer.state,
                tx = recognizer.translationX,
                ty = recognizer.translationY;

            if (state == WorldWind.BEGAN) {
                this.lastPoint.set(0, 0);
            } else if (state == WorldWind.CHANGED) {
                // Convert the translation from screen coordinates to arc degrees. Use this navigator's range as a
                // metric for converting screen pixels to meters, and use the globe's radius for converting from meters
                // to arc degrees.
                var canvas = this.worldWindow.canvas,
                    globe = this.worldWindow.globe,
                    globeRadius = WWMath.max(globe.equatorialRadius, globe.polarRadius),
                    distance = WWMath.max(1, this.range),
                    metersPerPixel = WWMath.perspectivePixelSize(canvas.clientWidth, canvas.clientHeight, distance),
                    forwardMeters = (ty - this.lastPoint[1]) * metersPerPixel,
                    sideMeters = -(tx - this.lastPoint[0]) * metersPerPixel,
                    forwardDegrees = (forwardMeters / globeRadius) * Angle.RADIANS_TO_DEGREES,
                    sideDegrees = (sideMeters / globeRadius) * Angle.RADIANS_TO_DEGREES;

                // Apply the change in latitude and longitude to this navigator, relative to the current heading.
                var sinHeading = Math.sin(this.heading * Angle.DEGREES_TO_RADIANS),
                    cosHeading = Math.cos(this.heading * Angle.DEGREES_TO_RADIANS);

                this.lookAtLocation.latitude += forwardDegrees * cosHeading - sideDegrees * sinHeading;
                this.lookAtLocation.longitude += forwardDegrees * sinHeading + sideDegrees * cosHeading;
                this.lastPoint.set(tx, ty);
                this.applyLimits();
                this.worldWindow.redraw();
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handlePanOrDrag2D = function (recognizer) {
            var state = recognizer.state,
                x = recognizer.clientX,
                y = recognizer.clientY,
                tx = recognizer.translationX,
                ty = recognizer.translationY;

            if (state == WorldWind.BEGAN) {
                this.beginPoint.set(x, y);
                this.lastPoint.set(x, y);
            } else if (state == WorldWind.CHANGED) {
                var x1 = this.lastPoint[0],
                    y1 = this.lastPoint[1],
                    x2 = this.beginPoint[0] + tx,
                    y2 = this.beginPoint[1] + ty;
                this.lastPoint.set(x2, y2);

                var navState = this.currentState(),
                    globe = this.worldWindow.globe,
                    ray = navState.rayFromScreenPoint(this.worldWindow.canvasCoordinates(x1, y1)),
                    point1 = new Vec3(0, 0, 0),
                    point2 = new Vec3(0, 0, 0),
                    origin = new Vec3(0, 0, 0);
                if (!globe.intersectsLine(ray, point1)) {
                    return;
                }

                ray = navState.rayFromScreenPoint(this.worldWindow.canvasCoordinates(x2, y2));
                if (!globe.intersectsLine(ray, point2)) {
                    return;
                }

                // Transform the original navigator state's modelview matrix to account for the gesture's change.
                var modelview = Matrix.fromIdentity();
                modelview.copy(navState.modelview);
                modelview.multiplyByTranslation(point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]);

                // Compute the globe point at the screen center from the perspective of the transformed navigator state.
                modelview.extractEyePoint(ray.origin);
                modelview.extractForwardVector(ray.direction);
                if (!globe.intersectsLine(ray, origin)) {
                    return;
                }

                // Convert the transformed modelview matrix to a set of navigator properties, then apply those
                // properties to this navigator.
                var params = modelview.extractViewingParameters(origin, this.roll, globe, {});
                this.lookAtLocation.copy(params.origin);
                this.range = params.range;
                this.heading = params.heading;
                this.tilt = params.tilt;
                this.roll = params.roll;
                this.applyLimits();
                this.worldWindow.redraw();
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handleSecondaryDrag = function (recognizer) {
            var state = recognizer.state,
                tx = recognizer.translationX,
                ty = recognizer.translationY;

            if (state == WorldWind.BEGAN) {
                this.beginHeading = this.heading;
                this.beginTilt = this.tilt;
            } else if (state == WorldWind.CHANGED) {
                // Compute the current translation from screen coordinates to degrees. Use the canvas dimensions as a
                // metric for converting the gesture translation to a fraction of an angle.
                var headingDegrees = 180 * tx / this.worldWindow.canvas.clientWidth,
                    tiltDegrees = 90 * ty / this.worldWindow.canvas.clientHeight;

                // Apply the change in heading and tilt to this navigator's corresponding properties.
                this.heading = this.beginHeading + headingDegrees;
                this.tilt = this.beginTilt + tiltDegrees;
                this.applyLimits();
                this.worldWindow.redraw();
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handlePinch = function (recognizer) {
            var state = recognizer.state,
                scale = recognizer.scale;

            if (state == WorldWind.BEGAN) {
                this.beginRange = this.range;
            } else if (state == WorldWind.CHANGED) {
                if (scale != 0) {
                    // Apply the change in pinch scale to this navigator's range, relative to the range when the gesture
                    // began.
                    this.range = this.beginRange / scale;
                    this.applyLimits();
                    this.worldWindow.redraw();
                }
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handleRotation = function (recognizer) {
            var state = recognizer.state,
                rotation = recognizer.rotation;

            if (state == WorldWind.BEGAN) {
                this.lastRotation = 0;
            } else if (state == WorldWind.CHANGED) {
                // Apply the change in gesture rotation to this navigator's current heading. We apply relative to the
                // current heading rather than the heading when the gesture began in order to work simultaneously with
                // pan operations that also modify the current heading.
                this.heading -= rotation - this.lastRotation;
                this.lastRotation = rotation;
                this.applyLimits();
                this.worldWindow.redraw();
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handleTilt = function (recognizer) {
            var state = recognizer.state,
                ty = recognizer.translationY;

            if (state == WorldWind.BEGAN) {
                this.beginTilt = this.tilt;
            } else if (state == WorldWind.CHANGED) {
                // Compute the gesture translation from screen coordinates to degrees. Use the canvas dimensions as a
                // metric for converting the translation to a fraction of an angle.
                var tiltDegrees = -90 * ty / this.worldWindow.canvas.clientHeight;
                // Apply the change in heading and tilt to this navigator's corresponding properties.
                this.tilt = this.beginTilt + tiltDegrees;
                this.applyLimits();
                this.worldWindow.redraw();
            }
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.handleWheelEvent = function (event) {
            // Normalize the wheel delta based on the wheel delta mode. This produces a roughly consistent delta across
            // browsers and input devices.
            var normalizedDelta;
            if (event.deltaMode == WheelEvent.DOM_DELTA_PIXEL) {
                normalizedDelta = event.deltaY;
            } else if (event.deltaMode == WheelEvent.DOM_DELTA_LINE) {
                normalizedDelta = event.deltaY * 40;
            } else if (event.deltaMode == WheelEvent.DOM_DELTA_PAGE) {
                normalizedDelta = event.deltaY * 400;
            }

            // Compute a zoom scale factor by adding a fraction of the normalized delta to 1. When multiplied by the
            // navigator's range, this has the effect of zooming out or zooming in depending on whether the delta is
            // positive or negative, respectfully.
            var scale = 1 + (normalizedDelta / 1000);

            // Apply the scale to this navigator's properties.
            this.range *= scale;
            this.applyLimits();
            this.worldWindow.redraw();
        };

        // Intentionally not documented.
        LookAtNavigator.prototype.applyLimits = function () {
            // Clamp latitude to between -90 and +90, and normalize longitude to between -180 and +180.
            this.lookAtLocation.latitude = WWMath.clamp(this.lookAtLocation.latitude, -90, 90);
            this.lookAtLocation.longitude = Angle.normalizedDegreesLongitude(this.lookAtLocation.longitude);

            // Clamp range to values greater than 1 in order to prevent degenerating to a first-person navigator when
            // range is zero.
            this.range = WWMath.clamp(this.range, 1, Number.MAX_VALUE);

            // Normalize heading to between -180 and +180.
            this.heading = Angle.normalizedDegrees(this.heading);

            // Clamp tilt to between 0 and +90 to prevent the viewer from going upside down.
            this.tilt = WWMath.clamp(this.tilt, 0, 90);

            // Normalize heading to between -180 and +180.
            this.roll = Angle.normalizedDegrees(this.roll);

            // Apply 2D limits when the globe is 2D.
            if (this.worldWindow.globe.is2D() && this.enable2DLimits) {
                // Clamp range to prevent more than 360 degrees of visible longitude. Assumes a 45 degree horizontal
                // field of view.
                var maxRange = 2 * Math.PI * this.worldWindow.globe.equatorialRadius;
                this.range = WWMath.clamp(this.range, 1, maxRange);

                // Force tilt to 0 when in 2D mode to keep the viewer looking straight down.
                this.tilt = 0;
            }
        };

        return LookAtNavigator;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * Defines an interface for {@link MemoryCache} listeners.
 * @exports MemoryCacheListener
 * @interface MemoryCacheListener
 */
define('cache/MemoryCacheListener',[
        '../util/Logger',
        '../error/UnsupportedOperationError'
    ],
    function (Logger,
              UnsupportedOperationError) {
        "use strict";

        /**
         * @alias MemoryCacheListener
         * @constructor
         */
        var MemoryCacheListener = function () {};

        /**
         * Called when an entry is removed from the cache.
         * Implementers of this interface must implement this function.
         * @param {String} key The key of the entry removed.
         * @param {Object} entry The entry removed.
         */
        MemoryCacheListener.prototype.entryRemoved = function (key, entry) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCacheListener", "entryRemoved", "abstractInvocation"));
        };

        /**
         * Called when an error occurs during entry removal.
         * Implementers of this interface must implement this function.
         * @param {Object} error The error object describing the error that occurred.
         * @param {String} key The key of the entry being removed.
         * @param {Object} entry The entry being removed.
         */
        MemoryCacheListener.prototype.removalError = function (error, key, entry) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "MemoryCacheListener", "removalError", "abstractInvocation"));
        };

        return MemoryCacheListener;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports NominatimGeocoder
 */
define('util/NominatimGeocoder',[
        '../util/Logger'
    ],
    function (Logger) {
        "use strict";

        /**
         * Constructs a Nominatim geocoder.
         * @alias NominatimGeocoder
         * @constructor
         * @classdesc Provides a gazetteer that uses Open Street Map Nominatim geocoder at Mapquest.
         */
        var NominatimGeocoder = function () {
            /**
             * The URL of the geocoder service.
             * @type {String}
             * @default http://open.mapquestapi.com/nominatim/v1/search/
             */
            this.service = "https://open.mapquestapi.com/nominatim/v1/search/";
        };

        /**
         * Queries the geocoder service with a specified query string.
         * @param {String} queryString The query string.
         * @param {Function} callback The function to call when the service returns the query results. This
         * function is passed two arguments: this geocoder and an array containing the query results. See
         * [the OpenStreetMap Nominatim Wiki] {@link http://wiki.openstreetmap.org/wiki/Nominatim} for a description
         * of the results. The result passed to the callback is parsed JSON.
         * @param {String} accessKey The MapQuest API access key to use for the request. See
         * https://developer.mapquest.com/plan_purchase/free/business_edition/business_edition_free
         * to obtain a key.
         */
        NominatimGeocoder.prototype.lookup = function (queryString, callback, accessKey) {
            var url = this.service + queryString.replace(" ", "%20") + "?format=json",
                xhr = new XMLHttpRequest(),
                thisGeocoder = this;

            url += "&key=" + (accessKey || "lUvVRchXGGDh5Xwk3oidrXeIDAAevOUS");

            xhr.open("GET", url, true);

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    var results = JSON.parse(xhr.responseText);

                    callback(thisGeocoder, results);
                }
            };

            xhr.send(null);
        };

        return NominatimGeocoder;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OpenStreetMapImageLayer
 */
define('layer/OpenStreetMapImageLayer',[
        '../geom/Angle',
        '../util/Color',
        '../geom/Location',
        '../geom/Sector',
        '../layer/MercatorTiledImageLayer'
    ],
    function (Angle,
              Color,
              Location,
              Sector,
              MercatorTiledImageLayer) {
        "use strict";

        /**
         * Constructs an Open Street Map layer.
         * @alias OpenStreetMapImageLayer
         * @constructor
         * @augments MercatorTiledImageLayer
         * @classdesc Provides a layer that shows Open Street Map imagery.
         *
         * @param {String} displayName This layer's display name. "Open Street Map" if this parameter is
         * null or undefined.
         */
        var OpenStreetMapImageLayer = function (displayName) {
            this.imageSize = 256;
            displayName = displayName || "Open Street Map";

            MercatorTiledImageLayer.call(this,
                new Sector(-85.05, 85.05, -180, 180), new Location(85.05, 180), 19, "image/png", displayName,
                this.imageSize, this.imageSize);

            this.displayName = displayName;
            this.pickEnabled = false;

            // Create a canvas we can use when unprojecting retrieved images.
            this.destCanvas = document.createElement("canvas");
            this.destContext = this.destCanvas.getContext("2d");

            this.urlBuilder = {
                urlForTile: function (tile, imageFormat) {
                    //var url = "https://a.tile.openstreetmap.org/" +
                    return "https://otile1.mqcdn.com/tiles/1.0.0/osm/" +
                        (tile.level.levelNumber + 1) + "/" + tile.column + "/" + tile.row + ".png";
                }
            };
        };

        OpenStreetMapImageLayer.prototype = Object.create(MercatorTiledImageLayer.prototype);

        OpenStreetMapImageLayer.prototype.doRender = function (dc) {
            MercatorTiledImageLayer.prototype.doRender.call(this, dc);
            if (this.inCurrentFrame) {
                dc.screenCreditController.addStringCredit("\u00A9OpenStreetMap", Color.DARK_GRAY);
                dc.screenCreditController.addStringCredit("Tiles Courtesy of MapQuest", Color.DARK_GRAY);
            }
        };

        // Overridden from TiledImageLayer.
        OpenStreetMapImageLayer.prototype.createTopLevelTiles = function (dc) {
            this.topLevelTiles = [];

            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 0, 1));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 0));
            this.topLevelTiles.push(this.createTile(null, this.levels.firstLevel(), 1, 1));
        };

        // Determines the Bing map size for a specified level number.
        OpenStreetMapImageLayer.prototype.mapSizeForLevel = function (levelNumber) {
            return 256 << (levelNumber + 1);
        };

        return OpenStreetMapImageLayer;
    }
)
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionGnomonic
 */
define('projections/ProjectionGnomonic',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger',
        '../geom/Sector',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger,
              Sector,
              WWMath) {
        "use strict";

        /**
         * Constructs a gnomonic geographic projection.
         * @alias ProjectionGnomonic
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents a polar gnomonic geographic projection.
         * @param {String} pole Indicates the north or south aspect. Specify "North" for the north aspect or "South"
         * for the south aspect.
         */
        var ProjectionGnomonic = function (pole) {

            // Internal. Intentionally not documented. See "pole" property accessor below for public interface.

            // Internal. Intentionally not documented.
            this.north = !(pole === "South");

            var limits = this.north ? new Sector(30, 90, -180, 180) : new Sector(-90, -30, -180, 180);

            GeographicProjection.call(this, "Polar Gnomonic", false, limits);

            // Internal. Intentionally not documented. See "pole" property accessor below for public interface.
            this._pole = pole;

            // Documented in superclass.
            this.displayName = this.north ? "North Gnomonic" : "South Gnomonic";

            // Internal. Intentionally not documented. See "stateKey" property accessor below for public interface.
            this._stateKey = "projection polar gnomonic " + this._pole + " ";
        };

        ProjectionGnomonic.prototype = Object.create(GeographicProjection.prototype);

        Object.defineProperties(ProjectionGnomonic.prototype, {

            /**
             * Indicates the north or south aspect. Specify "North" or "South".
             * @memberof ProjectionGnomonic.prototype
             * @type {String}
             */
            pole: {
                get: function () {
                    return this._pole;
                },
                set: function (pole) {
                    this._pole = pole;
                    this.north = !(this._pole === "South");
                    this.projectionLimits = this.north ? new Sector(30, 90, -180, 180) : new Sector(-90, -30, -180, 180);
                    this._stateKey = "projection polar gnomonic " + this._pole + " ";
                }
            },

            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionGnomonic.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return this._stateKey;
                }
            }
        });

        // Documented in base class.
        ProjectionGnomonic.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                       offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesian", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesian", "missingResult"));
            }

            // Formulae taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 167.

            if ((this.north && latitude === 90) || (!this.north && latitude === -90)) {
                result[0] = 0;
                result[1] = 0;
                result[2] = elevation;
            } else {
                var poleFactor = this.north ? 1 : -1,
                    a = globe.equatorialRadius / Math.tan(latitude * Angle.DEGREES_TO_RADIANS); // R cot(phi)

                result[0] = a * Math.sin(longitude * Angle.DEGREES_TO_RADIANS) * poleFactor; // eqs. 22-6, 22-10
                result[1] = a * -Math.cos(longitude * Angle.DEGREES_TO_RADIANS); // eqs. 22-7, 22-11
                result[2] = elevation;
            }

            return result;
        };

        // Documented in base class.
        ProjectionGnomonic.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon,
                                                                           elevations, referencePoint,
                                                                           offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesianGrid", "missingSector"));
            }

            if (!elevations || elevations.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesianGrid",
                    "The specified elevations array is null, undefined or insufficient length"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "geographicToCartesianGrid", "missingResult"));
            }

            var minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                minLatLimit = this.projectionLimits.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLatLimit = this.projectionLimits.maxLatitude * Angle.DEGREES_TO_RADIANS,
                poleFactor = this.north ? 1 : -1,
                refPoint = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                lat, lon, clampedLat, a;

            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                clampedLat = WWMath.clamp(lat, minLatLimit, maxLatLimit);

                a = globe.equatorialRadius / Math.tan(clampedLat);
                if ((this.north && clampedLat === Math.PI / 2) || (!this.north && clampedLat === -Math.PI / 2)) {
                    a = 0;
                }

                for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                    if (lonIndex === numLon - 1) {
                        lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                    }

                    // Formulae taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 167.

                    result[resultIndex++] = a * Math.sin(lon) * poleFactor - refPoint[0]; // eqs. 22-6, 22-10
                    result[resultIndex++] = a * -Math.cos(lon) - refPoint[1]; // eqs. 22-7, 22-11
                    result[resultIndex++] = elevations[elevIndex++] - refPoint[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionGnomonic.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "cartesianToGeographic", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "cartesianToGeographic", "missingResult"));
            }

            // Formulae taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 167.

            var rho = Math.sqrt(x * x + y * y),// eq. 20-18
                c;

            if (rho < 1.0e-4) {
                result.latitude = this.north ? 90 : -90;
                result.longitude = 0;
                result.altitude = z;
            } else {
                c = Math.atan2(rho, globe.equatorialRadius); // eq. 22-16
                if (c > Math.PI) {
                    c = Math.PI; // map cartesian points beyond the projection's radius to the edge of the projection
                }

                result.latitude = Math.asin(Math.cos(c) * (this.north ? 1 : -1)) * Angle.RADIANS_TO_DEGREES; // eq. 20-14
                result.longitude = Math.atan2(x, y * (this.north ? -1 : 1)) * Angle.RADIANS_TO_DEGREES; // use atan2(x,y) instead of atan(x/y). 20-16, 20-17
                result.altitude = z;
            }

            return result;
        };

        // Documented in base class.
        ProjectionGnomonic.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "northTangentAtLocation", "missingResult"));
            }

            // The north pointing tangent depends on the pole. With the south pole, the north pointing tangent points in
            // the same direction as the vector returned by cartesianToGeographic. With the north pole, the north
            // pointing tangent has the opposite direction.

            result[0] = Math.sin(longitude * Angle.DEGREES_TO_RADIANS) * (this.north ? -1 : 1);
            result[1] = Math.cos(longitude * Angle.DEGREES_TO_RADIANS);
            result[2] = 0;

            return result;
        };

        // Documented in base class.
        ProjectionGnomonic.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionGnomonic",
                    "northTangentAtLocation", "missingResult"));
            }

            // The north pointing tangent depends on the pole. With the south pole, the north pointing tangent points in
            // the same direction as the vector returned by cartesianToGeographic. With the north pole, the north
            // pointing tangent has the opposite direction.

            var rho = Math.sqrt(x * x + y * y);

            if (rho < 1.0e-4) {
                result[0] = 0;
                result[1] = 1;
                result[2] = 0;
            } else {
                result[0] = x / rho * (this.north ? -1 : 1);
                result[1] = y / rho * (this.north ? -1 : 1);
                result[2] = 0;
            }

            return result;
        };

        return ProjectionGnomonic;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionMercator
 */
define('projections/ProjectionMercator',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger',
        '../geom/Sector',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger,
              Sector,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a Mercator geographic projection.
         * @alias ProjectionMercator
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents a Mercator geographic projection.
         */
        var ProjectionMercator = function () {

            GeographicProjection.call(this, "Mercator", true, new Sector(-78, 78, -180, 180));
        };

        ProjectionMercator.prototype = Object.create(GeographicProjection.prototype);

        // Documented in base class.
        ProjectionMercator.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation, offset,
                                                                       result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesian", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesian", "missingResult"));
            }

            if (latitude > this.projectionLimits.maxLatitude) {
                latitude = this.projectionLimits.maxLatitude;
            }
            if (latitude < this.projectionLimits.minLatitude) {
                latitude = this.projectionLimits.minLatitude;
            }

            // See "Map Projections: A Working Manual", page 44 for the source of the below formulas.

            var ecc = Math.sqrt(globe.eccentricitySquared),
                sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
                s = ((1 + sinLat) / (1 - sinLat)) * Math.pow((1 - ecc * sinLat) / (1 + ecc * sinLat), ecc);

            result[0] = globe.equatorialRadius * longitude * Angle.DEGREES_TO_RADIANS + (offset ? offset[0] : 0);
            result[1] = 0.5 * globe.equatorialRadius * Math.log(s);
            result[2] = elevation;

            return result;
        };

        Object.defineProperties(ProjectionMercator.prototype, {
            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionMercator.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return "projection mercator ";
                }
            }
        });

        // Documented in base class.
        ProjectionMercator.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
                                                                           referencePoint, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesianGrid", "missingSector"));
            }

            if (!elevations || elevations.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesianGrid",
                    "The specified elevations array is null, undefined or insufficient length"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "geographicToCartesianGrid", "missingResult"));
            }

            var eqr = globe.equatorialRadius,
                ecc = Math.sqrt(globe.eccentricitySquared),
                minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                minLatLimit = this.projectionLimits.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLatLimit = this.projectionLimits.maxLatitude * Angle.DEGREES_TO_RADIANS,
                refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                offsetX = offset ? offset[0] : 0,
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                lat, lon, clampedLat, sinLat, s, y;

            // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian point
            // corresponding to each latitude and longitude.
            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                clampedLat = WWMath.clamp(lat, minLatLimit, maxLatLimit);
                sinLat = Math.sin(clampedLat);
                s = ((1 + sinLat) / (1 - sinLat)) * Math.pow((1 - ecc * sinLat) / (1 + ecc * sinLat), ecc);
                y = eqr * Math.log(s) * 0.5 - refCenter[1];

                for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                    if (lonIndex === numLon - 1) {
                        lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                    }

                    result[resultIndex++] = eqr * lon - refCenter[0] + offsetX;
                    result[resultIndex++] = y;
                    result[resultIndex++] = elevations[elevIndex++] - refCenter[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionMercator.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "cartesianToGeographic", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
                    "cartesianToGeographic", "missingResult"));
            }

            // See "Map Projections: A Working Manual", pages 45 and 19 for the source of the below formulas.

            var ecc2 = globe.eccentricitySquared,
                ecc4 = ecc2 * ecc2,
                ecc6 = ecc4 * ecc2,
                ecc8 = ecc6 * ecc2,
                t = Math.pow(Math.E, - y / globe.equatorialRadius),
                A = Math.PI / 2 - 2 * Math.atan(t),
                B = ecc2 / 2 + 5 * ecc4 / 24 + ecc6 / 12 + 13 * ecc8 / 360,
                C = 7 * ecc4 / 48 + 29 * ecc6 / 240 + 811 * ecc8 / 11520,
                D = 7 * ecc6 / 120 + 81 * ecc8 / 1120,
                E = 4279 * ecc8 / 161280,
                Ap = A - C + E,
                Bp = B - 3 * D,
                Cp = 2 * C - 8 * E,
                Dp = 4 * D,
                Ep = 8 * E,
                s2p = Math.sin(2 * A),
                lat = Ap + s2p * (Bp + s2p * (Cp + s2p * (Dp + Ep * s2p)));

            result.latitude = lat * Angle.RADIANS_TO_DEGREES;
            result.longitude = ((x - (offset ? offset[0] : 0)) / globe.equatorialRadius) * Angle.RADIANS_TO_DEGREES;
            result.altitude = z;

            return result;
        };

        return ProjectionMercator;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionPolarEquidistant
 */
define('projections/ProjectionPolarEquidistant',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger) {
        "use strict";

        /**
         * Constructs a polar equidistant geographic projection.
         * @alias ProjectionPolarEquidistant
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents a polar equidistant geographic projection.
         * @param {String} pole Indicates the north or south aspect. Specify "North" for the north aspect or "South"
         * for the south aspect.
         */
        var ProjectionPolarEquidistant = function (pole) {

            GeographicProjection.call(this, "Polar Equidistant", false, null);

            // Internal. Intentionally not documented. See "pole" property accessor below for public interface.
            this._pole = pole;

            // Internal. Intentionally not documented.
            this.north = !(pole === "South");

            // Documented in superclass.
            this.displayName = this.north ? "North Polar" : "South Polar";

            // Internal. Intentionally not documented. See "stateKey" property accessor below for public interface.
            this._stateKey = "projection polar equidistant " + this._pole + " ";
        };

        ProjectionPolarEquidistant.prototype = Object.create(GeographicProjection.prototype);

        Object.defineProperties(ProjectionPolarEquidistant.prototype, {

            /**
             * Indicates the north or south aspect. Specify "North" or "South".
             * @memberof ProjectionPolarEquidistant.prototype
             * @type {String}
             */
            pole: {
                get: function () {
                    return this._pole;
                },
                set: function (pole) {
                    this._pole = pole;
                    this.north = !(this._pole === "South");
                    this._stateKey = "projection polar equidistant " + this._pole + " ";
                }
            },

            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionPolarEquidistant.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return this._stateKey;
                }
            }
        });

        // Documented in base class.
        ProjectionPolarEquidistant.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                              offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesian", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesian", "missingResult"));
            }

            // Formulae taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 195.

            if ((this.north && latitude === 90) || (!this.north && latitude === -90)) {
                result[0] = 0;
                result[1] = 0;
                result[2] = elevation;
            } else {
                var northSouthFactor = this.north ? -1 : 1,
                    a = globe.equatorialRadius * (Math.PI / 2 + latitude * Angle.DEGREES_TO_RADIANS * northSouthFactor);

                result[0] = a * Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
                result[1] = a * Math.cos(longitude * Angle.DEGREES_TO_RADIANS) * northSouthFactor;
                result[2] = elevation;
            }

            return result;
        };

        // Documented in base class.
        ProjectionPolarEquidistant.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon,
                                                                                  elevations, referencePoint,
                                                                                  offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesianGrid", "missingSector"));
            }

            if (!elevations || elevations.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesianGrid",
                    "The specified elevations array is null, undefined or insufficient length"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "geographicToCartesianGrid", "missingResult"));
            }

            var eqr = globe.equatorialRadius,
                minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                northSouthFactor = this.north ? -1 : 1,
                refPoint = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                pi_2 = Math.PI / 2,
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                cosLon = new Float64Array(numLon), sinLon = new Float64Array(numLon),
                lat, lon, a;

            // Compute and save values that are a function of each unique longitude value in the specified sector. This
            // eliminates the need to re-compute these values for each column of constant longitude.
            for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                if (lonIndex === numLon - 1) {
                    lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                }

                cosLon[lonIndex] = Math.cos(lon);
                sinLon[lonIndex] = Math.sin(lon);
            }

            // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian point
            // corresponding to each latitude and longitude.
            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                a = eqr * (pi_2 + lat * northSouthFactor);
                if ((this.north && lat === pi_2) || (!this.north && lat === -pi_2)) {
                    a = 0;
                }

                for (lonIndex = 0; lonIndex < numLon; lonIndex++) {
                    result[resultIndex++] = a * sinLon[lonIndex] - refPoint[0];
                    result[resultIndex++] = a * cosLon[lonIndex] * northSouthFactor - refPoint[1];
                    result[resultIndex++] = elevations[elevIndex++] - refPoint[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionPolarEquidistant.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "cartesianToGeographic", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "cartesianToGeographic", "missingResult"));
            }

            // Formulae taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 196.

            var rho = Math.sqrt(x * x + y * y),
                c;

            if (rho < 1.0e-4) {
                result.latitude = this.north ? 90 : -90;
                result.longitude = 0;
                result.altitude = z;
            } else {
                c = rho / globe.equatorialRadius;
                if (c > Math.PI) {
                    c = Math.PI; // map cartesian points beyond the projection's radius to the edge of the projection
                }

                result.latitude = Math.asin(Math.cos(c) * (this.north ? 1 : -1)) * Angle.RADIANS_TO_DEGREES;
                result.longitude = Math.atan2(x, y * (this.north ? -1 : 1)) * Angle.RADIANS_TO_DEGREES; // use atan2(x,y) instead of atan(x/y)
                result.altitude = z;
            }

            //console.log(x + ", " + y + ", " + z + " --> " + result.toString());
            return result;
        };

        // Documented in base class.
        ProjectionPolarEquidistant.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "northTangentAtLocation", "missingResult"));
            }

            // The north pointing tangent depends on the pole. With the south pole, the north pointing tangent points in
            // the same direction as the vector returned by cartesianToGeographic. With the north pole, the north
            // pointing tangent has the opposite direction.

            result[0] = Math.sin(longitude * Angle.DEGREES_TO_RADIANS) * (this.north ? -1 : 1);
            result[1] = Math.cos(longitude * Angle.DEGREES_TO_RADIANS);
            result[2] = 0;

            return result;
        };

        // Documented in base class.
        ProjectionPolarEquidistant.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "northTangentAtLocation", "missingResult"));
            }

            // The north pointing tangent depends on the pole. With the south pole, the north pointing tangent points in
            // the same direction as the vector returned by cartesianToGeographic. With the north pole, the north
            // pointing tangent has the opposite direction.

            var rho = Math.sqrt(x * x + y * y);

            if (rho < 1.0e-4) {
                result[0] = 0;
                result[1] = 1;
                result[2] = 0;
            } else {
                result[0] = x / rho * (this.north ? -1 : 1);
                result[1] = y / rho * (this.north ? -1 : 1);
                result[2] = 0;
            }

            return result;
        };

        return ProjectionPolarEquidistant;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ProjectionUPS
 */
define('projections/ProjectionUPS',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../projections/GeographicProjection',
        '../util/Logger',
        '../geom/Sector',
        '../geom/Vec3',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              GeographicProjection,
              Logger,
              Sector,
              Vec3,
              WWMath) {
        "use strict";

        /**
         * Constructs a Uniform Polar Stereographic geographic projection.
         * @alias ProjectionUPS
         * @constructor
         * @augments GeographicProjection
         * @classdesc Represents a Uniform Polar Stereographic geographic projection.
         * @param {String} pole Indicates the north or south aspect. Specify "North" for the north aspect or "South"
         * for the south aspect.
         */
        var ProjectionUPS = function (pole) {

            // Internal. Intentionally not documented.
            this.north = !(pole === "South");

            var limits = this.north ? new Sector(0, 90, -180, 180) : new Sector(-90, 0, -180, 180);

            GeographicProjection.call(this, "Uniform Polar Stereographic", false, limits);

            // Internal. Intentionally not documented. See "pole" property accessor below for public interface.
            this._pole = pole;

            // Documented in superclass.
            this.displayName = this.north ? "North UPS" : "South UPS";

            // Internal. Intentionally not documented. See "stateKey" property accessor below for public interface.
            this._stateKey = "projection ups " + this._pole + " ";
        };

        ProjectionUPS.prototype = Object.create(GeographicProjection.prototype);

        Object.defineProperties(ProjectionUPS.prototype, {

            /**
             * Indicates the north or south aspect. Specify "North" or "South".
             * @memberof ProjectionPolarEquidistant.prototype
             * @type {String}
             */
            pole: {
                get: function () {
                    return this._pole;
                },
                set: function (pole) {
                    this._pole = pole;
                    this.north = !(this._pole === "South");
                    this.projectionLimits = this.north ? new Sector(0, 90, -180, 180) : new Sector(-90, 0, -180, 180);
                    this._stateKey = "projection ups " + this._pole + " ";
                }
            },

            /**
             * A string identifying this projection's current state. Used to compare states during rendering to
             * determine whether globe-state dependent cached values must be updated. Applications typically do not
             * interact with this property.
             * @memberof ProjectionPolarEquidistant.prototype
             * @readonly
             * @type {String}
             */
            stateKey: {
                get: function () {
                    return this._stateKey;
                }
            }
        });

        // Documented in base class.
        ProjectionUPS.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                  offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesian", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesian", "missingResult"));
            }

            // Formulas taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 161.

            if ((this.north && latitude === 90) || (!this.north && latitude === -90)) {
                result[0] = 0;
                result[1] = 0;
                result[2] = elevation;
            } else {
                var poleFactor = this.north ? 1 : -1,
                    lat = latitude * Angle.DEGREES_TO_RADIANS,
                    lon = longitude * Angle.DEGREES_TO_RADIANS,
                    k0 = 0.994, // standard UPS scale factor -- see above reference pg.157, pp 2.
                    ecc = Math.sqrt(globe.eccentricitySquared),
                    s = Math.sqrt(Math.pow(1 + ecc, 1 + ecc) * Math.pow(1 - ecc, 1 - ecc)),
                    sp, t, r;

                if ((this.north && lat < 0) || (!this.north && lat > 0)) {
                    lat = 0;
                }

                sp = Math.sin(lat * poleFactor);
                t = Math.sqrt(((1 - sp) / (1 + sp)) * Math.pow((1 + ecc * sp) / (1 - ecc * sp), ecc));
                r = 2 * globe.equatorialRadius * k0 * t / s;

                result[0] = r * Math.sin(lon);
                result[1] = -r * Math.cos(lon) * poleFactor;
                result[2] = elevation;
            }

            return result;
        };

        // Documented in base class.
        ProjectionUPS.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon,
                                                                      elevations, referencePoint,
                                                                      offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesianGrid", "missingGlobe"));
            }

            if (!sector) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesianGrid", "missingSector"));
            }

            if (!elevations || elevations.length < numLat * numLon) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesianGrid",
                    "The specified elevations array is null, undefined or insufficient length"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "geographicToCartesianGrid", "missingResult"));
            }

            // Formulas taken from "Map Projections -- A Working Manual", Snyder, USGS paper 1395, pg. 161.

            var eqr = globe.equatorialRadius,
                minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
                minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
                maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
                deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
                deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
                minLatLimit = this.projectionLimits.minLatitude * Angle.DEGREES_TO_RADIANS,
                maxLatLimit = this.projectionLimits.maxLatitude * Angle.DEGREES_TO_RADIANS,
                k0 = 0.994, // standard UPS scale factor -- see above reference pg.157, pp 2.
                ecc = Math.sqrt(globe.eccentricitySquared),
                s = Math.sqrt(Math.pow(1 + ecc, 1 + ecc) * Math.pow(1 - ecc, 1 - ecc)),
                poleFactor = this.north ? 1 : -1,
                refPoint = referencePoint ? referencePoint : new Vec3(0, 0, 0),
                latIndex, lonIndex,
                elevIndex = 0, resultIndex = 0,
                lat, lon, clampedLat, sp, t, r;

            // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian point
            // corresponding to each latitude and longitude.
            for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
                if (latIndex === numLat - 1) {
                    lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
                }

                // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
                clampedLat = WWMath.clamp(lat, minLatLimit, maxLatLimit);
                sp = Math.sin(clampedLat * poleFactor);
                t = Math.sqrt(((1 - sp) / (1 + sp)) * Math.pow((1 + ecc * sp) / (1 - ecc * sp), ecc));
                r = 2 * eqr * k0 * t / s;

                for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
                    if (lonIndex === numLon - 1) {
                        lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
                    }

                    result[resultIndex++] = r * Math.sin(lon) - refPoint[0];
                    result[resultIndex++] = -r * Math.cos(lon) * poleFactor - refPoint[1];
                    result[resultIndex++] = elevations[elevIndex++] - refPoint[2];
                }
            }

            return result;
        };

        // Documented in base class.
        ProjectionUPS.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            if (!globe) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "cartesianToGeographic", "missingGlobe"));
            }

            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "cartesianToGeographic", "missingResult"));
            }

            var lon = Math.atan2(x, y * (this.north ? -1 : 1)),
                k0 = 0.994,
                ecc = Math.sqrt(globe.eccentricitySquared),
                r = Math.sqrt(x * x + y * y),
                s = Math.sqrt(Math.pow(1 + ecc, 1 + ecc) * Math.pow(1 - ecc, 1 - ecc)),
                t = r * s / (2 * globe.equatorialRadius * k0),
                ecc2 = globe.eccentricitySquared,
                ecc4 = ecc2 * ecc2,
                ecc6 = ecc4 * ecc2,
                ecc8 = ecc6 * ecc2,
                A = Math.PI / 2 - 2 * Math.atan(t),
                B = ecc2 / 2 + 5 * ecc4 / 24 + ecc6 / 12 + 13 * ecc8 / 360,
                C = 7 * ecc4 / 48 + 29 * ecc6 / 240 + 811 * ecc8 / 11520,
                D = 7 * ecc6 / 120 + 81 * ecc8 / 1120,
                E = 4279 * ecc8 / 161280,
                Ap = A - C + E,
                Bp = B - 3 * D,
                Cp = 2 * C - 8 * E,
                Dp = 4 * D,
                Ep = 8 * E,
                s2p = Math.sin(2 * A),
                lat = Ap + s2p * (Bp + s2p * (Cp + s2p * (Dp + Ep * s2p)));

            lat *= this.north ? 1 : -1;

            result.latitude = lat * Angle.RADIANS_TO_DEGREES;
            result.longitude = lon * Angle.RADIANS_TO_DEGREES;
            result.altitude = z;

            return result;
        };

        // Documented in base class.
        ProjectionUPS.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionUPS",
                    "northTangentAtLocation", "missingResult"));
            }

            // The north pointing tangent depends on the pole. With the south pole, the north pointing tangent points in
            // the same direction as the vector returned by cartesianToGeographic. With the north pole, the north
            // pointing tangent has the opposite direction.

            result[0] = Math.sin(longitude * Angle.DEGREES_TO_RADIANS) * (this.north ? -1 : 1);
            result[1] = Math.cos(longitude * Angle.DEGREES_TO_RADIANS);
            result[2] = 0;

            return result;
        };

        // Documented in base class.
        ProjectionUPS.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionPolarEquidistant",
                    "northTangentAtLocation", "missingResult"));
            }

            var r = Math.sqrt(x * x + y * y);

            if (r < 1.0e-4) {
                result[0] = 0;
                result[1] = 1;
                result[2] = 0;
            } else {
                result[0] = x / r * (this.north ? -1 : 1);
                result[1] = y / r * (this.north ? -1 : 1);
                result[2] = 0;
            }

            return result;
        };

        return ProjectionUPS;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/kml/util/Scale',[
    './../KmlElements',
    '../KmlObject',
    './NodeTransformers'
], function (
    KmlElements,
    KmlObject,
    NodeTransformers
) {
    "use strict";

    /**
     * Constructs a Scale. Application usually don't call this constructor. It is called by {@link KmlFile} as
     * Objects from KmlFile are read. It is concrete implementation.
     * @alias Scale
     * @constructor
     * @classdesc Contains the data associated with Kml Scale
     * @param options {Object}
     * @param options.objectNode {Node} Node representing the Kml Scale
     * @throws {ArgumentError} If either the node is null or undefined.
     * @see https://developers.google.com/kml/documentation/kmlreference#scale
     * @augments KmlObject
     */
    var Scale = function (options) {
        KmlObject.call(this, options);
    };

    Scale.prototype = Object.create(KmlObject.prototype);

    Object.defineProperties(Scale.prototype, {
        /**
         * Scales model along x axis
         * @memberof Scale.prototype
         * @readonly
         * @type {Number}
         */
        kmlX: {
            get: function() {
                return this._factory.specific(this, {name: 'x', transformer: NodeTransformers.number});
            }
        },

        /**
         * Scales model along y axis
         * @memberof Scale.prototype
         * @readonly
         * @type {Number}
         */
        kmlY: {
            get: function() {
                return this._factory.specific(this, {name: 'y', transformer: NodeTransformers.number});
            }
        },

        /**
         * Scales model along z axis
         * @memberof Scale.prototype
         * @readonly
         * @type {Number}
         */
        kmlZ: {
            get: function() {
                return this._factory.specific(this, {name: 'z', transformer: NodeTransformers.number});
            }
        }
    });

    /**
     * @inheritDoc
     */
    Scale.prototype.getTagNames = function () {
        return ['Scale'];
    };

    KmlElements.addKey(Scale.prototype.getTagNames()[0], Scale);

    return Scale;
});

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ByteBuffer
 */
define('util/ByteBuffer',['../error/ArgumentError',
        '../util/Logger'
        ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a wrapper around an array buffer that enables byte-level access to its data.
         * This wrapper strives to minimize secondary allocations when subarrays are accessed.
         * The one exception is when double precision floating point data is access that is not properly aligned.
         * @alias ByteBuffer
         * @classdesc A structured wrapper around an array buffer that provides byte-level access to its data.
         * @param {ArrayBuffer} array An array buffer containing source data.
         * @constructor
         */
        var ByteBuffer = function(array) {
            if (!array) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ByteBuffer", "constructor", "missingArray"));
            }

            /**
             * The raw data of the array buffer.
             * @type {ArrayBuffer}
             */
            this.array = array;

            /**
             * A data view on the array buffer.
             * This data view is used to extract integer and floating point data from that array buffer.
             * @type {DataView}
             */
            this.data = new DataView(array);

            /**
             * The current position in the array buffer.
             * This position is implicitly used to access all data.
             * @type {Number}
             */
            this.position = 0;

            /**
             * The byte order in which the data is encoded.
             * Byte order will either be big endian or little endian.
             * @type {Boolean}
             * @default ByteByffer.LITTLE_ENDIAN
             * @private
             */
            this._order = ByteBuffer.LITTLE_ENDIAN;
        };

        /**
         * Get a byte from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getByte = function() {
            var result = this.data.getUint8(this.position);
            this.position += ByteBuffer.BYTE_SIZE;
            return result;
        };

        /**
         * Get a byte array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numBytes The number of bytes in the desired array.
         * @returns {Uint8Array}
         */
        ByteBuffer.prototype.getByteArray = function(numBytes) {
            var result = new Uint8Array(this.array, this.position, numBytes);
            this.position += ByteBuffer.BYTE_SIZE * numBytes;
            return result;
        };

        /**
         * Get a 16-bit integer from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getInt16 = function() {
            var result = this.data.getInt16(this.position, this._order);
            this.position += ByteBuffer.INT16_SIZE;
            return result;
        };

        /**
         * Get a 16-bit integer array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numInt16s The number of 16-bit integers in the desired array.
         * @returns {Int16Array}
         */
        ByteBuffer.prototype.getInt16Array = function(numInt16s) {
            var result = new Int16Array(this.array, this.position, numInt16s);
            this.position += ByteBuffer.INT16_SIZE * numInt16s;
            return result;
        };

        /**
         * Get a 32-bit integer from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getInt32 = function() {
            var result = this.data.getInt32(this.position, this._order);
            this.position += ByteBuffer.INT32_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numInt32s The number of 32-bit integers in the desired array.
         * @returns {Int32Array}
         */
        ByteBuffer.prototype.getInt32Array = function(numInt32s) {
            var result = new Int32Array(this.array, this.position, numInt32s);
            this.position += ByteBuffer.INT32_SIZE * numInt32s;
            return result;
        };

        /**
         * Get a single precision floating point number from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getFloat = function() {
            var result = this.data.getFloat32(this.position, this._order);
            this.position += ByteBuffer.FLOAT_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numFloats The number of single precision floating point numbers in the desired array.
         * @returns {Float32Array}
         */
        ByteBuffer.prototype.getFloatArray = function(numFloats) {
            var result = new Float32Array(this.array, this.position, numFloats);
            this.position += ByteBuffer.FLOAT_SIZE * numFloats;
            return result;
        };

        /**
         * Get a double precision floating point number from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getDouble = function() {
            var result = this.data.getFloat64(this.position, this._order);
            this.position += ByteBuffer.DOUBLE_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numDoubles The number of double precision floating point numbers in the desired array.
         * @returns {Float64Array}
         */
        ByteBuffer.prototype.getDoubleArray = function(numDoubles) {
            // Issue: Float64Array c'tor throws an exception if the starting offset is not a multiple of 8.
            // We see this in shapefiles.
            var result;
            // If the data is not DWORD aligned, ...
            if (this.position % 8 != 0) {
                var bytes = this.array.slice(this.position, this.position + numDoubles * ByteBuffer.DOUBLE_SIZE);
                result = new Float64Array(bytes);
            }
            else {
                result = new Float64Array(this.array, this.position, numDoubles);
            }
            this.position += ByteBuffer.DOUBLE_SIZE * numDoubles;
            return result;
        };

        /**
         * Skip over the specified number of bytes.
         * @param {Number} numBytes The number of bytes to skip.
         */
        ByteBuffer.prototype.skipBytes = function(numBytes) {
            this.position += numBytes * ByteBuffer.BYTE_SIZE;
        };

        /**
         * Skip over the specified number of 16-bit integers.
         * @param {Number} numInt16s The number of 16-bit integers to skip.
         */
        ByteBuffer.prototype.skipInt16s = function(numInt16s) {
            this.position += numInt16s * ByteBuffer.INT16_SIZE;
        };

        /**
         * Skip over the specified number of 32-bit integers.
         * @param {Number} numInt32s The number of 32-bit integers to skip.
         */
        ByteBuffer.prototype.skipInt32s = function(numInt32s) {
            this.position += numInt32s * ByteBuffer.INT32_SIZE;
        };

        /**
         * Skip over the specified number of single precision floating point numbers.
         * @param {Number} numFloats The number of single precision floating point numbers to skip.
         */
        ByteBuffer.prototype.skipFloats = function(numFloats) {
            this.position += numFloats * ByteBuffer.FLOAT_SIZE;
        };

        /**
         * Skip over the specified number of double precision floating point numbers.
         * @param {Number} numDoubles The number of double precision floating point numbers to skip.
         */
        ByteBuffer.prototype.skipDoubles = function(numDoubles) {
            this.position += numDoubles * ByteBuffer.DOUBLE_SIZE;
        };

        /**
         * Advance to a specific position.
         * @param {Number} position The specified position.
         */
        ByteBuffer.prototype.seek = function(position) {
            this.position = position;
        };

        /**
         * Set the byte order of the underlying data.
         * @param {Boolean} order The byte order of the underlying data.
         */
        ByteBuffer.prototype.order = function(order) {
            this._order = order;
        };

        /**
         * Return the total size of the underlying data.
         * @returns {Number} The size of the underlying data.
         */
        ByteBuffer.prototype.limit = function() {
            return this.data.byteLength;
        };

        /**
         * Indicates whether there remains any data to be accessed sequentially.
         * @returns {Boolean} True if more data can be accessed sequentially.
         */
        ByteBuffer.prototype.hasRemaining = function() {
            return this.position < this.data.byteLength;
        };

        /**
         * Access the underlying data in big endian order, where the most significant bits of the data are encountered first.
         * @type {Boolean}
         * @constant
         */
        ByteBuffer.BIG_ENDIAN = false;

        /**
         * Access the underlying data in little endian order, where the least significant bits of the data are encountered first.
         * @type {Boolean}
         * @constant
         */
        ByteBuffer.LITTLE_ENDIAN = true;

        /**
         * The size of a byte.
         * @type {Number}
         * @constant
         */
        ByteBuffer.BYTE_SIZE = 1;

        /**
         * The size of a 16-bit integer.
         * @type {Number}
         * @constant
         */
        ByteBuffer.INT16_SIZE = 2;

        /**
         * The size of a 32-bit integer.
         * @type {Number}
         * @constant
         */
        ByteBuffer.INT32_SIZE = 4;

        /**
         * The size of a single precision floating point number.
         * @type {Number}
         * @constant
         */
        ByteBuffer.FLOAT_SIZE = 4;

        /**
         * The size of a double precision floating point number.
         * @type {Number}
         * @constant
         */
        ByteBuffer.DOUBLE_SIZE = 8;

        return ByteBuffer;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DBaseField
 */
define('formats/shapefile/DBaseField',['../../error/ArgumentError',
        '../../util/ByteBuffer',
        '../../formats/shapefile/DBaseFile',
        '../../util/Logger'
    ],
    function (ArgumentError,
              ByteBuffer,
              DBaseFile,
              Logger) {
        "use strict";

        /**
         * Constructs a dBase record field. Applications typically do not call this constructor. It is called by
         * {@link {DBaseRecord} as attribute fields are read.
         * @param {DBaseFile} dbaseFile A dBase attribute file.
         * @param {ByteBuffer} buffer A buffer descriptor from which to parse a field.
         * @returns {DBaseField} The dBase field that was parsed.
         * @constructor
         */
        var DBaseField = function(dbaseFile, buffer) {
            if (!dbaseFile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseField", "constructor", "missingAttributeName")
                );
            }

            if (!buffer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseField", "constructor", "missingBuffer")
                );
            }

            /**
             * The name of the field.
             * @type {String}
             */
            this.name = null;

            /**
             * The type of the field.
             * @type {String}
             */
            this.type = null;

            /**
             * The code byte for the field.
             * @type {Number}
             */
            this.typeCode = -1;

            /**
             * The length of the field.
             * @type {Number}
             */
            this.length = -1;

            /**
             * The number of decimals in the field.
             * @type {Number}
             */
            this.decimals = -1;

            this.readFieldDescriptor(dbaseFile, buffer);
        };

        /**
         * The name of the field.
         * @returns {String} The name of the field.
         */
        DBaseField.prototype.getName = function() {
            return this.name;
        };

        /**
         * The type of the field.
         * @returns {String} The type of the field.
         */
        DBaseField.prototype.getType = function() {
            return this.type;
        };

        /**
         * The length of the field.
         * @returns {Number} The length of the field.
         */
        DBaseField.prototype.getLength = function() {
            return this.length;
        };

        /**
         * The number of decimal places in the field.
         * @returns {Number} The number of decimal places.
         */
        DBaseField.prototype.getDecimals = function() {
            return this.decimals;
        };

        /**
         * Read the field descriptor.
         * @param {DBaseFile} dbaseFile The dBase file to read.
         * @param {ByteBuffer} buffer The descriptor of the buffer to read from.
         */
        DBaseField.prototype.readFieldDescriptor = function(dbaseFile, buffer) {
            buffer.order(ByteBuffer.LITTLE_ENDIAN);

            var pos = buffer.position;

            this.name = dbaseFile.readNullTerminatedString(buffer, DBaseField.FIELD_NAME_LENGTH);

            buffer.seek(pos + DBaseField.FIELD_NAME_LENGTH);
            this.typeCode = String.fromCharCode(buffer.getByte());
            this.type = DBaseField.getFieldType(this.typeCode);
            if (this.type == null) {
                // TODO: firgure out type of error.
                throw new Error(
                    Logger.log(Logger.LEVEL_SEVERE, "Shapefile dBase encountered unsupported field type: " + this.typeCode)
                );
            }

            // Skip four byte field address.
            buffer.skipBytes(4);

            this.length = buffer.getByte();
            this.decimals = buffer.getByte();

            buffer.seek(pos + DBaseField.FIELD_DESCRIPTOR_LENGTH); // move to next field
        };

        /**
         * Indicate the type of the field.
         * @param {String} type The type of the field.
         * @returns {String} A description of the field type.
         */
        DBaseField.getFieldType = function(type) {
            switch (type) {
                case 'C':
                    return DBaseField.TYPE_CHAR;
                case 'D':
                    return DBaseField.TYPE_DATE;
                case 'F':
                    return DBaseField.TYPE_NUMBER;
                case 'L':
                    return DBaseField.TYPE_BOOLEAN;
                case 'N':
                    return DBaseField.TYPE_NUMBER;
                default:
                    return null;
            }
        };

        /**
         * Create a string from the field.
         * @returns {String} The dtring for the field.
         */
        DBaseField.prototype.toString = function() {
            return this.name + "(" + this.typeCode + ")";
        };

        /**
         * The description of a character field.
         * @type {String}
         */
        DBaseField.TYPE_CHAR = "DBase.FieldTypeChar";

        /**
         * The description of a number field.
         * @type {String}
         */
        DBaseField.TYPE_NUMBER = "DBase.FieldTypeNumber";

        /**
         * The description of a date field.
         * @type {String}
         */
        DBaseField.TYPE_DATE = "DBase.FieldTypeDate";

        /**
         * The description of a boolean field.
         * @type {String}
         */
        DBaseField.TYPE_BOOLEAN = "DBase.FieldTypeBoolean";

        /**
         * The length of the name field.
         * @type {Number}
         */
        DBaseField.FIELD_NAME_LENGTH = 11;

        /**
         * The length of a descriptor field.
         * @type {Number}
         */
        DBaseField.FIELD_DESCRIPTOR_LENGTH = 32;

        return DBaseField;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DBaseRecord
 */
define('formats/shapefile/DBaseRecord',['../../error/ArgumentError',
        '../../util/ByteBuffer',
        '../../formats/shapefile/DBaseField',
        '../../formats/shapefile/DBaseFile',
        '../../util/Logger'
    ],
    function (ArgumentError,
              ByteBuffer,
              DBaseField,
              DBaseFile,
              Logger) {
        "use strict";

        /**
         * Create a DBase record. Applications typically do not call this constructor. It is called by
         * {@link DBaseFile} as attribute records are read.
         * @param {DBaseFile} dbaseFile A dBase attribute file.
         * @param {ByteBuffer} buffer A buffer descriptor from which to parse a record.
         * @param {Number} recordNumber The number of the record to parse.
         * @returns {DBaseRecord} The DBase record that was parsed.
         * @constructor
         */
        var DBaseRecord = function(dbaseFile, buffer, recordNumber) {
            if (!dbaseFile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseRecord", "constructor", "missingAttributeName")
                );
            }

            if (!buffer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseRecord", "constructor", "missingBuffer")
                );
            }

            /**
             * Indicates whether the record was deleted.
             * @type {Boolean}
             */
            this.deleted = false;

            /**
             * Indicates the current record number.
             * @type {Number}
             */
            this.recordNumber = recordNumber;

            //DateFormat dateformat = new SimpleDateFormat("yyyyMMdd");

            this.values = null;

            this.readFromBuffer(dbaseFile, buffer, recordNumber);
        };

        /**
         * Returned whether the record was deleted
         * @returns {Boolean} True if the record was deleted.
         */
        DBaseRecord.prototype.isDeleted = function() {
            return this.deleted;
        };

        /**
         * Returns the number of the record.
         * @returns {Number} The number of the record.
         */
        DBaseRecord.prototype.getRecordNumber = function() {
            return this.recordNumber;
        };

        /**
         * Reads a dBase record from the buffer.
         * @param {DBaseFile} dbaseFile The dBase file from which to read a record.
         * @param {ByteBuffer} buffer The buffer descriptor to read the record from.
         * @param {Number} recordNumber The record number to read.
         */
        DBaseRecord.prototype.readFromBuffer = function(dbaseFile, buffer, recordNumber) {
            buffer.order(ByteBuffer.LITTLE_ENDIAN);

            this.recordNumber = recordNumber;

            // Read deleted record flag.
            var b = buffer.getByte();
            this.deleted = (b == 0x2A);

            var fields = dbaseFile.getFields();

            this.values = {};

            for (var idx = 0, len = fields.length; idx < len; idx += 1) {
                var field = fields[idx];

                var key = field.getName();

                var value = dbaseFile.readNullTerminatedString(buffer, field.getLength()).trim();

                try {
                    if (field.getType() == DBaseField.TYPE_BOOLEAN) {
                        var firstChar = value.charAt(0);
                        this.values[key] = firstChar == 't' || firstChar == 'T' || firstChar == 'Y' || firstChar == 'y';
                    }
                    else if (field.getType() == DBaseField.TYPE_CHAR) {
                        this.values[key] = value;
                    }
                    else if (field.getType() == DBaseField.TYPE_DATE) {
                        this.values[key] = new Date(value);
                    }
                    else if (field.getType() == DBaseField.TYPE_NUMBER) {
                        this.values[key] = +value;
                    }
                }
                catch (e) {
                    // Log warning but keep reading.
                    Logger.log(Logger.LEVEL_WARNING,
                        "Shapefile attribute parsing error:" +
                        field.toString() +
                        " -> " +
                        value.toString() +
                        " [" + e + "]"
                    );
                }
            }
        };

        return DBaseRecord;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports DBaseFile
 */
define('formats/shapefile/DBaseFile',['../../error/ArgumentError',
        '../../util/ByteBuffer',
        '../../formats/shapefile/DBaseField',
        '../../formats/shapefile/DBaseRecord',
        '../../util/Logger'
    ],
    function (ArgumentError,
              ByteBuffer,
              DBaseField,
              DBaseRecord,
              Logger) {
        "use strict";

        /**
         * Constructs an object for dBase file at a specified URL. Applications typically do not call this constructor.
         * It is called by {@link {Shapefile} to read attributes for shapes.
         * @alias DBaseFile
         * @constructor
         * @classdesc Parses a dBase file.
         * @param {String} url The location of the dBase file.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        var DBaseFile = function(url) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseFile", "constructor", "missingUrl"));
            }

            this.url = url;

            // Internal use only. Intentionally not documented.
            // DBase file data.
            this.header = null;
            this.fields = null;

            // Internal use only. Intentionally not documented.
            // The buffer descriptor to read records from.
            this.buffer = null;

            // Internal use only. Intentionally not documented.
            // Source read parameters.
            this.boolean = true;
            this.numRecordsRead = 0;

            this._completionCallback = null;
        };

        /**
         * The modification date of the the dBase file.
         * @returns {String} The modification date.
         */
        DBaseFile.prototype.getLastModificationDate = function() {
            return this.header.lastModificationDate;
        };

        /**
         * The number of records in the dBase file.
         * @returns {Number} The number of records.
         */
        DBaseFile.prototype.getNumberOfRecords = function() {
            return this.header.numberOfRecords;
        };

        /**
         * The length of the header of the dBase file.
         * @returns {Number} The length of the header.
         */
        DBaseFile.prototype.getHeaderLength = function() {
            return this.header.headerLength;
        };

        /**
         * The length of a record in the dBase file.
         * @returns {Number} The lenght of a recrod.
         */
        DBaseFile.prototype.getRecordLength = function() {
            return this.header.recordLength;
        };

        /**
         * The number of fields in a dBase file.
         * @returns {Number} The number of fields.
         */
        DBaseFile.prototype.getNumberOfFields = function() {
            return (this.header.headerLength - 1 - DBaseFile.FIXED_HEADER_LENGTH) / DBaseFile.FIELD_DESCRIPTOR_LENGTH;
        };

        /**
         * The field descriptors of the dBase file.
         * @returns {DBaseField[]} The field descriptors.
         */
        DBaseFile.prototype.getFields = function() {
            return this.fields;
        };

        /**
         * Indicates whether the dBase file has additional records to read.
         * @returns {Boolean} True if more records can be read.
         */
        DBaseFile.prototype.hasNext = function() {
            return this.numRecordsRead < this.header.numberOfRecords;
        };

        /**
         * Read the next record in the dBase file.
         * @returns {DBaseRecord} The next record.
         */
        DBaseFile.prototype.nextRecord = function() {
            if (this.numRecordsRead >= this.getNumberOfRecords()) {
                return null;
            }

            return this.readNextRecord(this._buffer, ++this.numRecordsRead);
        };

        //**************************************************************//
        //********************  Initialization  ************************//
        //**************************************************************//

        /**
         * Initiate loading of the dBase file.
         * @param completionCallback
         */
        DBaseFile.prototype.load = function(completionCallback) {
            this._completionCallback = completionCallback;

            this.requestUrl(this.url);
        };

        /**
         * Internal use only.
         * Request data from the URL.
         * @param {String} url The URL for the requested data.
         */
        DBaseFile.prototype.requestUrl = function(url) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        this._buffer = new ByteBuffer(xhr.response);

                        this.parse();

                        if (!!this._completionCallback) {
                            this._completionCallback(this);
                        }
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "DBaseFile retrieval failed (" + xhr.statusText + "): " + url);

                        if (!!this._completionCallback) {
                            this._completionCallback(this);
                        }
                    }
                }
            }).bind(this);

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "DBaseFile retrieval failed: " + url);

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "DBaseFile retrieval timed out: " + url);

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            };

            xhr.send(null);
        };

        /**
         * Parse the dBase file.
         */
        DBaseFile.prototype.parse = function() {
            this.header = this.readHeader(this._buffer);
            this.fields = this.readFieldDescriptors(this._buffer, this.getNumberOfFields());
        };

        //**************************************************************//
        //********************  Header  ********************************//
        //**************************************************************//

        /**
         * Read the header of the dBase file.
         * @param {ByteBuffer} buffer The buffer descriptor to read from.
         * @returns {{
         *      fileCode: Number,
         *      lastModificationDate: {year: number, month: number, day: Number},
         *      numberOfRecords: Number,
         *      headerLength: Number,
         *      recordLength: Number
         * }}
         */
        DBaseFile.prototype.readHeader = function(buffer) {
            var pos = buffer.position;

            buffer.order(ByteBuffer.LITTLE_ENDIAN);

            // Read file code - first byte
            var fileCode = buffer.getByte();
            if (fileCode > 5) {
                // Let the caller catch and log the message.
                // TODO: ??? determine correct type of error
                throw new Error("???");
                //throw new WWUnrecognizedException(Logging.getMessage("SHP.UnrecognizedDBaseFile", fileCode));
            }

            // Last update date
            var yy = buffer.getByte();
            var mm = buffer.getByte();
            var dd = buffer.getByte();

            // Number of records
            var numRecords = buffer.getInt32();

            // Header struct length
            var headerLength = buffer.getInt16();

            // Record length
            var recordLength = buffer.getInt16();

            var date = {
                year: 1900 + yy,
                month: mm - 1,
                day: dd
            };

            // Assemble the header.
            var header = {
                'fileCode': fileCode,
                'lastModificationDate': date,
                'numberOfRecords': numRecords,
                'headerLength': headerLength,
                'recordLength': recordLength
            };

            buffer.seek(pos + DBaseFile.FIXED_HEADER_LENGTH); // Move to end of header.

            return header;
        };

        //**************************************************************//
        //********************  Fields  ********************************//
        //**************************************************************//

        /**
         * Reads a sequence of {@link DBaseField} descriptors from the given buffer;
         * <p/>
         * The buffer current position is assumed to be set at the start of the sequence and will be set to the end of the
         * sequence after this method has completed.
         *
         * @param {ByteBuffer} buffer    A byte buffer descriptor to read from.
         * @param {Number} numFields The number of DBaseFields to read.
         *
         * @return {DBaseField[]} An array of {@link DBaseField} instances.
         */
        DBaseFile.prototype.readFieldDescriptors = function(buffer, numFields) {
            var pos = buffer.position;

            var fields = [];

            for (var i = 0; i < numFields; i += 1) {
                fields[i] = new DBaseField(this, buffer);
            }

            var fieldsLength = this.header.headerLength - DBaseFile.FIXED_HEADER_LENGTH;
            buffer.seek(pos + fieldsLength); // Move to end of fields.

            return fields;
        };

        //**************************************************************//
        //********************  Records  *******************************//
        //**************************************************************//

        /**
         * Reads a {@link DBaseRecord} instance from the given buffer;
         * <p/>
         * The buffer current position is assumed to be set at the start of the record and will be set to the start of the
         * next record after this method has completed.
         *
         * @param {ByteBuffer} buffer       The buffer descriptor to read from.
         * @param {Number} recordNumber The record's sequence number.
         *
         * @return {DBaseRecord} A {@link DBaseRecord} instance.
         */
        DBaseFile.prototype.readNextRecord = function(buffer, recordNumber) {
            return new DBaseRecord(this, buffer, recordNumber);
        };

        //**************************************************************//
        //********************  String Parsing  ************************//
        //**************************************************************//

        /**
         * Read a null-terminated string.
         * @param {ByteBuffer} buffer A buffer descriptor to read from.
         * @param {Number} maxLength The number of maximum characters.
         * @returns {String}
         */
        DBaseFile.prototype.readNullTerminatedString = function(buffer, maxLength) {
            if (maxLength <= 0) {
                return 0;
            }

            var string = "";

            for (var length = 0; length < maxLength; length += 1) {
                var byte = buffer.getByte();
                if (byte == 0) {
                    break;
                }
                string += String.fromCharCode(byte);
            }

            if (this.isStringEmpty(string))
                return "";

            return string;
        };

        /**
         * Indicate whether the string is "logically" empty in the dBase sense.
         * @param {String} string The string of characters.
         * @returns {Boolean} True if the string is logically empty.
         */
        DBaseFile.prototype.isStringEmpty = function(string) {
            return string.length <= 0 ||
                DBaseFile.isStringFilled(string, 0x20) || // Space character.
                DBaseFile.isStringFilled(string, 0x2A); // Asterisk character.
        };

        /**
         * Indicates if the string is filled with constant data of a particular kind.
         * @param {String} string The string of characters.
         * @param {Number} fillValue The character value to test.
         * @returns {Boolean} True if the character array is filled with the specified value.
         */
        DBaseFile.isStringFilled = function(string, fillValue) {
            if (string.length <= 0) {
                return false;
            }

            for (var i = 0; i < string.length; i++) {
                if (string.charAt(i) != fillValue)
                    return false;
            }

            return true;
        };

        /**
         * The length of a dBase file header.
         * @type {Number}
         */
        DBaseFile.FIXED_HEADER_LENGTH = 32;

        /**
         * The length of a dBase file field descriptor.
         * @type {Number}
         */
        DBaseFile.FIELD_DESCRIPTOR_LENGTH = 32;

        return DBaseFile;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports PrjFile
 */
define('formats/shapefile/PrjFile',['../../error/ArgumentError',
        '../../util/Logger',
        '../../error/NotYetImplementedError'
    ],

    function (ArgumentError,
              Logger,
              NotYetImplementedError) {
        "use strict";

        /**
         * Constructs an object for a projection descriptor file at a specified URL.
         * Applications typically do not call this constructor.
         * It is called by {@link Shapefile} to read the projection descriptor.
         * @alias PrjFile
         * @constructor
         * @classdesc Parses a projection descriptor file.
         * @param {String} url The location of the dBase file.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        var PrjFile = function(url) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseFile", "constructor", "missingUrl"));
            }

            // Internal use only. Intentionally not documented.
            this._url = url;

            // Internal use only. Intentionally not documented.
            this._completionCallback = null;

            // Internal use only. Intentionally not documented.
            this._params = null;
        };

        Object.defineProperties(PrjFile.prototype, {
            /**
             * The URL as specified to this projection file's constructor.
             * @memberof PrjFile.prototype
             * @type {String}
             * @readonly
             */
            url: {
                get: function () {
                    return this._url;
                }
            },
            /**
             * The OGC coordinate system descriptor.
             * @member PrjFile.prototype
             * @type {Object}
             * @readonly
             */
            coordinateSystem: {
                get: function () {
                    if (!this.params) {
                        return null;
                    }
                    else {
                        return this.params[PrjFile.COORDINATE_SYSTEM];
                    }
                }
            },
            /**
             * The full parameter descriptor.
             * @member PrjFile.prototype
             * @type {Object}
             * @readonly
             */
            params: {
                get: function () {
                    return this._params;
                }
            }
        });

        PrjFile.prototype.load = function(completionCallback) {
            this._completionCallback = completionCallback;

            this.requestUrl(this._url);
        };
        /**
         * TODO: this common code; refactor!
         * Internal use only.
         * Request data from the URL.
         * @param {String} url The URL for the requested data.
         */
        PrjFile.prototype.requestUrl = function(url) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var text = String.fromCharCode.apply(null, new Uint8Array(xhr.response));
                        this._params = this.decodeOGCCoordinateSystem(text);
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "PrjFile retrieval failed (" + xhr.statusText + "): " + url);
                    }

                    if (!!this._completionCallback) {
                        this._completionCallback(this);
                    }
                }
            }).bind(this);

            xhr.onerror = (function () {
                Logger.log(Logger.LEVEL_WARNING, "PrjFile retrieval failed: " + url);

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            }).bind(this);

            xhr.ontimeout = (function () {
                Logger.log(Logger.LEVEL_WARNING, "PrjFile retrieval timed out: " + url);

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            }).bind(this);

            xhr.send(null);
        };

        /**
         * Retrieves the coordinate system and its parameters from an OGC coordinate system encoded as well-known text. For
         * details, see to the OGC Coordinate Transform Service (CT) specification at <a
         * href="https://www.opengeospatial.org/standards/ct">https://www.opengeospatial.org/standards/ct</a>. This recognizes
         * Geographic and UTM coordinate systems.
         *
         * If an exception occurs while parsing the coordinate system text, the parameter list is left unchanged.
         *
         * @param {String} text   A string containing an OGC coordinate system in well-known text format.
         *
         * @return {Object} An object containing key/value pairs extracted from the PRJ data.
         *
         * @throws ArgumentError If text is null.
         */
        PrjFile.prototype.decodeOGCCoordinateSystem = function(text) {
            if (!text) {
                new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PrjFile", "decodeOGCCoordinateSystem", "missingText")
                );
            }

            var params = {};

            // Convert the coordinate system text to upper case. The coordinate system regular expressions match against
            // upper case characters.
            text = text.trim().toUpperCase();

            if (PrjFile.GEOGCS_WKT_PATTERN.test(text)) {
                params[PrjFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC;
            }
            else {
                var result = text.match(PrjFile.PROJCS_WKT_PATTERN);
                if (!!result) {
                    params[PrfFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_PROJECTED;

                    throw new NotYetImplementedError(Logger.log(Logger.LEVEL_SEVERE,
                        "PrjFile implementation  for projected coordinate systems in incomplete."));
                    // TODO: Complete the implementation; the Java implementation is summarized below.
                    //String
                    //csText = csMatcher.group(1);
                    //Matcher
                    //projMatcher = UTM_NAME_WKT_PATTERN.matcher(csText);
                    //if (projMatcher.matches()) {
                    //    params.setValue(AVKey.PROJECTION_NAME, AVKey.PROJECTION_UTM);
                    //
                    //    // Parse the UTM zone from the coordinate system name.
                    //    String
                    //    s = projMatcher.group(1);
                    //    if (s != null) {
                    //        Integer
                    //        i = WWUtil.makeInteger(s.trim());
                    //        if (i != null && i >= 1 && i <= 60)
                    //            params.setValue(AVKey.PROJECTION_ZONE, i);
                    //    }
                    //
                    //    if (params.getValue(AVKey.PROJECTION_ZONE) == null)
                    //        Logging.logger().warning(Logging.getMessage("generic.ZoneIsInvalid", s));
                    //
                    //    // Parse the UTM hemisphere form the coordinate system name.
                    //    s = projMatcher.group(2);
                    //    if (s != null) {
                    //        s = s.trim();
                    //        if (s.startsWith("N") || s.startsWith("n"))
                    //            params.setValue(AVKey.PROJECTION_HEMISPHERE, AVKey.NORTH);
                    //        else if (s.startsWith("S") || s.startsWith("s"))
                    //            params.setValue(AVKey.PROJECTION_HEMISPHERE, AVKey.SOUTH);
                    //    }
                    //
                    //    if (params.getValue(AVKey.PROJECTION_HEMISPHERE) == null)
                    //        Logging.logger().warning(Logging.getMessage("generic.HemisphereIsInvalid", s));
                    //}
                    //else {
                    //    params.setValue(AVKey.PROJECTION_NAME, AVKey.PROJECTION_UNKNOWN);
                    //}
                }
                else {
                    params[PrjFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_UNKNOWN;
                }
            }

            return params;
        };

        /**
         * Indicates that an unknown coordinate system was encountered.
         * @returns {Boolean} True if an unknown coordinate system was encountered.
         */
        PrjFile.prototype.isUnknownCoordinateSystem = function() {
            return !this.params || this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_UNKNOWN;
        };

        /**
         * Indicates that a known coordinate system was encountered.
         * @returns {Boolean} True if a known coordinate system was encountered.
         */
        PrjFile.prototype.isKnownCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem !== PrjFile.COORDINATE_SYSTEM_UNKNOWN;
        };

        /**
         * Indicates that a geographic coordinate system was encountered.
         * @returns {Boolean} True if a geographic coordinate system was encountered.
         */
        PrjFile.prototype.isGeographicCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC;
        };

        /**
         * Indicates that a projected coordinate system was encountered.
         * @returns {boolean} True if a projected coordinate system was encountered.
         */
        PrjFile.prototype.isProjectedCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_PROJECTED;
        };

        /** Pattern matching the geographic coordinate system keyword in an OGC coordinate system well-known text. */
        PrjFile.GEOGCS_WKT_PATTERN = new RegExp("\\{*GEOGCS[\\[\\(](.*)[\\]\\)]\\}*");

        /** Pattern matching the projected coordinate system keyword in an OGC coordinate system well-known text. */
        PrjFile.PROJCS_WKT_PATTERN = new RegExp("\\{*PROJCS[\\[\\(](.*)[\\]\\)]\\}*");

        /** Pattern matching the UTM name in an projected coordinate system's well-known text. */
        PrjFile.UTM_NAME_WKT_PATTERN = new RegExp(".*UTM.*ZONE.*?(\\d+).*?([\\w\\s]+).*?");

        /**
         * A key for a coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM = 'Coordinate_system';

        /**
         * A geographic coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC = 'Coordinate_system_geographic';

        /**
         * A projected coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_PROJECTED = 'Coordinate_system_projected';

        /**
         * An unknown coordinate system.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_UNKNOWN = 'Coordinate_system_unknown';

        /**
         * The key for the name of the projection.
         * @type {String}
         */
        PrjFile.PROJECTION_NAME = 'Projection_name';

        /**
         * A UTM projection descriptor.
         * @type {String}
         */
        PrjFile.PROJECTION_UTM = 'Projection_UTM';

        /**
         * The key for the UTM projection zone.
         * @type {String}
         */
        PrjFile.PROJECTION_ZONE = 'Projection_zone';

        /**
         * The key for the hemisphere descriptor.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE = 'Projection_hemisphere';

        /**
         * The descriptor for the northern hemisphere.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE_NORTH = 'Projection_hemisphere_north';

        /**
         * The descriptor for the southern hemisphere.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE_SOUTH = 'Projection_hemisphere_south';


        return PrjFile;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecord
 */
define('formats/shapefile/ShapefileRecord',[
        '../../geom/Angle',
        '../../error/ArgumentError',
        '../../util/ByteBuffer',
        '../../geom/Location',
        '../../util/Logger',
        '../../formats/shapefile/Shapefile'
    ],
    function (Angle,
              ArgumentError,
              ByteBuffer,
              Location,
              Logger,
              Shapefile) {
        "use strict";

        /**
         * Constructs a shapefile record. Applications typically do not call this constructor. It is called by
         * {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecord
         * @constructor
         * @classdesc Contains the data associated with a shapefile record.
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer The buffer descriptor of the shapefile record's contents.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecord = function (shapefile, buffer) {
            if (!shapefile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ShapefileRecord", "constructor",
                        "The specified shapefile is null or undefined"));
            }

            if (!buffer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ShapefileRecord", "constructor",
                        "The specified buffer is null or undefined"));
            }

            // All these are documented in their property definition below. All but the shapefile and point buffer
            // are determined when the record is read by this class.
            this._shapefile = shapefile;
            this._recordNumber = -1;
            this._attributes = {};
            this._numberOfParts = 0;
            this._firstPartNumber = 0;
            this._lastPartNumber = 0;
            this._numberOfPoints = 0;
            this._boundingRectangle = [];
            this._zRange = null;
            this._zValues = null;
            this._mRange = null;
            this._mValues = null;

            // Internal use only. Intentionally not documented.
            this._contentLengthInBytes = -1;

            // Internal use only. Intentionally not documented.
            this._buffer = buffer;

            // Internal use only. Intentionally not documented.
            this._isNormalized = false;

            // Internal use only. Intentionally not documented.
            this._parts = [];

            // Prime the input pump.
            this.readRecord();
        };

        Object.defineProperties(ShapefileRecord.prototype, {
            /**
             * The shapefile containing this record.
             * @memberof ShapefileRecord.prototype
             * @type {Shapefile}
             * @readonly
             */
            shapefile: {
                get: function () {
                    return this._shapefile;
                }
            },

            /**
             * This record's ordinal position in the shapefile. 0 indicates the first record in the shapefile.
             * @memberof ShapefileRecord.prototype
             * @type {Number}
             * @readonly
             */
            recordNumber: {
                get: function () {
                    return this._recordNumber;
                }
            },

            /**
             * The attributes associated with this record, as read from the attribute file associated with the
             * shapefile. Empty if there are no attributes associated with this record or with the shapefile.
             * @memberof ShapefileRecord.prototype
             * @type {Object}
             * @readonly
             */
            attributes: {
                get: function () {
                    return this._attributes;
                }
            },

            /**
             * The number of parts in the shapefile.
             * @memberof ShapefileRecord.prototype
             * @type {Number}
             * @readonly
             */
            numberOfParts: {
                get: function () {
                    return this._numberOfParts;
                }
            },

            /**
             * The first part number in the record.
             * @memberof ShapefileRecord.prototype
             * @type {Number}
             * @readonly
             */
            firstPartNumber: {
                get: function () {
                    return this._firstPartNumber;
                }
            },

            /**
             * The last part number in the record.
             * @memberof ShapefileRecord.prototype
             * @type {Number}
             * @readonly
             */
            lastPartNumber: {
                get: function () {
                    return this._lastPartNumber;
                }
            },

            /**
             * The number of points in the record.
             * @memberof ShapefileRecord.prototype
             * @type {Number}
             * @readonly
             */
            numberOfPoints: {
                get: function () {
                    return this._numberOfPoints;
                }
            },

            /**
             * A four-element array containing this record's bounding rectangle, or null if this record has no
             * bounding rectangle. The returned array is ordered as follows: minimum Y, maximum Y, minimum X,
             * maximum X. If the shapefile's coordinate system is geographic then the elements can be interpreted
             * as angular degrees in the order minimum latitude, maximum latitude, minimum longitude, maximum
             * longitude.
             * @memberof ShapefileRecord.prototype
             * @type {Number[]}
             * @readonly
             */
            boundingRectangle: {
                get: function () {
                    return this._boundingRectangle;
                }
            },

            /**
             * The record's Z range if the shapefile's shape type is a Z type, otherwise null.
             * @memberof ShapefileRecord.prototype
             * @type {Number[]}
             * @readonly
             */
            zRange: {
                get: function () {
                    return this._zRange;
                }
            },

            /**
             * The record's Z values if the shapefile's shape type is a Z type, otherwise null.
             * @memberof ShapefileRecord.prototype
             * @type {Number[]}
             * @readonly
             */
            zValues: {
                get: function () {
                    return this._zValues;
                }
            },

            /**
             * The record's M range if the shapefile's shape type is an M type, otherwise null.
             * @memberof ShapefileRecord.prototype
             * @type {Number[]}
             * @readonly
             */
            mRange: {
                get: function () {
                    return this._mRange;
                }
            },

            /**
             * The record's M values if the shapefile's shape type is an M type, otherwise null.
             * @memberof ShapefileRecord.prototype
             * @type {Number[]}
             * @readonly
             */
            mValues: {
                get: function () {
                    return this._mValues;
                }
            }
        });

        /**
         * Returns the points of a specified part of this record.
         * @param {Number} partNumber The part number of interest. The range of part numbers can be determined via
         * [firstPartNumber]{@link ShapefileRecord#firstPartNumber} and
         * [lastPartNumber]{@link ShapefileRecord#lastPartNumber}.
         * @returns {Float64Array} The part's points in the order X0, Y0, X1, Y1, ..., Xn, Yn, where n is the number
         * of points in the part minus one. Returns null if the specified part does not exist.
         */
        ShapefileRecord.prototype.pointBuffer = function (partNumber) {
            if (partNumber >= 0 && partNumber < this._parts.length) {
                return this._parts[partNumber];
            }
            else {
                return null;
            }
        };

        ShapefileRecord.prototype.readRecord = function() {
            this.readHeader();

            // Technically, the shape type in the record header is considered a part of the contents according to the
            // ESRI specification. However, every record has a shape type, so we will read before reading the record contents.
            // Read shape type as little endian.
            this._buffer.order(ByteBuffer.LITTLE_ENDIAN);
            var type = this._buffer.getInt32();
            var shapeType = this.shapefile.getShapeType(type);
            this.validateShapeType(shapeType);

            this.readContents();
        };

        /**
         * Reads and parses the contents of a shapefile record from a specified buffer. The buffer's current position must
         * be the start of the record and will be the start of the next record when the method returns.
         *
         */
        ShapefileRecord.prototype.readHeader = function() {
            // Read record number and record length - big endian.
            this._buffer.order(ByteBuffer.BIG_ENDIAN);
            this._recordNumber = this._buffer.getInt32();
            this._contentLengthInBytes = this._buffer.getInt32() * 2;
        };

        /**
         * Verifies that the record's shape type matches that of the shapefile. All non-null
         * records in a Shapefile must be of the same type. Throws an exception if the types do not match and the shape type
         * is not {@link Shapefile#NULL}. Records of type NULL are always valid, and
         * may appear in any Shapefile.
         * <p/>
         * For details, see the ESRI Shapefile specification at <a href="https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf"/>,
         * pages 4 and 5.
         *
         * @throws Error If the shape types do not match.
         */
        ShapefileRecord.prototype.validateShapeType = function(shapeType) {
            if (shapeType !== this.shapefile.NULL &&
                shapeType !== this.shapefile.shapeType) {
                // TODO: throw the correct error
                throw new Error(
                    Logger.log(Logger.LEVEL_SEVERE, "Shapefile record is not supported.")
                );
            }
        };

        // Internal use only. Intentionally not documented.
        ShapefileRecord.prototype.readNullContents = function() {
            this._numberOfParts = 0;
            this._numberOfPoints = 0;
            this._parts = null;
            this._boundingRectangle = null;

            // Skip over the remaining contents of the record after the record's shape type.
            this._buffer.seek(this._contentLengthInBytes - ByteBuffer.INT32_SIZE);
        };

        // Internal use only. Intentionally not documented.
        ShapefileRecord.prototype.readPointContents = function() {
            this._numberOfParts = 1;
            this._firstPartNumber = 0;
            this._lastPartNumber = this._numberOfParts - 1;
            this._numberOfPoints = 1;

            this._parts = [this._buffer.getDoubleArray(2)];

            var latitude = this._parts[0][1];
            var longitude = this._parts[0][0];
            this._boundingRectangle = [latitude, latitude, longitude, longitude];

            // Read the optional Z value.
            if (this.isZType()) {
                this.readZ(true);
            }

            // Read the optional measure value.
            if (this.isMeasureType()) {
                this.readOptionalMeasures(true);
            }
        };

        // Internal use only. Intentionally not documented.
        ShapefileRecord.prototype.readPolylineContents = function() {
            // Read the bounding rectangle.
            var rect = this.shapefile.readBoundingRectangle(this._buffer);
            this._boundingRectangle = rect.coords;

            // Specify that the record's points should be normalized if the bounding rectangle is normalized. Ignore the
            // shapefile's normalizePoints property to avoid normalizing records that don't need it.
            this._isNormalized = rect.isNormalized;

            // Read the number of parts and the number of points.
            this._numberOfParts = this._buffer.getInt32();
            this._firstPartNumber = 0;
            this._lastPartNumber = this._numberOfParts - 1;
            this._numberOfPoints = this._buffer.getInt32();

            if (this._numberOfParts > 0 && this._numberOfPoints > 0) {
                // Read the part positions.
                var partPositions = this._buffer.getInt32Array(this.numberOfParts);

                for (var partNumber = 0; partNumber < this.numberOfParts; partNumber += 1) {
                    var numPointsInPart = (partNumber == this.numberOfParts - 1) ?
                        this._numberOfPoints - partPositions[partNumber] :
                        partPositions[partNumber + 1] - partPositions[partNumber];

                    // Add the record's points to the Shapefile's point buffer, and record this record's part offset in the
                    // Shapefile's point buffer.
                    this._parts[partNumber] = this._buffer.getDoubleArray(numPointsInPart * 2);
                    ShapefileRecord.normalizeLocations(this._parts[partNumber]);
                }
            }

            // Read the optional Z value.
            if (this.isZType()) {
                this.readZ(false);
            }

            // Read the optional measure value.
            if (this.isMeasureType()) {
                this.readOptionalMeasures(false);
            }
        };

        // Internal use only. Intentionally not documented.
        ShapefileRecord.prototype.readMultiPointContents = function() {
            // Read the bounding rectangle.
            var rect = this.shapefile.readBoundingRectangle(this._buffer);
            this._boundingRectangle = rect.coords;

            // Specify that the record's points should be normalized if the bounding rectangle is normalized. Ignore the
            // shapefile's normalizePoints property to avoid normalizing records that don't need it.
            this._isNormalized = rect.isNormalized;

            // Read the number of parts and the number of points.
            this._numberOfParts = 1;
            this._numberOfPoints = this._buffer.getInt32();

            this._parts = [this._buffer.getDoubleArray(this._numberOfPoints * 2)];
            ShapefileRecord.normalizeLocations(this._parts[0]);

            // Read the optional Z value.
            if (this.isZType()) {
                this.readZ(false);
            }

            // Read the optional measure value.
            if (this.isMeasureType()) {
                this.readOptionalMeasures(false);
            }
        };

        /**
         * Read's the shape's Z values from the record buffer.
         */
        ShapefileRecord.prototype.readZ = function(isPoint) {
            if (isPoint) {
                this._zValues = this._buffer.getDoubleArray(1);
                var z = this._zValues[0];
                this._zRange = [z, z];
            }
            else {
                this._zRange = this._buffer.getDoubleArray(2);
                this._zValues = this._buffer.getDoubleArray(this.numberOfPoints);
            }
        };

        /**
         * Reads any optional measure values from the record buffer.
         */
        ShapefileRecord.prototype.readOptionalMeasures = function(isPoint) {
            // Measure values are optional.
            if (this._buffer.hasRemaining() && (this._buffer.limit() - this._buffer.position) >= (this.numberOfPoints * 8)) {
                if (isPoint) {
                    this._mValues = this._buffer.getDoubleArray(1);
                    var m = this._mValues[0];
                    this._mRange = [m, m];
                }
                else {
                    this._mRange = this._buffer.getDoubleArray(2);
                    this._mValues = this._buffer.getDoubleArray(this.numberOfPoints);
                }
            }
        };

        /**
         * Normalize an array of doubles and treat them as lat/lon pairs,
         * where the longitude is the first value of the pair, and
         * the latitude is the second value of the pair.
         * <p>
         * This pair ordering is dictated by the format of shapefiles.
         * @param {Number} array
         */
        ShapefileRecord.normalizeLocations = function(array) {
            for (var idx = 0, len = array.length; idx < len; idx += 2) {
                var longitude = array[idx];
                var latitude = array[idx + 1];

                array[idx] = Angle.normalizedDegreesLongitude(longitude);
                array[idx + 1] = Angle.normalizedDegreesLatitude(latitude);
            }
        };

        /**
         * Indicate whether the record is of a point type.
         * @returns {Boolean} True if the record is of a point type.
         */
        ShapefileRecord.prototype.isPointType = function() {
            return this.shapefile.isPointType();
        };

        /**
         * Indicate whether the record is of a point type.
         * @returns {Boolean} True if the record is of a point type.
         */
        ShapefileRecord.prototype.isMultiPointType = function() {
            return this.shapefile.isMultiPointType();
        };

        /**
         * Indicate whether the record is of a polyline type.
         * @returns {Boolean} True if the record is of a polyline type.
         */
        ShapefileRecord.prototype.isPolylineType = function() {
            return this.shapefile.isPolylineType();
        };

        /**
         * Indicate whether the record is of a polygon type.
         * @returns {Boolean} True if the record is of a polygon type.
         */
        ShapefileRecord.prototype.isPolygonType = function() {
            return this.shapefile.isPolygonType();
        };

        /**
         * Indicate whether the record is of a depth type.
         * @returns {Boolean} True if the record is of a depth type.
         */
        ShapefileRecord.prototype.isZType = function() {
            return this.shapefile.isZType();
        };

        /**
         * Indicate whether the record is of a measure type.
         * @returns {Boolean} True if the record is of a measure type.
         */
        ShapefileRecord.prototype.isMeasureType = function() {
            return this.shapefile.isMeasureType();

        };

        /**
         * Internal use only.
         * Set the attributes of the record from a dBase file.
         * @param {Object} attributes Attributes contained in a dBase file.
         */
        ShapefileRecord.prototype.setAttributes = function(attributes) {
            this._attributes = attributes;
        };

        ShapefileRecord.RECORD_HEADER_LENGTH = 8;

        return ShapefileRecord;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecordMultiPoint
 */
define('formats/shapefile/ShapefileRecordMultiPoint',['../../util/ByteBuffer',
        '../../formats/shapefile/Shapefile',
        '../../formats/shapefile/ShapefileRecord'
    ],
    function (ByteBuffer,
              Shapefile,
              ShapefileRecord) {
        "use strict";

        /**
         * Constructs a shapefile record for a multi-point. Applications typically do not call this constructor.
         * It is called by {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecordMultiPoint
         * @constructor
         * @classdesc Contains the data associated with a shapefile multi-point record.
         * @augments ShapefileRecord
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer A buffer descriptor to read data from.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecordMultiPoint = function (shapefile, buffer) {
            ShapefileRecord.call(this, shapefile, buffer);
        };

        ShapefileRecordMultiPoint.prototype = Object.create(ShapefileRecord.prototype);

        ShapefileRecordMultiPoint.prototype.readContents = function() {
            this.readMultiPointContents();
        };

        return ShapefileRecordMultiPoint;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecordNull
 */
define('formats/shapefile/ShapefileRecordNull',['../../formats/shapefile/Shapefile',
        '../../formats/shapefile/ShapefileRecord'
    ],
    function (Shapefile,
              ShapefileRecord) {
        "use strict";

        /**
         * Constructs a null shapefile record. Applications typically do not call this constructor. It is called by
         * {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecordNull
         * @constructor
         * @classdesc Contains the data associated with a null shapefile record.
         * @augments ShapefileRecord
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer A buffer descriptor to read data from.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecordNull = function (shapefile, buffer) {
            ShapefileRecord.call(this, shapefile, buffer);
        };

        ShapefileRecordNull.prototype = Object.create(ShapefileRecord.prototype);

        ShapefileRecordNull.prototype.readContents = function() {
            this.readNullContents();
        };

        return ShapefileRecordNull;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecordPoint
 */
define('formats/shapefile/ShapefileRecordPoint',['../../formats/shapefile/Shapefile',
        '../../formats/shapefile/ShapefileRecord'
    ],
    function (Shapefile,
              ShapefileRecord) {
        "use strict";

        /**
         * Constructs a shapefile record for a point. Applications typically do not call this constructor. It is called by
         * {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecordPoint
         * @constructor
         * @classdesc Contains the data associated with a shapefile point record.
         * @augments ShapefileRecord
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer A buffer descriptor to read data from.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecordPoint = function (shapefile, buffer) {
            ShapefileRecord.call(this, shapefile, buffer);
        };

        ShapefileRecordPoint.prototype = Object.create(ShapefileRecord.prototype);

        ShapefileRecordPoint.prototype.readContents = function() {
            this.readPointContents();
        };

        return ShapefileRecordPoint;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecordPolygon
 */
define('formats/shapefile/ShapefileRecordPolygon',['../../formats/shapefile/Shapefile',
        '../../formats/shapefile/ShapefileRecord'
    ],
    function (Shapefile,
              ShapefileRecord) {
        "use strict";

        /**
         * Constructs a shapefile record for a polygon. Applications typically do not call this constructor. It is called by
         * {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecordPolygon
         * @constructor
         * @classdesc Contains the data associated with a shapefile record.
         * @augments ShapefileRecord
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer A buffer descriptor to read data from.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecordPolygon = function (shapefile, buffer) {
            ShapefileRecord.call(this, shapefile, buffer);
        };

        ShapefileRecordPolygon.prototype = Object.create(ShapefileRecord.prototype);

        ShapefileRecordPolygon.prototype.readContents = function() {
            this.readPolylineContents();
        };

        return ShapefileRecordPolygon;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShapefileRecordPolyline
 */
define('formats/shapefile/ShapefileRecordPolyline',['../../formats/shapefile/Shapefile',
        '../../formats/shapefile/ShapefileRecord'
    ],
    function (Shapefile,
              ShapefileRecord) {
        "use strict";

        /**
         * Constructs a shapefile record for a polyline. Applications typically do not call this constructor. It is called by
         * {@link Shapefile} as shapefile records are read.
         * @alias ShapefileRecordPolyline
         * @constructor
         * @classdesc Contains the data associated with a shapefile polyline record.
         * @augments ShapefileRecord
         * @param {Shapefile} shapefile The shapefile containing this record.
         * @param {ByteBuffer} buffer A buffer descriptor to read data from.
         * @throws {ArgumentError} If either the specified shapefile or buffer are null or undefined.
         */
        var ShapefileRecordPolyline = function (shapefile, buffer) {
            ShapefileRecord.call(this, shapefile, buffer);
        };

        ShapefileRecordPolyline.prototype = Object.create(ShapefileRecord.prototype);

        ShapefileRecordPolyline.prototype.readContents = function() {
            this.readPolylineContents();
        };

        return ShapefileRecordPolyline;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports Shapefile
 */
define('formats/shapefile/Shapefile',[
        '../../geom/Angle',
        '../../error/ArgumentError',
        '../../util/ByteBuffer',
        '../../util/Color',
        '../../formats/shapefile/DBaseFile',
        '../../geom/Location',
        '../../util/Logger',
        '../../error/NotYetImplementedError',
        '../../shapes/Path',
        '../../shapes/Placemark',
        '../../shapes/PlacemarkAttributes',
        '../../shapes/Polygon',
        '../../geom/Position',
        '../../formats/shapefile/PrjFile',
        '../../layer/RenderableLayer',
        '../../shapes/ShapeAttributes',
        '../../formats/shapefile/ShapefileRecord',
        '../../formats/shapefile/ShapefileRecordMultiPoint',
        '../../formats/shapefile/ShapefileRecordNull',
        '../../formats/shapefile/ShapefileRecordPoint',
        '../../formats/shapefile/ShapefileRecordPolygon',
        '../../formats/shapefile/ShapefileRecordPolyline',
        '../../shapes/SurfacePolygon',
        '../../shapes/SurfacePolyline'
    ],
    function (Angle,
              ArgumentError,
              ByteBuffer,
              Color,
              DBaseFile,
              Location,
              Logger,
              NotYetImplementedError,
              Path,
              Placemark,
              PlacemarkAttributes,
              Polygon,
              Position,
              PrjFile,
              RenderableLayer,
              ShapeAttributes,
              ShapefileRecord,
              ShapefileRecordMultiPoint,
              ShapefileRecordNull,
              ShapefileRecordPoint,
              ShapefileRecordPolygon,
              ShapefileRecordPolyline,
              SurfacePolygon,
              SurfacePolyline) {
        "use strict";

        /**
         * Constructs a shapefile object for a specified shapefile URL. Call [load]{@link Shapefile#load} to retrieve the
         * shapefile and create shapes for it.
         * @alias Shapefile
         * @constructor
         * @classdesc Parses a shapefile and creates shapes representing its contents. Points in the shapefile are
         * represented by [Placemarks]{@link Placemark}, lines are represented by [Paths]{@link Path} or
         * [SurfacePolylines]{@link SurfacePolyline}, and polygons
         * are represented by [Polygons]{@link Polygon} or [SurfacePolygons]{@link SurfacePolygon}.
         * A parser completion callback may be specified and is
         * called when the shapefile is fully parsed but before shapes are created.
         * <p>
         * An attribute callback may also be specified to examine each record and configure the shape created for it.
         * This function enables the application to assign independent attributes to each
         * shape. An argument to this function provides any attributes specified in an attribute file (.dbf)
         * accompanying the shapefile. That attribute file is automatically detected, retrieved and parsed along
         * with the shapefile.
         * @param {String} url The location of the shapefile.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        var Shapefile = function (url) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "constructor", "missingUrl"));
            }

            // Documented in defineProperties below.
            this._url = url;

            // Documented in defineProperties below.
            this._shapeType = null;

            // Documented in defineProperties below.
            this._layer = null;

            // Documented in defineProperties below.
            this._shapeConfigurationCallback = this.defaultShapeConfigurationCallback;

            this._parserCompletionCallback = this.defaultParserCompletionCallback;

            // Internal use only. Intentionally not documented.
            this._buffer = null;

            // Internal use only. Intentionally not documented.
            this.attributeFile = new DBaseFile(url.replace(".shp", ".dbf"));

            // Internal use only. Intentionally not documented.
            this.projectionFile = new PrjFile(url.replace(".shp", ".prj"));

            this.defaultPlacemarkAttributes = new PlacemarkAttributes(null);
            this.defaultShapeAttributes = new ShapeAttributes(null);
        };

        Object.defineProperties(Shapefile.prototype, {
            /**
             * The shapefile URL as specified to this shapefile's constructor.
             * @memberof Shapefile.prototype
             * @type {String}
             * @readonly
             */
            url: {
                get: function () {
                    return this._url;
                }
            },

            /**
             * The shape type of the shapefile. The type can be one of the following:
             * <ul>
             *     <li>WorldWind.POINT</li>
             *     <li>WorldWind.MULTI_POINT</li>
             *     <li>WorldWind.POLYLINE</li>
             *     <li>WorldWind.POLYGON</li>
             * </ul>
             * This value is defined during shapefile loading.
             * @memberof Shapefile.prototype
             * @type {String}
             * @readonly
             */
            shapeType: {
                get: function () {
                    return this._shapeType;
                }
            },

            /**
             * The layer containing the shapes representing the records in this shapefile, as specified to this
             * shapefile's constructor or created by the constructor if no layer was specified.
             * @memberof Shapefile.prototype
             * @type {RenderableLayer}
             * @readonly
             */
            layer: {
                get: function () {
                    return this._layer;
                }
            },

            /**
             * The completion callback specified to [load]{@link Shapefile#load}. This function is called when
             * shapefile parsing is done but before creating shapes for the shapefile. It's single argument is
             * this shapefile.
             * @memberof Shapefile.prototype
             * @type {Function}
             * @default [defaultParserCompletionCallback]{@link Shapefile#defaultParserCompletionCallback}
             * @readonly
             */
            parserCompletionCallback: {
                get: function () {
                    return this._parserCompletionCallback;
                }
            },

            /**
             * The attribute callback specified to [load]{@link Shapefile#load}.
             * See that method's description for details.
             * @memberof Shapefile.prototype
             * @type {Function}
             * @default [defaultShapeConfigurationCallback]{@link Shapefile#defaultShapeConfigurationCallback}
             * @readonly
             */
            shapeConfigurationCallback: {
                get: function () {
                    return this._shapeConfigurationCallback;
                }
            }
        });

        /**
         * Retrieves the shapefile, parses it and creates shapes representing its contents. The result is a layer
         * containing the created shapes. A function can be specified to be called when parsing is complete.
         * A function can also be specified to be called for each shapefile record so that the attributes and
         * other properties of the shape created for it can be assigned.
         *
         * @param {Function} parserCompletionCallback An optional function called when shapefile loading is complete but
         * shape creation has not begun. If none is specified,
         * [defaultParserCompletionCallback]{@link Shapefile#defaultParserCompletionCallback} is called. That function creates
         * WorldWind shapes for the parsed shapefile records.
         * The single argument to the callback function is this shapefile object. When the callback function is
         * called, the layer containing the shapes is available via this shapefile's
         * [layer]{@link Shapefile#layer} property.
         *
         * @param {Function} shapeConfigurationCallback An optional function called by the addRenderablesFor*
         * methods just prior to creating a shape for the indicated shapefile record. This function
         * can be used to assign attributes to newly created shapes. The callback function's first argument is an
         * object containing the properties read from the corresponding shapefile attributes file, if any.
         * This file, which has a .dbf suffix, is automatically detected, retrieved and parsed if it exists. The second
         * argument to the callback function is the {@link ShapefileRecord} currently being operated on. The return
         * value of the callback function must be either an object whose properties define attributes and other
         * information for the shape, or null, in which case no shape is created for that record. See the following
         * methods for descriptions of the configuration properties they recognize:
         * <ul>
         *     <li>[addRenderablesForPoint]{@link Shapefile#addRenderablesForPoints}</li>
         *     <li>[addRenderablesForMultiPoint]{@link Shapefile#addRenderablesForMultiPoints}</li>
         *     <li>[addRenderablesForPolylines]{@link Shapefile#addRenderablesForPolylines}</li>
         *     <li>[addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}</li>
         * </ul>
         *
         * @param {RenderableLayer} layer A {@link RenderableLayer} to hold the shapes created for each shapefile
         * record. If null, a new layer is created and assigned to this object's [layer]{@link Shapefile#layer}
         * property.
         */
        Shapefile.prototype.load = function (parserCompletionCallback, shapeConfigurationCallback, layer) {
            if (parserCompletionCallback) {
                this._parserCompletionCallback = parserCompletionCallback;
            }

            if (shapeConfigurationCallback) {
                this._shapeConfigurationCallback = shapeConfigurationCallback;
            }

            this._layer = layer || new RenderableLayer();

            // Load primary and secondary files in the following order:
            //      1) Projection file,
            //      2) Attribute file, and
            //      3) Shapefile.
            // This is done because the projection and attribute files modify the interpretation of the shapefile.
            var projectionFileCallback = (function () {
                var attributeFileCallback = (function () {
                    this.requestUrl(this.url);
                }).bind(this);

                this.attributeFile.load(attributeFileCallback);
            }).bind(this);

            this.projectionFile.load(projectionFileCallback);
        };

        /**
         * The default parser completion callback, called if none was specified to the [load]{@link Shapefile#load} method.
         * This default callback merely calls [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}
         * to create shapes for this shapefile's records.
         * @param {Shapefile} shapefile This shapefile.
         */
        Shapefile.prototype.defaultParserCompletionCallback = function (shapefile) {
            this.addRenderablesForShapefile(this.layer);
        };

        /**
         * The default [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} for this shapefile.
         * It is called if none was specified to the [load]{@link Shapefile#load} method.
         * This method assigns shared, default attributes to the shapes created for each record. Any changes to these
         * attributes will have an effect in all shapes created by this shapefile.
         * <p>
         * For polygon records, the record's attributes are checked for an attribute named "height", "Height",
         * or "HEIGHT". If found, the returned shape configuration contains a height property holding the
         * value associated with the record attribute. This causes the default shape creation function,
         * [addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}, to create a
         * {@link Polygon} with its extrude property set to true and position altitudes equal to the specified
         * height value.
         * <p>
         * For all records, the record's attributes are checked for an attribute named "name", "Name" or "NAME".
         * If found, the returned shape configuration contains a name property holding the value associated with
         * the record attribute. This value is specified as the label displayName property for all shapes created.
         * For {@link Placemark} shapes it is also specified as the placemark label.
         * It is specified as the displayName for all other shapes.
         *
         * @param {{}} attributes An object containing the attribute-value pairs found in the database file
         * associated with this shapefile. See [load]{@link Shapefile#load} for more information.
         * @param {ShapefileRecord} record The current shapefile record.
         * @returns {{}} An object with properties as described above.
         */
        Shapefile.prototype.defaultShapeConfigurationCallback = function (attributes, record) {
            var configuration = {};

            var name = attributes.values.name || attributes.values.Name || attributes.values.NAME;
            if (name) {
                configuration.name = name;
            }

            if (record.isPointType()) {
                configuration.attributes = this.defaultPlacemarkAttributes;
            } else if (record.isMultiPointType()) {
                configuration.attributes = this.defaultPlacemarkAttributes;
            } else if (record.isPolylineType()) {
                configuration.attributes = this.defaultShapeAttributes;
            } else if (record.isPolygonType()) {
                configuration.attributes = this.defaultShapeAttributes;
                var height = attributes.values.height || attributes.values.Height || attributes.values.HEIGHT;
                if (height) {
                    configuration.height = height;
                }
            }

            return configuration;
        };

        /**
         * Iterates over this shapefile's records and creates shapes for them. See the following methods for the
         * details of the shapes created and their use of the
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}:
         * <ul>
         *     <li>[addRenderablesForPoints]{@link Shapefile#addRenderablesForPoints}</li>
         *     <li>[addRenderablesForMultiPoints]{@link Shapefile#addRenderablesForMultiPoints}</li>
         *     <li>[addRenderablesForPolylines]{@link Shapefile#addRenderablesForPolylines}</li>
         *     <li>[addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        Shapefile.prototype.addRenderablesForShapefile = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForShapefile", "missingLayer"));
            }

            if (this.isPointType()) {
                this.addRenderablesForPoints(layer);
            } else if (this.isMultiPointType()) {
                this.addRenderablesForMultiPoints(layer);
            } else if (this.isPolylineType()) {
                this.addRenderablesForPolylines(layer);
            } else if (this.isPolygonType()) {
                this.addRenderablesForPolygons(layer);
            }
        };

        /**
         * Iterates over this shapefile's records and creates {@link Placemark}s for the shapefile's point records.
         * One placemark is created for each record.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
         * <p>
         * This method invokes this shapefile's
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
         * If that function returns null, the record is skipped. If it returns non-null, the returned value is
         * assumed to be an object with any or all of the following optional properties:
         * <ul>
         *     <li><i>attributes:</i> A {@link PlacemarkAttributes} object to assign to the placemark created
         *     for the record.</li>
         *     <li><i>highlightAttributes:</i> A {@link PlacemarkAttributes} object to assign to the
         *     highlight attributes of the placemark
         *     created for the record.</li>
         *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
         *     created placemark. If not specified,
         *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.</li>
         *     <li><i>name:</i> A String to assign as the created placemark's label.</li>
         *     <li><i>altitude:</i> A Number indicating the altitude of the created placemark.
         *     If not specified, the altitude of all created placemarks is 0.</li>
         *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
         *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        Shapefile.prototype.addRenderablesForPoints = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPoints", "missingLayer"));
            }

            // Note: for points, there should be only ONE part, and only ONE point per record.
            for (var record = this.next(); !!record; record = this.next()) {
                var configuration = this.shapeConfigurationCallback(record.attributes, record),
                    altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;

                if (!configuration) {
                    continue;
                }

                for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                    var points = record.pointBuffer(part);
                    for (var idx = 0, len = points.length; idx < len; idx += 2) {
                        var longitude = points[idx],
                            latitude = points[idx + 1],
                            position = new Position(latitude, longitude, altitude),
                            placemark = new Placemark(position, false, configuration.attributes);

                        placemark.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                        if (configuration.highlightAttributes) {
                            placemark.highlightAttributes = configuration.highlightAttributes;
                        }
                        if (configuration.name) {
                            placemark.label = configuration.name;
                        }
                        if (configuration.pickDelegate) {
                            placemark.pickDelegate = configuration.pickDelegate;
                        }
                        if (configuration.userProperties) {
                            placemark.userProperties = configuration.userProperties;
                        }
                        layer.addRenderable(placemark);
                    }
                }
            }
        };

        /**
         * Iterates over this shapefile's records and creates {@link Placemark}s for each point in the shapefile's
         * multi-point records.
         * One placemark is created for each point.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
         * <p>
         * This method invokes this shapefile's
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
         * If that function returns null, the record is skipped. If it returns non-null, the returned value is
         * assumed to be an object with any or all of the following optional properties:
         * <ul>
         *     <li><i>attributes:</i> A {@link PlacemarkAttributes} object to assign to the placemarks created
         *     for the record.</li>
         *     <li><i>highlightAttributes:</i> A {@link PlacemarkAttributes} object to assign to the
         *     highlight attributes of the placemarks created for the record.</li>
         *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
         *     created placemarks. If not specified,
         *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.</li>
         *     <li><i>label:</i> A String to assign as the created placemarks' label.</li>
         *     <li><i>altitude:</i> A Number indicating the altitude of the created placemarks.
         *     If not specified, the altitude of all created placemarks is 0.</li>
         *     <li><i>name:</i> A String to assign as the created placemarks' label.</li>
         *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
         *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        Shapefile.prototype.addRenderablesForMultiPoints = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForMultiPoints", "missingLayer"));
            }

            // Note: for multi-points, there should only be ONE part.
            for (var record = this.next(); !!record; record = this.next()) {
                var configuration = this.shapeConfigurationCallback(record.attributes, record),
                    altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;

                if (!configuration) {
                    continue;
                }

                for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                    var points = record.pointBuffer(part);
                    for (var idx = 0, len = points.length; idx < len; idx += 2) {
                        var longitude = points[idx],
                            latitude = points[idx + 1],
                            position = new Position(latitude, longitude, altitude),
                            placemark = new Placemark(position, false, configuration.attributes);

                        placemark.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                        if (configuration.highlightAttributes) {
                            placemark.highlightAttributes = configuration.highlightAttributes;
                        }
                        if (configuration.name) {
                            placemark.label = configuration.name;
                        }
                        if (configuration.pickDelegate) {
                            placemark.pickDelegate = configuration.pickDelegate;
                        }
                        if (configuration.userProperties) {
                            placemark.userProperties = configuration.userProperties;
                        }
                        layer.addRenderable(placemark);
                    }
                }
            }
        };

        /**
         * Iterates over this shapefile's records and creates {@link Path}s or {@link SurfacePolyline}s for the
         * shapefile's polyline records, depending on the altitude optionally returned by the
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}.
         * One shape is created for each record.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
         * <p>
         * This method invokes this shapefile's
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
         * If that function returns null, the record is skipped. If it returns non-null, the returned value is
         * assumed to be an object with any or all of the following optional properties:
         * <ul>
         *     <li><i>attributes:</i> A {@link ShapeAttributes} object to assign to the shape created
         *     for the record.</li>
         *     <li><i>highlightAttributes:</i> A {@link ShapeAttributes} object to assign to the highlight
         *     attributes of the shape created for the record.</li>
         *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
         *     created shape. If not specified,
         *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.
         *     <li><i>altitude:</i> A Number indicating the altitude of the created shape.
         *     If unspecified or 0, a {@link SurfacePolyline} is created for the record, otherwise a
         *     {@link Path} is created.</li>
         *     <li><i>name:</i> A String to assign as the created shape's displayName property.</li>
         *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
         *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        Shapefile.prototype.addRenderablesForPolylines = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPolylines", "missingLayer"));
            }

            for (var record = this.next(); !!record; record = this.next()) {
                var configuration = this.shapeConfigurationCallback(record.attributes, record),
                    altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;

                if (!configuration) {
                    continue;
                }

                for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                    var points = record.pointBuffer(part);

                    var positions = [];

                    for (var idx = 0, len = points.length; idx < len; idx += 2) {
                        var longitude = points[idx],
                            latitude = points[idx + 1],
                            position = !altitude ?
                                new Location(latitude, longitude) : new Position(latitude, longitude, altitude);

                        positions.push(position);
                    }

                    var shape;
                    if (!altitude) {
                        shape = new SurfacePolyline(positions, configuration.attributes);
                    } else {
                        shape = new Path(positions, configuration.attributes);
                        shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                    }

                    if (configuration.highlightAttributes) {
                        shape.highlightAttributes = configuration.highlightAttributes;
                    }
                    if (configuration.name) {
                        shape.displayName = configuration.name;
                    }
                    if (configuration.pickDelegate) {
                        shape.pickDelegate = configuration.pickDelegate;
                    }
                    if (configuration.userProperties) {
                        shape.userProperties = configuration.userProperties;
                    }
                    layer.addRenderable(shape);
                }
            }
        };

        /**
         * Iterates over this shapefile's records and creates {@link Polygon}s or {@link SurfacePolygon}s for the
         * shapefile's polygon records, depending on the altitude and height optionally returned by the
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}.
         * One shape is created for each record.
         * Applications typically do not call this method directly. It is called by
         * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
         * <p>
         * This method invokes this shapefile's
         * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
         * If that function returns null, the record is skipped. If it returns non-null, the returned value is
         * assumed to be an object with any or all of the following optional properties:
         * <ul>
         *     <li><i>attributes:</i> A {@link ShapeAttributes} object to assign to the shape created
         *     for the record.</li>
         *     <li><i>highlightAttributes:</i> A {@link ShapeAttributes} object to assign to the highlight
         *     attributes of the shape created for the record.</li>
         *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
         *     created shape. If not specified,
         *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.
         *     If the altitude is 0, this property is ignored.</li>
         *     <li><i>altitude:</i> A Number indicating the altitude of the created shape.
         *     If unspecified or 0 and the <i>height</i> property (see next line) is undefined or 0,
         *     a {@link SurfacePolygon} is created for the record, otherwise a {@link Polygon} is created.</li>
         *     <li><i>height:</i> A Number indicating polygon height. If defined and non-zero, a
         *     {@link Polygon} is created for this record with its position altitudes set to the specified height
         *     relative to ground and its [extrude]{@link Polygon#extrude} property set to true to create an
         *     extruded polygon. A height specified here overrides an altitude if both are specified.</li>
         *     <li><i>name:</i> A String to assign as the created shape's displayName property.</li>
         *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
         *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
         * </ul>
         * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
         * @throws {ArgumentError} If the specified layer is null or undefined.
         */
        Shapefile.prototype.addRenderablesForPolygons = function (layer) {
            if (!layer) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPolygons", "missingLayer"));
            }

            for (var record = this.next(); !!record; record = this.next()) {
                var configuration = this.shapeConfigurationCallback(record.attributes, record);

                var boundaries = [],
                    position,
                    height = configuration.height,
                    altitude = configuration.altitude;

                for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                    var points = record.pointBuffer(part),
                        positions = [];

                    // The shapefile duplicates the first and last point in each record, but WW shapes do not
                    // require this. So skip the last point in each record.
                    for (var idx = 0, len = points.length - 2; idx < len; idx += 2) {
                        var longitude = points[idx],
                            latitude = points[idx + 1];

                        if (height) {
                            position = new Position(latitude, longitude, height);
                        } else if (altitude) {
                            position = new Position(latitude, longitude, altitude);
                        } else {
                            position = new Location(latitude, longitude);
                        }

                        positions.push(position);
                    }

                    boundaries.push(positions);
                }

                var shape;
                if (height) {
                    shape = new Polygon(boundaries, configuration.attributes);
                    shape.extrude = true;
                    shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                } else if (!altitude) {
                    shape = new SurfacePolygon(boundaries, configuration.attributes);
                } else {
                    shape = new Polygon(boundaries, configuration.attributes);
                    shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                }

                if (configuration.highlightAttributes) {
                    shape.highlightAttributes = configuration.highlightAttributes;
                }
                if (configuration.name) {
                    shape.displayName = configuration.name;
                }
                if (configuration.pickDelegate) {
                    shape.pickDelegate = configuration.pickDelegate;
                }
                if (configuration.userProperties) {
                    shape.userProperties = configuration.userProperties;
                }
                layer.addRenderable(shape);
            }
        };

        /**
         * Returns the next {@link ShapefileRecord} in the shapefile, or null if no more records exist. This method
         * can be used to iterate through the shapefile records. Only one such iteration is possible.
         *
         * @returns {ShapefileRecord} The next shapefile record in the shapefile, or null if no more records exist.
         */
        Shapefile.prototype.next = function () {
            while (this._buffer.position < this._buffer.limit()) {
                var record = this.readRecord(this._buffer);
                if (!(record instanceof ShapefileRecordNull)) {
                    return record;
                }
            }

            // If you get hear, the shapefile is out of records.
            return null;
        };

        // Intentionally not documented.
        Shapefile.prototype.requestUrl = function (url) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        this._buffer = new ByteBuffer(xhr.response);

                        this.parse();
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "Shapefile retrieval failed (" + xhr.statusText + "): " + url);
                    }

                    if (!!this._parserCompletionCallback) {
                        this._parserCompletionCallback(this);
                    }
                }
            }).bind(this);

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "Shapefile retrieval failed: " + url);

                if (!!this._parserCompletionCallback) {
                    this._parserCompletionCallback(this);
                }
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "Shapefile retrieval timed out: " + url);

                if (!!this._parserCompletionCallback) {
                    this._parserCompletionCallback(this);
                }
            };

            xhr.send(null);
        };

        // Internal use only. Intentionally not documented.
        Shapefile.prototype.parse = function () {
            try {
                var header = this.readHeader(this._buffer);
                this._shapeType = header.shapeType;
            }
            catch (e) {
                console.log(e);
            }
            finally {
            }
        };

        // Intentionally not documented.
        Shapefile.prototype.readHeader = function (buffer) {
            buffer.order(ByteBuffer.BIG_ENDIAN);
            var fileCode = buffer.getInt32();
            if (fileCode != Shapefile.FILE_CODE) {
                // Let the caller catch and log the message.
                throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile header is invalid"));
            }

            // Skip 5 unused ints.
            buffer.skipInt32s(5);

            // File length.
            var lengthInWords = buffer.getInt32();

            // Switch to little endian for the remaining part.
            buffer.order(ByteBuffer.LITTLE_ENDIAN);

            // Read remaining header data.
            var version = buffer.getInt32();
            var type = buffer.getInt32();

            var rect = this.readBoundingRectangle(buffer);

            // Check whether the shape type is supported.
            var shapeType = this.getShapeType(type);
            if (shapeType == null) {
                // Let the caller catch and log the message.
                // TODO: ??? figure out the correct type of error to throw
                throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile type is unsupported: " + type.toString()));
            }

            // Assemble header
            var header = {
                'fileLength': lengthInWords * 2, // One word = 2 bytes.
                'version': version,
                'shapeType': shapeType,
                'boundingRectangle': rect.coords,
                'normalizePoints': rect.isNormalized
            };

            // Skip over bounds for measures and Z.
            buffer.skipDoubles(4);

            return header;
        };

        //**************************************************************//
        //********************  Bounding Rectangle  ********************//
        //**************************************************************//

        /*
         * Stores a bounding rectangle's coordinates, and if the coordinates are normalized. If isNormalized is
         * true, this indicates that the original coordinate values are out of range and required
         * normalization. The shapefile and shapefile records use this to determine which records must have their point
         * coordinates normalized. Normalization is rarely needed, and this enables the shapefile to normalize only point
         * coordinates associated with records that require it.
         *
         * The Javascript implementation inherits from the following Java implementation:
         * protected static class BoundingRectangle
         * {
         *    // Four-element array of the bounding rectangle's coordinates, ordered as follows: (minY, maxY, minX, maxX).
         *    public double[] coords;
         *    // True if the coordinates are normalized, and false otherwise.
         *    public boolean isNormalized;
         * }
         *
         *  In Javascript, this is represented as the object {'coords': coords, 'isNormalized': isNormalized}
         */

        // Intentionally not documented.
        Shapefile.prototype.readBoundingRectangle = function (buffer) {
            if (!this.projectionFile) {
                return this.readUnspecifiedBoundingRectangle(buffer);
            }
            else if (this.projectionFile.isGeographicCoordinateSystem()) {
                return this.readGeographicBoundingRectangle(buffer);
            }
            else if (this.projectionFile.isProjectedCoordinateSystem()) {
                return this.readProjectedBoundingRectangle(buffer);
            }
            else {
                return this.readUnspecifiedBoundingRectangle(buffer);
            }
        };

        // Intentionally not documented.
        Shapefile.prototype.readUnspecifiedBoundingRectangle = function (buffer) {
            // Read the bounding rectangle coordinates in the following order: minY, maxY, minX, maxX.
            var coords = this.readBoundingRectangleCoordinates(buffer);
            return {'coords': coords, 'isNormalized': false};
        };

        // Intentionally not documented.
        Shapefile.prototype.readGeographicBoundingRectangle = function (buffer) {
            // Read the bounding rectangle coordinates in the following order: minLat, maxLat, minLon, maxLon.
            var coords = this.readBoundingRectangleCoordinates(buffer),
                isNormalized = false,
                normalizedLat = 0;

            // The bounding rectangle's min latitude exceeds -90. Set the min latitude to -90. Correct the max latitude if
            // the normalized min latitude is greater than the max latitude.
            if (coords[0] < -90) {
                normalizedLat = Angle.normalizedDegreesLatitude(coords[0]);

                coords[0] = 90;
                isNormalized = true;

                if (coords[1] < normalizedLat) {
                    coords[1] = normalizedLat;
                }
            }

            // The bounding rectangle's max latitude exceeds +90. Set the max latitude to +90. Correct the min latitude if
            // the normalized max latitude is less than the min latitude.
            if (coords[1] > 90) {
                normalizedLat = Angle.normalizedDegreesLatitude(coords[1]);

                coords[1] = 90;
                isNormalized = true;

                if (coords[0] > normalizedLat)
                    coords[0] = normalizedLat;
            }

            // The bounding rectangle's longitudes exceed +-180, therefore the rectangle spans the international
            // dateline. Set the longitude bound to (-180, 180) to contain the dateline spanning rectangle.
            if (coords[2] < -180 || coords[3] > 180) {
                coords[2] = -180;
                coords[3] = 180;
                isNormalized = true;
            }

            return {'coords': coords, 'isNormalized': isNormalized};
        };

        // Intentionally not documented.
        Shapefile.prototype.readProjectedBoundingRectangle = function (buffer) {
            throw new NotYetImplementedError(
                Logger.log(Logger.LEVEL_SEVERE, "Shapefile.readProjectedBoundingRectangle() not yet implemented"));

            // TODO: complete the implementation; the Java implementation is summarized below.
            //Object o = this.getValue(AVKey.PROJECTION_NAME);
            //
            //if (AVKey.PROJECTION_UTM.equals(o)) {
            //    // Read the bounding rectangle coordinates in the following order: minEast, minNorth, maxEast, maxNorth.
            //    var coords = ShapefileUtils.readDoubleArray(buffer, 4);
            //    // Convert the UTM bounding rectangle to a geographic bounding rectangle. The zone and hemisphere parameters
            //    // have already been validated in validateBounds.
            //    var zone = (Integer) this.getValue(AVKey.PROJECTION_ZONE);
            //    var hemisphere = (String) this.getValue(AVKey.PROJECTION_HEMISPHERE);
            //    Sector sector = Sector.fromUTMRectangle(zone, hemisphere, coords[0], coords[2], coords[1], coords[3]);
            //    // Return an array with bounding rectangle coordinates in the following order: minLon, maxLon, minLat, maxLat.
            //    BoundingRectangle rect = new BoundingRectangle();
            //    rect.coords = sector.toArrayDegrees();
            //    return rect;
            //}
            //else {
            //    // The Shapefile's coordinate system projection is unsupported. This should never happen because the
            //    // projection is validated during initialization, but we check anyway. Let the caller catch and log the
            //    // message.
            //    throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile has an unsupported projection"));
            //}
        };

        // Intentionally not documented.
        Shapefile.prototype.readBoundingRectangleCoordinates = function (buffer) {
            // Read the bounding rectangle coordinates in the following order: minX, minY, maxX, maxY.
            var minx = buffer.getDouble(),
                miny = buffer.getDouble(),
                maxx = buffer.getDouble(),
                maxy = buffer.getDouble();

            // Return an array with bounding rectangle coordinates in the following order: minY, maxY, minX, maxX.
            return [miny, maxy, minx, maxx];
        };

        //**************************************************************//
        //********************  Shape Records  *************************//
        //**************************************************************//

        // Intentionally not documented.
        Shapefile.prototype.readRecord = function (buffer) {
            // The buffer current position is assumed to be set at the start of the record and will be set to the
            // start of the next record after this method has completed.
            var record = this.createRecord(buffer);

            if (record != null) {
                // Read the record's attribute data.
                if (this.attributeFile != null && this.attributeFile.hasNext()) {
                    var attributes = this.attributeFile.nextRecord();
                    record.setAttributes(attributes);
                }
            }

            return record;
        };

        // Intentionally not documented.
        Shapefile.prototype.createRecord = function (buffer) {
            // Select proper record class
            if (this.isNullType()) {
                return this.createNull(buffer);
            }
            else if (this.isPointType()) {
                return this.createPoint(buffer);
            }
            else if (this.isMultiPointType()) {
                return this.createMultiPoint(buffer);
            }
            else if (this.isPolygonType()) {
                return this.createPolygon(buffer);
            }
            else if (this.isPolylineType()) {
                return this.createPolyline(buffer);
            }

            return null;
        };

        // Intentionally not documented.
        Shapefile.prototype.createNull = function (buffer) {
            return new ShapefileRecordNull(this, buffer);
        };

        // Intentionally not documented.
        Shapefile.prototype.createPoint = function (buffer) {
            return new ShapefileRecordPoint(this, buffer);
        };

        // Intentionally not documented.
        Shapefile.prototype.createMultiPoint = function (buffer) {
            return new ShapefileRecordMultiPoint(this, buffer);
        };

        // Intentionally not documented.
        Shapefile.prototype.createPolyline = function (buffer) {
            return new ShapefileRecordPolyline(this, buffer);
        };

        // Intentionally not documented.
        Shapefile.prototype.createPolygon = function (buffer) {
            return new ShapefileRecordPolygon(this, buffer);
        };

        // Intentionally not documented.
        Shapefile.prototype.getShapeType = function (shapeType) {
            // Cases commented out indicate shape types not implemented
            switch (shapeType) {
                case 0:
                    return Shapefile.NULL;
                case 1:
                    return Shapefile.POINT;
                case 3:
                    return Shapefile.POLYLINE;
                case 5:
                    return Shapefile.POLYGON;
                case 8:
                    return Shapefile.MULTI_POINT;

                case 11:
                    return Shapefile.POINT_Z;
                case 13:
                    return Shapefile.POLYLINE_Z;
                case 15:
                    return Shapefile.POLYGON_Z;
                case 18:
                    return Shapefile.MULTI_POINT_Z;

                case 21:
                    return Shapefile.POINT_M;
                case 23:
                    return Shapefile.POLYLINE_M;
                case 25:
                    return Shapefile.POLYGON_M;
                case 28:
                    return Shapefile.MULTI_POINT_M;

//            case 31:
//                return Shapefile.SHAPE_MULTI_PATCH;

                default:
                    return null; // unsupported shape type
            }
        };

        //**************************************************************//
        //********************  Utilities  *****************************//
        //**************************************************************//

        /**
         * Indicates whether this shapefile contains optional measure values.
         *
         * @return {Boolean} True if this shapefile is one that contains measure values.
         */
        Shapefile.prototype.isMeasureType = function () {
            return Shapefile.measureTypes.hasOwnProperty(this._shapeType);
        };

        /**
         * Indicates whether this shapefile contains Z values.
         *
         * @return {Boolean} True if this shapefile contains Z values.
         */
        Shapefile.prototype.isZType = function () {
            return Shapefile.zTypes.hasOwnProperty(this._shapeType);
        };

        /**
         * Indicates whether this shapefile is [Shapefile.NULL]{@link Shapefile#NULL}.
         *
         * @return {Boolean} True if this shapefile is a null type.
         */
        Shapefile.prototype.isNullType = function () {
            return this._shapeType === Shapefile.NULL;
        };

        /**
         * Indicates whether this shapefile is either
         * [Shapefile.POINT]{@link Shapefile#POINT},
         * [Shapefile.POINT_M]{@link Shapefile#POINT_M}
         * or [Shapefile.POINT_Z]{@link Shapefile#POINT_Z}.
         *
         * @return {Boolean} True if the shapefile is a point type.
         */
        Shapefile.prototype.isPointType = function () {
            return Shapefile.pointTypes.hasOwnProperty(this._shapeType);
        };

        /**
         * Indicates whether this shapefile is either
         * [Shapefile.MULTI_POINT]{@link Shapefile#MULTI_POINT},
         * [Shapefile.MULTI_POINT_M]{@link Shapefile#MULTI_POINT_M}
         * or [Shapefile.MULTI_POINT_Z]{@link Shapefile#MULTI_POINT_Z}.
         *
         * @return {Boolean} True if this shapefile is a multi-point type.
         */
        Shapefile.prototype.isMultiPointType = function () {
            return Shapefile.multiPointTypes.hasOwnProperty(this._shapeType);
        };

        /**
         * Indicates whether this shapefile is either
         * [Shapefile.POLYLINE]{@link Shapefile#POLYLINE},
         * [Shapefile.POLYLINE_M]{@link Shapefile#POLYLINE_M}
         * or [Shapefile.POLYLINE_Z]{@link Shapefile#POLYLINE_Z}.
         *
         * @return {Boolean} True if this shapefile is a polyline type.
         */
        Shapefile.prototype.isPolylineType = function () {
            return Shapefile.polylineTypes.hasOwnProperty(this._shapeType);
        };

        /**
         * Indicates whether this shapefile is either
         * [Shapefile.POLYGON]{@link Shapefile#POLYGON},
         * [Shapefile.POLYGON_M]{@link Shapefile#POLYGON_M}
         * or [Shapefile.POLYGON_Z]{@link Shapefile#POLYGON_Z}.
         *
         * @return {Boolean} True if this shapefile is a polygon type.
         */
        Shapefile.prototype.isPolygonType = function () {
            return Shapefile.polygonTypes.hasOwnProperty(this._shapeType);
        };

        Shapefile.NULL = "null";
        Shapefile.POINT = "point";
        Shapefile.MULTI_POINT = "multiPoint";
        Shapefile.POLYLINE = "polyline";
        Shapefile.POLYGON = "polygon";

        Shapefile.POINT_M = Shapefile.POINT + "M";
        Shapefile.MULTI_POINT_M = Shapefile.MULTI_POINT + "M";
        Shapefile.POLYLINE_M = Shapefile.POLYLINE + "M";
        Shapefile.POLYGON_M = Shapefile.POLYGON + "M";

        Shapefile.POINT_Z = Shapefile.POINT + "Z";
        Shapefile.MULTI_POINT_Z = Shapefile.MULTI_POINT + "Z";
        Shapefile.POLYLINE_Z = Shapefile.POLYLINE + "Z";
        Shapefile.POLYGON_Z = Shapefile.POLYGON + "Z";

        Shapefile.SHAPE_MULTI_PATCH = "multiPatch";

        // Internal use only. Intentionally not documented.
        Shapefile.measureTypes = {
            pointM: Shapefile.POINT_M,
            pointZ: Shapefile.POINT_Z,
            multiPointM: Shapefile.MULTI_POINT_M,
            multiPointZ: Shapefile.MULTI_POINT_Z,
            polylineM: Shapefile.POLYLINE_M,
            polylineZ: Shapefile.POLYLINE_Z,
            polygonM: Shapefile.POLYGON_M,
            polygonZ: Shapefile.POLYGON_Z
        };

        // Internal use only. Intentionally not documented.
        Shapefile.zTypes = {
            pointZ: Shapefile.POINT_Z,
            multiPointZ: Shapefile.MULTI_POINT_Z,
            polylineZ: Shapefile.POLYLINE_Z,
            polygonZ: Shapefile.POLYGON_Z
        };

        // Internal use only. Intentionally not documented.
        Shapefile.pointTypes = {
            point: Shapefile.POINT,
            pointZ: Shapefile.POINT_Z,
            pointM: Shapefile.POINT_M
        };

        // Internal use only. Intentionally not documented.
        Shapefile.multiPointTypes = {
            multiPoint: Shapefile.MULTI_POINT,
            multiPointZ: Shapefile.MULTI_POINT_Z,
            multiPointM: Shapefile.MULTI_POINT_M
        };

        // Internal use only. Intentionally not documented.
        Shapefile.polylineTypes = {
            polyline: Shapefile.POLYLINE,
            polylineZ: Shapefile.POLYLINE_Z,
            polylineM: Shapefile.POLYLINE_M
        };

        // Internal use only. Intentionally not documented.
        Shapefile.polygonTypes = {
            polygon: Shapefile.POLYGON,
            polygonZ: Shapefile.POLYGON_Z,
            polygonM: Shapefile.POLYGON_M
        };

        // Intentionally not documented.
        Shapefile.FILE_CODE = 0x0000270A;

        return Shapefile;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ShowTessellationLayer
 */
define('layer/ShowTessellationLayer',[
        '../shaders/BasicProgram',
        '../layer/Layer'
    ],
    function (BasicProgram,
              Layer) {
        "use strict";

        /* INTENTIONALLY NOT DOCUMENTED. FOR DIAGNOSTIC USE ONLY.
         * Constructs a layer that displays a globe's tessellated geometry.
         * @alias ShowTessellationLayer
         * @constructor
         * @augments Layer
         * @classdesc Displays a globe's tessellated geometry.
         */
        var ShowTessellationLayer = function () {
            Layer.call(this, "Show Tessellation");

            /**
             * Indicates whether to display terrain geometry.
             * @type {Boolean}
             * @default true
             */
            this.enableTerrainGeometry = true;

            /**
             * Indicates whether to display terrain geometry extent.
             * @type {Boolean}
             * @default false
             */
            this.enableTerrainExtent = false;
        };

        ShowTessellationLayer.prototype = Object.create(Layer.prototype);

        ShowTessellationLayer.prototype.doRender = function (dc) {
            try {
                this.beginRendering(dc);

                if (this.enableTerrainGeometry) {
                    this.drawTerrainGeometry(dc);
                }

                if (this.enableTerrainExtent) {
                    this.drawTerrainExtent(dc);
                }
            } finally {
                this.endRendering(dc)
            }
        };

        ShowTessellationLayer.prototype.beginRendering = function (dc) {
            var gl = dc.currentGlContext;
            gl.depthMask(false); // Disable depth buffer writes. Diagnostics should not occlude any other objects.
        };

        ShowTessellationLayer.prototype.endRendering = function (dc) {
            var gl = dc.currentGlContext;
            gl.depthMask(true); // re-enable depth buffer writes that were disabled in beginRendering.
        };

        ShowTessellationLayer.prototype.drawTerrainGeometry = function (dc) {
            if (!dc.terrain || !dc.terrain.tessellator)
                return;

            var gl = dc.currentGlContext,
                terrain = dc.terrain,
                tessellator = terrain.tessellator,
                surfaceGeometry = terrain.surfaceGeometry,
                program,
                terrainTile;

            try {
                program = dc.findAndBindProgram(BasicProgram);
                tessellator.beginRendering(dc);

                for (var i = 0, len = surfaceGeometry.length; i < len; i++) {
                    terrainTile = surfaceGeometry[i];
                    tessellator.beginRenderingTile(dc, terrainTile);
                    program.loadColorComponents(gl, 1, 1, 1, 0.3);
                    tessellator.renderWireframeTile(dc, terrainTile);
                    program.loadColorComponents(gl, 1, 0, 0, 0.6);
                    tessellator.renderTileOutline(dc, terrainTile);
                    tessellator.endRenderingTile(dc, terrainTile);
                }

            } finally {
                tessellator.endRendering(dc);
            }
        };

        ShowTessellationLayer.prototype.drawTerrainExtent = function (dc) {
            var surfaceGeometry = dc.terrain.surfaceGeometry,
                terrainTile;

            for (var i = 0, len = surfaceGeometry.length; i < len; i++) {
                terrainTile = surfaceGeometry[i];
                terrainTile.extent.render(dc);
            }
        };

        return ShowTessellationLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports StarFieldProgram
 */
define('shaders/StarFieldProgram',[
        '../error/ArgumentError',
        '../shaders/GpuProgram',
        '../util/Logger'
    ],
    function (ArgumentError,
              GpuProgram,
              Logger) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
         * This method then compiles the shaders and then links the program if compilation is successful.
         * Use the bind method to make the program current during rendering.
         *
         * @alias StarFieldProgram
         * @constructor
         * @augments GpuProgram
         * @classdesc StarFieldProgram is a GLSL program that draws points representing stars.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of the compiled shaders into a program
         * fails.
         */
        var StarFieldProgram = function (gl) {
            var vertexShaderSource =
                    //.x = declination
                    //.y = right ascension
                    //.z = point size
                    //.w = magnitude
                    'attribute vec4 vertexPoint;\n' +

                    'uniform mat4 mvpMatrix;\n' +
                    //number of days (positive or negative) since Greenwich noon, Terrestrial Time,
                    // on 1 January 2000 (J2000.0)
                    'uniform float numDays;\n' +
                    'uniform vec2 magnitudeRange;\n' +

                    'varying float magnitudeWeight;\n' +

                    //normalizes an angle between 0.0 and 359.0
                    'float normalizeAngle(float angle) {\n' +
                    '   float angleDivisions = angle / 360.0;\n' +
                    '   return 360.0 * (angleDivisions - floor(angleDivisions));\n' +
                    '}\n' +

                    //transforms declination and right ascension in cartesian coordinates
                    'vec3 computePosition(float dec, float ra) {\n' +
                    '   float GMST = normalizeAngle(280.46061837 + 360.98564736629 * numDays);\n' +
                    '   float GHA = normalizeAngle(GMST - ra);\n' +
                    '   float lon = -GHA + 360.0 * step(180.0, GHA);\n' +
                    '   float latRad = radians(dec);\n' +
                    '   float lonRad = radians(lon);\n' +
                    '   float radCosLat = cos(latRad);\n' +
                    '   return vec3(radCosLat * sin(lonRad), sin(latRad), radCosLat * cos(lonRad));\n' +
                    '}\n' +

                    //normalizes a value between 0.0 and 1.0
                    'float normalizeScalar(float value, float minValue, float maxValue){\n' +
                    '   return (value - minValue) / (maxValue - minValue);\n' +
                    '}\n' +

                    'void main() {\n' +
                    '   vec3 vertexPosition = computePosition(vertexPoint.x, vertexPoint.y);\n' +
                    '   gl_Position = mvpMatrix * vec4(vertexPosition.xyz, 1.0);\n' +
                    '   gl_Position.z = gl_Position.w - 0.00001;\n' +
                    '   gl_PointSize = vertexPoint.z;\n' +
                    '   magnitudeWeight = normalizeScalar(vertexPoint.w, magnitudeRange.x, magnitudeRange.y);\n' +
                    '}',
                fragmentShaderSource =
                    'precision mediump float;\n' +

                    'uniform sampler2D textureSampler;\n' +
                    'uniform int textureEnabled;\n' +

                    'varying float magnitudeWeight;\n' +

                    'const vec4 white = vec4(1.0, 1.0, 1.0, 1.0);\n' +
                    'const vec4 grey = vec4(0.5, 0.5, 0.5, 1.0);\n' +

                    'void main() {\n' +
                    '   if (textureEnabled == 1) {\n' +
                    '       gl_FragColor = texture2D(textureSampler, gl_PointCoord);\n' +
                    '   }\n' +
                    '   else {\n' +
                    //paint the starts in shades of grey, where the brightest star is white and the dimmest star is grey
                    '       gl_FragColor = mix(white, grey, magnitudeWeight);\n' +
                    '   }\n' +
                    '}';

            // Call to the superclass, which performs shader program compiling and linking.
            GpuProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint"]);

            /**
             * The WebGL location for this program's 'vertexPoint' attribute.
             * @type {Number}
             * @readonly
             */
            this.vertexPointLocation = this.attributeLocation(gl, "vertexPoint");

            /**
             * The WebGL location for this program's 'mvpMatrix' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.mvpMatrixLocation = this.uniformLocation(gl, "mvpMatrix");

            /**
             * The WebGL location for this program's 'numDays' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.numDaysLocation = this.uniformLocation(gl, "numDays");

            /**
             * The WebGL location for this program's 'magnitudeRangeLocation' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.magnitudeRangeLocation = this.uniformLocation(gl, "magnitudeRange");

            /**
             * The WebGL location for this program's 'textureSampler' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.textureUnitLocation = this.uniformLocation(gl, "textureSampler");

            /**
             * The WebGL location for this program's 'textureEnabled' uniform.
             * @type {WebGLUniformLocation}
             * @readonly
             */
            this.textureEnabledLocation = this.uniformLocation(gl, "textureEnabled");
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        StarFieldProgram.key = "WorldWindGpuStarFieldProgram";

        // Inherit from GpuProgram.
        StarFieldProgram.prototype = Object.create(GpuProgram.prototype);

        /**
         * Loads the specified matrix as the value of this program's 'mvpMatrix' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Matrix} matrix The matrix to load.
         * @throws {ArgumentError} If the specified matrix is null or undefined.
         */
        StarFieldProgram.prototype.loadModelviewProjection = function (gl, matrix) {
            if (!matrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadModelviewProjection", "missingMatrix"));
            }

            this.loadUniformMatrix(gl, matrix, this.mvpMatrixLocation);
        };

        /**
         * Loads the specified number as the value of this program's 'numDays' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} numDays The number of days (positive or negative) since Greenwich noon, Terrestrial Time,
         * on 1 January 2000 (J2000.0)
         * @throws {ArgumentError} If the specified number is null or undefined.
         */
        StarFieldProgram.prototype.loadNumDays = function (gl, numDays) {
            if (numDays == null) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadNumDays", "missingNumDays"));
            }
            gl.uniform1f(this.numDaysLocation, numDays);
        };

        /**
         * Loads the specified numbers as the value of this program's 'magnitudeRange' uniform variable.
         *
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} minMag
         * @param {Number} maxMag
         * @throws {ArgumentError} If the specified numbers are null or undefined.
         */
        StarFieldProgram.prototype.loadMagnitudeRange = function (gl, minMag, maxMag) {
            if (minMag == null) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadMagRange", "missingMinMag"));
            }
            if (maxMag == null) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "StarFieldProgram", "loadMagRange", "missingMaxMag"));
            }
            gl.uniform2f(this.magnitudeRangeLocation, minMag, maxMag);
        };

        /**
         * Loads the specified number as the value of this program's 'textureSampler' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Number} unit The texture unit.
         */
        StarFieldProgram.prototype.loadTextureUnit = function (gl, unit) {
            gl.uniform1i(this.textureUnitLocation, unit - gl.TEXTURE0);
        };

        /**
         * Loads the specified boolean as the value of this program's 'textureEnabledLocation' uniform variable.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @param {Boolean} value
         */
        StarFieldProgram.prototype.loadTextureEnabled = function (gl, value) {
            gl.uniform1i(this.textureEnabledLocation, value ? 1 : 0);
        };

        return StarFieldProgram;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports StarFieldLayer
 */
define('layer/StarFieldLayer',[
        './Layer',
        '../util/Logger',
        '../geom/Matrix',
        '../shaders/StarFieldProgram',
        '../util/SunPosition'
    ],
    function (Layer,
              Logger,
              Matrix,
              StarFieldProgram,
              SunPosition) {
        'use strict';

        /**
         * Constructs a layer showing stars and the Sun around the Earth.
         * If used together with the AtmosphereLayer, the StarFieldLayer must be inserted before the AtmosphereLayer.
         *
         * If you want to use your own star data, the file provided must be .json
         * and the fields 'ra', 'dec' and 'vmag' must be present in the metadata.
         * ra and dec must be expressed in degrees.
         *
         * This layer uses J2000.0 as the ref epoch.
         *
         * If the star data .json file is too big, consider enabling gzip compression on your web server.
         * For more info about enabling gzip compression consult the configuration for your web server.
         *
         * @alias StarFieldLayer
         * @constructor
         * @classdesc Provides a layer showing stars, and the Sun around the Earth
         * @param {URL} starDataSource optional url for the stars data
         * @augments Layer
         */
        var StarFieldLayer = function (starDataSource) {
            Layer.call(this, 'StarField');

            // The StarField Layer is not pickable.
            this.pickEnabled = false;

            /**
             * The size of the Sun in pixels.
             * This can not exceed the maximum allowed pointSize of the GPU.
             * A warning will be given if the size is too big and the allowed max size will be used.
             * @type {Number}
             * @default 128
             */
            this.sunSize = 128;

            /**
             * Indicates weather to show or hide the Sun
             * @type {Boolean}
             * @default true
             */
            this.showSun = true;

            //Documented in defineProperties below.
            this._starDataSource = starDataSource || WorldWind.configuration.baseUrl + 'images/stars.json';
            this._sunImageSource = WorldWind.configuration.baseUrl + 'images/sunTexture.png';

            //Internal use only.
            //The MVP matrix of this layer.
            this._matrix = Matrix.fromIdentity();

            //Internal use only.
            //gpu cache key for the stars vbo.
            this._starsPositionsVboCacheKey = null;

            //Internal use only.
            this._numStars = 0;

            //Internal use only.
            this._starData = null;

            //Internal use only.
            this._minMagnitude = Number.MAX_VALUE;
            this._maxMagnitude = Number.MIN_VALUE;

            //Internal use only.
            //A flag to indicate the star data is currently being retrieved.
            this._loadStarted = false;

            //Internal use only.
            this._minScale = 10e6;

            //Internal use only.
            this._sunPositionsCacheKey = '';
            this._sunBufferView = new Float32Array(4);

            //Internal use only.
            this._MAX_GL_POINT_SIZE = 0;
        };

        StarFieldLayer.prototype = Object.create(Layer.prototype);

        Object.defineProperties(StarFieldLayer.prototype, {
            /**
             * Url for the stars data.
             * @memberof StarFieldLayer.prototype
             * @type {URL}
             */
            starDataSource: {
                get: function () {
                    return this._starDataSource;
                },
                set: function (value) {
                    this._starDataSource = value;
                    this.invalidateStarData();
                }
            },

            /**
             * Url for the sun texture image.
             * @memberof StarFieldLayer.prototype
             * @type {URL}
             */
            sunImageSource: {
                get: function () {
                    return this._sunImageSource;
                },
                set: function (value) {
                    this._sunImageSource = value;
                }
            }
        });

        // Documented in superclass.
        StarFieldLayer.prototype.doRender = function (dc) {
            if (dc.globe.is2D()) {
                return;
            }

            if (!this.haveResources(dc)) {
                this.loadResources(dc);
                return;
            }

            this.beginRendering(dc);
            try {
                this.doDraw(dc);
            }
            finally {
                this.endRendering(dc);
            }
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.haveResources = function (dc) {
            var sunTexture = dc.gpuResourceCache.resourceForKey(this._sunImageSource);
            return (
                this._starData != null &&
                sunTexture != null
            );
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.loadResources = function (dc) {
            var gl = dc.currentGlContext;
            var gpuResourceCache = dc.gpuResourceCache;

            if (!this._starData) {
                this.fetchStarData();
            }

            var sunTexture = gpuResourceCache.resourceForKey(this._sunImageSource);
            if (!sunTexture) {
                gpuResourceCache.retrieveTexture(gl, this._sunImageSource);
            }
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.beginRendering = function (dc) {
            var gl = dc.currentGlContext;
            dc.findAndBindProgram(StarFieldProgram);
            gl.enableVertexAttribArray(0);
            gl.depthMask(false);
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.doDraw = function (dc) {
            this.loadCommonUniforms(dc);
            this.renderStars(dc);
            if (this.showSun) {
                this.renderSun(dc);
            }
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.loadCommonUniforms = function (dc) {
            var gl = dc.currentGlContext;
            var program = dc.currentProgram;

            var eyePoint = dc.navigatorState.eyePoint;
            var eyePosition = dc.globe.computePositionFromPoint(eyePoint[0], eyePoint[1], eyePoint[2], {});
            var scale = Math.max(eyePosition.altitude * 1.5, this._minScale);
            this._matrix.copy(dc.navigatorState.modelviewProjection);
            this._matrix.multiplyByScale(scale, scale, scale);
            program.loadModelviewProjection(gl, this._matrix);

            //this subtraction does not work properly on the GPU, it must be done on the CPU
            //possibly due to precision loss
            //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
            var julianDate = SunPosition.computeJulianDate(this.time || new Date());
            program.loadNumDays(gl, julianDate - 2451545.0);
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.renderStars = function (dc) {
            var gl = dc.currentGlContext;
            var gpuResourceCache = dc.gpuResourceCache;
            var program = dc.currentProgram;

            if (!this._starsPositionsVboCacheKey) {
                this._starsPositionsVboCacheKey = gpuResourceCache.generateCacheKey();
            }
            var vboId = gpuResourceCache.resourceForKey(this._starsPositionsVboCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                var positions = this.createStarsGeometry();
                gpuResourceCache.putResource(this._starsPositionsVboCacheKey, vboId, positions.length * 4);
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
            }
            else {
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
            }
            dc.frameStatistics.incrementVboLoadCount(1);

            gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0);

            program.loadMagnitudeRange(gl, this._minMagnitude, this._maxMagnitude);
            program.loadTextureEnabled(gl, false);

            gl.drawArrays(gl.POINTS, 0, this._numStars);
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.renderSun = function (dc) {
            var gl = dc.currentGlContext;
            var program = dc.currentProgram;
            var gpuResourceCache = dc.gpuResourceCache;

            if (!this._MAX_GL_POINT_SIZE) {
                this._MAX_GL_POINT_SIZE = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)[1];
            }
            if (this.sunSize > this._MAX_GL_POINT_SIZE) {
                Logger.log(Logger.LEVEL_WARNING, 'StarFieldLayer - sunSize is to big, max size allowed is: ' +
                    this._MAX_GL_POINT_SIZE);
            }

            var sunCelestialLocation = SunPosition.getAsCelestialLocation(this.time || new Date());

            //.x = declination
            //.y = right ascension
            //.z = point size
            //.w = magnitude
            this._sunBufferView[0] = sunCelestialLocation.declination;
            this._sunBufferView[1] = sunCelestialLocation.rightAscension;
            this._sunBufferView[2] = Math.min(this.sunSize, this._MAX_GL_POINT_SIZE);
            this._sunBufferView[3] = 1;

            if (!this._sunPositionsCacheKey) {
                this._sunPositionsCacheKey = gpuResourceCache.generateCacheKey();
            }
            var vboId = gpuResourceCache.resourceForKey(this._sunPositionsCacheKey);
            if (!vboId) {
                vboId = gl.createBuffer();
                gpuResourceCache.putResource(this._sunPositionsCacheKey, vboId, this._sunBufferView.length * 4);
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferData(gl.ARRAY_BUFFER, this._sunBufferView, gl.DYNAMIC_DRAW);
            }
            else {
                gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
                gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._sunBufferView);
            }
            dc.frameStatistics.incrementVboLoadCount(1);
            gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0);

            program.loadTextureEnabled(gl, true);

            var sunTexture = dc.gpuResourceCache.resourceForKey(this._sunImageSource);
            sunTexture.bind(dc);

            gl.drawArrays(gl.POINTS, 0, 1);
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.endRendering = function (dc) {
            var gl = dc.currentGlContext;
            gl.depthMask(true);
            gl.disableVertexAttribArray(0);
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.fetchStarData = function () {
            if (this._loadStarted) {
                return;
            }

            this._loadStarted = true;
            var self = this;
            var xhr = new XMLHttpRequest();

            xhr.onload = function () {
                if (this.status >= 200 && this.status < 300) {
                    try {
                        self._starData = JSON.parse(this.response);
                        self.sendRedrawRequest();
                    }
                    catch (e) {
                        Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to parse JSON for star data ' +
                            e.toString());
                    }
                }
                else {
                    Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to fetch star data. Status: ' +
                        this.status + ' ' + this.statusText);
                }
                self._loadStarted = false;
            };

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer unable to fetch star data');
                self._loadStarted = false;
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_SEVERE, 'StarFieldLayer fetch star data has timeout');
                self._loadStarted = false;
            };

            xhr.open('GET', this._starDataSource, true);
            xhr.send();
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.createStarsGeometry = function () {
            var indexes = this.parseStarsMetadata(this._starData.metadata);

            if (indexes.raIndex === -1) {
                throw new Error(
                    Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
                        'Missing ra field in star data.'));
            }
            if (indexes.decIndex === -1) {
                throw new Error(
                    Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
                        'Missing dec field in star data.'));
            }
            if (indexes.magIndex === -1) {
                throw new Error(
                    Logger.logMessage(Logger.LEVEL_SEVERE, 'StarFieldLayer', 'createStarsGeometry',
                        'Missing vmag field in star data.'));
            }

            var data = this._starData.data;
            var positions = [];

            this._minMagnitude = Number.MAX_VALUE;
            this._maxMagnitude = Number.MIN_VALUE;

            for (var i = 0, len = data.length; i < len; i++) {
                var starInfo = data[i];
                var declination = starInfo[indexes.decIndex]; //for latitude
                var rightAscension = starInfo[indexes.raIndex]; //for longitude
                var magnitude = starInfo[indexes.magIndex];
                var pointSize = magnitude < 2 ? 2 : 1;

                positions.push(declination, rightAscension, pointSize, magnitude);

                this._minMagnitude = Math.min(this._minMagnitude, magnitude);
                this._maxMagnitude = Math.max(this._maxMagnitude, magnitude);
            }
            this._numStars = Math.floor(positions.length / 4);

            return positions;
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.parseStarsMetadata = function (metadata) {
            var raIndex = -1,
                decIndex = -1,
                magIndex = -1;
            for (var i = 0, len = metadata.length; i < len; i++) {
                var starMetaInfo = metadata[i];
                if (starMetaInfo.name === 'ra') {
                    raIndex = i;
                }
                if (starMetaInfo.name === 'dec') {
                    decIndex = i;
                }
                if (starMetaInfo.name === 'vmag') {
                    magIndex = i;
                }
            }
            return {
                raIndex: raIndex,
                decIndex: decIndex,
                magIndex: magIndex
            };
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.invalidateStarData = function () {
            this._starData = null;
            this._starsPositionsVboCacheKey = null;
        };

        // Internal. Intentionally not documented.
        StarFieldLayer.prototype.sendRedrawRequest = function () {
            var e = document.createEvent('Event');
            e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
            window.dispatchEvent(e);
        };

        return StarFieldLayer;

    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceCircle
 */
define('shapes/SurfaceCircle',['../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape'
    ],
    function (ArgumentError,
              Location,
              Logger,
              ShapeAttributes,
              SurfaceShape) {
        "use strict";

        /**
         * Constructs a surface circle with a specified center and radius and an optional attributes bundle.
         * @alias SurfaceCircle
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents a circle draped over the terrain surface.
         * <p>
         *     SurfaceCircle uses the following attributes from its associated shape attributes bundle:
         *     <ul>
         *         <li>Draw interior</li>
         *         <li>Draw outline</li>
         *         <li>Interior color</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         *     </ul>
         * @param {Location} center The circle's center location.
         * @param {Number} radius The circle's radius in meters.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         * @throws {ArgumentError} If the specified center location is null or undefined or the specified radius
         * is negative.
         */
        var SurfaceCircle = function (center, radius, attributes) {
            if (!center) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceCircle", "constructor", "missingLocation"));
            }

            if (radius < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceCircle", "constructor", "Radius is negative"));
            }

            SurfaceShape.call(this, attributes);

            // All these are documented with their property accessors below.
            this._center = center;
            this._radius = radius;
            this._intervals = SurfaceCircle.DEFAULT_NUM_INTERVALS;
        };

        SurfaceCircle.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfaceCircle.prototype, {
            /**
             * This shape's center location.
             * @memberof SurfaceCircle.prototype
             * @type {Location}
             */
            center: {
                get: function () {
                    return this._center;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._center = value;
                }
            },

            /**
             * This shape's radius, in meters.
             * @memberof SurfaceCircle.prototype
             * @type {Number}
             */
            radius: {
                get: function () {
                    return this._radius;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._radius = value;
                }
            },

            /**
             * The number of intervals to generate locations for.
             * @type {Number}
             * @memberof SurfaceCircle.prototype
             * @default SurfaceCircle.DEFAULT_NUM_INTERVALS
             */
            intervals: {
                get: function () {
                    return this._intervals;
                },
                set: function (value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._intervals = value;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfaceCircle.staticStateKey = function (shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey +
                " ce " + shape.center.toString() +
                " ra " + shape.radius.toString();
        };

        // Internal use only. Intentionally not documented.
        SurfaceCircle.prototype.computeStateKey = function () {
            return SurfaceCircle.staticStateKey(this);
        };

        // Internal. Intentionally not documented.
        SurfaceCircle.prototype.computeBoundaries = function (dc) {
            if (this.radius === 0) {
                return null;
            }

            var numLocations = 1 + Math.max(SurfaceCircle.MIN_NUM_INTERVALS, this.intervals),
                da = 360 / (numLocations - 1),
                arcLength = this.radius / dc.globe.radiusAt(this.center.latitude, this.center.longitude);

            this._boundaries = new Array(numLocations);

            for (var i = 0; i < numLocations; i++) {
                var azimuth = (i !== numLocations - 1) ? (i * da) : 0;
                this._boundaries[i] = Location.greatCircleLocation(
                    this.center,
                    azimuth,   // In degrees
                    arcLength, // In radians
                    new Location(0, 0)
                );
            }
        };

        /**
         * The minimum number of intervals the circle generates.
         * @type {Number}
         */
        SurfaceCircle.MIN_NUM_INTERVALS = 8;

        /**
         * The default number of intervals the circle generates.
         * @type {Number}
         */
        SurfaceCircle.DEFAULT_NUM_INTERVALS = 64;

        return SurfaceCircle;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceEllipse
 */
define('shapes/SurfaceEllipse',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger,
              ShapeAttributes,
              SurfaceShape,
              WWMath) {
        "use strict";

        /**
         * Constructs a surface ellipse with a specified center and radii and an optional attributes bundle.
         * @alias SurfaceEllipse
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents an ellipse draped over the terrain surface.
         * <p>
         * SurfaceEllipse uses the following attributes from its associated shape attributes bundle:
         * <ul>
         *         <li>Draw interior</li>
         *         <li>Draw outline</li>
         *         <li>Interior color</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         * </ul>
         * @param {Location} center The ellipse's center location.
         * @param {Number} majorRadius The ellipse's major radius in meters.
         * @param {Number} minorRadius The ellipse's minor radius in meters.
         * @param {Number} heading The heading of the major axis in degrees.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         * @throws {ArgumentError} If the specified center location is null or undefined or if either specified radii
         * is negative.
         */
        var SurfaceEllipse = function (center, majorRadius, minorRadius, heading, attributes) {
            if (!center) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceEllipse", "constructor", "missingLocation"));
            }

            if (majorRadius < 0 || minorRadius < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceEllipse", "constructor", "Radius is negative."));
            }

            SurfaceShape.call(this, attributes);

            // All these are documented with their property accessors below.
            this._center = center;
            this._majorRadius = majorRadius;
            this._minorRadius = minorRadius;
            this._heading = heading;
            this._intervals = SurfaceEllipse.DEFAULT_NUM_INTERVALS;
        };

        SurfaceEllipse.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfaceEllipse.prototype, {
            /**
             * This shape's center location.
             * @memberof SurfaceEllipse.prototype
             * @type {Location}
             */
            center: {
                get: function() {
                    return this._center;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._center = value;
                }
            },

            /**
             * This shape's major radius, in meters.
             * @memberof SurfaceEllipse.prototype
             * @type {Number}
             */
            majorRadius: {
                get: function() {
                    return this._majorRadius;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._majorRadius = value;
                }
            },

            /**
             * This shape's minor radius in meters.
             * @memberof SurfaceEllipse.prototype
             * @type {Number}
             */
            minorRadius: {
                get: function() {
                    return this._minorRadius;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._minorRadius = value;
                }
            },

            /**
             * The heading of the major axis, specified as degrees clockwise from North.
             * @type {Number}
             * @memberof SurfaceEllipse.prototype
             * @default 0
             */
            heading: {
                get: function() {
                    return this._heading;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._heading = value;
                }
            },

            /**
             * The number of intervals to generate locations for.
             * @type {Number}
             * @memberof SurfaceEllipse.prototype
             * @default SurfaceEllipse.DEFAULT_NUM_INTERVALS
             */
            intervals: {
                get: function() {
                    return this._intervals;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._intervals = value;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfaceEllipse.staticStateKey = function(shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey +
                " ce " + shape.center.toString() +
                " ma " + shape.majorRadius.toString() +
                " mi " + shape.minorRadius.toString() +
                " he " + shape.heading.toString() +
                " in " + shape.intervals.toString();
        };

        // Internal use only. Intentionally not documented.
        SurfaceEllipse.prototype.computeStateKey = function() {
            return SurfaceEllipse.staticStateKey(this);
        };

        // Internal. Intentionally not documented.
        SurfaceEllipse.prototype.computeBoundaries = function(dc) {
            if (this.majorRadius == 0 && this.minorRadius == 0) {
                return null;
            }

            var globe = dc.globe,
                numLocations = 1 + Math.max(SurfaceEllipse.MIN_NUM_INTERVALS, this.intervals),
                da = (2 * Math.PI) / (numLocations - 1),
                globeRadius = globe.radiusAt(this.center.latitude, this.center.longitude);

            this._boundaries = new Array(numLocations);

            for (var i = 0; i < numLocations; i++) {
                var angle = (i != numLocations - 1) ? i * da : 0,
                    xLength = this.majorRadius * Math.cos(angle),
                    yLength = this.minorRadius * Math.sin(angle),
                    distance = Math.sqrt(xLength * xLength + yLength * yLength);

                // azimuth runs positive clockwise from north and through 360 degrees.
                var azimuth = (Math.PI / 2.0) -
                    (Math.acos(xLength / distance) * WWMath.signum(yLength) - this.heading * Angle.DEGREES_TO_RADIANS);

                this._boundaries[i] = Location.greatCircleLocation(this.center, azimuth * Angle.RADIANS_TO_DEGREES,
                    distance / globeRadius, new Location(0, 0));
            }
        };

        /**
         * The minimum number of intervals the ellipse generates.
         * @type {Number}
         */
        SurfaceEllipse.MIN_NUM_INTERVALS = 8;

        /**
         * The default number of intervals the ellipse generates.
         * @type {Number}
         */
        SurfaceEllipse.DEFAULT_NUM_INTERVALS = 64;

        return SurfaceEllipse;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceRectangle
 */
define('shapes/SurfaceRectangle',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape',
        '../util/WWMath'
    ],
    function (Angle,
              ArgumentError,
              Location,
              Logger,
              ShapeAttributes,
              SurfaceShape,
              WWMath) {
        "use strict";

        /**
         * Constructs a surface rectangle with a specified center and size and an optional attributes bundle.
         * @alias SurfaceRectangle
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents a rectangle draped over the terrain surface.
         * <p>
         * SurfaceRectangle uses the following attributes from its associated shape attributes bundle:
         * <ul>
         *         <li>Draw interior</li>
         *         <li>Draw outline</li>
         *         <li>Interior color</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         * </ul>
         * @param {Location} center The rectangle's center location.
         * @param {Number} width The rectangle's width in meters.
         * @param {Number} height The rectangle's height in meters.
         * @param {Number} heading The rectangle's heading.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         * @throws {ArgumentError} If the specified center location is null or undefined or if either specified width
         * or height is negative.
         */
        var SurfaceRectangle = function (center, width, height, heading, attributes) {
            if (!center) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRectangle", "constructor", "missingLocation"));
            }

            if (width < 0 || height < 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRectangle", "constructor", "Size is negative."));
            }

            SurfaceShape.call(this, attributes);

            // All these are documented with their property accessors below.
            this._center = center;
            this._width = width;
            this._height = height;
            this._heading = heading;
        };

        SurfaceRectangle.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfaceRectangle.prototype, {
            /**
             * This shape's center location.
             * @memberof SurfaceRectangle.prototype
             * @type {Location}
             */
            center: {
                get: function() {
                    return this._center;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._center = value;
                }
            },

            /**
             * This shape's width, in meters.
             * @memberof SurfaceRectangle.prototype
             * @type {Number}
             */
            width: {
                get: function() {
                    return this._width;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._width = value;
                }
            },

            /**
             * This shape's height in meters.
             * @memberof SurfaceRectangle.prototype
             * @type {Number}
             */
            height: {
                get: function() {
                    return this._height;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._height = value;
                }
            },

            /**
             * The shape's heading, specified as degrees clockwise from North. This shape's height and width are
             * relative to its heading.
             * @memberof SurfaceRectangle.prototype
             * @type {number}
             * @default 0
             */
            heading: {
                get: function() {
                    return this._heading;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._heading = value;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfaceRectangle.staticStateKey = function(shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey +
                " ce " + shape.center.toString() +
                " wi " + shape.width.toString() +
                " he " + shape.height.toString() +
                " hd " + shape.heading.toString();
        };

        // Internal use only. Intentionally not documented.
        SurfaceRectangle.prototype.computeStateKey = function() {
            return SurfaceRectangle.staticStateKey(this);
        };

        // Internal. Intentionally not documented.
        SurfaceRectangle.prototype.computeBoundaries = function(dc) {
            var halfWidth = 0.5 * this.width,
                halfHeight = 0.5 * this.height,
                globeRadius = dc.globe.radiusAt(this.center.latitude, this.center.longitude);


            this._boundaries = new Array(4);

            this.addLocation(0, -halfWidth, -halfHeight, globeRadius);
            this.addLocation(1,  halfWidth, -halfHeight, globeRadius);
            this.addLocation(2,  halfWidth,  halfHeight, globeRadius);
            this.addLocation(3, -halfWidth,  halfHeight, globeRadius);
        };

        SurfaceRectangle.prototype.addLocation = function(idx, xLength, yLength, globeRadius) {
            var distance = Math.sqrt(xLength * xLength + yLength * yLength);

            // azimuth runs positive clockwise from north and through 360 degrees.
            var azimuth = (Math.PI / 2.0) - (Math.acos(xLength / distance) * WWMath.signum(yLength) - this.heading * Angle.DEGREES_TO_RADIANS);

            this._boundaries[idx] = Location.greatCircleLocation(this.center, azimuth * Angle.RADIANS_TO_DEGREES,
                distance / globeRadius, new Location(0, 0));

        };

        return SurfaceRectangle;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceRenderable
 */
define('render/SurfaceRenderable',['../util/Logger',
        '../error/UnsupportedOperationError'
    ],
    function (Logger,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Applications must not call this constructor. It is an interface class and is not meant to be instantiated
         * directly.
         * @alias SurfaceRenderable
         * @constructor
         * @classdesc Represents a surface renderable.
         * This is an interface class and is not meant to be instantiated directly.
         */
        var SurfaceRenderable = function () {

            /**
             * This surface renderable's display name.
             * @type {String}
             * @default Renderable
             */
            this.displayName = "Renderable";

            /**
             * Indicates whether this surface renderable is enabled.
             * @type {Boolean}
             * @default true
             */
            this.enabled = true;

            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRenderable", "constructor", "abstractInvocation"));
        };

        /**
         * Renders this surface renderable.
         * @param {DrawContext} dc The current draw context.
         */
        SurfaceRenderable.prototype.renderSurface = function (dc) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceRenderable", "renderSurface", "abstractInvocation"));
        };

        return SurfaceRenderable;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports SurfaceSector
 */
define('shapes/SurfaceSector',[
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../shapes/ShapeAttributes',
        '../shapes/SurfaceShape'
    ],
    function (ArgumentError,
              Location,
              Logger,
              ShapeAttributes,
              SurfaceShape) {
        "use strict";

        /**
         * Constructs a surface sector.
         * @alias SurfaceSector
         * @constructor
         * @augments SurfaceShape
         * @classdesc Represents a sector draped over the terrain surface. The sector is specified as a rectangular
         * region in geographic coordinates.
         * <p>
         * SurfaceSector uses the following attributes from its associated shape attributes bundle:
         * <ul>
         *         <li>Draw interior</li>
         *         <li>Draw outline</li>
         *         <li>Interior color</li>
         *         <li>Outline color</li>
         *         <li>Outline width</li>
         *         <li>Outline stipple factor</li>
         *         <li>Outline stipple pattern</li>
         * </ul>
         * @param {Sector} sector This surface sector's sector.
         * @param {ShapeAttributes} attributes The attributes to apply to this shape. May be null, in which case
         * attributes must be set directly before the shape is drawn.
         * @throws {ArgumentError} If the specified boundaries are null or undefined.
         */
        var SurfaceSector = function (sector, attributes) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "SurfaceSector", "constructor", "missingSector"));
            }

            SurfaceShape.call(this, attributes);

            /**
             * This shape's sector.
             * @type {Sector}
             */
            this._sector = sector;
        };

        SurfaceSector.prototype = Object.create(SurfaceShape.prototype);

        Object.defineProperties(SurfaceSector.prototype, {
            /**
             * This shape's sector.
             * @memberof SurfaceSector.prototype
             * @type {Sector}
             */
            sector: {
                get: function() {
                    return this._sector;
                },
                set: function(value) {
                    this.stateKeyInvalid = true;
                    this.resetBoundaries();
                    this._sector = value;
                }
            }
        });

        // Internal use only. Intentionally not documented.
        SurfaceSector.staticStateKey = function(shape) {
            var shapeStateKey = SurfaceShape.staticStateKey(shape);

            return shapeStateKey;
        };

        // Internal use only. Intentionally not documented.
        SurfaceSector.prototype.computeStateKey = function() {
            return SurfaceSector.staticStateKey(this);
        };

        // Internal. Intentionally not documented.
        SurfaceSector.prototype.computeBoundaries = function(dc) {
            var sector = this._sector;

            this._boundaries = new Array(4);

            this._boundaries[0] = new Location(sector.minLatitude, sector.minLongitude);
            this._boundaries[1] = new Location(sector.maxLatitude, sector.minLongitude);
            this._boundaries[2] = new Location(sector.maxLatitude, sector.maxLongitude);
            this._boundaries[3] = new Location(sector.minLatitude, sector.maxLongitude);
        };

        return SurfaceSector;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TapRecognizer
 */
define('gesture/TapRecognizer',['../gesture/GestureRecognizer'],
    function (GestureRecognizer) {
        "use strict";

        /**
         * Constructs a tap gesture recognizer.
         * @alias TapRecognizer
         * @constructor
         * @augments GestureRecognizer
         * @classdesc A concrete gesture recognizer subclass that looks for single or multiple taps.
         * @param {EventTarget} target The document element this gesture recognizer observes for mouse and touch events.
         * @param {Function} callback An optional function to call when this gesture is recognized. If non-null, the
         * function is called when this gesture is recognized, and is passed a single argument: this gesture recognizer,
         * e.g., <code>gestureCallback(recognizer)</code>.
         * @throws {ArgumentError} If the specified target is null or undefined.
         */
        var TapRecognizer = function (target, callback) {
            GestureRecognizer.call(this, target, callback);

            /**
             *
             * @type {Number}
             */
            this.numberOfTaps = 1;

            /**
             *
             * @type {Number}
             */
            this.numberOfTouches = 1;

            // Intentionally not documented.
            this.maxTouchMovement = 20;

            // Intentionally not documented.
            this.maxTapDuration = 500;

            // Intentionally not documented.
            this.maxTapInterval = 400;

            // Intentionally not documented.
            this.taps = [];

            // Intentionally not documented.
            this.timeout = null;
        };

        TapRecognizer.prototype = Object.create(GestureRecognizer.prototype);

        // Documented in superclass.
        TapRecognizer.prototype.reset = function () {
            GestureRecognizer.prototype.reset.call(this);

            this.taps = [];
            this.cancelFailAfterDelay();
        };

        // Documented in superclass.
        TapRecognizer.prototype.mouseDown = function (event) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            this.state = WorldWind.FAILED; // touch gestures fail upon receiving a mouse event
        };

        // Documented in superclass.
        TapRecognizer.prototype.touchStart = function (touch) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            var tap;

            if (this.touchCount > this.numberOfTouches) {
                this.state = WorldWind.FAILED;
            } else if (this.touchCount == 1) { // first touch started
                tap = {
                    touchCount: this.touchCount,
                    clientX: this.clientX,
                    clientY: this.clientY
                };
                this.taps.push(tap);
                this.failAfterDelay(this.maxTapDuration); // fail if the tap is down too long
            } else {
                tap = this.taps[this.taps.length - 1];
                tap.touchCount = this.touchCount; // max number of simultaneous touches
                tap.clientX = this.clientX; // touch centroid
                tap.clientY = this.clientY;
            }
        };

        // Documented in superclass.
        TapRecognizer.prototype.touchMove = function (touch) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            var dx = this.translationX,
                dy = this.translationY,
                distance = Math.sqrt(dx * dx + dy * dy);
            if (distance > this.maxTouchMovement) {
                this.state = WorldWind.FAILED;
            }
        };

        // Documented in superclass.
        TapRecognizer.prototype.touchEnd = function (touch) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            if (this.touchCount != 0) {
                return; // wait until the last touch ends
            }

            var tapCount = this.taps.length,
                tap = this.taps[tapCount - 1];
            if (tap.touchCount != this.numberOfTouches) {
                this.state = WorldWind.FAILED; // wrong number of touches
            } else if (tapCount == this.numberOfTaps) {
                this.clientX = this.taps[0].clientX;
                this.clientY = this.taps[0].clientY;
                this.state = WorldWind.RECOGNIZED;
            } else {
                this.failAfterDelay(this.maxTapInterval); // fail if the interval between taps is too long
            }
        };

        // Documented in superclass.
        TapRecognizer.prototype.touchCancel = function (touch) {
            if (this.state != WorldWind.POSSIBLE) {
                return;
            }

            this.state = WorldWind.FAILED;
        };

        // Intentionally not documented.
        TapRecognizer.prototype.failAfterDelay = function (delay) {
            var self = this;
            if (self.timeout) {
                window.clearTimeout(self.timeout);
            }

            self.timeout = window.setTimeout(function () {
                self.timeout = null;
                if (self.state == WorldWind.POSSIBLE) {
                    self.state = WorldWind.FAILED; // fail if we haven't already reached a terminal state
                }
            }, delay);
        };

        // Intentionally not documented.
        TapRecognizer.prototype.cancelFailAfterDelay = function () {
            var self = this;
            if (self.timeout) {
                window.clearTimeout(self.timeout);
                self.timeout = null;
            }
        };

        return TapRecognizer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TectonicPlatesLayer
 */
define('layer/TectonicPlatesLayer',[
    '../error/ArgumentError',
    '../util/Color',
    '../geom/Location',
    '../util/Logger',
    '../layer/RenderableLayer',
    '../shapes/ShapeAttributes',
    '../shapes/SurfacePolygon'
], function (ArgumentError,
             Color,
             Location,
             Logger,
             RenderableLayer,
             ShapeAttributes,
             SurfacePolygon) {
    "use strict";

    /**
     * Constructs a layer showing the Earth's tectonic plates.
     * @alias TectonicPlatesLayer
     * @constructor
     * @classdesc Provides a layer showing the Earth's tectonic plates. The plates are drawn as
     * [SurfacePolygons]{@link SurfacePolygon}.
     * @param {ShapeAttributes} shapeAttributes The attributes to use when drawing the plates.
     * May be null or undefined, in which case the shapes are drawn using default attributes.
     * The default attributes draw only the outlines of the plates, in a solid color.
     * @augments RenderableLayer
     */
    var TectonicPlatesLayer = function (shapeAttributes) {
        RenderableLayer.call(this, "Tectonic Plates");

        if (shapeAttributes) {
            this._attributes = shapeAttributes;
        } else {
            this._attributes = new ShapeAttributes(null);
            this._attributes.drawInterior = false;
            this._attributes.drawOutline = true;
            this._attributes.outlineColor = Color.RED;
        }

        this.loadPlateData();
    };

    TectonicPlatesLayer.prototype = Object.create(RenderableLayer.prototype);

    Object.defineProperties(TectonicPlatesLayer.prototype, {
        /**
         * The attributes to use when drawing the plates.
         * @type {ShapeAttributes}
         * @memberof TectonicPlatesLayer.prototype
         */
        attributes: {
            get: function () {
                return this._attributes;
            },
            set: function (value) {
                if (value) {
                    this.renderables.map(function (shape, index, shapes){
                        shape.attributes = value;
                    });
                }
            }
        }
    });

    TectonicPlatesLayer.prototype.loadPlateData = function () {
        var url = WorldWind.configuration.baseUrl + "images/TectonicPlates.json";

        var xhr = new XMLHttpRequest();

        xhr.open("GET", url, true);
        xhr.onreadystatechange = (function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    this.parse(xhr.responseText);
                }
                else {
                    Logger.log(Logger.LEVEL_WARNING,
                        "Tectonic plate data retrieval failed (" + xhr.statusText + "): " + url);
                }
            }
        }).bind(this);

        xhr.onerror = function () {
            Logger.log(Logger.LEVEL_WARNING, "Tectonic plate data retrieval failed: " + url);
        };

        xhr.ontimeout = function () {
            Logger.log(Logger.LEVEL_WARNING, "Tectonic plate data retrieval timed out: " + url);
        };

        xhr.send(null);
    };

    TectonicPlatesLayer.prototype.parse = function (jsonText) {
        var plateData = JSON.parse(jsonText);

        var self = this;
        plateData.features.map(function(feature, featureIndex, features) {
            var locations = [];
            feature.geometry.coordinates.map(function(coordinate, geometryIndex, coordinates) {
                locations.push(new Location(coordinate[1], coordinate[0]));
            });

            var polygon = new SurfacePolygon(locations, self._attributes);

            self.addRenderable(polygon);
        });
    };

    return TectonicPlatesLayer;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TileFactory
 */
define('util/TileFactory',['../util/Logger',
        '../error/UnsupportedOperationError'
    ],
    function (Logger,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Applications must not call this constructor. It is an interface class and is not meant to be instantiated
         * directly.
         * @alias TileFactory
         * @constructor
         * @classdesc
         * Represents a tile factory.
         * This is an interface class and is not meant to be instantiated directly.
         */
        var TileFactory = function () {};

        /**
         * Creates a tile for a specified sector, level and row and column within that level.
         * Implementers of this interface must implement this function.
         * @param {Sector} sector The sector the tile spans.
         * @param {Level} level The level the tile is a member of.
         * @param {Number} row The tile's row within the specified level.
         * @param {Number} column The tile's column within the specified level.
         * @throws {ArgumentError} If the specified sector is null or undefined.
         */
        TileFactory.prototype.createTile = function (sector, level, row, column) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "TileFactory", "createTile", "abstractInvocation"));
        };

        return TileFactory;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports TriangleMesh
 */
define('shapes/TriangleMesh',[
        '../shapes/AbstractMesh',
        '../error/ArgumentError',
        '../shaders/BasicTextureProgram',
        '../geom/BoundingBox',
        '../util/Color',
        '../util/ImageSource',
        '../geom/Location',
        '../util/Logger',
        '../geom/Matrix',
        '../pick/PickedObject',
        '../geom/Position',
        '../shapes/ShapeAttributes',
        '../shapes/SurfacePolygon',
        '../geom/Vec2',
        '../geom/Vec3',
    ],
    function (AbstractMesh,
              ArgumentError,
              BasicTextureProgram,
              BoundingBox,
              Color,
              ImageSource,
              Location,
              Logger,
              Matrix,
              PickedObject,
              Position,
              ShapeAttributes,
              SurfacePolygon,
              Vec2,
              Vec3) {
        "use strict";

        /**
         * Constructs a triangle mesh.
         * @alias TriangleMesh
         * @constructor
         * @augments AbstractMesh
         * @classdesc Represents a 3D triangle mesh.
         * <p>
         *     Altitudes within the mesh's positions are interpreted according to the mesh's altitude mode, which
         *     can be one of the following:
         * <ul>
         *     <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
         *     <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
         *     <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
         * </ul>
         * If the latter, the mesh positions' altitudes are ignored. (If the mesh should be draped onto the
         * terrain, you might want to use {@link SurfacePolygon} instead.)
         * <p>
         *     Meshes have separate attributes for normal display and highlighted display. They use the interior and
         *     outline attributes of {@link ShapeAttributes}. If those attributes identify an image, that image is
         *     applied to the mesh. Texture coordinates for the image may be specified, but if not specified the full
         *     image is stretched over the full mesh. If texture coordinates are specified, there must be one texture
         *     coordinate for each vertex in the mesh.
         *
         * @param {Position[]} positions An array containing the mesh vertices.
         * There must be no more than 65536 positions. Use [split]{@link TriangleMesh#split} to subdivide large meshes
         * into smaller ones that fit this limit.
         * @param {Number[]} indices An array of integers identifying the positions of each mesh triangle.
         * Each sequence of three indices defines one triangle in the mesh. The indices identify the index of the
         * position in the associated positions array. The indices for each triangle should be in counter-clockwise
         * order to identify the triangles as front-facing.
         * @param {ShapeAttributes} attributes The attributes to associate with this mesh. May be null, in which case
         * default attributes are associated.
         *
         * @throws {ArgumentError} If the specified positions array is null, empty or undefined, the number of indices
         * is less than 3 or too many positions are specified (limit is 65536).
         */
        var TriangleMesh = function (positions, indices, attributes) {
            if (!positions) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "constructor", "missingPositions"));
            }

            if (positions.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "constructor", "missingPositions"));
            }

            // Check for size limit, which is the max number of available indices for a 16-bit unsigned int.
            if (positions.length > 65536) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "constructor",
                        "Too many positions. Must be fewer than 65537. Use TriangleMesh.split to split the shape."));
            }

            if (!indices) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "constructor",
                        "Indices array is null or undefined"));
            }

            if (indices.length < 3) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "constructor", "Too few indices."));
            }

            AbstractMesh.call(this, attributes);

            // Private. Documentation is with the defined property below and the constructor description above.
            this._positions = positions;

            // Private. Documentation is with the defined property below and the constructor description above.
            this._indices = indices;

            this.referencePosition = this._positions[0];
        };

        TriangleMesh.prototype = Object.create(AbstractMesh.prototype);

        Object.defineProperties(TriangleMesh.prototype, {
            /**
             * This mesh's positions.
             *
             * @type {Position[]}
             * @memberof TriangleMesh.prototype
             */
            positions: {
                get: function () {
                    return this._positions;
                },
                set: function (positions) {
                    if (!positions) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "positions", "missingPositions"));
                    }

                    this._positions = positions;
                    this.referencePosition = this._positions[0];
                    this.reset();
                }
            },

            /**
             * The mesh indices, an array of integers identifying the indexes of each triangle. Each index in this
             * array identifies the index of the corresponding position in the [positions]{@link TriangleMesh#positions}
             * array. Each group of three indices in this array identifies the positions of one triangle.
             *
             *
             * @type {Number[]}
             * @memberof TriangleMesh.prototype
             */
            indices: {
                get: function () {
                    return this._indices;
                },
                set: function (indices) {
                    if (!indices) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "indices",
                                "Indices array is null or undefined"));
                    }

                    this._indices = indices;
                    this.meshIndices = null;
                    this.reset();
                }
            },

            /**
             * The mesh outline indices, an array of integers identifying the positions in the outline. Each index in
             * this array identifies the index of the corresponding position in the
             * [positions]{@link TriangleMesh#positions} array. The collection of these positions forms the outline
             * of this mesh. May be null, in which case no outline is drawn.
             *
             * @type {Number[]}
             * @default null
             * @memberof TriangleMesh.prototype
             */
            outlineIndices: {
                get: function () {
                    return this._outlineIndices;
                },
                set: function (indices) {
                    this._outlineIndices = indices;
                    this.meshOutlineIndices = null;
                    this.reset();
                }
            },

            /**
             * This mesh's texture coordinates if this mesh is textured. A texture coordinate must be
             * provided for each mesh position. Each texture coordinate is a {@link Vec2} containing the s and t
             * coordinates, in that order. If no texture coordinates are specified then texture is not applied to
             * this mesh.
             * @type {Vec2[]}
             * @default null
             * @memberof TriangleMesh.prototype
             */
            textureCoordinates: {
                get: function () {
                    return this._textureCoordinates;
                },
                set: function (coords) {

                    if (coords && (coords.length != this._positions.length)) {
                        throw new ArgumentError(
                            Logger.logMessage(Logger.LEVEL_SEVERE, "TriangleMesh", "textureCoordinates",
                                "Number of texture coordinates is inconsistent with the currently specified positions."));
                    }

                    this._textureCoordinates = coords;
                    this.reset();
                    this.texCoords = null;
                }
            }
        });

        // Overridden from AbstractShape base class.
        TriangleMesh.prototype.createSurfaceShape = function () {
            if (this._outlineIndices) {
                var boundaries = [];

                for (var i = 0; i < this._outlineIndices.length; i++) {
                    boundaries.push(this._positions[this._outlineIndices[i]]);
                }

                return new SurfacePolygon(boundaries, null);
            } else {
                return null;
            }

        };

        // Overridden from AbstractShape base class.
        TriangleMesh.prototype.computeMeshPoints = function (dc, currentData) {
            var eyeDistSquared = Number.MAX_VALUE,
                eyePoint = dc.navigatorState.eyePoint,
                meshPoints = new Float32Array(this._positions.length * 3),
                pt = new Vec3(0, 0, 0),
                k = 0,
                pos, dSquared;

            for (var i = 0; i < this._positions.length; i++) {
                pos = this._positions[i];

                dc.surfacePointForMode(pos.latitude, pos.longitude, pos.altitude * this._altitudeScale,
                    this.altitudeMode, pt);

                dSquared = pt.distanceToSquared(eyePoint);
                if (dSquared < eyeDistSquared) {
                    eyeDistSquared = dSquared;
                }

                pt.subtract(this.currentData.referencePoint);

                meshPoints[k++] = pt[0];
                meshPoints[k++] = pt[1];
                meshPoints[k++] = pt[2];
            }

            currentData.eyeDistance = Math.sqrt(eyeDistSquared);

            return meshPoints;
        };

        // Overridden from AbstractShape base class.
        TriangleMesh.prototype.computeTexCoords = function () {

            if (!this._textureCoordinates) {
                return null;
            } else {
                // Capture the texture coordinates to a single array parallel to the mesh points array.
                var texCoords = new Float32Array(2 * this._textureCoordinates.length),
                    k = 0;

                for (var i = 0, len = this._textureCoordinates.length; i < len; i++) {
                    var texCoord = this._textureCoordinates[i];

                    texCoords[k++] = texCoord[0];
                    texCoords[k++] = texCoord[1];
                }

                return texCoords;
            }
        };

        // Overridden from AbstractShape base class.
        TriangleMesh.prototype.computeMeshIndices = function () {
            var meshIndices = new Uint16Array(this._indices.length);

            for (var i = 0, len = this._indices.length; i < len; i++) {
                meshIndices[i] = this._indices[i];
            }

            return meshIndices;
        };

        // Overridden from AbstractShape base class.
        TriangleMesh.prototype.computeOutlineIndices = function () {
            if (!this._outlineIndices) {
                return null;
            } else {
                var meshOutlineIndices = new Uint16Array(this._outlineIndices.length);

                for (var i = 0; i < this._outlineIndices.length; i++) {
                    meshOutlineIndices[i] = this._outlineIndices[i];
                }

                return meshOutlineIndices;
            }
        };

        /**
         * Splits a triangle mesh into several meshes, each of which contains fewer than 65536 positions.
         * @param {Position[]} positions An array containing the mesh vertices.
         * @param {Number[]} indices An array of integers identifying the positions of each mesh triangle.
         * Each sequence of three indices defines one triangle in the mesh. The indices identify the index of the
         * position in the associated positions array.
         * @param {Vec2[]} textureCoords The mesh's texture coordinates.
         * @param {Number[]} outlineIndices The mesh's outline indices.
         * @returns {Object[]} An array of objects, each of which defines one subdivision of the full mesh. Each object
         * in the array has the properties of the same name as the input arguments.
         */
        TriangleMesh.split = function (positions, indices, textureCoords, outlineIndices) {
            var splitPositions = [],
                splitTexCoords = [],
                splitIndices = [],
                indexMap = [],
                result = [],
                originalIndex, mappedIndex;

            for (var i = 0; i <= indices.length; i++) {
                if ((i === indices.length) || ((splitPositions.length > 65533) && splitIndices.length % 3 === 0)) {
                    if (splitPositions.length > 0) {
                        var shape = {
                            positions: splitPositions,
                            indices: splitIndices
                        };

                        if (textureCoords) {
                            shape.textureCoords = splitTexCoords;
                        }

                        if (outlineIndices) {
                            var splitOutline = [];
                            for (var j = 0; j < outlineIndices.length; j++) {
                                originalIndex = outlineIndices[j];
                                mappedIndex = indexMap[originalIndex];
                                if (mappedIndex) {
                                    splitOutline.push(indexMap[outlineIndices[j]]);
                                }
                            }

                            shape.outlineIndices = splitOutline;
                        }

                        result.push(shape);
                    }

                    if (i === indices.length) {
                        break;
                    }

                    splitPositions = [];
                    splitIndices = [];
                    indexMap = [];
                }

                originalIndex = indices[i];
                mappedIndex = indexMap[originalIndex];

                if (!mappedIndex) {
                    mappedIndex = splitPositions.length;
                    indexMap[originalIndex] = mappedIndex;

                    splitPositions.push(positions[originalIndex]);

                    if (textureCoords) {
                        splitTexCoords.push(textureCoords[originalIndex]);
                    }
                }

                splitIndices.push(mappedIndex);
            }

            return result;
        };

        return TriangleMesh;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports ViewControlsLayer
 */
define('layer/ViewControlsLayer',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../layer/Layer',
        '../geom/Location',
        '../util/Logger',
        '../util/Offset',
        '../shapes/ScreenImage',
        '../geom/Vec2',
        '../util/WWUtil'
    ],
    function (Angle,
              ArgumentError,
              Layer,
              Location,
              Logger,
              Offset,
              ScreenImage,
              Vec2,
              WWUtil) {
        "use strict";

        /**
         * Constructs a view controls layer.
         * @alias ViewControlsLayer
         * @constructor
         * @augments {WorldWindow}
         * @classdesc Displays and manages view controls.
         * @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
         * This layer may not be associated with more than one WorldWindow. Each WorldWindow must have it's own
         * instance of this layer if each window is to have view controls.
         *
         * <p>
         *     Placement of the controls within the WorldWindow is defined by the
         *     [placement]{@link ViewControlsLayer#placement} and [alignment]{@link ViewControlsLayer#alignment}
         *     properties. The default values of these properties place the view controls at the lower left corner
         *     of the WorldWindow. The placement property specifies the overall position of the controls within the
         *     WorldWindow. The alignment property specifies the alignment of the controls collection relative to
         *     the placement position. Some common combinations are:
         *     <table>
         *         <tr>
         *             <th>Location</th>
         *             <th>Placement</th>
         *             <th>Alignment</th>
         *         </tr>
         *         <tr>
         *             <td>Bottom Left</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0</td>
         *         </tr>
         *         <tr>
         *             <td>Top Right</td>
         *             <td>WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 1</td>
         *             <td>WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 1</td>
         *         </tr>
         *         <tr>
         *             <td>Top Left</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 1</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 1</td>
         *         </tr>
         *         <tr>
         *             <td>Bottom Center</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0</td>
         *             <td>WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0</td>
         *         </tr>
         *         <tr>
         *             <td>Southeast</td>
         *             <td>WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 0.25</td>
         *             <td>WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, 0.5</td>
         *         </tr>
         *     </table>
         * </p>
         * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
         */
        var ViewControlsLayer = function (worldWindow) {
            if (!worldWindow) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ViewControlsLayer", "constructor", "missingWorldWindow"));
            }

            Layer.call(this, "View Controls");

            /**
             * The WorldWindow associated with this layer.
             * @type {WorldWindow}
             * @readonly
             */
            this.wwd = worldWindow;

            /**
             * An {@link Offset} indicating where to place the controls on the screen.
             * @type {Offset}
             * @default The lower left corner of the window.
             */
            this.placement = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0);

            /**
             * An {@link Offset} indicating the alignment of the control collection relative to the
             * [placement position]{@link ViewControlsLayer#placement}. A value of
             * {WorldWind.FRACTION, 0, WorldWind.Fraction 0} places the bottom left corner of the control collection
             * at the placement position.
             * @type {Offset}
             * @default The lower left corner of the control collection.
             */
            this.alignment = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0);

            /**
             * The incremental vertical exaggeration to apply each cycle.
             * @type {Number}
             * @default 0.01
             */
            this.exaggerationIncrement = 0.01;

            /**
             * The incremental amount to increase or decrease the eye distance (for zoom) each cycle.
             * @type {Number}
             * @default 0.04 (4%)
             */
            this.zoomIncrement = 0.04;

            /**
             * The incremental amount to increase or decrease the heading each cycle, in degrees.
             * @type {Number}
             * @default 1.0
             */
            this.headingIncrement = 1.0;

            /**
             * The incremental amount to increase or decrease the tilt each cycle, in degrees.
             * @type {Number}
             */
            this.tiltIncrement = 1.0;

            /**
             * The incremental amount to narrow or widen the field of view each cycle, in degrees.
             * @type {Number}
             * @default 0.1
             */
            this.fieldOfViewIncrement = 0.1;

            /**
             * The scale factor governing the pan speed. Increased values cause faster panning.
             * @type {Number}
             * @default 0.001
             */
            this.panIncrement = 0.001;

            // These are documented in their property accessors below.
            this._inactiveOpacity = 0.5;
            this._activeOpacity = 1.0;

            // Set the screen and image offsets of each control to the lower left corner.
            var screenOffset = new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0),
                imagePath = WorldWind.configuration.baseUrl + "images/view/";

            // These controls are all internal and intentionally not documented.
            this.panControl = new ScreenImage(screenOffset.clone(), imagePath + "view-pan-64x64.png");
            this.zoomInControl = new ScreenImage(screenOffset.clone(), imagePath + "view-zoom-in-32x32.png");
            this.zoomOutControl = new ScreenImage(screenOffset.clone(), imagePath + "view-zoom-out-32x32.png");
            this.headingLeftControl = new ScreenImage(screenOffset.clone(), imagePath + "view-heading-left-32x32.png");
            this.headingRightControl = new ScreenImage(screenOffset.clone(), imagePath + "view-heading-right-32x32.png");
            this.tiltUpControl = new ScreenImage(screenOffset.clone(), imagePath + "view-pitch-up-32x32.png");
            this.tiltDownControl = new ScreenImage(screenOffset.clone(), imagePath + "view-pitch-down-32x32.png");
            this.exaggerationUpControl = new ScreenImage(screenOffset.clone(), imagePath + "view-elevation-up-32x32.png");
            this.exaggerationDownControl = new ScreenImage(screenOffset.clone(), imagePath + "view-elevation-down-32x32.png");
            this.fovNarrowControl = new ScreenImage(screenOffset.clone(), imagePath + "view-fov-narrow-32x32.png");
            this.fovWideControl = new ScreenImage(screenOffset.clone(), imagePath + "view-fov-wide-32x32.png");

            // Disable the FOV controls by default.
            this.fovNarrowControl.enabled = false;
            this.fovWideControl.enabled = false;

            // Put the controls in an array so we can easily apply bulk operations.
            this.controls = [
                this.panControl,
                this.zoomInControl,
                this.zoomOutControl,
                this.headingLeftControl,
                this.headingRightControl,
                this.tiltUpControl,
                this.tiltDownControl,
                this.exaggerationUpControl,
                this.exaggerationDownControl,
                this.fovNarrowControl,
                this.fovWideControl
            ];

            // Set the default alignment and opacity for each control.
            for (var i = 0; i < this.controls.length; i++) {
                this.controls[i].imageOffset = screenOffset.clone();
                this.controls[i].opacity = this._inactiveOpacity;
                if (this.controls[i] === this.panControl) {
                    this.controls[i].size = 64;
                } else {
                    this.controls[i].size = 32;
                }
            }

            // Internal variable to keep track of pan control center for use during interaction.
            this.panControlCenter = new Vec2(0, 0);

            // Internal variable to indicate whether the device is a touch device. Set to false until a touch event
            // occurs.
            this.isTouchDevice = false;

            // No picking for this layer. It performs its own picking.
            this.pickEnabled = false;

            // Establish event handlers.
            this.setupInteraction();
        };

        ViewControlsLayer.prototype = Object.create(Layer.prototype);

        Object.defineProperties(ViewControlsLayer.prototype, {
            /**
             * Indicates whether to display the pan control.
             * @type {Boolean}
             * @default true
             * @memberof ViewControlsLayer.prototype
             */
            showPanControl: {
                get: function () {
                    return this.panControl.enabled;
                },
                set: function (value) {
                    this.panControl.enabled = value;
                }
            },

            /**
             * Indicates whether to display the zoom control.
             * @type {Boolean}
             * @default true
             * @memberof ViewControlsLayer.prototype
             */
            showZoomControl: {
                get: function () {
                    return this.zoomInControl.enabled;
                },
                set: function (value) {
                    this.zoomInControl.enabled = value;
                    this.zoomOutControl.enabled = value;
                }
            },

            /**
             * Indicates whether to display the heading control.
             * @type {Boolean}
             * @default true
             * @memberof ViewControlsLayer.prototype
             */
            showHeadingControl: {
                get: function () {
                    return this.headingLeftControl.enabled;
                },
                set: function (value) {
                    this.headingLeftControl.enabled = value;
                    this.headingRightControl.enabled = value;
                }
            },

            /**
             * Indicates whether to display the tilt control.
             * @type {Boolean}
             * @default true
             * @memberof ViewControlsLayer.prototype
             */
            showTiltControl: {
                get: function () {
                    return this.tiltUpControl.enabled;
                },
                set: function (value) {
                    this.tiltUpControl.enabled = value;
                    this.tiltDownControl.enabled = value;
                }
            },

            /**
             * Indicates whether to display the vertical exaggeration control.
             * @type {Boolean}
             * @default true
             * @memberof ViewControlsLayer.prototype
             */
            showExaggerationControl: {
                get: function () {
                    return this.exaggerationUpControl.enabled;
                },
                set: function (value) {
                    this.exaggerationUpControl.enabled = value;
                    this.exaggerationDownControl.enabled = value;
                }
            },

            /**
             * Indicates whether to display the field of view control.
             * @type {Boolean}
             * @default false
             * @memberof ViewControlsLayer.prototype
             */
            showFieldOfViewControl: {
                get: function () {
                    return this.fovNarrowControl.enabled;
                },
                set: function (value) {
                    this.fovNarrowControl.enabled = value;
                    this.fovWideControl.enabled = value;
                }
            },

            /**
             * The opacity of the controls when they are not in use. The opacity should be a value between 0 and 1,
             * with 1 indicating fully opaque.
             * @type {Number}
             * @default 0.5
             * @memberof ViewControlsLayer.prototype
             */
            inactiveOpacity: {
                get: function () {
                    return this._inactiveOpacity;
                },
                set: function (value) {
                    this._inactiveOpacity = value;
                    for (var i = 0; i < this.controls.length; i++) {
                        this.controls[i].opacity = value;
                    }
                }
            },

            /**
             * The opacity of the controls when they are in use. The opacity should be a value between 0 and 1,
             * with 1 indicating fully opaque.
             * @type {Number}
             * @default 1
             * @memberof ViewControlsLayer.prototype
             */
            activeOpacity: {
                get: function () {
                    return this._activeOpacity;
                },
                set: function (value) {
                    this._activeOpacity = value;
                    for (var i = 0; i < this.controls.length; i++) {
                        this.controls[i].opacity = value;
                    }
                }
            }
        });

        // Documented in superclass.
        ViewControlsLayer.prototype.doRender = function (dc) {
            var controlPanelWidth = 0, controlPanelHeight = 64,
                panelOffset, screenOffset,
                x, y;

            this.inCurrentFrame = false; // to track whether any control is displayed this frame

            // Determine the dimensions of the control panel and whether any control is displayed.
            if (this.showPanControl) {
                controlPanelWidth += this.panControl.size;
                this.inCurrentFrame = true;
            }
            if (this.showZoomControl) {
                controlPanelWidth += this.zoomInControl.size;
                this.inCurrentFrame = true;
            }
            if (this.showHeadingControl) {
                controlPanelWidth += this.headingLeftControl.size;
                this.inCurrentFrame = true;
            }
            if (this.showTiltControl) {
                controlPanelWidth += this.tiltDownControl.size;
                this.inCurrentFrame = true;
            }
            if (this.showExaggerationControl) {
                controlPanelWidth += this.exaggerationDownControl.size;
                this.inCurrentFrame = true;
            }
            if (this.showFieldOfViewControl) {
                controlPanelWidth += this.fovNarrowControl.size;
                this.inCurrentFrame = true;
            }

            // Determine the lower-left corner position of the control collection.
            screenOffset = this.placement.offsetForSize(dc.navigatorState.viewport.width,
                dc.navigatorState.viewport.height);
            panelOffset = this.alignment.offsetForSize(controlPanelWidth, controlPanelHeight);
            x = screenOffset[0] - panelOffset[0];
            y = screenOffset[1] - panelOffset[1];

            // Determine the control positions and render the controls.

            if (this.showPanControl) {
                this.panControl.screenOffset.x = x;
                this.panControl.screenOffset.y = y;
                this.panControl.render(dc);
                this.panControlCenter[0] = x + this.panControl.size / 2;
                this.panControlCenter[1] = y + this.panControl.size / 2;
                x += this.panControl.size;
            }

            if (this.showZoomControl) {
                this.zoomOutControl.screenOffset.x = x;
                this.zoomOutControl.screenOffset.y = y;
                this.zoomInControl.screenOffset.x = x;
                this.zoomInControl.screenOffset.y = y + this.zoomOutControl.size;
                this.zoomOutControl.render(dc);
                this.zoomInControl.render(dc);
                x += this.zoomOutControl.size;
            }

            if (this.showHeadingControl) {
                this.headingRightControl.screenOffset.x = x;
                this.headingRightControl.screenOffset.y = y;
                this.headingLeftControl.screenOffset.x = x;
                this.headingLeftControl.screenOffset.y = y + this.headingLeftControl.size;
                this.headingRightControl.render(dc);
                this.headingLeftControl.render(dc);
                x += this.headingLeftControl.size;
            }

            if (this.showTiltControl) {
                this.tiltDownControl.screenOffset.x = x;
                this.tiltDownControl.screenOffset.y = y;
                this.tiltUpControl.screenOffset.x = x;
                this.tiltUpControl.screenOffset.y = y + this.tiltDownControl.size;
                this.tiltDownControl.render(dc);
                this.tiltUpControl.render(dc);
                x += this.tiltDownControl.size;
            }

            if (this.showExaggerationControl) {
                this.exaggerationDownControl.screenOffset.x = x;
                this.exaggerationDownControl.screenOffset.y = y;
                this.exaggerationUpControl.screenOffset.x = x;
                this.exaggerationUpControl.screenOffset.y = y + this.exaggerationDownControl.size;
                this.exaggerationUpControl.render(dc);
                this.exaggerationDownControl.render(dc);
                x += this.exaggerationDownControl.size;
            }

            if (this.showFieldOfViewControl) {
                this.fovNarrowControl.screenOffset.x = x;
                this.fovNarrowControl.screenOffset.y = y;
                this.fovWideControl.screenOffset.x = x;
                this.fovWideControl.screenOffset.y = y + this.fovNarrowControl.size;
                this.fovNarrowControl.render(dc);
                this.fovWideControl.render(dc);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.setupInteraction = function () {
            var wwd = this.wwd,
                thisLayer = this;

            var handleMouseEvent = function (e) {
                if (!thisLayer.enabled) {
                    return;
                }
                // Prevent handling of simulated mouse events on touch devices.
                if (thisLayer.isTouchDevice) {
                    return;
                }

                var topObject, operation;

                // Turn off any highlight. If a control is in use it will be highlighted later.
                if (thisLayer.highlightedControl) {
                    thisLayer.highlight(thisLayer.highlightedControl, false);
                    thisLayer.wwd.redraw();
                }

                // Terminate the active operation when the mouse button goes up.
                if (e.type && (e.type === "mouseup" && e.which === 1) && thisLayer.activeControl) {
                    thisLayer.activeControl = null;
                    thisLayer.activeOperation = null;
                    e.preventDefault();
                } else {
                    // Perform the active operation, or determine it and then perform it.
                    if (thisLayer.activeOperation) {
                        thisLayer.activeOperation.call(thisLayer, e, null);
                        e.preventDefault();
                    } else {
                        topObject = thisLayer.pickControl(wwd.canvasCoordinates(e.clientX, e.clientY));
                        operation = thisLayer.determineOperation(e, topObject);
                        if (operation) {
                            operation.call(thisLayer, e, topObject);
                        }
                    }

                    // Determine and display the new highlight state.
                    thisLayer.handleHighlight(e, topObject);
                    thisLayer.wwd.redraw();
                }

            };

            // Add the mouse listeners.
            wwd.addEventListener("mousedown", handleMouseEvent);
            wwd.addEventListener("mouseup", handleMouseEvent);
            wwd.addEventListener("mousemove", handleMouseEvent);
            window.addEventListener("mouseup", handleMouseEvent);
            window.addEventListener("mousemove", handleMouseEvent);

            var handleTouchEvent = function (e) {
                this.isTouchDevice = true;

                if (!thisLayer.enabled) {
                    return;
                }

                // Turn off any highlight. If a button is in use it will be highlighted later.
                if (thisLayer.highlightedControl) {
                    thisLayer.highlight(thisLayer.highlightedControl, false);
                    thisLayer.wwd.redraw();
                }

                // Terminate the active operation when the touch ends.
                if (e.type && (e.type === "touchend" || e.type === "touchcancel")) {
                    if (thisLayer.activeControl && thisLayer.isCurrentTouch(e)) {
                        thisLayer.activeControl = null;
                        thisLayer.activeOperation = null;
                        e.preventDefault();
                    }
                } else {
                    // Perform the active operation, or determine it and then perform it.
                    if (thisLayer.activeOperation) {
                        thisLayer.activeOperation.call(thisLayer, e, null);
                        e.preventDefault();
                    } else {
                        var topObject,
                            touch = e.changedTouches.item(0),
                            operation;

                        topObject = thisLayer.pickControl(wwd.canvasCoordinates(touch.clientX, touch.clientY));
                        operation = thisLayer.determineOperation(e, topObject);
                        if (operation) {
                            operation.call(thisLayer, e, topObject);
                        }
                    }
                }

                // Determine new highlight state.
                thisLayer.handleHighlight(e, topObject);
                thisLayer.wwd.redraw();
            };

            wwd.addEventListener("touchstart", handleTouchEvent);
            wwd.addEventListener("touchend", handleTouchEvent);
            wwd.addEventListener("touchcancel", handleTouchEvent);
            wwd.addEventListener("touchmove", handleTouchEvent);
        };

        // Intentionally not documented. Determines whether a picked object is a view control.
        ViewControlsLayer.prototype.isControl = function (controlCandidate) {
            for (var i = 0; i < this.controls.length; i++) {
                if (this.controls[i] == controlCandidate) {
                    return true;
                }
            }
            return false;
        };

        ViewControlsLayer.prototype.pickControl = function (pickPoint) {
            var x = pickPoint[0], y = this.wwd.canvas.height - pickPoint[1],
                control;

            for (var i = 0; i < this.controls.length; i++) {
                control = this.controls[i];

                if (control.enabled) {
                    if (x >= control.screenOffset.x && x <= (control.screenOffset.x + control.size)
                        && y >= control.screenOffset.y && y <= (control.screenOffset.y + control.size)) {
                        return control;
                    }
                }
            }

            return null;
        };

        // Intentionally not documented. Determines which operation to perform from the picked object.
        ViewControlsLayer.prototype.determineOperation = function (e, topObject) {
            var operation = null;

            if (topObject && (topObject instanceof ScreenImage)) {
                if (topObject === this.panControl) {
                    operation = this.handlePan;
                } else if (topObject === this.zoomInControl
                    || topObject === this.zoomOutControl) {
                    operation = this.handleZoom;
                } else if (topObject === this.headingLeftControl
                    || topObject === this.headingRightControl) {
                    operation = this.handleHeading;
                } else if (topObject === this.tiltUpControl
                    || topObject === this.tiltDownControl) {
                    operation = this.handleTilt;
                } else if (topObject === this.exaggerationUpControl
                    || topObject === this.exaggerationDownControl) {
                    operation = this.handleExaggeration;
                } else if (topObject === this.fovNarrowControl
                    || topObject === this.fovWideControl) {
                    operation = this.handleFov;
                }
            }

            return operation;
        };

        // Intentionally not documented. Determines whether an event represents the touch of the active operation.
        ViewControlsLayer.prototype.isCurrentTouch = function (e) {
            for (var i = 0; i < e.changedTouches.length; i++) {
                if (e.changedTouches.item(i).identifier === this.currentTouchId) {
                    return true;
                }
            }

            return false;
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handlePan = function (e, control) {
            // Capture the current position.
            if (e.type === "mousedown" || e.type === "mousemove") {
                this.currentEventPoint = this.wwd.canvasCoordinates(e.clientX, e.clientY);
            } else if (e.type === "touchstart" || e.type === "touchmove") {
                var touch = e.changedTouches.item(0);
                this.currentEventPoint = this.wwd.canvasCoordinates(touch.clientX, touch.clientY);
            }

            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handlePan;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setLookAtLocation = function () {
                    if (thisLayer.activeControl) {
                        var dx = thisLayer.panControlCenter[0] - thisLayer.currentEventPoint[0],
                            dy = thisLayer.panControlCenter[1]
                                - (thisLayer.wwd.viewport.height - thisLayer.currentEventPoint[1]),
                            oldLat = thisLayer.wwd.navigator.lookAtLocation.latitude,
                            oldLon = thisLayer.wwd.navigator.lookAtLocation.longitude,
                        // Scale the increment by a constant and the relative distance of the eye to the surface.
                            scale = thisLayer.panIncrement
                                * (thisLayer.wwd.navigator.range / thisLayer.wwd.globe.radiusAt(oldLat, oldLon)),
                            heading = thisLayer.wwd.navigator.heading + (Math.atan2(dx, dy) * Angle.RADIANS_TO_DEGREES),
                            distance = scale * Math.sqrt(dx * dx + dy * dy);

                        Location.greatCircleLocation(thisLayer.wwd.navigator.lookAtLocation, heading, -distance,
                            thisLayer.wwd.navigator.lookAtLocation);
                        thisLayer.wwd.redraw();
                        setTimeout(setLookAtLocation, 50);
                    }
                };
                setTimeout(setLookAtLocation, 50);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handleZoom = function (e, control) {
            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handleZoom;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setRange = function () {
                    if (thisLayer.activeControl) {
                        if (thisLayer.activeControl === thisLayer.zoomInControl) {
                            thisLayer.wwd.navigator.range *= (1 - thisLayer.zoomIncrement);
                        } else if (thisLayer.activeControl === thisLayer.zoomOutControl) {
                            thisLayer.wwd.navigator.range *= (1 + thisLayer.zoomIncrement);
                        }
                        thisLayer.wwd.redraw();
                        setTimeout(setRange, 50);
                    }
                };
                setTimeout(setRange, 50);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handleHeading = function (e, control) {
            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handleHeading;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setRange = function () {
                    if (thisLayer.activeControl) {
                        if (thisLayer.activeControl === thisLayer.headingLeftControl) {
                            thisLayer.wwd.navigator.heading += thisLayer.headingIncrement;
                        } else if (thisLayer.activeControl === thisLayer.headingRightControl) {
                            thisLayer.wwd.navigator.heading -= thisLayer.headingIncrement;
                        }
                        thisLayer.wwd.redraw();
                        setTimeout(setRange, 50);
                    }
                };
                setTimeout(setRange, 50);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handleTilt = function (e, control) {
            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handleTilt;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setRange = function () {
                    if (thisLayer.activeControl) {
                        if (thisLayer.activeControl === thisLayer.tiltUpControl) {
                            thisLayer.wwd.navigator.tilt =
                                Math.max(0, thisLayer.wwd.navigator.tilt - thisLayer.tiltIncrement);
                        } else if (thisLayer.activeControl === thisLayer.tiltDownControl) {
                            thisLayer.wwd.navigator.tilt =
                                Math.min(90, thisLayer.wwd.navigator.tilt + thisLayer.tiltIncrement);
                        }
                        thisLayer.wwd.redraw();
                        setTimeout(setRange, 50);
                    }
                };
                setTimeout(setRange, 50);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handleExaggeration = function (e, control) {
            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handleExaggeration;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setExaggeration = function () {
                    if (thisLayer.activeControl) {
                        if (thisLayer.activeControl === thisLayer.exaggerationUpControl) {
                            thisLayer.wwd.verticalExaggeration += thisLayer.exaggerationIncrement;
                        } else if (thisLayer.activeControl === thisLayer.exaggerationDownControl) {
                            thisLayer.wwd.verticalExaggeration =
                                Math.max(1, thisLayer.wwd.verticalExaggeration - thisLayer.exaggerationIncrement);
                        }
                        thisLayer.wwd.redraw();
                        setTimeout(setExaggeration, 50);
                    }
                };
                setTimeout(setExaggeration, 50);
            }
        };

        // Intentionally not documented.
        ViewControlsLayer.prototype.handleFov = function (e, control) {
            // Start an operation on left button down or touch start.
            if ((e.type === "mousedown" && e.which === 1) || (e.type === "touchstart")) {
                this.activeControl = control;
                this.activeOperation = this.handleFov;
                e.preventDefault();

                if (e.type === "touchstart") {
                    this.currentTouchId = e.changedTouches.item(0).identifier; // capture the touch identifier
                }

                // This function is called by the timer to perform the operation.
                var thisLayer = this; // capture 'this' for use in the function
                var setRange = function () {
                    if (thisLayer.activeControl) {
                        if (thisLayer.activeControl === thisLayer.fovWideControl) {
                            thisLayer.wwd.navigator.fieldOfView =
                                Math.max(90, thisLayer.wwd.navigator.fieldOfView + thisLayer.fieldOfViewIncrement);
                        } else if (thisLayer.activeControl === thisLayer.fovNarrowControl) {
                            thisLayer.wwd.navigator.fieldOfView =
                                Math.min(0, thisLayer.wwd.navigator.fieldOfView - thisLayer.fieldOfViewIncrement);
                        }
                        thisLayer.wwd.redraw();
                        setTimeout(setRange, 50);
                    }
                };
                setTimeout(setRange, 50);
            }
        };

        // Intentionally not documented. Determines whether to highlight a control.
        ViewControlsLayer.prototype.handleHighlight = function (e, topObject) {
            if (this.activeControl) {
                // Highlight the active control.
                this.highlight(this.activeControl, true);
            } else if (topObject && this.isControl(topObject)) {
                // Highlight the control under the cursor or finger.
                this.highlight(topObject, true);
            }
        };

        // Intentionally not documented. Sets the highlight state of a control.
        ViewControlsLayer.prototype.highlight = function (control, tf) {
            control.opacity = tf ? this._activeOpacity : this._inactiveOpacity;

            if (tf) {
                this.highlightedControl = control;
            } else {
                this.highlightedControl = null;
            }
        };

        return ViewControlsLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WcsTileUrlBuilder
 */
define('util/WcsTileUrlBuilder',[
        '../error/ArgumentError',
        '../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a WCS tile URL builder.
         * @alias WcsTileUrlBuilder
         * @constructor
         * @classdesc Provides a factory to create URLs for WCS Get Coverage requests.
         * @param {String} serviceAddress The address of the WCS server.
         * @param {String} coverageName The name of the coverage to retrieve.
         * @param {String} wcsVersion The version of the WCS server. May be null, in which case version 1.0.0 is
         * assumed.
         * @constructor
         */
        var WcsTileUrlBuilder = function (serviceAddress, coverageName, wcsVersion) {
            if (!serviceAddress || (serviceAddress.length === 0)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsTileUrlBuilder", "constructor",
                        "The WCS service address is missing."));
            }

            if (!coverageName || (coverageName.length === 0)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsTileUrlBuilder", "constructor",
                        "The WCS coverage name is missing."));
            }

            /**
             * The address of the WCS server.
             * @type {String}
             */
            this.serviceAddress = serviceAddress;

            /**
             * The name of the coverage to retrieve.
             * @type {String}
             */
            this.coverageName = coverageName;

            /**
             * The WCS version to specify when requesting resources.
             * @type {String}
             * @default 1.0.0
             */
            this.wcsVersion = (wcsVersion && wcsVersion.length > 0) ? wcsVersion : "1.0.0";

            /**
             * The coordinate reference system to use when requesting coverages.
             * @type {String}
             * @default EPSG:4326
             */
            this.crs = "EPSG:4326";
        };

        /**
         * Creates the URL string for a WCS Get Coverage request.
         * @param {Tile} tile The tile for which to create the URL.
         * @param {String} coverageFormat The coverage format to request.
         * @throws {ArgumentError} If the specified tile or coverage format are null or undefined.
         */
        WcsTileUrlBuilder.prototype.urlForTile = function (tile, coverageFormat) {

            if (!tile) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsUrlBuilder", "urlForTile", "missingTile"));
            }

            if (!coverageFormat) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsUrlBuilder", "urlForTile",
                        "The coverage format is null or undefined."));
            }

            var sector = tile.sector;

            var sb = WcsTileUrlBuilder.fixGetCoverageString(this.serviceAddress);

            if (sb.search(/service=wcs/i) < 0) {
                sb = sb + "service=WCS";
            }

            sb = sb + "&request=GetCoverage";
            sb = sb + "&version=" + this.wcsVersion;
            sb = sb + "&coverage=" + this.coverageName;
            sb = sb + "&format=" + coverageFormat;
            sb = sb + "&width=" + tile.tileWidth;
            sb = sb + "&height=" + tile.tileHeight;

            sb = sb + "&crs=" + this.crs;
            sb = sb + "&bbox=";
            sb = sb + sector.minLongitude + "," + sector.minLatitude + ",";
            sb = sb + sector.maxLongitude + "," +sector. maxLatitude;

            sb = sb.replace(" ", "%20");

            return sb;
        };

        // Intentionally not documented.
        WcsTileUrlBuilder.fixGetCoverageString = function (serviceAddress) {
            if (!serviceAddress) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsTileUrlBuilder", "fixGetCoverageString",
                        "The specified service address is null or undefined."));
            }

            var index = serviceAddress.indexOf("?");

            if (index < 0) { // if string contains no question mark
                serviceAddress = serviceAddress + "?"; // add one
            } else if (index !== serviceAddress.length - 1) { // else if question mark not at end of string
                index = serviceAddress.search(/&$/);
                if (index < 0) {
                    serviceAddress = serviceAddress + "&"; // add a parameter separator
                }
            }

            return serviceAddress;
        };

        return WcsTileUrlBuilder;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsLanguageString
 */
define('ogc/wmts/OwsLanguageString',[
        '../../error/ArgumentError',
        '../../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs an OWS Constraint instance from an XML DOM.
         * @alias OwsLanguageString
         * @constructor
         * @classdesc Represents an OWS LanguageString element of an OGC document.
         * This object holds as properties all the fields specified in the OWS LanguageString definition.
         * Fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "value".
         * @param {Element} element An XML DOM element representing the OWS LanguageString element.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsLanguageString = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LanguageString", "constructor", "missingDomElement"));
            }

            /**
             * The text content of the element.
             * @type {string}
             */
            this.value = element.textContent;

            /**
             * Identifier of a language used by the data(set) contents. This language identifier shall be as specified
             * in IETF RFC 4646. When this element is omitted, the language used is not identified.
             * @type {string}
             */
            this.lang;

            var lang = element.getAttribute("lang");
            if (lang) {
                this.lang = lang;
            }
        };

        return OwsLanguageString;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsConstraint
 */
define('ogc/wmts/OwsConstraint',[
        '../../error/ArgumentError',
        '../../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs an OWS Constraint instance from an XML DOM.
         * @alias OwsConstraint
         * @constructor
         * @classdesc Represents an OWS Constraint element of an OGC capabilities document.
         * This object holds as properties all the fields specified in the OWS Constraint definition.
         * Fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "operation".
         * @param {Element} element An XML DOM element representing the OWS Constraint element.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsConstraint = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsConstraint", "constructor", "missingDomElement"));
            }

            this.name = element.getAttribute("name");

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "AllowedValues") {
                    this.allowedValues = this.allowedValues || [];

                    var childrenValues = child.children || child.childNodes;
                    for (var cc = 0; cc < childrenValues.length; cc++) {
                        if (childrenValues[cc].localName === "Value") {
                            this.allowedValues.push(childrenValues[cc].textContent);
                        }
                    }
                } else if (child.localName === "AnyValue") {
                    this.anyValue = true;
                } else if (child.localName === "NoValues") {
                    this.noValues = true;
                }
                // TODO: ValuesReference
            }

        };

        return OwsConstraint;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsOperationsMetadata
 */
define('ogc/wmts/OwsOperationsMetadata',[
        '../../error/ArgumentError',
        '../../util/Logger',
        '../../ogc/wmts/OwsConstraint'
    ],
    function (ArgumentError,
              Logger,
              OwsConstraint) {
        "use strict";

        /**
         * Constructs an OWS Operations Metadata instance from an XML DOM.
         * @alias OwsOperationsMetadata
         * @constructor
         * @classdesc Represents an OWS Operations Metadata section of an OGC capabilities document.
         * This object holds as properties all the fields specified in the OWS Operations Metadata section.
         * Most fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "operations".
         * @param {Element} element An XML DOM element representing the OWS Service Provider section.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsOperationsMetadata = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsOperationsMetadata", "constructor", "missingDomElement"));
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Operation") {
                    this.operation = this.operation || [];
                    this.operation.push(OwsOperationsMetadata.assembleOperation(child));
                }
                // TODO: Parameter, Constraint, ExtendedCapabilities
            }
        };

        /**
         * Attempts to find the first OwsOperationsMetadata object named GetCapabilities.
         * @returns {OwsOperationsMetadata} if a matching OwsOperationsMetadata object is found, otherwise null.
         */
        OwsOperationsMetadata.prototype.getGetCapabilities = function () {
            return this.getOperationMetadataByName("GetCapabilities");
        };

        /**
         * Attempts to find the first OwsOperationsMetadata object named GetTile.
         * @returns {OwsOperationsMetadata} if a matching OwsOperationsMetadata object is found, otherwise null.
         */
        OwsOperationsMetadata.prototype.getGetTile = function () {
            return this.getOperationMetadataByName("GetTile");
        };

        /**
         * Searches for the OWS Operations Metadata objects for the operation with a name matching the  provided name.
         * Returns the first successful match.
         * @returns {OwsOperationsMetadata} of a matching name or null if none was found
         */
        OwsOperationsMetadata.prototype.getOperationMetadataByName = function (name) {
            if (!name) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsOperationsMetadata", "getOperationsMetadataByName", "missingName"));
            }

            for (var i = 0; i < this.operation.length; i++) {
                if (this.operation[i].name === name) {
                    return this.operation[i];
                }
            }

            return null;
        };

        OwsOperationsMetadata.assembleOperation = function (element) {
            var operation = {};

            operation.name = element.getAttribute("name");

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "DCP") {
                    operation.dcp = operation.dcp || [];
                    operation.dcp.push(OwsOperationsMetadata.assembleDcp(child));
                }
                // TODO: Parameter, Constraint, Metadata
            }

            return operation;
        };

        OwsOperationsMetadata.assembleDcp = function (element) {
            var dcp = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "HTTP") {
                    var httpMethods = child.children || child.childNodes;
                    for (var c2 = 0; c2 < httpMethods.length; c2++) {
                        var httpMethod = httpMethods[c2];

                        if (httpMethod.localName === "Get") {
                            dcp.getMethods = dcp.getMethods || [];
                            dcp.getMethods.push(OwsOperationsMetadata.assembleMethod(httpMethod));
                        } else if (httpMethod.localName === "Post") {
                            dcp.postMethods = dcp.postMethods || [];
                            dcp.postMethods.push(OwsOperationsMetadata.assembleMethod(httpMethod));
                        }
                    }
                }
            }

            return dcp;
        };

        OwsOperationsMetadata.assembleMethod = function (element) {
            var result = {};

            result.url = element.getAttribute("xlink:href");

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Constraint") {
                    result.constraint = result.constraint || [];
                    result.constraint.push(new OwsConstraint(child));
                }
            }

            return result;
        };

        return OwsOperationsMetadata;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsDescription
 */
define('ogc/wmts/OwsDescription',[
        '../../error/ArgumentError',
        '../../util/Logger',
        '../../ogc/wmts/OwsLanguageString'
    ],

    function (ArgumentError,
              Logger,
              OwsLanguageString) {
        "use strict";

        /**
         * Constructs an OWS Description instance from an XML DOM.
         * @alias OwsDescription
         * @constructor
         * @classdesc Represents an OWS Description element of an OGC document.
         * This object holds as properties all the fields specified in the OWS Description definition.
         * Fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "value".
         * @param {Element} element An XML DOM element representing the OWS Description element.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsDescription = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsDescription", "assembleDescriptions", "missingDomElement"));
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Title") {
                    this.titles = this.titles || [];
                    this.titles.push(new OwsLanguageString(child));
                } else if (child.localName === "Abstract") {
                    this.abstracts = this.abstracts || [];
                    this.abstracts.push(new OwsLanguageString(child));
                } else if (child.localName === "Keywords") {
                    this.keywords = this.keywords || [];
                    var keywords = child.children || child.childNodes;
                    for (var i = 0; i < keywords.length; i++) {
                        var keyword = keywords[i];
                        // In IE 11, child.childNodes can contain more than just Element objects, checking localName has the side effect of ensuring the correct object type.
                        if (keyword.localName === "Keyword") {
                            this.keywords.push(new OwsLanguageString(keyword));
                        }
                    }
                }
            }

        };

        return OwsDescription;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsServiceIdentification
 */
define('ogc/wmts/OwsServiceIdentification',[
        '../../error/ArgumentError',
        '../../util/Logger',
        '../../ogc/wmts/OwsDescription'
    ],
    function (ArgumentError,
              Logger,
              OwsDescription) {
        "use strict";

        /**
         * Constructs an OWS Service Identification instance from an XML DOM.
         * @alias OwsServiceIdentification
         * @constructor
         * @classdesc Represents an OWS Service Identification section of an OGC capabilities document.
         * This object holds as properties all the fields specified in the OWS Service Identification.
         * Fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "serviceType" and "title".
         * Note that fields with multiple possible values are returned as arrays, such as "titles" and "abstracts".
         * @param {Element} element An XML DOM element representing the OWS Service Identification section.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsServiceIdentification = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceIdentification", "constructor", "missingDomElement"));
            }

            OwsDescription.call(this, element);

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ServiceType") {
                    this.serviceType = child.textContent;
                } else if (child.localName === "ServiceTypeVersion") {
                    this.serviceTypeVersions = this.serviceTypeVersions || [];
                    this.serviceTypeVersions.push(child.textContent);
                } else if (child.localName === "Profile") {
                    this.profile = this.profiles || [];
                    this.profile.push(child.textContent);
                } else if (child.localName === "Fees") {
                    this.fees = child.textContent;
                } else if (child.localName === "AccessConstraints") {
                    this.accessConstraints = this.accessConstraints || [];
                    this.accessConstraints.push(child.textContent);
                }
            }
        };

        OwsServiceIdentification.prototype = Object.create(OwsDescription.prototype);

        return OwsServiceIdentification;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports OwsServiceProvider
 */
define('ogc/wmts/OwsServiceProvider',[
        '../../error/ArgumentError',
        '../../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs an OWS Service Provider instance from an XML DOM.
         * @alias OwsServiceProvider
         * @constructor
         * @classdesc Represents an OWS Service Provider section of an OGC capabilities document.
         * This object holds as properties all the fields specified in the OWS Service Provider section.
         * Fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "providerName".
         * @param {Element} element An XML DOM element representing the OWS Service Provider section.
         * @throws {ArgumentError} If the specified XML DOM element is null or undefined.
         */
        var OwsServiceProvider = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceProvider", "constructor", "missingDomElement"));
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ProviderName") {
                    this.providerName = child.textContent;
                } else if (child.localName === "ProviderSite") {
                    this.providerSiteUrl = child.getAttribute("xlink:href");
                } else if (child.localName === "ServiceContact") {
                    this.serviceContact = OwsServiceProvider.assembleServiceContact(child);
                }
            }
        };

        OwsServiceProvider.assembleServiceContact = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceProvider", "assembleServiceContact", "missingDomElement"));
            }

            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "IndividualName") {
                    result.individualName = child.textContent;
                } else if (child.localName === "PositionName") {
                    result.positionName = child.textContent;
                } else if (child.localName === "ContactInfo") {
                    result.contactInfo = OwsServiceProvider.assembleContacts(child);
                }
            }

            return result;
        }

        OwsServiceProvider.assembleContacts = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceProvider", "assembleContacts", "missingDomElement"));
            }

            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "HoursOfService") {
                    result.hoursOfService = child.textContent;
                } else if (child.localName === "ContactInstructions") {
                    result.contactInstructions = child.textContent;
                } else if (child.localName === "Phone") {
                    result.phone = OwsServiceProvider.assemblePhone(child);
                } else if (child.localName === "Address") {
                    result.address = OwsServiceProvider.assembleAddress(child);
                }
            }

            return result;
        }

        OwsServiceProvider.assemblePhone = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceProvider", "assemblePhone", "missingDomElement"));
            }

            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Voice") {
                    result.voice = child.textContent;
                } else if (child.localName === "Facsimile") {
                    result.facsimile = child.textContent;
                }
            }

            return result;
        }

        OwsServiceProvider.assembleAddress = function (element) {
            if (!element) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "OwsServiceProvider", "assembleAddress", "missingDomElement"));
            }

            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "DeliveryPoint") {
                    result.deliveryPoints = result.deliveryPoints || [];
                    result.deliveryPoints.push(child.textContent);
                } else if (child.localName === "City") {
                    result.city = child.textContent;
                } else if (child.localName === "AdministrativeArea") {
                    result.administrativeArea = child.textContent;
                } else if (child.localName === "PostalCode") {
                    result.postalCodes = result.postalCodes || [];
                    result.postalCodes.push(child.textContent);
                } else if (child.localName === "Country") {
                    result.countries = result.countries || [];
                    result.countries.push(child.textContent);
                } else if (child.localName === "ElectronicMailAddress") {
                    result.electronicMailAddresses = result.electronicMailAddresses || [];
                    result.electronicMailAddresses.push(child.textContent);
                }
            }

            return result;
        }

        return OwsServiceProvider;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WfsCapabilities
 */
define('ogc/WfsCapabilities',[
        '../error/ArgumentError',
        '../util/Logger',
        '../ogc/wmts/OwsLanguageString',
        '../ogc/wmts/OwsOperationsMetadata',
        '../ogc/wmts/OwsServiceIdentification',
        '../ogc/wmts/OwsServiceProvider'
    ],
    function (ArgumentError,
              Logger,
              OwsLanguageString,
              OwsOperationsMetadata,
              OwsServiceIdentification,
              OwsServiceProvider) {
        "use strict";

        /**
         * Constructs an WFS Capabilities instance from an XML DOM.
         * @alias WFSCapabilities
         * @constructor
         * @classdesc Represents a WFS Capabilities document. This object holds as properties all the fields
         * specified in the given WFS Capabilities document. Most fields can be accessed as properties named
         * according to their document names converted to camel case. For example, "version", "service.title",
         * "service.contactInformation.contactPersonPrimary".
         * @param {{}} xmlDom An XML DOM representing the WFS Capabilities document.
         * @throws {ArgumentError} If the specified XML DOM is null or undefined.
         */
        var WfsCapabilities = function (xmlDom) {
            if (!xmlDom) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WfsCapabilities", "constructor", "No XML DOM specified."));
            }

            this.assembleDocument(xmlDom);
        };

        WfsCapabilities.prototype.assembleDocument = function (dom) {
            var root = dom.documentElement;

            this.version = root.getAttribute("version");
            this.updateSequence = root.getAttribute("updateSequence");

            var children = root.children || root.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ServiceIdentification") {
                    this.serviceIdentification = new OwsServiceIdentification(child);
                } else if (child.localName === "ServiceProvider") {
                    this.serviceProvider = new OwsServiceProvider(child);
                } else if (child.localName === "OperationsMetadata") {
                    this.operationsMetadata = new OwsOperationsMetadata(child);
                } else if (child.localName === "FeatureTypeList") {
                    this.featureTypeList = this.assembleFeatureTypeList(child);
                } else if (child.localName === "Filter_Capabilities") {
                    this.filterCapabilities = this.assembleFilterCapabilities(child);
                }
            }
        };

        WfsCapabilities.prototype.assembleFeatureTypeList = function (element) {
            var featureTypeList = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName == "Operations") {
                    featureTypeList.operations = featureTypeList.operations || [];
                    try {
                        featureTypeList.operations = WfsCapabilities.assembleOperations(child);
                    } catch (e) {
                        Logger.logMessage(Logger.LEVEL_SEVERE, "WfsCapabilities", "constructor",
                            "Exception reading WFS operations description: " + e.message);
                    }
                } else if (child.localName == "FeatureType") {
                    featureTypeList.featureType = featureTypeList.featureType || [];
                    try {
                        featureTypeList.featureType.push(WfsCapabilities.assembleFeatureType(child));
                    } catch (e) {
                        Logger.logMessage(Logger.LEVEL_SEVERE, "WfsCapabilities", "constructor",
                            "Exception reading WFS operations description: " + e.message);
                    }
                }
            }

            return featureTypeList;
        };

        WfsCapabilities.prototype.assembleFilterCapabilities = function (element) {
            var filterCapabilities = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Conformance") {
                    filterCapabilities.conformance = WfsCapabilities.assembleConformance(child);
                } else if (child.localName === "Id_Capabilities") {
                    filterCapabilities.idCapabilities = WfsCapabilities.assembleIdCapabilities(child);
                } else if (child.localName === "Scalar_Capabilities") {
                    filterCapabilities.scalarCapabilities = WfsCapabilities.assembleScalarCapabilities(child);
                } else if (child.localName === "Spatial_Capabilities") {
                    filterCapabilities.spatialCapabilities = WfsCapabilities.assembleSpatialCapabilities(child);
                } else if (child.localName === "Functions") {
                    filterCapabilities.functions = WfsCapabilities.assembleFunctions(child);
                }
            }

            return filterCapabilities;
        };

        WfsCapabilities.assembleOperations = function (element) {
            var operations = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName == "Operation") {
                    operations.push(child.textContent);
                }
            }

            return operations;
        };

        WfsCapabilities.assembleFeatureType = function (element) {
            var featureType = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName == "Name") {
                    featureType.name = child.textContent;
                } else if (child.localName == "Title") {
                    featureType.title = child.textContent;
                } else if (child.localName == "Abstract") {
                    featureType.abstract = child.textContent;
                } else if (child.localName == "Keywords") {
                    featureType.keywords = featureType.keywords || [];
                    featureType.keywords = WfsCapabilities.assembleKeywords(child);
                } else if (child.localName == "DefaultSRS") {
                    featureType.defaultSRS = child.textContent;
                } else if (child.localName == "OtherSRS") {
                    featureType.otherSRS = featureType.otherSRS || [];
                    featureType.otherSRS.push(child.textContent);
                } else if (child.localName == "WGS84BoundingBox") {
                    featureType.wgs84BoundingBox = WfsCapabilities.assembleBoundingBox(child);
                } else if (child.localName == "DefaultCRS") {
                    featureType.defaultCRS = child.textContent;
                } else if (child.localName == "OtherCRS") {
                    featureType.otherCRS = featureType.otherCRS || [];
                    featureType.otherCRS.push(child.textContent);
                } else if (child.localName == "OutputFormats") {
                    featureType.outputFormats = WfsCapabilities.assembleOutputFormats(child);
                } else if (child.localName == "MetadataURL") {
                    featureType.metadataUrl = WfsCapabilities.assembleMetadataUrl(child);
                }
            }

            return featureType;
        };

        WfsCapabilities.assembleBoundingBox = function (element) {
            var result = {};

            var crs = element.getAttribute("crs");
            if (crs) {
                result.crs = crs;
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "LowerCorner") {
                    var lc = child.textContent.split(" ");
                    result.lowerCorner = [parseFloat(lc[0]), parseFloat(lc[1])];
                } else if (child.localName === "UpperCorner") {
                    var uc = child.textContent.split(" ");
                    result.upperCorner = [parseFloat(uc[0]), parseFloat(uc[1])];
                }
            }

            return result;
        };

        WfsCapabilities.assembleOutputFormats = function (element) {
            var outputFormats = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Format") {
                    outputFormats.push(child.textContent);
                }
            }

            return outputFormats;
        };

        WfsCapabilities.assembleMetadataUrl = function (element) {
            var metadataUrl = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];
                // TODO: This code is currently non-functional and will need upgrades when WFS functionality is addressed.
                // In IE 11, element.childNodes can contain more than just Element objects. When this code is upgraded, ensure
                // that the element objects accessed here are actually instances of Element. This is done elsewhere
                // by checking for an appropriate value in the localName attribute which has the side effect of ensuring
                // the correct object type.
                if (child.localName === "MetadataURL") {
                    metadataUrl.format = child.getAttribute("format");
                    metadataUrl.type = child.getAttribute("type");
                }
            }

            return outputFormats;
        };

        WfsCapabilities.assembleKeywords = function (element) {
            var keywords = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Keyword") {
                    keywords.push(child.textContent);
                }
            }

            return keywords;
        };

        WfsCapabilities.assembleConformance = function (element) {
            var conformance = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Constraint") {
                    var constraint;
                    constraint = WfsCapabilities.assembleConstraint(child);
                    constraint.name = child.getAttribute("name");
                    conformance.push(constraint);
                }
            }

            return conformance;
        };

        WfsCapabilities.assembleConstraint = function (element) {
            var constraint = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];
                if (child.localName === "DefaultValue") {
                    constraint.defaultValue = child.textContent;
                }
            }

            return constraint;
        };

        WfsCapabilities.assembleIdCapabilities = function (element) {
            var idCapabilities = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ResourceIdentifier") {
                    idCapabilities.resourceIdentifier = child.getAttribute("name");
                }
            }

            return idCapabilities;
        };

        WfsCapabilities.assembleScalarCapabilities = function (element) {
            var scalarCapabilities = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ComparisonOperators") {
                    scalarCapabilities.comparisonOperators = WfsCapabilities.assembleComparisonOperators(child);
                } else if (child.localName === "ArithmeticOperators") {
                    scalarCapabilities.arithmeticOperators = WfsCapabilities.assembleArithmeticOperators(child);
                }
            }

            return scalarCapabilities;
        };

        WfsCapabilities.assembleComparisonOperators = function (element) {
            var comparisonOperators = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ComparisonOperator") {
                    comparisonOperators.push(child.textContent);
                }
            }

            return comparisonOperators;
        };

        WfsCapabilities.assembleArithmeticOperators = function (element) {
            var arithmeticOperators = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Functions") {
                    arithmeticOperators.functions = WfsCapabilities.assembleArithmeticFunctions(child);
                }
            }

            return arithmeticOperators;
        };

        WfsCapabilities.assembleArithmeticFunctions = function (element) {
            var functionNames = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "FunctionNames") {
                    functionNames = WfsCapabilities.assembleFunctionNames(child);
                }
            }

            return functionNames;
        };

        WfsCapabilities.assembleFunctionNames = function (element) {
            var functionNames = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "FunctionName") {
                    var functionName = {name: child.textContent, nArgs: child.getAttribute("nArgs")};
                    functionNames.push(functionName);
                }
            }

            return functionNames;
        };

        WfsCapabilities.assembleSpatialCapabilities = function (element) {
            var spatialCapabilities = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "GeometryOperands") {
                    spatialCapabilities.geometryOperands = WfsCapabilities.assembleGeometryOperands(child);
                } else if (child.localName === "SpatialOperators") {
                    spatialCapabilities.spatialOperators = WfsCapabilities.assembleSpatialOperators(child);
                }
            }

            return spatialCapabilities;
        };

        WfsCapabilities.assembleGeometryOperands = function (element) {
            var geometryOperands = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "GeometryOperand") {
                    geometryOperands.push(child.getAttribute("name"));
                }
            }

            return geometryOperands;
        };

        WfsCapabilities.assembleSpatialOperators = function (element) {
            var spatialOperators = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "SpatialOperator") {
                    spatialOperators.push(child.getAttribute("name"));
                }
            }

            return spatialOperators;
        };

        WfsCapabilities.assembleFunctions = function (element) {
            var functions = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Function") {
                    functions.push(WfsCapabilities.assembleFunction(child));
                }
            }

            return functions;
        };

        WfsCapabilities.assembleFunction = function (element) {
            var _function = {};

            _function.name = element.getAttribute("name");
            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Returns") {
                    _function.returns = child.textContent;
                } else if (child.localName === "Arguments") {
                    _function.arguments = WfsCapabilities.assembleArguments(child);
                }
            }

            return _function;
        };

        WfsCapabilities.assembleArguments = function (element) {
            var _arguments = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Argument") {
                    _arguments.push(WfsCapabilities.assembleArgument(child));
                }
            }

            return _arguments;
        };

        WfsCapabilities.assembleArgument = function (element) {
            var argument = {};

            argument.name = element.getAttribute("name");
            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Type") {
                    argument.type = child.textContent;
                }
            }

            return argument;
        };

        return WfsCapabilities;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/WktElements',[], function () {
    //noinspection UnnecessaryLocalVariableJS
    /**
     * Map representing the available elements. Basically this is a way to overcome circular dependencies issues. They
     * might happen when there are inter dependencies among objects. It shouldn't happen in case of WKT.
     * @exports WktElements
     */
    var WktElements = {
    };

    return WktElements;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WktType
 */
define('formats/wkt/WktType',[], function () {
    /**
     * Enumerations used throughout the implementation of the WKT
     * @constructor
     * @alias WktType
     */
    var WktType = function () {};

    /**
     * Names of supported geometries.
     * @type {{LINE_STRING: string, MULTI_LINE_STRING: string, POLYGON: string, MULTI_POLYGON: string, POINT: string, MULTI_POINT: string, TRIANGLE: string, GEOMETRY_COLLECTION: string}}
     */
    WktType.SupportedGeometries = {
        LINE_STRING: 'LINESTRING',
        MULTI_LINE_STRING: 'MULTILINESTRING',
        POLYGON: 'POLYGON',
        MULTI_POLYGON: 'MULTIPOLYGON',
        POINT: 'POINT',
        MULTI_POINT: 'MULTIPOINT',
        TRIANGLE: 'TRIANGLE',
        GEOMETRY_COLLECTION: 'GEOMETRYCOLLECTION'
    };

    /**
     * Types of tokens from parsing the text.
     * @type {{LEFT_PARENTHESIS: number, COMMA: number, RIGHT_PARENTHESIS: number, NUMBER: number, TEXT: number}}
     */
    WktType.TokenType = {
        LEFT_PARENTHESIS: 0,
        COMMA: 1,
        RIGHT_PARENTHESIS: 2,
        NUMBER: 3,
        TEXT: 4
    };

    return WktType;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WKTObject
 */
define('formats/wkt/geom/WktObject',[
    '../../../geom/Location',
    '../../../geom/Position',
    '../WktElements',
    '../WktType'
], function (Location,
             Position,
             WktElements,
             WktType) {
    /**
     * THis shouldn't be initiated from outside. It is only for internal use. Every other WKT Objects are themselves
     * WktObject
     * @alias WktObject
     * @param type {String} Textual representation of the type of current object.
     * @constructor
     */
    var WktObject = function (type) {
        /**
         * Type of this object.
         * @type {WKTType}
         */
        this.type = type;

        /**
         * It is possible for the WKT object to be displayed not in 2D but in 3D.
         * @type {Boolean}
         * @private
         */
        this._is3d = false;

        /**
         * It is possible for the WKT object to be referenced using Linear referencing system. This is flag for it.
         * @type {boolean}
         * @private
         */
        this._isLrs = false;

        /**
         *
         * @type {Position[]|Location[]}
         */
        this.coordinates = [];

        /**
         * Options contains information relevant for parsing of this specific Object. Basically processed tokens, parsed
         * coordinates and amounts of parntheses used to find out whether the object was already finished.
         * @type {{coordinates: Array, leftParenthesis: number, rightParenthesis: number, tokens: Array}}
         */
        this.options = {
            coordinates: [],
            leftParenthesis: 0,
            rightParenthesis: 0,
            tokens: []
        };
    };

    /**
     * It sets the information that this object is actually represented in 3D
     */
    WktObject.prototype.set3d = function () {
        this._is3d = true;
    };

    /**
     * It sets the information that the object contain information about LRS offset.
     */
    WktObject.prototype.setLrs = function () {
        this._isLrs = true;
    };

    /**
     * Array containing latitude, longitude and potentially either altitude or LRS.
     * @coordinates {Number[]} Array containing longitude, latitude and potentially altitude of another point in the
     *  object.
     */
    WktObject.prototype.addCoordinates = function (coordinates) {
        if (this._is3d) {
            this.coordinates.push(new Position(coordinates[0], coordinates[1], coordinates[2] || 0));
        } else {
            this.coordinates.push(new Location(coordinates[0], coordinates[1]));
        }
    };

    /**
     * It is used to retrieve and create the shape or shapes associated.
     * @returns {Renderable[]} Array of renderables associated with given shape.
     */
    WktObject.prototype.shapes = function() {
        return [];
    };

    /**
     * Token handling is delegated to the objects.
     * @param token {Object} It contains type and value.
     */
    WktObject.prototype.handleToken = function(token) {
        var value = token.value;
        var options = this.options;
        if (token.type === WktType.TokenType.TEXT) {
            // In this part retain only the information about new Object?
            this.text(options, value);
        } else if (token.type === WktType.TokenType.LEFT_PARENTHESIS) {
            this.leftParenthesis(options);
        } else if (token.type === WktType.TokenType.RIGHT_PARENTHESIS) {
            this.rightParenthesis(options);
        } else if (token.type === WktType.TokenType.NUMBER) {
            this.number(options, value);
        } else if (token.type === WktType.TokenType.COMMA) {
            this.comma(options);
        }
        options.tokens.push(token);
    };

    /**
     * There are basically three types of tokens in the Text line. The name of the type for the next shape, Empty
     * representing the empty shape and M or Z or MZ expressing whether it is in 3D or whether Linear Referencing System
     * should be used.
     * @private
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     * @param value {String} Value to use for distinguishing among options.
     */
    WktObject.prototype.text = function(options, value) {
        value = value.toUpperCase();
        var started = null;
        if (value.length <= 2) {
            this.setOptions(value, this);
        } else if (value.indexOf('EMPTY') === 0) {
            options.leftParenthesis = 1;
            options.rightParenthesis = 1;
        } else {
            var founded = value.match('[M]?[Z]?$');

            if(founded && founded.length > 0 && founded[0] != '') {
                this.setOptions(founded, started);
            }

            // Handle the GeometryCollection.
            var currentObject = WktElements[value] && new WktElements[value]();
            if(!currentObject) {
                currentObject = new WktObject();
            }

            if(founded && founded.length > 0 && founded[0] != '') {
                currentObject.setOptions(founded[0], currentObject);
            }
            this.add(currentObject);
        }
    };

    /**
     * Right parenthesis either end coordinates for an object or ends current shape.
     * @private
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     */
    WktObject.prototype.rightParenthesis = function(options) {
        options.rightParenthesis++;

        if (options.coordinates) {
            this.addCoordinates(options.coordinates);
            options.coordinates = null;
        }
    };

    /**
     * Mainly to be used in specific subclasses.
     * @private
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     */
    WktObject.prototype.leftParenthesis = function(options) {
        options.leftParenthesis++;
    };

    /**
     * Comma either means another set of coordinates, or for certain shapes for example another shape or just another
     * boundary
     * @private
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     */
    WktObject.prototype.comma = function(options) {
        if (!options.coordinates) {
            this.commaWithoutCoordinates(options);
        } else {
            this.addCoordinates(options.coordinates);
            options.coordinates = null;
        }
    };

    /**
     * Used by Multi objects to delineate the internal objects. This is default implementation doing nothing.
     * @protected
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     */
    WktObject.prototype.commaWithoutCoordinates = function(options){};

    /**
     * Handle Number by adding it among coordinates in the current object.
     * @private
     * @param options {Object} Options specifying current status of the implementation
     * @param options.coordinates {Number[]} Passed in coordinates
     * @param options.leftParenthesis {Number} Amount of the left parenthesis
     * @param options.rightParenthesis {Number} Amount of the right parenthesis
     * @param options.tokens {Object[]} Processed tokens.
     * @param value {Number}
     */
    WktObject.prototype.number = function(options, value) {
        options.coordinates = options.coordinates || [];
        options.coordinates.push(value);
    };

    /**
     * It sets the options of the current object. This means setting up the 3D and the linear space.
     * @param text {String} Specific text used as options.
     * @param currentObject {WktObject} Object to apply the options to.
     */
    WktObject.prototype.setOptions = function(text, currentObject) {
        if (text == 'Z') {
            currentObject.set3d();
        } else if (text == 'M') {
            currentObject.setLrs();
        } else if (text == 'MZ') {
            currentObject.set3d();
            currentObject.setLrs();
        }
    };

    /**
     * It returns true when the object is finished.
     * @return {Boolean} True if the parentheses are closed, false otherwise
     */
    WktObject.prototype.isFinished = function() {
        return this.options.leftParenthesis === this.options.rightParenthesis && this.options.leftParenthesis > 0;
    };

    return WktObject;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/WktTokens',[
    './WktElements',
    './geom/WktObject',
    './WktType'
], function (WktElements,
             WktObject,
             WktType) {
    /**
     * Tokenizer, which parses the source texts into the meaningful tokens and then transforms them to the objects.
     * Intended for the internal use only.
     * @private
     * @constructor
     * @alias WktTokens
     */
    var WktTokens = function (sourceText) {
        this.sourceText = sourceText;
    };

    /**
     * It returns correctly initialized objects. It is possible to retrieve relevant shapes from all WKT Objects.
     * @return {WKTObject[]}
     */
    WktTokens.prototype.objects = function () {
        var currentObject;
        var objects = [];

        this.tokenize(this.sourceText).forEach(function (token) {
            if(currentObject && currentObject.isFinished() || !currentObject) {
                // It represents new object.
                var value = token.value;
                var founded = value.match('[M]?[Z]?$');
                if(founded && founded.length > 0 && founded[0] != '') {
                    value = value.substring(0, value.length - founded.length);
                }

                currentObject = WktElements[value] && new WktElements[value]();
                if(!currentObject) {
                    currentObject = new WktObject();
                }

                if(founded && founded.length > 0 && founded[0] != '') {
                    currentObject.setOptions(founded[0], currentObject);
                }
                objects.push(currentObject);
            } else {
                currentObject.handleToken(token);
            }
        });

        return objects;
    };

    /**
     * It continues character by character through the string. The empty spaces works always as delimiter.
     * It begins with the information about the type. It is one of the WKT types with potential ending with M or Z
     * I have the complete tokens containing the basic information we need.
     * @private
     * @return {String[]}
     */
    WktTokens.prototype.tokenize = function (textToParse) {
        this.currentPosition = 0;

        var tokens = [];
        for (; this.currentPosition < textToParse.length; this.currentPosition++) {
            var c = textToParse.charAt(this.currentPosition);

            if (c == '(') {
                tokens.push({
                    type: WktType.TokenType.LEFT_PARENTHESIS
                });
            } else if (c == ',') {
                tokens.push({
                    type: WktType.TokenType.COMMA
                });
            } else if (c == ')') {
                tokens.push({
                    type: WktType.TokenType.RIGHT_PARENTHESIS
                });
            } else if (this.isAlpha(c)) {
                var text = this.readText(textToParse);
                tokens.push({
                    type: WktType.TokenType.TEXT,
                    value: text
                });
            } else if (this.isNumeric(c)) {
                var numeric = this.readNumeric(textToParse);
                tokens.push({
                    type: WktType.TokenType.NUMBER,
                    value: numeric
                });
            } else if (this.isWhiteSpace(c)) {
                continue;
            } else {
                throw new Error('Invalid character: {{', c, '}}');
            }
        }

        return tokens;
    };


    /**
     * It returns true if the character is letter, regardless of whether uppercase or lowercase.
     * @private
     * @param c {String} character to test
     * @return {boolean} True if it is lowercase or uppercase
     */
    WktTokens.prototype.isAlpha = function (c) {
        return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
    };

    /**
     * It returns true if the character is part of the number. It has certain limitations such as -1- is considered as
     * a number
     * @private
     * @param c {String} character to test
     * @return {boolean} True if it is either Number or - or .
     */
    WktTokens.prototype.isNumeric = function (c) {
        return c >= '0' && c <= '9' || c == '.' || c == '-';
    };

    /**
     * It returns true if the character represents whitespace. It is mainly relevant as whitespaces are one of the
     * delimiters
     * @private
     * @param c {String} character to test
     * @return {boolean} True if it is any type of white space.
     */
    WktTokens.prototype.isWhiteSpace = function (c) {
        return c == ' ' || c == '\t' || c == '\r' || c == '\n';
    };

    /**
     * It returns the next chunk of the String, which represents the text. Non alpha characters end the text.
     * @private
     * @param textToParse {String} The text to use in parsing.
     * @return {string} The full chunk of text
     */
    WktTokens.prototype.readText = function (textToParse) {
        var text = '';
        while (this.isAlpha(textToParse.charAt(this.currentPosition))) {
            text += textToParse.charAt(this.currentPosition);
            this.currentPosition++;
        }
        this.currentPosition--;
        return text;
    };

    /**
     * It returns the next chunk of the String, which represents the number. Non numeric characters end the text.
     * @private
     * @param textToParse {String} The text to use in parsing.
     * @return {Number} The full chunk of number
     */
    WktTokens.prototype.readNumeric = function (textToParse) {
        var numeric = '';
        while (this.isNumeric(textToParse.charAt(this.currentPosition))) {
            numeric += textToParse.charAt(this.currentPosition);
            this.currentPosition++;
        }
        this.currentPosition--;
        return Number(numeric);
    };

    return WktTokens;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/Wkt',[
    '../../error/ArgumentError',
    '../../util/Logger',
    './WktTokens'
], function (ArgumentError,
             Logger,
             WktTokens) {
    /**
     * Wkt is capable of parsing the text representation of the WKT objects. The explanation of what all is
     * supported is to be found in the README.MD in this directory.<br/>
     * <br/>
     * The simplest possible usage is:<br/>
     * var layer = new WorldWind.RenderableLayer();<br/>
     * var parser = new Wkt('POINT (19 23)');<br/>
     * parser.load(null, null, layer);<br/>
     * wwd.addLayer(layer);<br/>
     * This example adds the WKT into the map<br/>
     *
     * The more complex usage allows you to do whatever you want with the parsed objects. For example you can make
     * sure that only points are displayed<br/>
     * var layer = new WorldWind.RenderableLayer();<br/>
     * var parser = new Wkt('POINT (19 23)');<br/>
     * parser.load(function(wkt, objects){<br/>
     *   var shapes = []; <br/>
     *   objects.forEach(function(object){<br/>
     *     if(object.type == WorldWind.WktType.SupportedGeometries.POINT) { <br/>
     *       shapes.push.apply(shapes, object.shapes());<br/>
     *     } <br/>
     *   }); <br/>
     *   <br/>
     *   if(wkt.layer) {<br/>
     *     wkt.layer.addRenderables(shapes);<br/>
     *   }<br/>
     * }, null, layer);<br/>
     * wwd.addLayer(layer);<br/>
     *
     * The most complex usage is when you want to supply different configuration for object before it is added to the layer.<br/>
     * var layer = new WorldWind.RenderableLayer();<br/>
     * var parser = new Wkt('POINT (19 23)');<br/>
     * parser.load(null, function(shape) {<br/>
     *   if(shape.type == WktType.SupportedGeometries.POINT) {<br/>
     *     var shapeAttributes = new ShapeAttributes(null);<br/>
     *     shapeAttributes.fontColor = Color.RED;<br/>
     *     return {<br/>
     *         attributes: shapeAttributes<br/>
     *     };<br/>
     *   }<br/>
     * }, layer);<br/>
     * wwd.addLayer(layer);<br/>
     *
     * @param textRepresentation {String} Text representation of WKT objects.
     * @constructor
     * @alias Wkt
     */
    var Wkt = function (textRepresentation) {
        this.textRepresentation = textRepresentation;

        this._parserCompletionCallback = this.defaultParserCompletionCallback;
        this._shapeConfigurationCallback = this.defaultShapeConfigurationCallback;

        this._layer = null;
    };

    Object.defineProperties(Wkt.prototype, {
        /**
         * The completion callback specified to [load]{@link Wkt#load}. This function is called when
         * wkt parsing is done but before creating shapes for the wkt. It's single argument is
         * the WKT string.
         * @memberof Wkt.prototype
         * @type {Function}
         * @default [defaultParserCompletionCallback]{@link Wkt#defaultParserCompletionCallback}
         * @readonly
         */
        parserCompletionCallback: {
            get: function () {
                return this._parserCompletionCallback;
            }
        },

        /**
         * The attribute callback specified to [load]{@link Wkt#load}.
         * See that method's description for details.
         * @memberof Wkt.prototype
         * @type {Function}
         * @default [defaultShapeConfigurationCallback]{@link Wkt#defaultShapeConfigurationCallback}
         * @readonly
         */
        shapeConfigurationCallback: {
            get: function () {
                return this._shapeConfigurationCallback;
            }
        },

        /**
         * The layer containing the shapes representing the records in this wkt, as specified to this
         * wkt's constructor.
         * @memberof Wkt.prototype
         * @type {RenderableLayer}
         * @readonly
         */
        layer: {
            get: function() {
                return this._layer;
            }
        }
    });

    /**
     * It parses the received string and create the Objects, which then can be rendered.
     * @param parserCompletionCallback {Function} An optional function called when the WKT loading is
     *   complete and all the shapes have been added to the layer.
     * @param shapeConfigurationCallback {Function} This function  is called whenever new shape is created. It provides
     *   the current shape as the first argument. In this way it is possible to modify the shape even provide another one.
     *   If any shape is returned it is used in place of the previous one. This function should be synchronous and if
     *   you want to provide custom shape, it has to be synchronous.
     * @param layer {RenderableLayer} Layer to use for adding all the parsed shapes. It is optional. It is possible to
     *   use this class as only a parser by providing custom parser completion callback.
     */
    Wkt.prototype.load = function (parserCompletionCallback, shapeConfigurationCallback, layer) {
        if(layer) {
            this._layer = layer;
        }

        if (parserCompletionCallback) {
            this._parserCompletionCallback = parserCompletionCallback;
        }

        if (shapeConfigurationCallback) {
            this._shapeConfigurationCallback = shapeConfigurationCallback;
        }

        this.parserCompletionCallback(
            this,
            new WktTokens(this.textRepresentation).objects()
        );
    };

    /**
     * It is the default implementation of the shape configuration callback. It is called for every generated shape.
     * @param shape {Renderable} It is a renderable for which you can provide custom attributes.
     * @returns {Object} This object can contain attributes to be used for the shape, highlight attributes to be used
     *   for the shape, pickDelegate to be used and userProperties. All these properties are applied to the shape.
     */
    Wkt.prototype.defaultShapeConfigurationCallback = function(shape) {
        // The default implementation doesn't change the defaults for the Shapes.
    };

    /**
     * It is the default implementation of the parser completion callback. It is called with all parsed objects and
     * the default one then calls shape configuration callback for each of them and then add the shapes into
     * the provided layer if such is provided.
     * @param objects {WktObject[]} Array of the Renderables to be displayed. This is the last time to modify them.
     * @param wkt {Wkt} Object representing the Wkt. It is used to retrieve layer, shape configuration callback.
     */
    Wkt.prototype.defaultParserCompletionCallback = function(wkt, objects) {
        // The default implementation doesn't change the defaults for the Shapes.
        var shapeConfigurationCallback = wkt.shapeConfigurationCallback;
        var shapes = [];
        objects.forEach(function(object){
            object.shapes().forEach(function(shape){
                var configuration = shapeConfigurationCallback(object);
                if(configuration && configuration.attributes) {
                    shape.attributes = configuration.attributes;
                }
                if(configuration && configuration.highlightAttributes) {
                    shape.highlightAttributes = configuration.highlightAttributes;
                }
                if(configuration && configuration.pickDelegate) {
                    shape.pickDelegate = configuration.pickDelegate;
                }
                if(configuration && configuration.userProperties) {
                    shape.userProperties = configuration.userProperties;
                }
                shapes.push(shape);
            });
        });

        if(wkt.layer) {
            wkt.layer.addRenderables(shapes);
        }
    };

    return Wkt;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktGeometryCollection',[
    '../../../geom/Location',
    '../../../geom/Position',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Location,
             Position,
             WktElements,
             WktObject,
             WktType) {
    /**
     * This item can contain other geometries to be shown.
     * @alias WktGeometryCollection
     * @augments WktObject
     * @constructor
     */
    var WktGeometryCollection = function () {
        WktObject.call(this, WktType.SupportedGeometries.GEOMETRY_COLLECTION);

        this.objects = [];
    };

    WktGeometryCollection.prototype = Object.create(WktObject.prototype);

    /**
     * It takes an object and adds it among those, it will render
     * @param object {WKTObject} Object to be added to this collection.
     */
    WktGeometryCollection.prototype.add = function (object) {
        this.objects.push(object);
    };

    /**
     * In geometry collection the coordinates should belong to the currently parsed object.
     * Array containing latitude, longitude and potentially either altitude or LRS.
     * @inheritDoc
     */
    WktGeometryCollection.prototype.addCoordinates = function (coordinates) {
        var object = this.objects[this.objects.length - 1];
        if (this._is3d) {
            object.coordinates.push(new Position(coordinates[0], coordinates[1], coordinates[2] || 0));
        } else {
            object.coordinates.push(new Location(coordinates[0], coordinates[1]));
        }
    };

    /**
     * It returns representation for all shapes in the GeometryCollection.
     * @inheritDoc
     * @return {Renderable[]}
     */
    WktGeometryCollection.prototype.shapes = function () {
        var shapes = [];

        this.objects.forEach(function (associatedShapes) {
            associatedShapes.shapes().forEach(function (shape) {
                shapes.push(shape);
            });
        });

        return shapes;
    };

    WktElements['GEOMETRYCOLLECTION'] = WktGeometryCollection;

    return WktGeometryCollection;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktLineString',[
    '../../../shapes/Path',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolyline',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Path,
             ShapeAttributes,
             SurfacePolyline,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents WKT LineString.
     * @alias WktLineString
     * @augments WktObject
     * @constructor
     */
    var WktLineString = function () {
        WktObject.call(this, WktType.SupportedGeometries.LINE_STRING);
    };

    WktLineString.prototype = Object.create(WktObject.prototype);

    /**
     * In case of 2D return SurfacePolyline, in case of 3D returns Path.
     * @inheritDoc
     * @return {Path[]|SurfacePolyline[]}
     */
    WktLineString.prototype.shapes = function () {
        if (this._is3d) {
            return [new Path(this.coordinates, new ShapeAttributes(null))];
        } else {
            return [new SurfacePolyline(this.coordinates, new ShapeAttributes(null))];
        }
    };

    WktElements['LINESTRING'] = WktLineString;

    return WktLineString;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktMultiLineString',[
    '../../../shapes/Path',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolyline',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Path,
             ShapeAttributes,
             SurfacePolyline,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents multiple line string as one object.
     * @alias WktMultiLineString
     * @augments WktObject
     * @constructor
     */
    var WktMultiLineString = function () {
        WktObject.call(this, WktType.SupportedGeometries.MULTI_LINE_STRING);

        this.objectBoundaries = [];
    };

    WktMultiLineString.prototype = Object.create(WktObject.prototype);

    /**
     * Specific for Multi objects as it depicts the boundaries.
     */
    WktMultiLineString.prototype.commaWithoutCoordinates = function() {
        this.objectBoundaries.push(this.coordinates.slice());
        this.coordinates = [];
    };

    /**
     * In case of 2D it returns SurfacePolyline, In case of 3D return Path.
     * @inheritDoc
     * @return {Path[]|SurfacePolyline[]}
     */
    WktMultiLineString.prototype.shapes = function() {
        this.commaWithoutCoordinates(); // This needs to be more careful and probably move to the stuff

        if(this._is3d){
            return this.objectBoundaries.map(function(boundaries){
                return new Path(boundaries, new ShapeAttributes(null));
            }.bind(this));
        } else {
            return this.objectBoundaries.map(function(boundaries){
                return new SurfacePolyline(boundaries, new ShapeAttributes(null));
            }.bind(this));
        }
    };

    WktElements['MULTILINESTRING'] = WktMultiLineString;

    return WktMultiLineString;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktPoint',[
    '../../../shapes/Placemark',
    '../../../shapes/PlacemarkAttributes',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Placemark,
             PlacemarkAttributes,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents Point
     * @alias WktPoint
     * @augments WktObject
     * @constructor
     */
    var WktPoint = function () {
        WktObject.call(this, WktType.SupportedGeometries.POINT);
    };

    WktPoint.prototype = Object.create(WktObject.prototype);

    /**
     * It returns Placemark representing this point.
     * @return {Placemark[]}
     */
    WktPoint.prototype.shapes = function () {
        return [WktPoint.placemark(this.coordinates[0])];
    };

    /**
     * Default Placemark implementation for the Point and MultiPoint.
     * @param coordinates {Location|Position} Location or Position for the Placemark
     * @return {Placemark} Placemark to be displayed on the map.
     */
    WktPoint.placemark = function(coordinates) {
        var placemarkAttributes = new PlacemarkAttributes(null);
        placemarkAttributes.imageScale = 1;
        placemarkAttributes.imageOffset = new WorldWind.Offset(
            WorldWind.OFFSET_FRACTION, 0.3,
            WorldWind.OFFSET_FRACTION, 0.0);
        placemarkAttributes.imageColor = WorldWind.Color.WHITE;
        placemarkAttributes.labelAttributes.offset = new WorldWind.Offset(
            WorldWind.OFFSET_FRACTION, 0.5,
            WorldWind.OFFSET_FRACTION, 1.0);
        placemarkAttributes.labelAttributes.color = WorldWind.Color.YELLOW;
        placemarkAttributes.drawLeaderLine = true;
        placemarkAttributes.leaderLineAttributes.outlineColor = WorldWind.Color.RED;
        placemarkAttributes.imageSource = WorldWind.configuration.baseUrl + "images/pushpins/castshadow-purple.png";

        var placemark = new Placemark(coordinates, true, placemarkAttributes);
        placemark.altitudeMode = WorldWind.RELATIVE_TO_GROUND;

        return placemark;
    };

    WktElements['POINT'] = WktPoint;

    return WktPoint;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktMultiPoint',[
    '../../../shapes/Placemark',
    '../../../shapes/PlacemarkAttributes',
    '../WktElements',
    './WktObject',
    './WktPoint',
    '../WktType'
], function (Placemark,
             PlacemarkAttributes,
             WktElements,
             WktObject,
             WktPoint,
             WktType) {
    /**
     * It represents multiple points.
     * @alias WktMultiPoint
     * @augments WktObject
     * @constructor
     */
    var WktMultiPoint = function () {
        WktObject.call(this, WktType.SupportedGeometries.MULTI_POINT);
    };

    WktMultiPoint.prototype = Object.create(WktObject.prototype);

    /**
     * Specific for Multi objects as it depicts the boundaries.
     */
    WktMultiPoint.prototype.commaWithoutCoordinates = function() {};

    /**
     * It returns Placemark for each point.
     * @inheritDoc
     * @return {Placemark[]}
     */
    WktMultiPoint.prototype.shapes = function() {
        return this.coordinates.map(function(coordinate){
            return WktPoint.placemark(coordinate);
        }.bind(this));
    };

    WktElements['MULTIPOINT'] = WktMultiPoint;

    return WktMultiPoint;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktMultiPolygon',[
    '../../../shapes/Polygon',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolygon',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Polygon,
             ShapeAttributes,
             SurfacePolygon,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents multiple polygons.
     * @alias WktMultiPolygon
     * @augments WktObject
     * @constructor
     */
    var WktMultiPolygon = function () {
        WktObject.call(this, WktType.SupportedGeometries.MULTI_POLYGON);

        /**
         * Internal object boundaries for used polygons. Some polygons may have inner and outer boundaries.
         * @type {Array}
         */
        this.objectBoundaries = [];

        /**
         * Used to decide what objects do we add the boundaries to.
         * @type {number}
         */
        this.currentIndex = 0;
    };

    WktMultiPolygon.prototype = Object.create(WktObject.prototype);

    /**
     * In case of right parenthesis, it means either that the boundaries ends or that the object ends or that the WKT
     * object ends.
     *
     * @inheritDoc
     * @private
     */
    WktMultiPolygon.prototype.rightParenthesis = function(options) {
        WktObject.prototype.rightParenthesis.call(this, options);

        // MultiPolygon object is distinguished by )),
        if(options.tokens[options.tokens.length -1].type != WktType.TokenType.RIGHT_PARENTHESIS) {
            this.addBoundaries();
            // MultiPolygon boundaries are distinguished by ),
        } else if(options.tokens[options.tokens.length -1].type == WktType.TokenType.RIGHT_PARENTHESIS &&
            options.tokens[options.tokens.length -2].type != WktType.TokenType.RIGHT_PARENTHESIS) {
            this.addObject();
        }
    };

    /**
     * It adds outer or inner boundaries to current polygon.
     * @private
     */
    WktMultiPolygon.prototype.addBoundaries = function() {
        if(!this.objectBoundaries[this.currentIndex]) {
            this.objectBoundaries[this.currentIndex] = [];
        }
        this.objectBoundaries[this.currentIndex].push(this.coordinates.slice());
        this.coordinates = [];
    };

    /**
     * It ends boundaries for current polygon.
     * @private
     */
    WktMultiPolygon.prototype.addObject = function() {
        this.currentIndex++;
    };

    /**
     * It returns array of SurfacePolygon in 2D or array of Polygons in 3D
     * @inheritDoc
     * @return {Polygon[]|SurfacePolygon[]}
     */
    WktMultiPolygon.prototype.shapes = function () {
        if (this._is3d) {
            return this.objectBoundaries.map(function (boundaries) {
                return new Polygon(boundaries, new ShapeAttributes(null));
            }.bind(this));
        } else {
            return this.objectBoundaries.map(function (boundaries) {
                return new SurfacePolygon(boundaries, new ShapeAttributes(null));
            }.bind(this));
        }
    };

    WktElements['MULTIPOLYGON'] = WktMultiPolygon;

    return WktMultiPolygon;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktPolygon',[
    '../../../shapes/Polygon',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolygon',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Polygon,
             ShapeAttributes,
             SurfacePolygon,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents the polygon.
     * @alias WktPolygon
     * @augments WktObject
     * @constructor
     */
    var WktPolygon = function () {
        WktObject.call(this, WktType.SupportedGeometries.POLYGON);

        this._renderable = null;
    };

    WktPolygon.prototype = Object.create(WktObject.prototype);

    /**
     * @inheritDoc
     */
    WktPolygon.prototype.commaWithoutCoordinates = function() {
        this.outerBoundaries = this.coordinates.slice();
        this.coordinates = [];
    };

    /**
     * It returns SurfacePolygon for 2D. It returns Polygon for 3D.
     * @inheritDoc
     * @return {Polygon[]|SurfacePolyline[]}
     */
    WktPolygon.prototype.shapes = function () {
        if (this._is3d) {
            if(this.outerBoundaries) {
                return [new Polygon([this.outerBoundaries, this.coordinates], new ShapeAttributes(null))];
            } else {
                return [new Polygon(this.coordinates, new ShapeAttributes(null))];
            }
        } else {
            if(this.outerBoundaries) {
                return [new SurfacePolygon([this.outerBoundaries, this.coordinates], new ShapeAttributes(null))];
            } else {
                return [new SurfacePolygon(this.coordinates, new ShapeAttributes(null))];
            }
        }
    };

    WktElements['POLYGON'] = WktPolygon;

    return WktPolygon;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('formats/wkt/geom/WktTriangle',[
    '../../../shapes/Polygon',
    '../../../shapes/ShapeAttributes',
    '../../../shapes/SurfacePolygon',
    '../WktElements',
    './WktObject',
    '../WktType'
], function (Polygon,
             ShapeAttributes,
             SurfacePolygon,
             WktElements,
             WktObject,
             WktType) {
    /**
     * It represents triangle.
     * @alias WktTriangle
     * @augments WktObject
     * @constructor
     */
    var WktTriangle = function () {
        WktObject.call(this, WktType.SupportedGeometries.TRIANGLE);

        this._renderable = null;
    };

    WktTriangle.prototype = Object.create(WktObject.prototype);

    /**
     * It returns SurfacePolygon for 2D. It returns Polygon for 3D. Triangle doesn't support inner boundaries.
     * @inheritDoc
     * @return {Polygon|SurfacePolyline}
     */
    WktTriangle.prototype.shapes = function () {
        if (this._is3d) {
            return [new Polygon(this.coordinates, new ShapeAttributes(null))];
        } else {
            return [new SurfacePolygon(this.coordinates, new ShapeAttributes(null))];
        }
    };

    WktElements['TRIANGLE'] = WktTriangle;

    return WktTriangle;
});
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmsLayerCapabilities
 */
define('ogc/wms/WmsLayerCapabilities',[
        '../../error/ArgumentError',
        '../../util/Logger'
    ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs an WMS Layer instance from an XML DOM.
         * @alias WmsLayerCapabilities
         * @constructor
         * @classdesc Represents a WMS layer description from a WMS Capabilities document. This object holds all the
         * fields specified in the associated WMS Capabilities document.
         * @param {{}} layerElement A WMS Layer element describing the layer.
         * @param {{}} parentNode An object indicating the new layer object's parent object.
         * @throws {ArgumentError} If the specified layer element is null or undefined.
         */
        var WmsLayerCapabilities = function (layerElement, parentNode) {
            if (!layerElement) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsLayerCapabilities", "constructor",
                        "Layer element is null or undefined."));
            }

            /**
             * The parent object, as specified to the constructor of this object.
             * @type {{}}
             * @readonly
             */
            this.parent = parentNode;

            /**
             * The layers that are children of this layer.
             * @type {WmsLayerCapabilities[]}
             * @readonly
             */
            this.layers;

            /**
             * The name of this layer description.
             * @type {String}
             * @readonly
             */
            this.name;

            /**
             * The title of this layer.
             * @type {String}
             * @readonly
             */
            this.title;

            /**
             * The abstract of this layer.
             * @type {String}
             * @readonly
             */
            this.abstract;

            /**
             * The list of keywords associated with this layer description.
             * @type {String[]}
             * @readonly
             */
            this.keywordList;

            /**
             * The identifiers associated with this layer description. Each identifier has the following properties:
             * authority, content.
             * @type {Object[]}
             */
            this.identifiers;

            /**
             * The metadata URLs associated with this layer description. Each object in the returned array has the
             * following properties: type, format, url.
             * @type {Object[]}
             * @readonly
             */
            this.metadataUrls;

            /**
             * The data URLs associated with this layer description. Each object in the returned array has the
             * following properties: format, url.
             * @type {Object[]}
             * @readonly
             */
            this.dataUrls;

            /**
             * The feature list URLs associated with this layer description. Each object in the returned array has the
             * following properties: format, url.
             * @type {Object[]}
             * @readonly
             */
            this.featureListUrls;

            this.assembleLayer(layerElement);
        };

        Object.defineProperties(WmsLayerCapabilities.prototype, {
            /**
             * The WMS capability section containing this layer description.
             * @type {{}}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            capability: {
                get: function () {
                    var o = this;

                    while (o && (o instanceof WmsLayerCapabilities)) {
                        o = o.parent;
                    }

                    return o;
                }
            },

            /**
             * The WMS queryable attribute.
             * @type {Boolean}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            queryable: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_queryable");
                }
            },

            /**
             * The WMS cascaded attribute.
             * @type {Boolean}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            cascaded: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_cascaded");
                }
            },

            /**
             * The WMS opaque attribute.
             * @type {Boolean}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            opaque: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_opaque");
                }
            },

            /**
             * The WMS noSubsets attribute.
             * @type {Boolean}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            noSubsets: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_noSubsets");
                }
            },

            /**
             * The WMS fixedWidth attribute.
             * @type {Number}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            fixedWidth: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_fixedWidth");
                }
            },

            /**
             * The WMS fixedHeight attribute.
             * @type {Number}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            fixedHeight: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_fixedHeight");
                }
            },

            /**
             * The list of styles associated with this layer description, accumulated from this layer and its parent
             * layers. Each object returned may have the following properties: name {String}, title {String},
             * abstract {String}, legendUrls {Object[]}, styleSheetUrl, styleUrl. Legend urls may have the following
             * properties: width, height, format, url. Style sheet urls and style urls have the following properties:
             * format, url.
             * @type {Object[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            styles: {
                get: function () {
                    return WmsLayerCapabilities.accumulate(this, "_styles", []);
                }
            },

            /**
             * The list of coordinate system descriptions associated with this layer, accumulated from this layer
             * and its parent layers. WMS servers implementing WMS version 1.3.0 and above have this field.
             * @type {String[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            crses: {
                get: function () {
                    return WmsLayerCapabilities.accumulate(this, "_crses", []);
                }
            },

            /**
             * The list of coordinate system descriptions associated with this layer, accumulated from this layer
             * and its parent layers. WMS servers implementing WMS version 1.1.1 and below have this field.
             * @type {String[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            srses: {
                get: function () {
                    return WmsLayerCapabilities.accumulate(this, "_srses", []);
                }
            },

            /**
             * This layer description's geographic bounding box. WMS servers implementing WMS 1.3.0 and above have
             * this field. The returned object has properties for each of the WMS-specified fields. For example,
             * "westBoundingLongitude".
             * @type {{}}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            geographicBoundingBox: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_geographicBoundingBox");
                }
            },

            /**
             * This layer description's geographic bounding box. WMS servers implementing WMS 1.1.1 and below have
             * this field. The returned object has properties for each of the WMS-specified fields. For example,
             * "maxx".
             * @type {{}}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            latLonBoundingBox: { // WMS 1.1.1
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_latLonBoundingBox");
                }
            },

            /**
             * The bounding boxes associated with this layer description. The returned object has properties for each
             * of the defined attributes. For example, "minx".
             * @type {{}}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            boundingBoxes: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_boundingBoxes");
                }
            },

            /**
             * The list of dimensions associated with this layer description, accumulated from this layer and its
             * parent layers. WMS servers implementing WMS version 1.3.0 and above provide this field.
             * @type {String[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            dimensions: {
                get: function () {
                    var accumulatedDimensions = [],
                        layer = this;

                    // Accumulate only dimensions with unique names with descendants overriding ancestors.
                    while (layer && (layer instanceof WmsLayerCapabilities)) {
                        if (layer._dimensions && layer._dimensions.length > 0) {
                            layer._dimensions.forEach(function (ancestorDimension) {
                                var name = ancestorDimension.name;
                                var include = true;
                                accumulatedDimensions.forEach(function (descendantDimension) {
                                    if (descendantDimension.name === name) {
                                        include = false;
                                    }
                                });
                                if (include) {
                                    accumulatedDimensions.push(ancestorDimension);
                                }
                            });
                        }

                        layer = layer.parent;
                    }

                    return accumulatedDimensions.length > 0 ? accumulatedDimensions : undefined;
                }
            },

            /**
             * The list of extents associated with this layer description, accumulated from this layer and its
             * parent layers. WMS servers implementing WMS version 1.3.0 and above provide this field.
             * @type {String[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            extents: {
                get: function () {
                    var accumulatedDimensions = [],
                        layer = this;

                    // Accumulate only extents with unique names with descendants overriding ancestors.
                    while (layer && (layer instanceof WmsLayerCapabilities)) {
                        if (layer._extents && layer._extents.length > 0) {
                            layer._extents.forEach(function (ancestorDimension) {
                                var name = ancestorDimension.name;
                                var include = true;
                                accumulatedDimensions.forEach(function (descendantDimension) {
                                    if (descendantDimension.name === name) {
                                        include = false;
                                    }
                                });
                                if (include) {
                                    accumulatedDimensions.push(ancestorDimension);
                                }
                            });
                        }

                        layer = layer.parent;
                    }

                    return accumulatedDimensions.length > 0 ? accumulatedDimensions : undefined;
                }
            },

            /**
             * The attribution element associated with this layer description. The returned object has the following
             * properties: title {String}, url {String}, logoUrl {{format, url}}.
             * @type {{}}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            attribution: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_attribution");
                }
            },

            /**
             * The authority URLs associated with this layer description, accumulated from this layer and its parent
             * layers. The returned objects have the following properties: name {String}, url {String}.
             * @type {Object[]}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            authorityUrls: {
                get: function () {
                    return WmsLayerCapabilities.accumulate(this, "_authorityUrls", []);
                }
            },

            /**
             * The minimum-scale-denominator associated with this layer description.
             * WMS servers implementing WMS version 1.3.0 and above provide this field.
             * @type {Number}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            minScaleDenominator: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_minScaleDenominator");
                }
            },

            /**
             * The maximum-scale-denominator associated with this layer description.
             * WMS servers implementing WMS version 1.3.0 and above provide this field.
             * @type {Number}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            maxScaleDenominator: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_maxScaleDenominator");
                }
            },

            /**
             * The scale hint associated with this layer description.
             * WMS servers implementing WMS version 1.1.1 and below provide this field.
             * @type {Number}
             * @readonly
             * @memberof WmsLayerCapabilities.prototype
             */
            scaleHint: {
                get: function () {
                    return WmsLayerCapabilities.replace(this, "_scaleHint");
                }
            }
        });

        WmsLayerCapabilities.prototype.style = function(name) {
            if (!name) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsLayerCapabilities", "style",
                        "Style name is null or undefined."));
            }

            var styles = this.styles;
            if (!styles) {
                return null;
            }

            for (var i = 0, len = styles.length, style; i < len; i++) {
                style = styles[i];
                if (style.name === name) {
                    return style;
                }
            }
        }

        WmsLayerCapabilities.accumulate = function (layer, propertyName, accumulation) {
            // Accumulate all of the named properties in the specified layer and its ancestors.
            while (layer && (layer instanceof WmsLayerCapabilities)) {
                var property = layer[propertyName];

                if (property) {
                    for (var i = 0; i < property.length; i++) {
                        accumulation.push(property[i]);
                    }
                }

                layer = layer.parent;
            }

            return accumulation.length > 0 ? accumulation : null;
        };

        WmsLayerCapabilities.replace = function (layer, propertyName) {
            // Find the first property instance encountered from the specified layer upwards through its ancestors.
            while (layer && (layer instanceof WmsLayerCapabilities)) {
                var property = layer[propertyName];

                if (property) {
                    return property;
                } else {
                    layer = layer.parent;
                }
            }
        };

        WmsLayerCapabilities.prototype.assembleLayer = function (layerElement) {
            var elements, attrValue, c, e;

            attrValue = layerElement.getAttribute("queryable");
            if (attrValue) {
                this._queryable = attrValue === "1" || attrValue === "true"
            }

            attrValue = layerElement.getAttribute("opaque");
            if (attrValue) {
                this._opaque = attrValue === "1" || attrValue === "true"
            }

            attrValue = layerElement.getAttribute("noSubsets");
            if (attrValue) {
                this._noSubsets = attrValue === "1" || attrValue === "true"
            }

            attrValue = layerElement.getAttribute("cascaded");
            if (attrValue) {
                this._cascaded = parseInt("10");
            }

            attrValue = layerElement.getAttribute("fixedWidth");
            if (attrValue) {
                this._fixedWidth = parseInt("10");
            }

            attrValue = layerElement.getAttribute("fixedHeight");
            if (attrValue) {
                this._fixedHeight = parseInt("10");
            }

            var children = layerElement.children || layerElement.childNodes;
            for (c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Layer") {
                    if (!this.layers) {
                        this.layers = [];
                    }
                    this.layers.push(new WmsLayerCapabilities(childElement, this));

                } else if (childElement.localName === "Name") {
                    this.name = childElement.textContent;

                } else if (childElement.localName === "Title") {
                    this.title = childElement.textContent;

                } else if (childElement.localName === "Abstract") {
                    this.abstract = childElement.textContent;

                } else if (childElement.localName === "KeywordList") {
                    this.keywordList = this.keywordList || [];

                    var children2 = childElement.children || childElement.childNodes;
                    for (var c2 = 0; c2 < children2.length; c2++) {
                        var child2 = children2[c2];

                        if (child2.localName === "Keyword") {
                            this.keywordList.push(child2.textContent);
                        }
                    }
                } else if (childElement.localName === "Style") {
                    if (!this._styles) {
                        this._styles = [];
                    }
                    this._styles.push(WmsLayerCapabilities.assembleStyle(childElement))

                } else if (childElement.localName === "CRS") {
                    if (!this._crses) {
                        this._crses = [];
                    }
                    this._crses.push(childElement.textContent);

                } else if (childElement.localName === "SRS") { // WMS 1.1.1
                    if (!this._srses) {
                        this._srses = [];
                    }
                    this._srses.push(childElement.textContent);

                } else if (childElement.localName === "EX_GeographicBoundingBox") {
                    this._geographicBoundingBox = WmsLayerCapabilities.assembleGeographicBoundingBox(childElement);

                } else if (childElement.localName === "LatLonBoundingBox") { // WMS 1.1.1
                    this._geographicBoundingBox = WmsLayerCapabilities.assembleLatLonBoundingBox(childElement);

                } else if (childElement.localName === "BoundingBox") {
                    if (!this._boundingBoxes) {
                        this._boundingBoxes = [];
                    }
                    this._boundingBoxes.push(WmsLayerCapabilities.assembleBoundingBox(childElement));

                } else if (childElement.localName === "Dimension") {
                    if (!this._dimensions) {
                        this._dimensions = [];
                    }
                    this._dimensions.push(WmsLayerCapabilities.assembleDimension(childElement));

                } else if (childElement.localName === "Extent") { // WMS 1.1.1
                    if (!this._extents) {
                        this._extents = [];
                    }
                    this._extents.push(WmsLayerCapabilities.assembleDimension(childElement)); // same schema as 1.3.0 Dimension

                } else if (childElement.localName === "Attribution") {
                    this._attribution = WmsLayerCapabilities.assembleAttribution(childElement);

                } else if (childElement.localName === "AuthorityURL") {
                    if (!this._authorityUrls) {
                        this._authorityUrls = [];
                    }
                    this._authorityUrls.push(WmsLayerCapabilities.assembleAuthorityUrl(childElement));

                } else if (childElement.localName === "Identifier") {
                    if (!this.identifiers) {
                        this.identifiers = [];
                    }
                    this.identifiers.push(WmsLayerCapabilities.assembleIdentifier(childElement));

                } else if (childElement.localName === "MetadataURL") {
                    if (!this.metadataUrls) {
                        this.metadataUrls = [];
                    }
                    this.metadataUrls.push(WmsLayerCapabilities.assembleMetadataUrl(childElement));

                } else if (childElement.localName === "DataURL") {
                    if (!this.dataUrls) {
                        this.dataUrls = [];
                    }
                    this.dataUrls.push(WmsLayerCapabilities.assembleUrl(childElement));

                } else if (childElement.localName === "FeatureListURL") {
                    if (!this.featureListUrls) {
                        this.featureListUrls = [];
                    }
                    this.featureListUrls.push(WmsLayerCapabilities.assembleUrl(childElement));

                } else if (childElement.localName === "MinScaleDenominator") {
                    this._minScaleDenominator = parseFloat(childElement.textContent);

                } else if (childElement.localName === "MaxScaleDenominator") {
                    this._maxScaleDenominator = parseFloat(childElement.textContent);

                } else if (childElement.localName === "ScaleHint") { // WMS 1.1.1
                    this._scaleHint = {};
                    this._scaleHint.min = WmsLayerCapabilities.getFloatAttribute(childElement, "min");
                    this._scaleHint.max = WmsLayerCapabilities.getFloatAttribute(childElement, "max");
                }
            }
        };

        WmsLayerCapabilities.assembleStyle = function (styleElement) {
            var result = {};

            var children = styleElement.children || styleElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Name") {
                    result.name = childElement.textContent;

                } else if (childElement.localName === "Title") {
                    result.title = childElement.textContent;

                } else if (childElement.localName === "Abstract") {
                    result.abstract = childElement.textContent;

                } else if (childElement.localName === "LegendURL") {
                    if (!result.legendUrls) {
                        result.legendUrls = [];
                    }
                    result.legendUrls.push(WmsLayerCapabilities.assembleLegendUrl(childElement));

                } else if (childElement.localName === "StyleSheetURL") {
                    result.styleSheetUrl = WmsLayerCapabilities.assembleUrl(childElement);

                } else if (childElement.localName === "StyleURL") {
                    result.styleUrl = WmsLayerCapabilities.assembleUrl(childElement);
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleGeographicBoundingBox = function (bboxElement) {
            var result = {};

            var children = bboxElement.children || bboxElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "westBoundLongitude") {
                    result.westBoundLongitude = parseFloat(childElement.textContent);

                } else if (childElement.localName === "eastBoundLongitude") {
                    result.eastBoundLongitude = parseFloat(childElement.textContent);

                } else if (childElement.localName === "southBoundLatitude") {
                    result.southBoundLatitude = parseFloat(childElement.textContent);

                } else if (childElement.localName === "northBoundLatitude") {
                    result.northBoundLatitude = parseFloat(childElement.textContent);
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleLatLonBoundingBox = function (bboxElement) { // WMS 1.1.1
            var result = {};

            result.minx = WmsLayerCapabilities.getFloatAttribute(bboxElement, "minx");
            result.miny = WmsLayerCapabilities.getFloatAttribute(bboxElement, "miny");
            result.maxx = WmsLayerCapabilities.getFloatAttribute(bboxElement, "maxx");
            result.maxy = WmsLayerCapabilities.getFloatAttribute(bboxElement, "maxy");

            return result;
        };

        WmsLayerCapabilities.assembleBoundingBox = function (bboxElement) {
            var result = {};

            result.crs = bboxElement.getAttribute("CRS");
            result.minx = WmsLayerCapabilities.getFloatAttribute(bboxElement, "minx");
            result.miny = WmsLayerCapabilities.getFloatAttribute(bboxElement, "miny");
            result.maxx = WmsLayerCapabilities.getFloatAttribute(bboxElement, "maxx");
            result.maxy = WmsLayerCapabilities.getFloatAttribute(bboxElement, "maxy");
            result.resx = WmsLayerCapabilities.getFloatAttribute(bboxElement, "resx");
            result.resy = WmsLayerCapabilities.getFloatAttribute(bboxElement, "resy");

            return result;
        };

        WmsLayerCapabilities.assembleDimension = function (dimensionElement) {
            var result = {};

            result.name = dimensionElement.getAttribute("name");
            result.units = dimensionElement.getAttribute("units");
            result.unitSymbol = dimensionElement.getAttribute("unitSymbol");
            result.default = dimensionElement.getAttribute("default");
            result.multipleValues = dimensionElement.getAttribute("multipleValues");
            if (result.multipleValues) {
                result.multipleValues = result.multipleValues === "true" || result.multipleValues === "1";
            }
            result.nearestValue = dimensionElement.getAttribute("nearestValue");
            if (result.nearestValue) {
                result.nearestValue = result.nearestValue === "true" || result.nearestValue === "1";
            }
            result.current = dimensionElement.getAttribute("current");
            if (result.current) {
                result.current = result.current === "true" || result.current === "1";
            }

            result.content = dimensionElement.textContent;

            return result;
        };

        WmsLayerCapabilities.assembleAttribution = function (attributionElement) {
            var result = {};

            var children = attributionElement.children || attributionElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Title") {
                    result.title = childElement.textContent;

                } else if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");

                } else if (childElement.localName === "LogoUrul") {
                    result.logoUrl = WmsLayerCapabilities.assembleLogoUrl(childElement);
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleAuthorityUrl = function (urlElement) {
            var result = {};

            result.name = urlElement.getAttribute("name");

            var children = urlElement.children || urlElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleIdentifier = function (identifierElement) {
            var result = {};

            result.authority = identifierElement.getAttribute("authority");
            result.content = identifierElement.textContent;

            return result;
        };

        WmsLayerCapabilities.assembleMetadataUrl = function (urlElement) {
            var result = {};

            result.type = urlElement.getAttribute("type");

            var children = urlElement.children || urlElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Format") {
                    result.format = childElement.textContent;

                } else if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleLegendUrl = function (urlElement) {
            var result = {};

            result.width = WmsLayerCapabilities.getIntegerAttribute(urlElement, "width");
            result.height = WmsLayerCapabilities.getIntegerAttribute(urlElement, "height");

            var children = urlElement.children || urlElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Format") {
                    result.format = childElement.textContent;

                } else if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleLogoUrl = function (urlElement) {
            var result = {};

            result.width = WmsLayerCapabilities.getIntegerAttribute(urlElement, "width");
            result.height = WmsLayerCapabilities.getIntegerAttribute(urlElement, "height");

            var children = urlElement.children || urlElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Format") {
                    result.format = childElement.textContent;

                } else if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");
                }
            }

            return result;
        };

        WmsLayerCapabilities.assembleUrl = function (urlElement) {
            var result = {};

            var children = urlElement.children || urlElement.childNodes;
            for (var c = 0; c < children.length; c++) {
                var childElement = children[c];

                if (childElement.localName === "Format") {
                    result.format = childElement.textContent;

                } else if (childElement.localName === "OnlineResource") {
                    result.url = childElement.getAttribute("xlink:href");
                }
            }

            return result;
        };

        WmsLayerCapabilities.getIntegerAttribute = function (element, attrName) {
            var result = element.getAttribute(attrName);

            if (result) {
                result = parseInt(result);
            } else {
                result = undefined;
            }

            return result;
        };

        WmsLayerCapabilities.getFloatAttribute = function (element, attrName) {
            var result = element.getAttribute(attrName);

            if (result) {
                result = parseFloat(result);
            } else {
                result = undefined;
            }

            return result;
        };

        return WmsLayerCapabilities;
    }
)
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmsCapabilities
 */
define('ogc/wms/WmsCapabilities',[
        '../../error/ArgumentError',
        '../../util/Logger',
        '../../ogc/wms/WmsLayerCapabilities'
    ],
    function (ArgumentError,
              Logger,
              WmsLayerCapabilities) {
        "use strict";

        /**
         * Constructs an WMS Capabilities instance from an XML DOM.
         * @alias WMSCapabilities
         * @constructor
         * @classdesc Represents a WMS Capabilities document. This object holds as properties all the fields
         * specified in the given WMS Capabilities document. Most fields can be accessed as properties named
         * according to their document names converted to camel case. For example, "version", "service.title",
         * "service.contactInformation.contactPersonPrimary". The exceptions are online resources, whose property
         * path has been shortened. For example "capability.request.getMap.formats" and "capability.request.getMap.getUrl".
         * @param {{}} xmlDom An XML DOM representing the WMS Capabilities document.
         * @throws {ArgumentError} If the specified XML DOM is null or undefined.
         */
        var WmsCapabilities = function (xmlDom) {
            if (!xmlDom) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsCapabilities", "constructor", "No XML DOM specified."));
            }

            this.assembleDocument(xmlDom);
        };

        /**
         * Finds all named layers documented in this WMS capabilities document. Will recursively search sub-layers for
         * named layers.
         * @returns {WmsLayerCapabilities[]}
         */
        WmsCapabilities.prototype.getNamedLayers = function () {
            return this.accumulateNamedLayers(this.capability.layers);
        };

        WmsCapabilities.prototype.accumulateNamedLayers = function (startLayers, namedLayersArray) {
            var namedLayers = namedLayersArray || [];
            
            if (!startLayers) {
                return namedLayers;
            }

            for (var i = 0, len = startLayers.length; i < len; i++) {
                var layer = startLayers[i];
                if (layer.name) {
                    namedLayers.push(layer);
                }
                if (layer.layers) {
                    this.accumulateNamedLayers(layer.layers, namedLayers);
                }
            }

            return namedLayers;
        };

        /**
         * Searches for a named layer matching the provided name and returns the WmsLayerCapabilities object representing 
         * the named layer.
         * @param {String} name the layer name to find
         * @returns {WmsLayerCapabilities} if a matching named layer is found or null
         * @throws {ArgumentError} If the specified name is null or empty.
         */
        WmsCapabilities.prototype.getNamedLayer = function (name) {
            if (!name || (name.length === 0)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsCapabilities", "getNamedLayer", "No WMS layer name provided."));
            }

            var namedLayers = this.getNamedLayers();

            for (var i = 0, len = namedLayers.length; i < len; i++) {
                if (name === namedLayers[i].name) {
                    return namedLayers[i];
                }
            }

            return null;
        };

        WmsCapabilities.prototype.assembleDocument = function (dom) {
            var root = dom.documentElement;

            this.version = root.getAttribute("version");
            this.updateSequence = root.getAttribute("updateSequence");

            var children = root.children || root.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Service") {
                    this.service = this.assembleService(child);
                } else if (child.localName === "Capability") {
                    this.capability = this.assembleCapability(child);
                }
            }
        };

        WmsCapabilities.prototype.assembleService = function (element) {
            var service = {
                capsDoc: this
            };

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Name") {
                    service.name = child.textContent;
                } else if (child.localName === "Title") {
                    service.title = child.textContent;
                } else if (child.localName === "Abstract") {
                    service.abstract = child.textContent;
                } else if (child.localName === "KeywordList") {
                    service.keywordList = this.assembleKeywordList(child);
                } else if (child.localName === "OnlineResource") {
                    service.url = child.getAttribute("xlink:href");
                } else if (child.localName === "Fees") {
                    service.fees = child.textContent;
                } else if (child.localName === "AccessConstraints") {
                    service.accessConstraints = child.textContent;
                } else if (child.localName == "LayerLimit") {
                    service.layerLimit = parseInt(child.textContent);
                } else if (child.localName == "MaxWidth") {
                    service.maxWidth = parseInt(child.textContent);
                } else if (child.localName == "MaxHeight") {
                    service.maxHeight = parseInt(child.textContent);
                } else if (child.localName === "ContactInformation") {
                    service.contactInformation = this.assembleContactInformation(child);
                }
            }

            return service;
        };

        WmsCapabilities.prototype.assembleKeywordList = function (element) {
            var keywords = [];

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Keyword") {
                    keywords.push(child.textContent);
                }
            }

            return keywords;
        };

        WmsCapabilities.prototype.assembleContactInformation = function (element) {
            var contactInfo = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ContactPersonPrimary") {
                    contactInfo.contactPersonPrimary = this.assembleContactPersonPrimary(child);
                } else if (child.localName === "ContactPosition") {
                    contactInfo.contactPosition = child.textContent;
                } else if (child.localName === "ContactVoiceTelephone") {
                    contactInfo.contactVoiceTelephone = child.textContent;
                } else if (child.localName === "ContactFacsimileTelephone") {
                    contactInfo.contactFacsimileTelephone = child.textContent;
                } else if (child.localName === "ContactElectronicMailAddress") {
                    contactInfo.contactElectronicMailAddress = child.textContent;
                } else if (child.localName === "ContactAddress") {
                    contactInfo.contactAddress = this.assembleContactAddress(child);
                }
            }

            return contactInfo;
        };

        WmsCapabilities.prototype.assembleContactPersonPrimary = function (element) {
            var info = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ContactPerson") {
                    info.contactPerson = child.textContent;
                } else if (child.localName === "ContactOrganization") {
                    info.contactOrganization = child.textContent;
                }
            }

            return info;
        };

        WmsCapabilities.prototype.assembleContactAddress = function (element) {
            var address = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "AddressType") {
                    address.addressType = child.textContent;
                } else if (child.localName === "Address") {
                    address.address = child.textContent;
                } else if (child.localName === "City") {
                    address.city = child.textContent;
                } else if (child.localName === "StateOrProvince") {
                    address.stateOrProvince = child.textContent;
                } else if (child.localName === "PostCode") {
                    address.postCode = child.textContent;
                } else if (child.localName === "Country") {
                    address.country = child.textContent;
                }
            }

            return address;
        };

        WmsCapabilities.prototype.assembleCapability = function (element) {
            var capability = {
                capsDoc: this
            };

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Request") {
                    capability.request = this.assembleRequests(child);
                } else if (child.localName === "Exception") {
                    capability.exception = this.assembleException(child);
                } else if (child.localName === "Layer") {
                    capability.layers = capability.layers || [];
                    capability.layers.push(new WmsLayerCapabilities(child, capability));
                }
            }

            return capability;
        };

        WmsCapabilities.prototype.assembleRequests = function (element) {
            var requests = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "GetCapabilities") {
                    requests.getCapabilities = this.assembleRequest(child);
                } else if (child.localName === "GetMap") {
                    requests.getMap = this.assembleRequest(child);
                } else if (child.localName === "GetFeatureInfo") {
                    requests.getFeatureInfo = this.assembleRequest(child);
                }
            }

            return requests;
        };

        WmsCapabilities.prototype.assembleRequest = function (element) {
            var request = {
                name: element.localName
            };

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Format") {
                    request.formats = request.formats || [];
                    request.formats.push(child.textContent);
                } else if (child.localName === "DCPType") {
                    var children2 = child.children || child.childNodes;
                    for (var c2 = 0; c2 < children2.length; c2++) {
                        var child2 = children2[c2];
                        if (child2.localName === "HTTP") {
                            var children3 = child2.children || child2.childNodes;
                            for (var c3 = 0; c3 < children3.length; c3++) {
                                var child3 = children3[c3];
                                if (child3.localName === "Get") {
                                    var children4 = child3.children || child3.childNodes;
                                    for (var c4 = 0; c4 < children4.length; c4++) {
                                        var child4 = children4[c4];
                                        if (child4.localName === "OnlineResource") {
                                            request.getUrl = child4.getAttribute("xlink:href");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return request;
        };

        WmsCapabilities.prototype.assembleException = function (element) {
            var exception = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Format") {
                    exception.formats = exception.formats || [];
                    exception.formats.push(child.textContent);
                }
            }

            return exception;
        };

        return WmsCapabilities;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmsLayer
 */
define('layer/WmsLayer',[
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../util/PeriodicTimeSequence',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/WmsUrlBuilder'
    ],
    function (ArgumentError,
              Location,
              Logger,
              PeriodicTimeSequence,
              Sector,
              TiledImageLayer,
              WmsUrlBuilder) {
        "use strict";

        /**
         * Constructs a WMS image layer.
         * @alias WmsLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a WMS image layer.
         * @param {{}} config Specifies configuration information for the layer. Must contain the following
         * properties:
         * <ul>
         *     <li>service: {String} The URL of the WMS server.</li>
         *     <li>layerNames: {String} A comma separated list of the names of the WMS layers to include in this layer.</li>
         *     <li>sector: {Sector} The sector spanned by this layer.</li>
         *     <li>levelZeroDelta: {Location} The level-zero tile delta to use for this layer.</li>
         *     <li>numLevels: {Number} The number of levels to make for this layer.</li>
         *     <li>format: {String} The mime type of the image format to request, e.g., image/png.</li>
         *     <li>size: {Number} The size in pixels of tiles for this layer.</li>
         *     <li>coordinateSystem (optional): {String} The coordinate system to use for this layer, e.g., EPSG:4326.</li>
         *     <li>styleNames (optional): {String} A comma separated list of the styles to include in this layer.</li>
         * </ul>
         * The function [WmsLayer.formLayerConfiguration]{@link WmsLayer#formLayerConfiguration} will create an
         * appropriate configuration object given a {@link WmsLayerCapabilities} object.
         * @param {String} timeString The time parameter passed to the WMS server when imagery is requested. May be
         * null, in which case no time parameter is passed to the server.
         * @throws {ArgumentError} If the specified configuration is null or undefined.
         */
        var WmsLayer = function (config, timeString) {
            if (!config) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsLayer", "constructor", "No configuration specified."));
            }

            var cachePath = config.service + config.layerNames + config.styleNames;
            if (timeString) {
                cachePath = cachePath + timeString;
            }

            TiledImageLayer.call(this, config.sector, config.levelZeroDelta, config.numLevels, config.format,
                cachePath, config.size, config.size);

            this.displayName = config.title;
            this.pickEnabled = false;

            this.urlBuilder = new WmsUrlBuilder(config.service, config.layerNames, config.styleNames, config.version,
                timeString);
            if (config.coordinateSystem) {
                this.urlBuilder.crs = config.coordinateSystem;
            }

            /**
             * The time string passed to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.timeString = timeString;
        };

        WmsLayer.prototype = Object.create(TiledImageLayer.prototype);

        /**
         * Forms a configuration object for a specified {@link WmsLayerCapabilities} layer description. The
         * configuration object created and returned is suitable for passing to the WmsLayer constructor.
         * <p>
         *     This method also parses any time dimensions associated with the layer and returns them in the
         *     configuration object's "timeSequences" property. This property is a mixed array of Date objects
         *     and {@link PeriodicTimeSequence} objects describing the dimensions found.
         * @param wmsLayerCapabilities {WmsLayerCapabilities} The WMS layer capabilities to create a configuration for.
         * @returns {{}} A configuration object.
         * @throws {ArgumentError} If the specified WMS layer capabilities is null or undefined.
         */
        WmsLayer.formLayerConfiguration = function (wmsLayerCapabilities) {
            var config = {
                title: wmsLayerCapabilities.title,
                version: wmsLayerCapabilities.capability.capsDoc.version
            };

            // Determine the layer's sector.
            var bbox = wmsLayerCapabilities.geographicBoundingBox || wmsLayerCapabilities.latLonBoundingBox;
            if (bbox && bbox.westBoundLongitude) {
                config.sector = new Sector(bbox.southBoundLatitude, bbox.northBoundLatitude,
                    bbox.westBoundLongitude, bbox.eastBoundLongitude);
            } else if (bbox && bbox.minx) {
                config.sector = new Sector(bbox.miny, bbox.maxy, bbox.minx, bbox.maxx);
            } else {
                config.sector = Sector.FULL_SPHERE;
            }

            // Determine level 0 delta.
            config.levelZeroDelta = new Location(36, 36); // TODO: How to determine best delta

            // Determine number of levels.
            config.numLevels = 19; // TODO: How to determine appropriate num levels

            config.size = 256;

            // Assign layer name.
            config.layerNames = wmsLayerCapabilities.name;

            // Determine image format
            var getMapInfo = wmsLayerCapabilities.capability.request.getMap,
                formats = getMapInfo.formats;

            if (formats.indexOf("image/png") >= 0) {
                config.format = "image/png";
            } else if (formats.indexOf("image/jpeg") >= 0) {
                config.format = "image/jpeg";
            } else if (formats.indexOf("image/tiff") >= 0) {
                config.format = "image/tiff";
            } else if (formats.indexOf("image/gif") >= 0) {
                config.format = "image/gif";
            }

            // Determine the GetMap service address.
            config.service = getMapInfo.getUrl;

            // Determine the coordinate system to use.
            var coordinateSystems = wmsLayerCapabilities.crses; // WMS 1.3.0 and greater
            if (!coordinateSystems) {
                coordinateSystems = wmsLayerCapabilities.srses; // WMS 1.1.1 and lower
            }

            if (coordinateSystems) {
                if ((coordinateSystems.indexOf("EPSG:4326") >= 0) || (coordinateSystems.indexOf("epsg:4326") >= 0)) {
                    config.coordinateSystem = "EPSG:4326";
                } else if ((coordinateSystems.indexOf("CRS84") >= 0) || (coordinateSystems.indexOf("CRS:84") >= 0)) {
                    config.coordinateSystem = "CRS:84";
                }
            }

            var dimensions = WmsLayer.parseTimeDimensions(wmsLayerCapabilities);
            if (dimensions && dimensions.length > 0) {
                config.timeSequences = dimensions;
            }

            return config;
        };

        WmsLayer.parseTimeDimensions = function (wmsLayerCapabilities) {
            var dimensions = wmsLayerCapabilities.extents || wmsLayerCapabilities.dimensions,
                parsedDimensions = null;

            if (dimensions) {
                parsedDimensions = [];

                for (var i = 0; i < dimensions.length; i++) {
                    var dimension = dimensions[i];

                    if (dimension.name.toLowerCase() === "time" &&
                        (!dimension.units || dimension.units.toLowerCase() === "iso8601")) {
                        var individualDimensions = dimension.content.split(",");

                        for (var j = 0; j < individualDimensions.length; j++) {
                            var individualDimension = individualDimensions[j],
                                splitDimension = individualDimension.split("/");

                            if (splitDimension.length === 1) {
                                parsedDimensions.push(new Date(individualDimension));
                            } else if (splitDimension.length === 3) {
                                parsedDimensions.push(new PeriodicTimeSequence(individualDimension));
                            }
                        }
                    }
                }
            }

            return parsedDimensions;
        };

        return WmsLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmsTimeDimensionedLayer
 */
define('layer/WmsTimeDimensionedLayer',[
        '../error/ArgumentError',
        '../layer/Layer',
        '../util/Logger',
        '../layer/WmsLayer'
    ],
    function (ArgumentError,
              Layer,
              Logger,
              WmsLayer) {
        "use strict";

        /**
         * Constructs a WMS time-dimensioned image layer.
         * @alias WmsTimeDimensionedLayer
         * @constructor
         * @augments Layer
         * @classdesc Displays a time-series WMS image layer. This layer contains a collection of {@link WmsLayer}s,
         * each representing a different time in a time sequence. Only the layer indicated by this layer's
         * [time]{@link WmsTimeDimensionedLayer#time} property is displayed during any frame.
         * @param {{}} config Specifies configuration information for the layer.
         * See the constructor description for {@link WmsLayer} for a description of the required properties.
         * @throws {ArgumentError} If the specified configuration is null or undefined.
         */
        var WmsTimeDimensionedLayer = function (config) {
            if (!config) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsTimeDimensionedLayer", "constructor",
                        "No configuration specified."));
            }

            Layer.call(this, "WMS Time Dimensioned Layer");

            /**
             * The configuration object specified at construction.
             * @type {{}}
             * @readonly
             */
            this.config = config;

            // Intentionally not documented.
            this.displayName = config.title;
            this.pickEnabled = false;

            // Intentionally not documented. Contains the lazily loaded list of sub-layers.
            this.layers = {};
        };

        WmsTimeDimensionedLayer.prototype = Object.create(Layer.prototype);

        WmsTimeDimensionedLayer.prototype.doRender = function (dc) {
            if (this.time) {
                var currentTimeString = this.time.toISOString(),
                    layer = this.layers[currentTimeString];

                if (!layer) {
                    layer = new WmsLayer(this.config, currentTimeString);
                    this.layers[currentTimeString] = layer;
                }

                layer.opacity = this.opacity;
                layer.doRender(dc);

                this.inCurrentFrame = layer.inCurrentFrame;
            }
        };

        return WmsTimeDimensionedLayer;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmtsLayerCapabilities
 */
define('ogc/wmts/WmtsLayerCapabilities',[
        '../../error/ArgumentError',
        '../../geom/Sector',
        '../../ogc/wmts/OwsDescription',
        '../../util/Logger'
    ],
    function (ArgumentError,
              Sector,
              OwsDescription,
              Logger) {
        "use strict";

        /**
         * Constructs an WMTS Layer instance from an XML DOM.
         * @alias WmtsLayerCapabilities
         * @constructor
         * @classdesc Represents a WMTS layer description from a WMTS Capabilities document. This object holds all the
         * fields specified in the associated WMTS Capabilities document.
         * @param {{}} layerElement A WMTS Layer element describing the layer.
         * @param {{}} capabilities The WMTS capabilities documented containing this layer.
         * @throws {ArgumentError} If the specified layer element is null or undefined.
         */
        var WmtsLayerCapabilities = function (layerElement, capabilities) {
            if (!layerElement) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayerCapabilities", "constructor", "missingDomElement"));
            }

            OwsDescription.call(this, layerElement);

            /**
             * This layer's WMTS capabilities document, as specified to the constructor of this object.
             * @type {{}}
             * @readonly
             */
            this.capabilities = capabilities;

            /**
             * The identifier of this layer description.
             * @type {String}
             * @readonly
             */
            this.identifier;

            /**
             * The titles of this layer.
             * @type {String[]}
             * @readonly
             */
            this.title;

            /**
             * The abstracts of this layer.
             * @type {String[]}
             * @readonly
             */
            this.abstract;

            /**
             * The list of keywords associated with this layer description.
             * @type {String[]}
             * @readonly
             */
            this.keywords;

            /**
             * The WGS84 bounding box associated with this layer. The returned object has the following properties:
             * "lowerCorner", "upperCorner".
             * @type {{}}
             * @readonly
             */
            this.wgs84BoundingBox;

            /**
             * The bounding boxes associated with this layer. The returned array contains objects with the following
             * properties: TODO
             * @type {Object[]}
             * @readonly
             */
            this.boundingBox;

            /**
             * The list of styles associated with this layer description, accumulated from this layer and its parent
             * layers. Each object returned may have the following properties: name {String}, title {String},
             * abstract {String}, legendUrls {Object[]}, styleSheetUrl, styleUrl. Legend urls may have the following
             * properties: width, height, format, url. Style sheet urls and style urls have the following properties:
             * format, url.
             * @type {Object[]}
             * @readonly
             */
            this.styles;

            /**
             * The formats supported by this layer.
             * @type {String[]}
             * @readonly
             */
            this.formats;

            /**
             * The Feature Info formats supported by this layer.
             * @type {String[]}
             * @readonly
             */
            this.infoFormat;

            /**
             * The dimensions associated with this layer. The returned array contains objects with the following
             * properties:
             * @type {Object[]}
             * @readonly
             */
            this.dimension;

            /**
             * The metadata associated with this layer description. Each object in the returned array has the
             * following properties: type, format, url.
             * @type {Object[]}
             * @readonly
             */
            this.metadata;

            /**
             * The tile matris sets associated with this layer.
             * @type {Object[]}
             * @readonly
             */
            this.tileMatrixSetLink;

            /**
             * The resource URLs associated with this layer description. Each object in the returned array has the
             * following properties: format, url.
             * @type {Object[]}
             * @readonly
             */
            this.resourceUrl;

            this.assembleLayer(layerElement);
        };

        WmtsLayerCapabilities.prototype = Object.create(OwsDescription.prototype);

        /**
         * Provides an array of the TileMatrixSet objects supported by this layer.
         * @returns {Array}
         */
        WmtsLayerCapabilities.prototype.getLayerSupportedTileMatrixSets = function () {
            var tileMatrixSets = [];

            for (var i = 0, lenA = this.tileMatrixSetLink.length; i < lenA; i++) {
                var supportedTileMatrixSetIdentifier = this.tileMatrixSetLink[i].tileMatrixSet;
                for (var j = 0, lenB = this.capabilities.contents.tileMatrixSet.length; j < lenB; j++) {
                    var tileMatrixSetIdentifier = this.capabilities.contents.tileMatrixSet[j].identifier;
                    if (tileMatrixSetIdentifier === supportedTileMatrixSetIdentifier) {
                        tileMatrixSets.push(this.capabilities.contents.tileMatrixSet[j]);
                    }
                }
            }

            return tileMatrixSets;
        };

        WmtsLayerCapabilities.prototype.assembleLayer = function (element) {
            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    this.identifier = child.textContent;
                } else if (child.localName === "WGS84BoundingBox") {
                    this.wgs84BoundingBox = WmtsLayerCapabilities.assembleBoundingBox(child);
                } else if (child.localName === "BoundingBox") {
                    this.boundingBox = this.boundingBox || [];
                    this.boundingBox.push(WmtsLayerCapabilities.assembleBoundingBox(child));
                } else if (child.localName === "Style") {
                    this.style = this.style || [];
                    this.style.push(WmtsLayerCapabilities.assembleStyle(child));
                } else if (child.localName === "Format") {
                    this.format = this.format || [];
                    this.format.push(child.textContent);
                } else if (child.localName === "InfoFormat") {
                    this.infoFormat = this.infoFormat || [];
                    this.infoFormat.push(child.textContent);
                } else if (child.localName === "Dimension") {
                    this.dimension = this.dimension || [];
                    this.dimension.push(WmtsLayerCapabilities.assembleDimension(child));
                } else if (child.localName === "Metadata") {
                    this.metadata = this.metadata || [];
                    this.metadata.push(WmtsLayerCapabilities.assembleMetadata(child));
                } else if (child.localName === "ResourceURL") {
                    this.resourceUrl = this.resourceUrl || [];
                    this.resourceUrl.push(WmtsLayerCapabilities.assembleResourceUrl(child));
                } else if (child.localName === "TileMatrixSetLink") {
                    this.tileMatrixSetLink = this.tileMatrixSetLink || [];
                    this.tileMatrixSetLink.push(WmtsLayerCapabilities.assembleTileMatrixSetLink(child));
                }
            }

        };

        WmtsLayerCapabilities.assembleStyle = function (element) {
            var result = new OwsDescription(element);

            result.isDefault = element.getAttribute("isDefault");

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    result.identifier = child.textContent;
                } else if (child.localName === "LegendURL") {
                    result.legendUrl = result.legendUrl || [];
                    result.legendUrl.push(WmtsLayerCapabilities.assembleLegendUrl(child));
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleBoundingBox = function (element) {
            var result = {};

            var crs = element.getAttribute("crs");
            if (crs) {
                result.crs = crs;
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "LowerCorner") {
                    var lc = child.textContent.split(" ");
                    result.lowerCorner = [parseFloat(lc[0]), parseFloat(lc[1])];
                } else if (child.localName === "UpperCorner") {
                    var uc = child.textContent.split(" ");
                    result.upperCorner = [parseFloat(uc[0]), parseFloat(uc[1])];
                }
            }

            // Add a utility which provides a Sector based on the WGS84BoundingBox element
            if (element.localName === "WGS84BoundingBox") {
                result.getSector = function () {
                    return new Sector(result.lowerCorner[1], result.upperCorner[1], result.lowerCorner[0], result.upperCorner[0]);
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleDimension = function (element) {
            var result = new OwsDescription(element);

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    result.identifier = child.textContent;
                } else if (child.localName === "UOM") {
                    result.uom = {
                        name: child.getAttribute("name"),
                        reference: child.getAttribute("reference")
                    }
                } else if (child.localName == "UnitSymbol") {
                    result.unitSymbol = child.textContent;
                } else if (child.localName === "Default") {
                    result.default = child.textContent;
                } else if (child.localName === "Current") {
                    result.current = (child.textContent === "true");
                } else if (child.localName === "Value") {
                    result.value = result.value || [];
                    result.value.push(child.textContent);
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleMetadata = function (element) { // TODO
            var result = {};

            var link = element.getAttribute("xlink:href");
            if (link) {
                result.url = link;
            }

            var about = element.getAttribute("about");
            if (link) {
                result.about = about;
            }

            var type = element.getAttribute("xlink:type");
            if (type) {
                result.type = type;
            }

            var role = element.getAttribute("xlink:role");
            if (role) {
                result.role = role;
            }

            var title = element.getAttribute("xlink:title");
            if (title) {
                result.title = title;
            }

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Metadata") {
                    result.metadata = WmsLayerCapabilities.assembleMetadata(child);
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleResourceUrl = function (element) {
            var result = {};

            result.format = element.getAttribute("format");
            result.resourceType = element.getAttribute("resourceType");
            result.template = element.getAttribute("template");

            return result;
        };

        WmtsLayerCapabilities.assembleLegendUrl = function (element) {
            var result = {};

            result.format = element.getAttribute("format");
            result.minScaleDenominator = element.getAttribute("minScaleDenominator");
            result.maxScaleDenominator = element.getAttribute("maxScaleDenominator");
            result.href = element.getAttribute("xlink:href");
            result.width = element.getAttribute("width");
            result.height = element.getAttribute("height");

            return result;
        };

        WmtsLayerCapabilities.assembleTileMatrixSetLink = function (element) {
            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "TileMatrixSet") {
                    result.tileMatrixSet = child.textContent;
                } else if (child.localName === "TileMatrixSetLimits") {
                    result.tileMatrixSetLimits = WmtsLayerCapabilities.assembleTileMatrixSetLimits(child);
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleTileMatrixSetLimits = function (element) {
            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "TileMatrixLimits") {
                    result.tileMatrixLimits = result.tileMatrixLimits || [];
                    result.tileMatrixLimits.push(WmtsLayerCapabilities.assembleTileMatrixLimits(child));
                }
            }

            return result;
        };

        WmtsLayerCapabilities.assembleTileMatrixLimits = function (element) {
            var result = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "TileMatrix") {
                    result.tileMatrix = child.textContent;
                } else if (child.localName === "MinTileRow") {
                    result.minTileRow = parseInt(child.textContent);
                } else if (child.localName === "MaxTileRow") {
                    result.maxTileRow = parseInt(child.textContent);
                } else if (child.localName === "MinTileCol") {
                    result.minTileCol = parseInt(child.textContent);
                } else if (child.localName === "maxTileCol") {
                    result.maxTileCol = parseInt(child.textContent);
                }
            }

            return result;
        };

        return WmtsLayerCapabilities;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmtsCapabilities
 */
define('ogc/wmts/WmtsCapabilities',[
        '../../error/ArgumentError',
        '../../util/Logger',
        '../../ogc/wmts/OwsDescription',
        '../../ogc/wmts/OwsLanguageString',
        '../../ogc/wmts/OwsOperationsMetadata',
        '../../ogc/wmts/OwsServiceIdentification',
        '../../ogc/wmts/OwsServiceProvider',
        '../../ogc/wms/WmsCapabilities',
        '../../ogc/wmts/WmtsLayerCapabilities'
    ],
    function (ArgumentError,
              Logger,
              OwsDescription,
              OwsLanguageString,
              OwsOperationsMetadata,
              OwsServiceIdentification,
              OwsServiceProvider,
              WmsCapabilities,
              WmtsLayerCapabilities) {
        "use strict";

        /**
         * Constructs an OGC WMTS capabilities document from an XML DOM.
         * @alias WmtsCapabilities
         * @constructor
         * @classdesc Represents an OGC WMTS capabilities document.
         * This object holds as properties all the fields specified in the OGC WMTS capabilities document.
         * Most fields can be accessed as properties named according to their document names converted to camel case.
         * For example, "serviceIdentification" and "contents".
         * @param {{}} xmlDom An XML DOM representing the OGC WMTS capabilities document.
         * @throws {ArgumentError} If the specified XML DOM is null or undefined.
         */
        var WmtsCapabilities = function (xmlDom) {
            if (!xmlDom) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsCapabilities", "constructor", "No XML DOM specified."));
            }

            this.assembleDocument(xmlDom);
        };

        /**
         * Provides all of the layers associated with this WMTS. This method is for convienence and returns the layer
         * array captured in the contents of this WmtsCapabilities object.
         * @returns {WmtsLayerCapabilities[]}
         */
        WmtsCapabilities.prototype.getLayers = function () {
            return this.contents.layer;
        };

        /**
         * Retrieve the WmtsLayerCapabilities object for the provided identifier.
         * @param identifier
         * @returns {WmtsLayerCapabilities} object for the provided identifier or null if no identifier was found in the
         * WmtsCapabilities object.
         * @throws {ArgumentError} If the specified identifier is null or undefined.
         */
        WmtsCapabilities.prototype.getLayer = function (identifier) {
            if (!identifier) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsCapabilities", "getLayer", "empty identifier"));
            }

            for (var i = 0, len = this.contents.layer.length; i < len; i++) {
                var wmtsLayerCapabilities = this.contents.layer[i];
                if (wmtsLayerCapabilities.identifier === identifier) {
                    return wmtsLayerCapabilities;
                }
            }

            return null;
        };

        WmtsCapabilities.prototype.assembleDocument = function (dom) {
            var root = dom.documentElement;

            this.version = root.getAttribute("version");
            this.updateSequence = root.getAttribute("updateSequence");

            var children = root.children || root.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "ServiceIdentification") {
                    this.serviceIdentification = new OwsServiceIdentification(child);
                } else if (child.localName === "ServiceProvider") {
                    this.serviceProvider = new OwsServiceProvider(child);
                } else if (child.localName === "OperationsMetadata") {
                    this.operationsMetadata = new OwsOperationsMetadata(child);
                } else if (child.localName === "Contents") {
                    this.contents = this.assembleContents(child);
                } else if (child.localName === "Themes") {
                    this.themes = WmtsCapabilities.assembleThemes(child);
                } else if (child.localName === "ServiceMetadataURL") {
                    this.serviceMetadataUrls = this.serviceMetadataUrls || [];
                    this.serviceMetadataUrls.push(WmtsCapabilities.assembleServiceMetadataURL(child));
                }
            }
        };

        WmtsCapabilities.prototype.assembleContents = function (element) {
            var contents = {};

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Layer") {
                    contents.layer = contents.layer || [];
                    try {
                        contents.layer.push(new WmtsLayerCapabilities(child, this));
                    } catch (e) {
                        Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsCapabilities", "constructor",
                            "Exception reading WMTS layer description: " + e.message);
                    }
                } else if (child.localName === "TileMatrixSet") {
                    contents.tileMatrixSet = contents.tileMatrixSet || [];
                    try {
                        contents.tileMatrixSet.push(WmtsCapabilities.assembleTileMatrixSet(child));
                    } catch (e) {
                        Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsCapabilities", "constructor",
                            "Exception reading WMTS tile matrix set description: " + e.message);
                    }
                }
                // TODO: OtherSource
            }

            return contents;
        };

        WmtsCapabilities.assembleTileMatrixSet = function (element) {
            var tileMatrixSet = new OwsDescription(element);

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    tileMatrixSet.identifier = child.textContent;
                } else if (child.localName === "SupportedCRS") {
                    tileMatrixSet.supportedCRS = child.textContent;
                } else if (child.localName === "WellKnownScaleSet") {
                    tileMatrixSet.wellKnownScaleSet = child.textContent;
                } else if (child.localName === "BoundingBox") {
                    tileMatrixSet.boundingBox = WmtsLayerCapabilities.assembleBoundingBox(child);
                } else if (child.localName === "TileMatrix") {
                    tileMatrixSet.tileMatrix = tileMatrixSet.tileMatrix || [];
                    tileMatrixSet.tileMatrix.push(WmtsCapabilities.assembleTileMatrix(child));
                }
            }

            WmtsCapabilities.sortTileMatrices(tileMatrixSet);

            for (var i = 0; i < tileMatrixSet.tileMatrix.length; i++) {
                tileMatrixSet.tileMatrix[i].levelNumber = i;
            }

            return tileMatrixSet;
        };

        WmtsCapabilities.assembleTileMatrix = function (element) {
            var tileMatrix = new OwsDescription(element);

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    tileMatrix.identifier = child.textContent;
                } else if (child.localName === "ScaleDenominator") {
                    tileMatrix.scaleDenominator = parseFloat(child.textContent);
                } else if (child.localName === "TileWidth") {
                    tileMatrix.tileWidth = parseFloat(child.textContent);
                } else if (child.localName === "TileHeight") {
                    tileMatrix.tileHeight = parseFloat(child.textContent);
                } else if (child.localName === "MatrixWidth") {
                    tileMatrix.matrixWidth = parseFloat(child.textContent);
                } else if (child.localName === "MatrixHeight") {
                    tileMatrix.matrixHeight = parseFloat(child.textContent);
                } else if (child.localName === "TopLeftCorner") {
                    var values = child.textContent.split(" ");
                    tileMatrix.topLeftCorner = [parseFloat(values[0]), parseFloat(values[1])];
                }
            }

            return tileMatrix;
        };

        WmtsCapabilities.assembleThemes = function (element) {
            var themes;

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];
                if (child.localName === "Theme") {
                    themes = themes || [];
                    themes.push(WmtsCapabilities.assembleTheme(child));
                }
            }

            return themes;
        };

        WmtsCapabilities.assembleTheme = function (element) {
            var theme = new OwsDescription(element);

            var children = element.children || element.childNodes;
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Identifier") {
                    theme.identifier = child.textContent;
                } else if (child.localName === "LayerRef") {
                    theme.layerRef = theme.layerRef || [];
                    theme.layerRef.push(child.textContent);
                } else if (child.localName === "Theme") {
                    theme.themes = theme.themes || [];
                    theme.themes.push(WmtsCapabilities.assembleTheme(child));
                }
            }

            return theme;
        };

        WmtsCapabilities.assembleServiceMetadataURL = function (element) {
            var result = {};

            var link = element.getAttribute("xlink:href");
            if (link) {
                result.url = link;
            }

            return result;
        };

        /**
         * Sorts a tile matrix set by the tile matrices scale denominator.
         * @param tileMatrixSet
         */
        WmtsCapabilities.sortTileMatrices = function (tileMatrixSet) {
            // This operation is not required by the WMTS specification. The WMTS specification assumes Tile Matrix
            // selection based on a scale denominator value. Web WorldWind currently matches the tile's Level to the
            // corresponding Tile Matrix index in the Tile Matrix Set. If the Tile Matrices are not ordered in a
            // typical pyramid fashion, this could result in undefined behavior. Sorting the matrices by the scale
            // denominator should ensure the WorldWind Level will match the Tile Matrix index. This operation will not
            // be required once a system which matches the scale denominator is implemented.
            tileMatrixSet.tileMatrix.sort(function (a, b) {
                return b.scaleDenominator - a.scaleDenominator;
            });
        };

        WmtsCapabilities.prototype.getGetTileKvpAddress = function () {
            for (var i = 0; i < this.operationsMetadata.operation.length; i++) {
                var operation = this.operationsMetadata.operation[i];
                if (operation.name === "GetTile") {
                    return operation.dcp[0].getMethods[0].url;
                }
            }

            return null;
        };

        return WmtsCapabilities;
    })
;
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('layer/WmtsLayerTile',[
        '../geom/Angle',
        '../error/ArgumentError',
        '../geom/BoundingBox',
        '../util/Logger',
        '../geom/Vec3',
        '../util/WWUtil'
    ],
    function (Angle,
              ArgumentError,
              BoundingBox,
              Logger,
              Vec3,
              WWUtil) {
        "use strict";

        // This is an internal class and is intentionally not documented.
        var WmtsLayerTile = function (sector, tileMatrix, row, column, imagePath) {
            this.sector = sector;
            this.tileMatrix = tileMatrix;
            this.row = row;
            this.column = column;
            this.imagePath = imagePath;

            this.texelSize = (sector.deltaLatitude() * Angle.DEGREES_TO_RADIANS) / tileMatrix.tileHeight;

            this.tileKey = tileMatrix.levelNumber.toString() + "." + row.toString() + "." + column.toString();

            this.gpuCacheKey = imagePath;
        };

        WmtsLayerTile.prototype.isEqual = function (that) {
            if (!that)
                return false;

            if (!that.tileKey)
                return false;

            return this.tileKey == that.tileKey;
        };

        WmtsLayerTile.prototype.distanceTo = function (vector) {
            if (!vector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "distanceTo", "missingVector"));
            }

            var px = vector[0], py = vector[1], pz = vector[2],
                dx, dy, dz,
                points = this.samplePoints,
                distance = Number.POSITIVE_INFINITY;

            for (var i = 0, len = points.length; i < len; i += 3) {
                dx = px - points[i];
                dy = py - points[i + 1];
                dz = pz - points[i + 2];
                distance = Math.min(distance, dx * dx + dy * dy + dz * dz); // minimum squared distance
            }

            return Math.sqrt(distance);
        };

        WmtsLayerTile.prototype.subdivide = function (tileMatrix, tileFactory) {
            if (!tileMatrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayerTile", "subdivide",
                        "The specified tile matrix is null or undefined."));
            }

            if (!tileFactory) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayerTile", "subdivide",
                        "The specified tile factory is null or undefined."));
            }

            var subRow,
                subCol,
                children = [],
                subFactorLat = tileMatrix.matrixHeight / this.tileMatrix.matrixHeight,
                subFactorLon = tileMatrix.matrixWidth / this.tileMatrix.matrixWidth;

            for (var i = 0; i < subFactorLat; i++) {
                for (var j = 0; j < subFactorLon; j++) {
                    subRow = subFactorLat * this.row + i;
                    subCol = subFactorLon * this.column + j;

                    children.push(tileFactory.createTile(tileMatrix, subRow, subCol));
                }
            }

            return children;
        };

        WmtsLayerTile.prototype.subdivideToCache = function (tileMatrix, tileFactory, cache) {
            if (!tileMatrix) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivideToCache",
                        "The specified tile matrix is null or undefined."));
            }

            if (!tileFactory) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "subdivideToCache",
                        "The specified tile factory is null or undefined."));
            }

            var childList = cache ? cache.entryForKey(this.tileKey) : null;
            if (!childList) {
                childList = this.subdivide(tileMatrix, tileFactory);
                if (childList && cache) {
                    cache.putEntry(this.tileKey, childList, childList.length);
                }
            }

            return childList;
        };

        WmtsLayerTile.prototype.mustSubdivide = function (dc, detailFactor) {
            var cellSize = dc.globe.equatorialRadius * this.texelSize,
                distance = this.distanceTo(dc.navigatorState.eyePoint),
                pixelSize = dc.navigatorState.pixelSizeAtDistance(distance);

            return cellSize > Math.max(detailFactor * pixelSize, 0.5);
        };

        WmtsLayerTile.prototype.update = function (dc) {
            var elevationTimestamp = dc.globe.elevationTimestamp(),
                verticalExaggeration = dc.verticalExaggeration,
                globeStateKey = dc.globeStateKey;

            if (this.updateTimestamp != elevationTimestamp
                || this.updateVerticalExaggeration != verticalExaggeration
                || this.updateGlobeStateKey != globeStateKey) {

                this.doUpdate(dc);
                dc.frameStatistics.incrementTileUpdateCount(1);

                // Set the geometry extent to the globe's elevation timestamp on which the geometry is based. This
                // ensures that the geometry timestamp can be reliably compared to the elevation timestamp in subsequent
                // frames.
                this.updateTimestamp = elevationTimestamp;
                this.updateVerticalExaggeration = verticalExaggeration;
                this.updateGlobeStateKey = globeStateKey;
            }
        };

        WmtsLayerTile.prototype.doUpdate = function (dc) {
            // Compute the minimum and maximum world coordinate height for this tile's sector by multiplying the minimum
            // and maximum elevations by the scene's vertical exaggeration. This ensures that the elevations to used
            // build the terrain are contained by this tile's extent. Use zero if the globe as no elevations in this
            // tile's sector.
            var globe = dc.globe,
                verticalExaggeration = dc.verticalExaggeration,
                extremes = globe.minAndMaxElevationsForSector(this.sector),
                minHeight = extremes ? (extremes[0] * verticalExaggeration) : 0,
                maxHeight = extremes ? (extremes[1] * verticalExaggeration) : 0;

            if (minHeight == maxHeight) {
                minHeight = maxHeight + 10; // TODO: Determine if this is necessary.
            }

            // Compute a bounding box for this tile that contains the terrain surface in the tile's coverage area.
            if (!this.extent) {
                this.extent = new BoundingBox();
            }
            this.extent.setToSector(this.sector, globe, minHeight, maxHeight);

            // Compute the cartesian points for a 3x3 geographic grid. This grid captures sufficiently close sample
            // points in order to estimate the distance from the viewer to this tile.
            if (!this.samplePoints) {
                this.sampleElevations = new Float64Array(9);
                this.samplePoints = new Float64Array(3 * this.sampleElevations.length);
            }
            WWUtil.fillArray(this.sampleElevations, 0.5 * (minHeight + maxHeight));
            globe.computePointsForGrid(this.sector, 3, 3, this.sampleElevations, Vec3.ZERO, this.samplePoints);

            // Compute the reference point used as a local coordinate origin for the tile.
            if (!this.referencePoint) {
                this.referencePoint = new Vec3(0, 0, 0);
            }

            globe.computePointFromPosition(this.sector.centroidLatitude(), this.sector.centroidLongitude(), 0,
                this.referencePoint);
        };

        WmtsLayerTile.prototype.bind = function (dc) {
            var texture = dc.gpuResourceCache.resourceForKey(this.gpuCacheKey);

            if (texture && texture.bind(dc)) {
                return true;
            }

            if (this.fallbackTile) {
                return this.fallbackTile.bind(dc);
            }

            return false;
        };

        WmtsLayerTile.prototype.applyInternalTransform = function (dc, matrix) {
            // This type of tile does not apply an internal transform.
        };

        return WmtsLayerTile;
    });
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WmtsLayer
 */
define('layer/WmtsLayer',[
        '../util/AbsentResourceList',
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Sector',
        '../layer/Layer',
        '../cache/MemoryCache',
        '../render/Texture',
        '../util/WmsUrlBuilder',
        '../layer/WmtsLayerTile',
        '../util/WWMath',
        '../util/WWUtil'
    ],
    function (AbsentResourceList,
              ArgumentError,
              Logger,
              Sector,
              Layer,
              MemoryCache,
              Texture,
              WmsUrlBuilder,
              WmtsLayerTile,
              WWMath,
              WWUtil) {
        "use strict";

        // TODO: Test Mercator layers.
        // TODO: Support tile matrix limits.
        // TODO: Extensibility for other projections.
        // TODO: Finish parsing capabilities document (ServiceIdentification and ServiceProvider).
        // TODO: Time dimensions.

        /**
         * Constructs a WMTS image layer.
         * @alias WmtsLayer
         * @constructor
         * @augments Layer
         * @classdesc Displays a WMTS image layer.
         * @param {{}} config Specifies configuration information for the layer. Must contain the following
         * properties:
         * <ul>
         *     <li>identifier: {String} The layer name.</li>
         *     <li>service: {String} The URL of the WMTS server</li>
         *     <li>format: {String} The mime type of the image format to request, e.g., image/png.</li>
         *     <li>tileMatrixSet: {{}} The tile matrix set to use for this layer.</li>
         *     <li>style: {String} The style to use for this layer.</li>
         *     <li>title: {String} The display name for this layer.</li>
         * </ul>
         * @param {String} timeString The time parameter passed to the WMTS server when imagery is requested. May be
         * null, in which case no time parameter is passed to the server.
         * @throws {ArgumentError} If the specified layer capabilities reference is null or undefined.
         */
        var WmtsLayer = function (config, timeString) {
            if (!config) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
                        "No layer configuration specified."));
            }

            Layer.call(this, "WMTS Layer");

            /**
             * The WMTS layer identifier of this layer.
             * @type {String}
             * @readonly
             */
            this.layerIdentifier = config.identifier;

            /**
             * The style identifier specified to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.styleIdentifier = config.style;

            /**
             * The time string passed to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.timeString = timeString;

            /**
             * The image format specified to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.imageFormat = config.format;

            /**
             * The url specified to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.resourceUrl = config.resourceUrl;
            this.serviceUrl = config.service;

            /**
             * The tileMatrixSet specified to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.tileMatrixSet = config.tileMatrixSet;


            // Determine the layer's sector if possible. Mandatory for EPSG:4326 tile matrix sets. (Others compute
            // it from tile Matrix Set metadata.)
            // Sometimes BBOX defined in Matrix and not in Layer
            if (!config.wgs84BoundingBox && !config.boundingBox) {
                if (this.tileMatrixSet.boundingBox) {
                    this.sector = new Sector(
                        config.tileMatrixSet.boundingBox.lowerCorner[1],
                        config.tileMatrixSet.boundingBox.upperCorner[1],
                        config.tileMatrixSet.boundingBox.lowerCorner[0],
                        config.tileMatrixSet.boundingBox.upperCorner[0]);
                } else {
                    // Throw an exception if there is no bounding box.
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
                            "No bounding box was specified in the layer or tile matrix set capabilities."));
                }
            } else if (config.wgs84BoundingBox) {
                this.sector = config.wgs84BoundingBox.getSector();
            } else if (this.tileMatrixSet.boundingBox &&
                WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.boundingBox.crs)) {
                this.sector = new Sector(
                    this.tileMatrixSet.boundingBox.lowerCorner[1],
                    this.tileMatrixSet.boundingBox.upperCorner[1],
                    this.tileMatrixSet.boundingBox.lowerCorner[0],
                    this.tileMatrixSet.boundingBox.upperCorner[0]);
            } else if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
                // Throw an exception if there is no 4326 bounding box.
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
                        "No EPSG:4326 bounding box was specified in the layer or tile matrix set capabilities."));
            }

            // Check if tile subdivision is valid
            var tileMatrix = config.tileMatrixSet.tileMatrix,
                widthArray = [],
                heightArray = [],
                invalidLevel;

            tileMatrix.forEach(function (matrix) {
                widthArray.push(matrix.matrixWidth);
                heightArray.push(matrix.matrixHeight);
            });

            if (WmtsLayer.checkTileSubdivision(widthArray) !== 0) {
                invalidLevel = WmtsLayer.checkTileSubdivision(widthArray);
                Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
                    "Tile subdivision not supported for layer : " + config.identifier + ". Display until level " + (invalidLevel - 1));
                tileMatrix.splice(invalidLevel);
            } else if (WmtsLayer.checkTileSubdivision(heightArray) !== 0) {
                invalidLevel = WmtsLayer.checkTileSubdivision(heightArray);
                Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "constructor",
                    "Tile subdivision not supported for layer : " + config.identifier + ". Display until level " + (invalidLevel - 1));
                tileMatrix.splice(invalidLevel);
            }

            // Form a unique string to identify cache entries.
            this.cachePath = (this.resourceUrl || this.serviceUrl) +
                this.layerIdentifier + this.styleIdentifier + this.tileMatrixSet.identifier;
            if (timeString) {
                this.cachePath = this.cachePath + timeString;
            }

            /**
             * The displayName specified to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.displayName = config.title;

            this.currentTiles = [];
            this.currentTilesInvalid = true;
            this.tileCache = new MemoryCache(500, 400);
            this.currentRetrievals = [];
            this.absentResourceList = new AbsentResourceList(3, 50e3);

            this.pickEnabled = false;

            /**
             * Controls the level of detail switching for this layer. The next highest resolution level is
             * used when an image's texel size is greater than this number of pixels, up to the maximum resolution
             * of this layer.
             * @type {Number}
             * @default 1.75
             */
            this.detailControl = 1.75;
        };

        WmtsLayer.checkTileSubdivision = function (dimensionArray) {
            if (dimensionArray.length < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "checkTileSubdivision",
                        "Empty dimension array"));
            }

            var ratio,
                invalidLevel = 0,
                i = 0;

            while (++i < dimensionArray.length && invalidLevel == 0) {
                var newRatio = dimensionArray[i] / dimensionArray[i - 1];

                // If the ratio is not an integer, the level is invalid
                if ((dimensionArray[i] % dimensionArray[i - 1]) !== 0) {
                    invalidLevel = i;
                } else if (ratio && (ratio !== newRatio)) {
                    // If ratios are different, the level is invalid
                    invalidLevel = i;
                }
                ratio = newRatio;
            }

            // Tile subdivision is valid when invalidLevel == 0
            return invalidLevel;
        };


        /**
         * Constructs a tile matrix set object.
         * @param {{}} params Specifies parameters for the tile matrix set. Must contain the following
         * properties:
         * <ul>
         *     <li>matrixSet: {String} The matrix name.</li>
         *     <li>prefix: {Boolean} It represents if the identifier of the matrix must be prefixed by the matrix name.</li>
         *     <li>projection: {String} The projection of the tiles.</li>
         *     <li>topLeftCorner: {Array} The coordinates of the top left corner.</li>
         *     <li>extent: {Array} The boundinx box for this matrix.</li>
         *     <li>resolutions: {Array} The resolutions array.</li>
         *     <li>matrixSet: {Number} The tile size.</li>
         * </ul>
         * @throws {ArgumentError} If the specified params.matrixSet is null or undefined. The name of the matrix to
         * use for this layer.
         * @throws {ArgumentError} If the specified params.prefix is null or undefined. It represents if the
         * identifier of the matrix must be prefixed by the matrix name
         * @throws {ArgumentError} If the specified params.projection is null or undefined.
         * @throws {ArgumentError} If the specified params.extent is null or undefined.
         * @throws {ArgumentError} If the specified params.resolutions is null or undefined.
         * @throws {ArgumentError} If the specified params.tileSize is null or undefined.
         * @throws {ArgumentError} If the specified params.topLeftCorner is null or undefined.
         */
        WmtsLayer.createTileMatrixSet = function (params) {

            if (!params.matrixSet) { // matrixSet
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No matrixSet provided."));
            }
            if (!params.projection) { // projection
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No projection provided."));
            }
            if (!params.extent || params.extent.length != 4) { // extent
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No extent provided."));
            }

            // Define the boundingBox
            var boundingBox = {
                lowerCorner: [params.extent[0], params.extent[1]],
                upperCorner: [params.extent[2], params.extent[3]]
            };

            // Resolutions
            if (!params.resolutions) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No resolutions provided."));
            }

            // Tile size
            if (!params.tileSize) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No tile size provided."));
            }

            // Top left corner
            if (!params.topLeftCorner || params.topLeftCorner.length != 2) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "No extent provided."));
            }

            // Prefix
            if (params.prefix === undefined) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "Prefix not provided."));
            }

            // Check if the projection is supported
            if (!(WmtsLayer.isEpsg4326Crs(params.projection) || WmtsLayer.isOGCCrs84(params.projection) || WmtsLayer.isEpsg3857Crs(params.projection))) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "createTileMatrixSet",
                        "Projection provided not supported."));
            }

            var tileMatrixSet = [],
                scale;

            // Construct the tileMatrixSet
            for (var i = 0; i < params.resolutions.length; i++) {
                // Compute the scaleDenominator
                if (WmtsLayer.isEpsg4326Crs(params.projection) || WmtsLayer.isOGCCrs84(params.projection)) {
                    scale = params.resolutions[i] * 6378137.0 * 2.0 * Math.PI / 360 / 0.00028;
                } else if (WmtsLayer.isEpsg3857Crs(params.projection)) {
                    scale = params.resolutions[i] / 0.00028;
                }

                // Compute the matrix width / height
                var unitWidth = params.tileSize * params.resolutions[i];
                var unitHeight = params.tileSize * params.resolutions[i];
                var matrixWidth = Math.ceil((params.extent[2] - params.extent[0] - 0.01 * unitWidth) / unitWidth);
                var matrixHeight = Math.ceil((params.extent[3] - params.extent[1] - 0.01 * unitHeight) / unitHeight);

                // Define the tile matrix
                var tileMatrix = {
                    identifier: params.prefix ? params.matrixSet + ":" + i : i,
                    levelNumber: i,
                    matrixHeight: matrixHeight,
                    matrixWidth: matrixWidth,
                    tileHeight: params.tileSize,
                    tileWidth: params.tileSize,
                    topLeftCorner: params.topLeftCorner,
                    scaleDenominator: scale
                };

                tileMatrixSet.push(tileMatrix);
            }

            return {
                identifier: params.matrixSet,
                supportedCRS: params.projection,
                boundingBox: boundingBox,
                tileMatrix: tileMatrixSet
            };
        };


        /**
         * Forms a configuration object for a specified {@link WmtsLayerCapabilities} layer description. The
         * configuration object created and returned is suitable for passing to the WmtsLayer constructor.
         * <p>
         *     This method also parses any time dimensions associated with the layer and returns them in the
         *     configuration object's "timeSequences" property. This property is a mixed array of Date objects
         *     and {@link PeriodicTimeSequence} objects describing the dimensions found.
         * @param wmtsLayerCapabilities {WmtsLayerCapabilities} The WMTS layer capabilities to create a configuration for.
         * @param style {string} The style to apply for this layer.  May be null, in which case the first style recognized is used.
         * @param matrixSet {string} The matrix to use for this layer.  May be null, in which case the first tileMatrixSet recognized is used.
         * @param imageFormat {string} The image format to use with this layer.  May be null, in which case the first image format recognized is used.
         * @returns {{}} A configuration object.
         * @throws {ArgumentError} If the specified WMTS layer capabilities is null or undefined.
         */
        WmtsLayer.formLayerConfiguration = function (wmtsLayerCapabilities, style, matrixSet, imageFormat) {

            var config = {};

            /**
             * The WMTS layer identifier of this layer.
             * @type {String}
             * @readonly
             */
            config.identifier = wmtsLayerCapabilities.identifier;

            // Validate that the specified image format exists, or determine one if not specified.
            if (imageFormat) {
                var formatIdentifierFound = false;
                for (var i = 0; i < wmtsLayerCapabilities.format.length; i++) {
                    if (wmtsLayerCapabilities.format[i] === imageFormat) {
                        formatIdentifierFound = true;
                        config.format = wmtsLayerCapabilities.format[i];
                        break;
                    }
                }

                if (!formatIdentifierFound) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
                        "The specified image format is not available. Another one will be used.");
                    config.format = null;
                }
            }

            if (!config.format) {
                if (wmtsLayerCapabilities.format.indexOf("image/png") >= 0) {
                    config.format = "image/png";
                } else if (wmtsLayerCapabilities.format.indexOf("image/jpeg") >= 0) {
                    config.format = "image/jpeg";
                } else if (wmtsLayerCapabilities.format.indexOf("image/tiff") >= 0) {
                    config.format = "image/tiff";
                } else if (wmtsLayerCapabilities.format.indexOf("image/gif") >= 0) {
                    config.format = "image/gif";
                } else {
                    config.format = wmtsLayerCapabilities.format[0];
                }
            }

            if (!config.format) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
                        "Layer does not provide a supported image format."));
            }

            // Configure URL
            if (wmtsLayerCapabilities.resourceUrl && (wmtsLayerCapabilities.resourceUrl.length >= 1)) {
                for (var i = 0; i < wmtsLayerCapabilities.resourceUrl.length; i++) {
                    if (config.format === wmtsLayerCapabilities.resourceUrl[i].format) {
                        config.resourceUrl = wmtsLayerCapabilities.resourceUrl[i].template;
                        break;
                    }
                }
            } else { // resource-oriented interface not supported, so use KVP interface
                config.service = wmtsLayerCapabilities.capabilities.getGetTileKvpAddress();
                if (config.service) {
                    config.service = WmsUrlBuilder.fixGetMapString(config.service);
                }
            }

            if (!config.resourceUrl && !config.service) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
                        "No resource URL or KVP GetTile service URL specified in WMTS capabilities."));
            }

            // Validate that the specified style identifier exists, or determine one if not specified.
            if (style) {
                var styleIdentifierFound = false;
                for (var i = 0; i < wmtsLayerCapabilities.style.length; i++) {
                    if (wmtsLayerCapabilities.style[i].identifier === style) {
                        styleIdentifierFound = true;
                        config.style = wmtsLayerCapabilities.style[i].identifier;
                        break;
                    }
                }

                if (!styleIdentifierFound) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
                        "The specified style identifier is not available. The server's default style will be used.");
                    config.style = null;
                }
            }

            if (!config.style) {
                for (i = 0; i < wmtsLayerCapabilities.style.length; i++) {
                    if (wmtsLayerCapabilities.style[i].isDefault) {
                        config.style = wmtsLayerCapabilities.style[i].identifier;
                        break;
                    }
                }
            }

            if (!config.style) {
                Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
                    "No default style available. A style will not be specified in tile requests.");
            }

            // Retrieve the supported tile matrix sets for testing against provided tile matrix set or for tile matrix
            // set negotiation.
            var supportedTileMatrixSets = wmtsLayerCapabilities.getLayerSupportedTileMatrixSets();

            // Validate that the specified style identifier exists, or determine one if not specified.
            if (matrixSet) {
                var tileMatrixSetFound = false;
                for (var i = 0, len = supportedTileMatrixSets.length; i < len; i++) {
                    if (supportedTileMatrixSets[i].identifier === matrixSet) {
                        tileMatrixSetFound = true;
                        config.tileMatrixSet = supportedTileMatrixSets[i];
                        break;
                    }
                }

                if (!tileMatrixSetFound) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WmtsLayer", "formLayerConfiguration",
                        "The specified tileMatrixSet is not available. Another one will be used.");
                    config.tileMatrixSet = null;
                }
            }

            if (!config.tileMatrixSet) {
                // Find the tile matrix set we want to use. Prefer EPSG:4326, then EPSG:3857.
                var tms, tms4326 = null, tms3857 = null, tmsCRS84 = null;

                for (var i = 0, len = supportedTileMatrixSets.length; i < len; i++) {
                    tms = supportedTileMatrixSets[i];

                    if (WmtsLayer.isEpsg4326Crs(tms.supportedCRS)) {
                        tms4326 = tms4326 || tms;
                    } else if (WmtsLayer.isEpsg3857Crs(tms.supportedCRS)) {
                        tms3857 = tms3857 || tms;
                    } else if (WmtsLayer.isOGCCrs84(tms.supportedCRS)) {
                        tmsCRS84 = tmsCRS84 || tms;
                    }
                }

                config.tileMatrixSet = tms4326 || tms3857 || tmsCRS84;
            }

            if (!config.tileMatrixSet) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmtsLayer", "formLayerConfiguration",
                        "No supported Tile Matrix Set could be found."));
            }

            // Configure boundingBox
            config.boundingBox = wmtsLayerCapabilities.boundingBox;
            config.wgs84BoundingBox = wmtsLayerCapabilities.wgs84BoundingBox;

            // Determine a default display name.
            if (wmtsLayerCapabilities.titles.length > 0) {
                config.title = wmtsLayerCapabilities.titles[0].value;
            } else {
                config.title = wmtsLayerCapabilities.identifier;
            }

            return config;
        };

        WmtsLayer.prototype = Object.create(Layer.prototype);

        WmtsLayer.prototype.doRender = function (dc) {
            if (!dc.terrain)
                return;

            if (this.currentTilesInvalid
                || !this.lasTtMVP || !dc.navigatorState.modelviewProjection.equals(this.lasTtMVP)
                || dc.globeStateKey != this.lastGlobeStateKey) {
                this.currentTilesInvalid = false;
                this.assembleTiles(dc);
            }

            this.lasTtMVP = dc.navigatorState.modelviewProjection;
            this.lastGlobeStateKey = dc.globeStateKey;

            if (this.currentTiles.length > 0) {
                dc.surfaceTileRenderer.renderTiles(dc, this.currentTiles, this.opacity);
                dc.frameStatistics.incrementImageTileCount(this.currentTiles.length);
                this.inCurrentFrame = true;
            }
        };

        WmtsLayer.prototype.isLayerInView = function (dc) {
            return dc.terrain && dc.terrain.sector && dc.terrain.sector.intersects(this.sector);
        };

        WmtsLayer.prototype.isTileVisible = function (dc, tile) {
            if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
                return false;
            }

            return tile.extent.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates);
        };

        WmtsLayer.prototype.assembleTiles = function (dc) {
            this.currentTiles = [];

            if (!this.topLevelTiles || (this.topLevelTiles.length === 0)) {
                this.createTopLevelTiles(dc);
            }

            for (var i = 0, len = this.topLevelTiles.length; i < len; i++) {
                var tile = this.topLevelTiles[i];

                tile.update(dc);

                this.currentAncestorTile = null;

                if (this.isTileVisible(dc, tile)) {
                    this.addTileOrDescendants(dc, tile);
                }
            }
        };

        WmtsLayer.prototype.addTileOrDescendants = function (dc, tile) {
            // Check if the new sub-tile fits in TileMatrix ranges
            if (tile.column >= tile.tileMatrix.matrixWidth) {
                tile.column = tile.column - tile.tileMatrix.matrixWidth;
            }
            if (tile.column < 0) {
                tile.column = tile.column + tile.tileMatrix.matrixWidth;
            }

            if (this.tileMeetsRenderingCriteria(dc, tile)) {
                this.addTile(dc, tile);
                return;
            }

            var ancestorTile = null;

            try {
                if (this.isTileTextureInMemory(dc, tile) || tile.tileMatrix.levelNumber === 0) {
                    ancestorTile = this.currentAncestorTile;
                    this.currentAncestorTile = tile;
                }
                var nextLevel = this.tileMatrixSet.tileMatrix[tile.tileMatrix.levelNumber + 1],
                    subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);

                for (var i = 0, len = subTiles.length; i < len; i++) {
                    var child = subTiles[i];

                    child.update(dc);

                    if (this.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
                        this.addTileOrDescendants(dc, child);
                    }
                }
            } finally {
                if (ancestorTile) {
                    this.currentAncestorTile = ancestorTile;
                }
            }
        };

        WmtsLayer.prototype.addTile = function (dc, tile) {
            tile.fallbackTile = null;

            var texture = dc.gpuResourceCache.resourceForKey(tile.imagePath);
            if (texture) {
                this.currentTiles.push(tile);

                // If the tile's texture has expired, cause it to be re-retrieved. Note that the current,
                // expired texture is still used until the updated one arrives.
                if (this.expiration && this.isTextureExpired(texture)) {
                    this.retrieveTileImage(dc, tile);
                }

                return;
            }

            this.retrieveTileImage(dc, tile);

            if (this.currentAncestorTile) {
                if (this.isTileTextureInMemory(dc, this.currentAncestorTile)) {
                    this.currentTiles.push(this.currentAncestorTile);
                }
            }
        };

        WmtsLayer.prototype.isTextureExpired = function (texture) {
            return this.expiration && (texture.creationTime.getTime() <= this.expiration.getTime());
        };

        WmtsLayer.prototype.isTileTextureInMemory = function (dc, tile) {
            return dc.gpuResourceCache.containsResource(tile.imagePath);
        };

        WmtsLayer.prototype.tileMeetsRenderingCriteria = function (dc, tile) {
            var s = this.detailControl;
            if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
                s *= 1.2;
            }

            return tile.tileMatrix.levelNumber === (this.tileMatrixSet.tileMatrix.length - 1) || !tile.mustSubdivide(dc, s);
        };

        WmtsLayer.prototype.retrieveTileImage = function (dc, tile) {
            if (this.currentRetrievals.indexOf(tile.imagePath) < 0) {
                if (this.absentResourceList.isResourceAbsent(tile.imagePath)) {
                    return;
                }

                var url = this.resourceUrlForTile(tile, this.imageFormat),
                    image = new Image(),
                    imagePath = tile.imagePath,
                    cache = dc.gpuResourceCache,
                    canvas = dc.currentGlContext.canvas,
                    layer = this;

                if (!url) {
                    this.currentTilesInvalid = true;
                    return;
                }

                image.onload = function () {
                    Logger.log(Logger.LEVEL_INFO, "Image retrieval succeeded: " + url);
                    var texture = layer.createTexture(dc, tile, image);
                    layer.removeFromCurrentRetrievals(imagePath);

                    if (texture) {
                        cache.putResource(imagePath, texture, texture.size);

                        layer.currentTilesInvalid = true;
                        layer.absentResourceList.unmarkResourceAbsent(imagePath);

                        // Send an event to request a redraw.
                        var e = document.createEvent('Event');
                        e.initEvent(WorldWind.REDRAW_EVENT_TYPE, true, true);
                        canvas.dispatchEvent(e);
                    }
                };

                image.onerror = function () {
                    layer.removeFromCurrentRetrievals(imagePath);
                    layer.absentResourceList.markResourceAbsent(imagePath);
                    Logger.log(Logger.LEVEL_WARNING, "Image retrieval failed: " + url);
                };

                this.currentRetrievals.push(imagePath);
                image.crossOrigin = 'anonymous';
                image.src = url;
            }
        };

        WmtsLayer.prototype.resourceUrlForTile = function (tile, imageFormat) {
            var url;

            if (this.resourceUrl) {
                url = this.resourceUrl.replace("{Style}", this.styleIdentifier).replace("{TileMatrixSet}", this.tileMatrixSet.identifier).replace("{TileMatrix}", tile.tileMatrix.identifier).replace("{TileCol}", tile.column).replace("{TileRow}", tile.row);

                if (this.timeString) {
                    url = url.replace("{Time}", this.timeString);
                }
            } else {
                url = this.serviceUrl + "service=WMTS&request=GetTile&version=1.0.0";

                url += "&Layer=" + this.layerIdentifier;

                if (this.styleIdentifier) {
                    url += "&Style=" + this.styleIdentifier;
                }

                url += "&Format=" + imageFormat;

                if (this.timeString) {
                    url += "&Time=" + this.timeString;
                }

                url += "&TileMatrixSet=" + this.tileMatrixSet.identifier;
                url += "&TileMatrix=" + tile.tileMatrix.identifier;
                url += "&TileRow=" + tile.row;
                url += "&TileCol=" + tile.column;
            }

            return url;
        };

        WmtsLayer.prototype.removeFromCurrentRetrievals = function (imagePath) {
            var index = this.currentRetrievals.indexOf(imagePath);
            if (index > -1) {
                this.currentRetrievals.splice(index, 1);
            }
        };

        WmtsLayer.prototype.createTopLevelTiles = function (dc) {
            var tileMatrix = this.tileMatrixSet.tileMatrix[0];

            this.topLevelTiles = [];
            for (var j = 0; j < tileMatrix.matrixHeight; j++) {
                for (var i = 0; i < tileMatrix.matrixWidth; i++) {
                    this.topLevelTiles.push(this.createTile(tileMatrix, j, i));
                }
            }
        };

        WmtsLayer.prototype.createTile = function (tileMatrix, row, column) {
            if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
                return this.createTile4326(tileMatrix, row, column);
            } else if (WmtsLayer.isEpsg3857Crs(this.tileMatrixSet.supportedCRS)) {
                return this.createTile3857(tileMatrix, row, column);
            } else if (WmtsLayer.isOGCCrs84(this.tileMatrixSet.supportedCRS)) {
                return this.createTileCrs84(tileMatrix, row, column);
            }
        };


        WmtsLayer.prototype.createTileCrs84 = function (tileMatrix, row, column) {
            var tileDeltaLat = this.sector.deltaLatitude() / tileMatrix.matrixHeight,
                tileDeltaLon = this.sector.deltaLongitude() / tileMatrix.matrixWidth,
                maxLat = tileMatrix.topLeftCorner[1] - row * tileDeltaLat,
                minLat = maxLat - tileDeltaLat,
                minLon = tileMatrix.topLeftCorner[0] + tileDeltaLon * column,
                maxLon = minLon + tileDeltaLon;

            var sector = new Sector(minLat, maxLat, minLon, maxLon);

            return this.makeTile(sector, tileMatrix, row, column);
        };


        WmtsLayer.prototype.createTile4326 = function (tileMatrix, row, column) {
            var tileDeltaLat = this.sector.deltaLatitude() / tileMatrix.matrixHeight,
                tileDeltaLon = this.sector.deltaLongitude() / tileMatrix.matrixWidth,
                maxLat = tileMatrix.topLeftCorner[0] - row * tileDeltaLat,
                minLat = maxLat - tileDeltaLat,
                minLon = tileMatrix.topLeftCorner[1] + tileDeltaLon * column,
                maxLon = minLon + tileDeltaLon;

            var sector = new Sector(minLat, maxLat, minLon, maxLon);

            return this.makeTile(sector, tileMatrix, row, column);
        };

        WmtsLayer.prototype.createTile3857 = function (tileMatrix, row, column) {
            if (!tileMatrix.mapWidth) {
                this.computeTileMatrixValues3857(tileMatrix);
            }

            var swX = WWMath.clamp(column * tileMatrix.tileWidth - 0.5, 0, tileMatrix.mapWidth),
                neY = WWMath.clamp(row * tileMatrix.tileHeight - 0.5, 0, tileMatrix.mapHeight),
                neX = WWMath.clamp(swX + (tileMatrix.tileWidth) + 0.5, 0, tileMatrix.mapWidth),
                swY = WWMath.clamp(neY + (tileMatrix.tileHeight) + 0.5, 0, tileMatrix.mapHeight),
                x, y, swLat, swLon, neLat, neLon;

            x = swX / tileMatrix.mapWidth;
            y = swY / tileMatrix.mapHeight;
            swLon = tileMatrix.topLeftCorner[0] + x * tileMatrix.tileMatrixDeltaX;
            swLat = tileMatrix.topLeftCorner[1] - y * tileMatrix.tileMatrixDeltaY;
            var swDegrees = WWMath.epsg3857ToEpsg4326(swLon, swLat);

            x = neX / tileMatrix.mapWidth;
            y = neY / tileMatrix.mapHeight;
            neLon = tileMatrix.topLeftCorner[0] + x * tileMatrix.tileMatrixDeltaX;
            neLat = tileMatrix.topLeftCorner[1] - y * tileMatrix.tileMatrixDeltaY;
            var neDegrees = WWMath.epsg3857ToEpsg4326(neLon, neLat);

            var sector = new Sector(swDegrees[0], neDegrees[0], swDegrees[1], neDegrees[1]);

            return this.makeTile(sector, tileMatrix, row, column);
        };

        WmtsLayer.prototype.computeTileMatrixValues3857 = function (tileMatrix) {
            var pixelSpan = tileMatrix.scaleDenominator * 0.28e-3,
                tileSpanX = tileMatrix.tileWidth * pixelSpan,
                tileSpanY = tileMatrix.tileHeight * pixelSpan,
                tileMatrixMaxX = tileMatrix.topLeftCorner[0] + tileSpanX * tileMatrix.matrixWidth,
                tileMatrixMinY = tileMatrix.topLeftCorner[1] - tileSpanY * tileMatrix.matrixHeight,
                bottomRightCorner = [tileMatrixMaxX, tileMatrixMinY],
                topLeftCorner = tileMatrix.topLeftCorner;

            tileMatrix.tileMatrixDeltaX = bottomRightCorner[0] - topLeftCorner[0];
            tileMatrix.tileMatrixDeltaY = topLeftCorner[1] - bottomRightCorner[1];
            tileMatrix.mapWidth = tileMatrix.tileWidth * tileMatrix.matrixWidth;
            tileMatrix.mapHeight = tileMatrix.tileHeight * tileMatrix.matrixHeight;
        };

        WmtsLayer.prototype.makeTile = function (sector, tileMatrix, row, column) {
            var path = this.cachePath + "-layer/" + tileMatrix.identifier + "/" + row + "/" + column + "."
                + WWUtil.suffixForMimeType(this.imageFormat);
            return new WmtsLayerTile(sector, tileMatrix, row, column, path);
        };

        WmtsLayer.prototype.createTexture = function (dc, tile, image) {
            if (WmtsLayer.isEpsg4326Crs(this.tileMatrixSet.supportedCRS)) {
                return new Texture(dc.currentGlContext, image);
            } else if (WmtsLayer.isEpsg3857Crs(this.tileMatrixSet.supportedCRS)) {
                return this.createTexture3857(dc, tile, image);
            } else if (WmtsLayer.isOGCCrs84(this.tileMatrixSet.supportedCRS)) {
                return new Texture(dc.currentGlContext, image);
            }
        };

        WmtsLayer.prototype.createTexture3857 = function (dc, tile, image) {
            if (!this.destCanvas) {
                // Create a canvas we can use when unprojecting retrieved images.
                this.destCanvas = document.createElement("canvas");
                this.destContext = this.destCanvas.getContext("2d");
            }

            var srcCanvas = dc.canvas2D,
                srcContext = dc.ctx2D,
                srcImageData,
                destCanvas = this.destCanvas,
                destContext = this.destContext,
                destImageData = destContext.createImageData(image.width, image.height),
                sector = tile.sector,
                tMin = WWMath.gudermannianInverse(sector.minLatitude),
                tMax = WWMath.gudermannianInverse(sector.maxLatitude),
                lat, g, srcRow, kSrc, kDest, sy, dy;

            srcCanvas.width = image.width;
            srcCanvas.height = image.height;
            destCanvas.width = image.width;
            destCanvas.height = image.height;

            // Draw the original image to a canvas so image data can be had for it.
            srcContext.drawImage(image, 0, 0, image.width, image.height);
            srcImageData = srcContext.getImageData(0, 0, image.width, image.height);

            // Unproject the retrieved image.
            for (var n = 0; n < 1; n++) {
                for (var y = 0; y < image.height; y++) {
                    sy = 1 - y / (image.height - 1);
                    lat = sy * sector.deltaLatitude() + sector.minLatitude;
                    g = WWMath.gudermannianInverse(lat);
                    dy = 1 - (g - tMin) / (tMax - tMin);
                    dy = WWMath.clamp(dy, 0, 1);
                    srcRow = Math.floor(dy * (image.height - 1));
                    for (var x = 0; x < image.width; x++) {
                        kSrc = 4 * (x + srcRow * image.width);
                        kDest = 4 * (x + y * image.width);

                        destImageData.data[kDest] = srcImageData.data[kSrc];
                        destImageData.data[kDest + 1] = srcImageData.data[kSrc + 1];
                        destImageData.data[kDest + 2] = srcImageData.data[kSrc + 2];
                        destImageData.data[kDest + 3] = srcImageData.data[kSrc + 3];
                    }
                }
            }

            destContext.putImageData(destImageData, 0, 0);

            return new Texture(dc.currentGlContext, destCanvas);
        };

        WmtsLayer.isEpsg4326Crs = function (crs) {
            return ((crs.indexOf("EPSG") >= 0) && (crs.indexOf("4326") >= 0));
        };

        WmtsLayer.isEpsg3857Crs = function (crs) {
            return (crs.indexOf("EPSG") >= 0)
                && ((crs.indexOf("3857") >= 0) || (crs.indexOf("900913") >= 0)); // 900913 is google's 3857 alias
        };

        WmtsLayer.isOGCCrs84 = function (crs) {
            return (crs.indexOf("OGC") >= 0) && (crs.indexOf("CRS84") >= 0);
        };

        return WmtsLayer;
    });

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WorldWindow
 */
define('WorldWindow',[
        './error/ArgumentError',
        './render/DrawContext',
        './globe/EarthElevationModel',
        './util/FrameStatistics',
        './globe/Globe',
        './globe/Globe2D',
        './util/GoToAnimator',
        './cache/GpuResourceCache',
        './util/Logger',
        './navigate/LookAtNavigator',
        './navigate/NavigatorState',
        './pick/PickedObjectList',
        './geom/Rectangle',
        './geom/Sector',
        './shapes/SurfaceShape',
        './shapes/SurfaceShapeTileBuilder',
        './globe/Terrain',
        './geom/Vec2'],
    function (ArgumentError,
              DrawContext,
              EarthElevationModel,
              FrameStatistics,
              Globe,
              Globe2D,
              GoToAnimator,
              GpuResourceCache,
              Logger,
              LookAtNavigator,
              NavigatorState,
              PickedObjectList,
              Rectangle,
              Sector,
              SurfaceShape,
              SurfaceShapeTileBuilder,
              Terrain,
              Vec2) {
        "use strict";

        /**
         * Constructs a WorldWind window for an HTML canvas.
         * @alias WorldWindow
         * @constructor
         * @classdesc Represents a WorldWind window for an HTML canvas.
         * @param {String} canvasName The name assigned to the HTML canvas in the document.
         * @param {ElevationModel} elevationModel An optional argument indicating the elevation model to use for the World
         * Window. If missing or null, a default elevation model is used.
         * @throws {ArgumentError} If there is no HTML element with the specified name in the document, or if the
         * HTML canvas does not support WebGL.
         */
        var WorldWindow = function (canvasName, elevationModel) {
            if (!(window.WebGLRenderingContext)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "constructor",
                        "The specified canvas does not support WebGL."));
            }

            // Attempt to get the HTML canvas with the specified name.
            var canvas = document.getElementById(canvasName);
            if (!canvas) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "constructor",
                        "The specified canvas name is not in the document."));
            }

            // Create the WebGL context associated with the HTML canvas.
            var gl = this.createContext(canvas);

            // Internal. Intentionally not documented.
            this.drawContext = new DrawContext(gl);

            // Internal. Intentionally not documented. Must be initialized before the navigator is created.
            this.eventListeners = {};

            // Internal. Intentionally not documented. Initially true in order to redraw at least once.
            this.redrawRequested = true;

            // Internal. Intentionally not documented.
            this.redrawRequestId = null;

            /**
             * The HTML canvas associated with this WorldWindow.
             * @type {HTMLElement}
             * @readonly
             */
            this.canvas = canvas;

            /**
             * The number of bits in the depth buffer associated with this WorldWindow.
             * @type {number}
             * @readonly
             */
            this.depthBits = gl.getParameter(gl.DEPTH_BITS);

            /**
             * The current viewport of this WorldWindow.
             * @type {Rectangle}
             * @readonly
             */
            this.viewport = new Rectangle(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

            /**
             * The globe displayed.
             * @type {Globe}
             */
            this.globe = new Globe(elevationModel || new EarthElevationModel());

            /**
             * The layers to display in this WorldWindow.
             * This property is read-only. Use [addLayer]{@link WorldWindow#addLayer} or
             * [insertLayer]{@link WorldWindow#insertLayer} to add layers to this WorldWindow.
             * Use [removeLayer]{@link WorldWindow#removeLayer} to remove layers from this WorldWindow.
             * @type {Layer[]}
             * @readonly
             */
            this.layers = [];

            /**
             * The navigator used to manipulate the globe.
             * @type {LookAtNavigator}
             * @default [LookAtNavigator]{@link LookAtNavigator}
             */
            this.navigator = new LookAtNavigator(this);

            /**
             * The vertical exaggeration to apply to the terrain.
             * @type {Number}
             */
            this.verticalExaggeration = 1;

            /**
             * Indicates that picking will return all objects at the pick point, if any. The top-most object will have
             * its isOnTop flag set to true.
             * If deep picking is false, the default, only the top-most object is returned, plus
             * the picked-terrain object if the pick point is over the terrain.
             * @type {boolean}
             * @default false
             */
            this.deepPicking = false;

            /**
             * Indicates whether this WorldWindow should be configured for sub-surface rendering. If true, shapes
             * below the terrain can be seen when the terrain is made transparent. If false, sub-surface shapes are
             * not visible, however, performance is slightly increased.
             * @type {boolean}
             * @default false
             */
            this.subsurfaceMode = false;

            /**
             * The opacity to apply to terrain and surface shapes. This property is typically used when viewing
             * the sub-surface. It modifies the opacity of the terrain and surface shapes as a whole. It should be
             * a number between 0 and 1. It is compounded with the individual opacities of the image layers and
             * surface shapes on the terrain.
             * @type {Number}
             * @default 1
             */
            this.surfaceOpacity = 1;

            /**
             * Performance statistics for this WorldWindow.
             * @type {FrameStatistics}
             */
            this.frameStatistics = new FrameStatistics();

            /**
             * The {@link GoToAnimator} used by this WorldWindow to respond to its goTo method.
             * @type {GoToAnimator}
             */
            this.goToAnimator = new GoToAnimator(this);

            // Documented with its property accessor below.
            this._redrawCallbacks = [];

            // Documented with its property accessor below.
            this._orderedRenderingFilters = [
                function (dc) {
                    thisWindow.declutter(dc, 1);
                },
                function (dc) {
                    thisWindow.declutter(dc, 2);
                }
            ];

            // Intentionally not documented.
            this.pixelScale = 1;

            // Set up to handle WebGL context lost events.
            var thisWindow = this;

            function handleContextLost(event) {
                thisWindow.handleContextLost(event);
            }

            this.canvas.addEventListener("webglcontextlost", handleContextLost, false);

            // Set up to handle WebGL context restored events.
            function handleContextRestored(event) {
                thisWindow.handleContextRestored(event);
            }

            this.canvas.addEventListener("webglcontextrestored", handleContextRestored, false);

            // Set up to handle WebGL context events and WorldWind redraw request events. Imagery uses the canvas
            // redraw events because images are generally specific to the WebGL context associated with the canvas.
            // Elevation models use the global window redraw events because they can be shared among WorldWindows.
            function handleRedrawEvent(event) {
                thisWindow.handleRedrawEvent(event)
            }

            this.canvas.addEventListener(WorldWind.REDRAW_EVENT_TYPE, handleRedrawEvent, false);
            window.addEventListener(WorldWind.REDRAW_EVENT_TYPE, handleRedrawEvent, false);

            // Render to the WebGL context in an animation frame loop until the WebGL context is lost.
            this.animationFrameLoop();
        };

        Object.defineProperties(WorldWindow.prototype, {
            /**
             * An array of functions to call during ordered rendering prior to rendering the ordered renderables.
             * Each function is passed one argument, the current draw context. The function may modify the
             * ordered renderables in the draw context's ordered renderable list, which has been sorted from front
             * to back when the filter function is called. Ordered rendering filters are typically used to apply
             * decluttering. The default set of filter functions contains one function that declutters shapes with
             * declutter group ID of 1 ({@link GeographicText} by default) and one function that declutters shapes
             * with declutter group ID 2 ({@link Placemark} by default). Applications can add functions to this
             * array or remove them.
             * @type {Function[]}
             * @default [WorldWindow.declutter]{@link WorldWindow#declutter} with a group ID of 1
             * @readonly
             * @memberof WorldWindow.prototype
             */
            orderedRenderingFilters: {
                get: function () {
                    return this._orderedRenderingFilters;
                }
            },
            /**
             * The list of callbacks to call immediately before and immediately after performing a redraw. The callbacks
             * have two arguments: this WorldWindow and the redraw stage, e.g., <code style='white-space:nowrap'>redrawCallback(worldWindow, stage);</code>.
             * The stage will be either WorldWind.BEFORE_REDRAW or WorldWind.AFTER_REDRAW indicating whether the
             * callback has been called either immediately before or immediately after a redraw, respectively.
             * Applications may add functions to this array or remove them.
             * @type {Function[]}
             * @readonly
             * @memberof WorldWindow.prototype
             */
            redrawCallbacks: {
                get: function () {
                    return this._redrawCallbacks;
                }
            }
        });

        /**
         * Converts window coordinates to coordinates relative to this WorldWindow's canvas.
         * @param {Number} x The X coordinate to convert.
         * @param {Number} y The Y coordinate to convert.
         * @returns {Vec2} The converted coordinates.
         */
        WorldWindow.prototype.canvasCoordinates = function (x, y) {
            var bbox = this.canvas.getBoundingClientRect(),
                xc = x - (bbox.left + this.canvas.clientLeft),// * (this.canvas.width / bbox.width),
                yc = y - (bbox.top + this.canvas.clientTop);// * (this.canvas.height / bbox.height);

            return new Vec2(xc, yc);
        };

        /**
         * Registers an event listener for the specified event type on this WorldWindow's canvas. This function
         * delegates the processing of events to the WorldWindow's canvas. For details on this function and its
         * arguments, see the W3C [EventTarget]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
         * documentation.
         *
         * Registering event listeners using this function enables applications to prevent the WorldWindow's default
         * navigation behavior. To prevent default navigation behavior, call the [Event]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-Event}'s
         * preventDefault method from within an event listener for any events the navigator should not respond to.
         *
         * When an event occurs, this calls the registered event listeners in order of reverse registration. Since the
         * WorldWindow registers its navigator event listeners first, application event listeners are called before
         * navigator event listeners.
         *
         * @param type The event type to listen for.
         * @param listener The function to call when the event occurs.
         * @throws {ArgumentError} If any argument is null or undefined.
         */
        WorldWindow.prototype.addEventListener = function (type, listener) {
            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "addEventListener", "missingType"));
            }

            if (!listener) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "addEventListener", "missingListener"));
            }

            var thisWorldWindow = this;
            var entry = this.eventListeners[type];
            if (!entry) {
                entry = {
                    listeners: [],
                    callback: function (event) { // calls listeners in reverse registration order
                        event.worldWindow = thisWorldWindow;
                        for (var i = 0, len = entry.listeners.length; i < len; i++) {
                            entry.listeners[i](event);
                        }
                    }
                };
                this.eventListeners[type] = entry;
            }

            var index = entry.listeners.indexOf(listener);
            if (index == -1) { // suppress duplicate listeners
                entry.listeners.splice(0, 0, listener); // insert the listener at the beginning of the list

                if (entry.listeners.length == 1) { // first listener added, add the event listener callback
                    this.canvas.addEventListener(type, entry.callback, false);
                }
            }
        };

        /**
         * Removes an event listener for the specified event type from this WorldWindow's canvas. The listener must be
         * the same object passed to addEventListener. Calling removeEventListener with arguments that do not identify a
         * currently registered listener has no effect.
         *
         * @param type Indicates the event type the listener registered for.
         * @param listener The listener to remove. Must be the same function object passed to addEventListener.
         * @throws {ArgumentError} If any argument is null or undefined.
         */
        WorldWindow.prototype.removeEventListener = function (type, listener) {
            if (!type) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "removeEventListener", "missingType"));
            }

            if (!listener) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "removeEventListener", "missingListener"));
            }

            var entry = this.eventListeners[type];
            if (!entry) {
                return; // no entry for the specified type
            }

            var index = entry.listeners.indexOf(listener);
            if (index != -1) {
                entry.listeners.splice(index, 1); // remove the listener from the list

                if (entry.listeners.length == 0) { // last listener removed, remove the event listener callback
                    this.canvas.removeEventListener(type, entry.callback, false);
                }
            }
        };

        /**
         * Causes this WorldWindow to redraw itself at the next available opportunity. The redraw occurs on the main
         * thread at a time of the browser's discretion. Applications should call redraw after changing the World
         * Window's state, but should not expect that change to be reflected on screen immediately after this function
         * returns. This is the preferred method for requesting a redraw of the WorldWindow.
         */
        WorldWindow.prototype.redraw = function () {
            this.redrawRequested = true; // redraw during the next animation frame
        };

        /**
         * Requests the WorldWind objects displayed at a specified screen-coordinate point.
         *
         * If the point intersects the terrain, the returned list contains an object identifying the associated geographic
         * position. This returns an empty list when nothing in the WorldWind scene intersects the specified point.
         *
         * @param pickPoint The point to examine in this WorldWindow's screen coordinates.
         * @returns {PickedObjectList} A list of picked WorldWind objects at the specified pick point.
         * @throws {ArgumentError} If the specified pick point is null or undefined.
         */
        WorldWindow.prototype.pick = function (pickPoint) {
            if (!pickPoint) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "pick", "missingPoint"));
            }

            // Suppress the picking operation and return an empty list when the WebGL context has been lost.
            if (this.drawContext.currentGlContext.isContextLost()) {
                return new PickedObjectList();
            }

            this.resize();
            this.resetDrawContext();
            this.drawContext.pickingMode = true;
            this.drawContext.pickPoint = pickPoint;
            this.drawFrame();

            return this.drawContext.objectsAtPickPoint;
        };

        /**
         * Requests the position of the WorldWind terrain at a specified screen-coordinate point. If the point
         * intersects the terrain, the returned list contains a single object identifying the associated geographic
         * position. Otherwise this returns an empty list.
         * @param pickPoint The point to examine in this WorldWindow's screen coordinates.
         * @returns {PickedObjectList} A list containing the picked WorldWind terrain position at the specified point,
         * or an empty list if the point does not intersect the terrain.
         * @throws {ArgumentError} If the specified pick point is null or undefined.
         */
        WorldWindow.prototype.pickTerrain = function (pickPoint) {
            if (!pickPoint) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "pickTerrain", "missingPoint"));
            }

            // Suppress the picking operation and return an empty list when the WebGL context has been lost.
            if (this.drawContext.currentGlContext.isContextLost()) {
                return new PickedObjectList();
            }

            this.resize();
            this.resetDrawContext();
            this.drawContext.pickingMode = true;
            this.drawContext.pickTerrainOnly = true;
            this.drawContext.pickPoint = pickPoint;
            this.drawFrame();

            return this.drawContext.objectsAtPickPoint;
        };

        /**
         * Requests the WorldWind objects displayed within a specified screen-coordinate region. This returns all
         * objects that intersect the specified region, regardless of whether or not an object is actually visible, and
         * marks objects that are visible as on top.
         * @param {Rectangle} rectangle The screen coordinate rectangle identifying the region to search.
         * @returns {PickedObjectList} A list of visible WorldWind objects within the specified region.
         * @throws {ArgumentError} If the specified rectangle is null or undefined.
         */
        WorldWindow.prototype.pickShapesInRegion = function (rectangle) {
            if (!rectangle) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "pickShapesInRegion", "missingRectangle"));
            }

            // Suppress the picking operation and return an empty list when the WebGL context has been lost.
            if (this.drawContext.currentGlContext.isContextLost()) {
                return new PickedObjectList();
            }

            this.resize();
            this.resetDrawContext();
            this.drawContext.pickingMode = true;
            this.drawContext.regionPicking = true;
            this.drawContext.pickRectangle =
                new Rectangle(rectangle.x, this.canvas.height - rectangle.y, rectangle.width, rectangle.height);
            this.drawFrame();

            return this.drawContext.objectsAtPickPoint;
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.createContext = function (canvas) {
            // Request a WebGL context with antialiasing is disabled. Antialiasing causes gaps to appear at the edges of
            // terrain tiles.
            var glAttrs = {antialias: false, stencil: true},
                gl = canvas.getContext("webgl", glAttrs);
            if (!gl) {
                gl = canvas.getContext("experimental-webgl", glAttrs);
            }

            var actualAttributes = gl.getContextAttributes();
            this.hasStencilBuffer = actualAttributes.stencil;

            // uncomment to debug WebGL
            //var gl = WebGLDebugUtils.makeDebugContext(this.canvas.getContext("webgl"),
            //        this.throwOnGLError,
            //        this.logAndValidate
            //);

            return gl;
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.handleContextLost = function (event) {
            Logger.log(Logger.LEVEL_INFO, "WebGL context event: " + event.statusMessage);
            // Inform WebGL that we handle context restoration, enabling the context restored event to be delivered.
            event.preventDefault();
            // Notify the draw context that the WebGL rendering context has been lost.
            this.drawContext.contextLost();
            // Stop the rendering animation frame loop, resuming only if the WebGL context is restored.
            window.cancelAnimationFrame(this.redrawRequestId);
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.handleContextRestored = function (event) {
            Logger.log(Logger.LEVEL_INFO, "WebGL context event: " + event.statusMessage);
            // Notify the draw context that the WebGL rendering context has been restored.
            this.drawContext.contextRestored();
            // Resume the rendering animation frame loop until the WebGL context is lost.
            this.redraw();
            this.animationFrameLoop();
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.handleRedrawEvent = function (event) {
            this.redraw(); // redraw in the next animation frame
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.animationFrameLoop = function () {
            // Render to the WebGL context as needed.
            this.redrawIfNeeded();

            // Continue the animation frame loop until the WebGL context is lost.
            var thisWindow = this;

            function animationFrameCallback() {
                thisWindow.animationFrameLoop();
            }

            this.redrawRequestId = window.requestAnimationFrame(animationFrameCallback);
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.redrawIfNeeded = function () {
            // Check if the drawing buffer needs to resize to match its screen size, which requires a redraw.
            this.resize();

            // Redraw the WebGL drawing buffer only when necessary.
            if (!this.redrawRequested) {
                return;
            }

            try {
                // Prepare to redraw and notify the redraw callbacks that a redraw is about to occur.
                this.redrawRequested = false;
                this.drawContext.previousRedrawTimestamp = this.drawContext.timestamp;
                this.callRedrawCallbacks(WorldWind.BEFORE_REDRAW);
                // Redraw the WebGL drawing buffer.
                this.resetDrawContext();
                this.drawFrame();
            } catch (e) {
                Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "redrawIfNeeded",
                    "Exception occurred during redrawing.\n" + e.toString());
            } finally {
                // Notify the redraw callbacks that a redraw has completed.
                this.callRedrawCallbacks(WorldWind.AFTER_REDRAW);
                // Handle rendering code redraw requests.
                if (this.drawContext.redrawRequested) {
                    this.redrawRequested = true;
                }
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.resize = function () {
            var gl = this.drawContext.currentGlContext,
                width = gl.canvas.clientWidth * this.pixelScale,
                height = gl.canvas.clientHeight * this.pixelScale;

            if (gl.canvas.width != width ||
                gl.canvas.height != height) {

                // Make the canvas drawing buffer size match its screen size.
                gl.canvas.width = width;
                gl.canvas.height = height;

                // Set the WebGL viewport to match the canvas drawing buffer size.
                gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
                this.viewport = new Rectangle(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

                // Cause this WorldWindow to redraw with the new size.
                this.redrawRequested = true;
            }
        };

        // Internal. Intentionally not documented.
        WorldWindow.prototype.resetDrawContext = function () {
            this.globe.offset = 0;

            var dc = this.drawContext;
            dc.reset();
            dc.globe = this.globe;
            dc.layers = this.layers;
            dc.navigatorState = this.navigator.currentState();
            dc.verticalExaggeration = this.verticalExaggeration;
            dc.surfaceOpacity = this.surfaceOpacity;
            dc.deepPicking = this.deepPicking;
            dc.frameStatistics = this.frameStatistics;
            dc.pixelScale = this.pixelScale;
            dc.update();
        };

        /* useful stuff to debug WebGL */
        /*
         function logGLCall(functionName, args) {
         console.log("gl." + functionName + "(" +
         WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
         };

         function validateNoneOfTheArgsAreUndefined(functionName, args) {
         for (var ii = 0; ii < args.length; ++ii) {
         if (args[ii] === undefined) {
         console.error("undefined passed to gl." + functionName + "(" +
         WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
         }
         }
         };

         WorldWindow.prototype.logAndValidate = function logAndValidate(functionName, args) {
         logGLCall(functionName, args);
         validateNoneOfTheArgsAreUndefined (functionName, args);
         };

         WorldWindow.prototype.throwOnGLError = function throwOnGLError(err, funcName, args) {
         throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName;
         };
         */

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.drawFrame = function () {
            try {
                this.drawContext.frameStatistics.beginFrame();
                this.beginFrame();

                if (this.drawContext.globe.is2D() && this.drawContext.globe.continuous) {
                    this.do2DContiguousRepaint();
                } else {
                    this.doNormalRepaint();
                }

            } finally {
                this.endFrame();
                this.drawContext.frameStatistics.endFrame();
                //console.log(this.drawContext.frameStatistics.frameTime);
            }
        };

        WorldWindow.prototype.doNormalRepaint = function () {
            this.createTerrain();
            this.clearFrame();
            this.deferOrderedRendering = false;
            if (this.drawContext.pickingMode) {
                if (this.drawContext.makePickFrustum()) {
                    this.doPick();
                    this.resolvePick();
                }
            } else {
                this.doDraw();
                if (this.subsurfaceMode && this.hasStencilBuffer) {
                    this.redrawSurface();
                    this.drawScreenRenderables();
                }
            }
        };

        WorldWindow.prototype.do2DContiguousRepaint = function () {
            this.createTerrain2DContiguous();
            this.clearFrame();
            if (this.drawContext.pickingMode) {
                if (this.drawContext.makePickFrustum()) {
                    this.pick2DContiguous();
                    this.resolvePick();
                }
            } else {
                this.draw2DContiguous();
            }
        };

        WorldWindow.prototype.resolvePick = function () {
            if (this.drawContext.pickTerrainOnly) {
                this.resolveTerrainPick();
            } else if (this.drawContext.regionPicking) {
                this.resolveRegionPick();
            } else {
                this.resolveTopPick();
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.beginFrame = function () {
            var gl = this.drawContext.currentGlContext;
            gl.enable(gl.BLEND);
            gl.enable(gl.CULL_FACE);
            gl.enable(gl.DEPTH_TEST);
            gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
            gl.depthFunc(gl.LEQUAL);

            if (this.drawContext.pickingMode) {
                this.drawContext.makePickFramebuffer();
                this.drawContext.bindFramebuffer(this.drawContext.pickFramebuffer);
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.endFrame = function () {
            var gl = this.drawContext.currentGlContext;
            gl.disable(gl.BLEND);
            gl.disable(gl.CULL_FACE);
            gl.disable(gl.DEPTH_TEST);
            gl.blendFunc(gl.ONE, gl.ZERO);
            gl.depthFunc(gl.LESS);
            gl.clearColor(0, 0, 0, 1);

            this.drawContext.bindFramebuffer(null);
            this.drawContext.bindProgram(null);
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.clearFrame = function () {
            var dc = this.drawContext,
                gl = dc.currentGlContext;

            gl.clearColor(dc.clearColor.red, dc.clearColor.green, dc.clearColor.blue, dc.clearColor.alpha);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.doDraw = function () {
            this.drawContext.renderShapes = true;

            if (this.subsurfaceMode && this.hasStencilBuffer) {
                // Draw the surface and collect the ordered renderables.
                this.drawContext.currentGlContext.disable(this.drawContext.currentGlContext.STENCIL_TEST);
                this.drawContext.surfaceShapeTileBuilder.clear();
                this.drawLayers(true);
                this.drawSurfaceRenderables();
                this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);

                if (!this.deferOrderedRendering) {
                    // Clear the depth and stencil buffers prior to rendering the ordered renderables. This allows
                    // sub-surface renderables to be drawn beneath the terrain. Turn on stenciling to capture the
                    // fragments that ordered renderables draw. The terrain and surface shapes will be subsequently
                    // drawn again, and the stencil buffer will ensure that they are drawn only where they overlap
                    // the fragments drawn by the ordered renderables.
                    this.drawContext.currentGlContext.clear(
                        this.drawContext.currentGlContext.DEPTH_BUFFER_BIT | this.drawContext.currentGlContext.STENCIL_BUFFER_BIT);
                    this.drawContext.currentGlContext.enable(this.drawContext.currentGlContext.STENCIL_TEST);
                    this.drawContext.currentGlContext.stencilFunc(this.drawContext.currentGlContext.ALWAYS, 1, 1);
                    this.drawContext.currentGlContext.stencilOp(
                        this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE);
                    this.drawOrderedRenderables();

                    this.drawContext.screenCreditController.drawCredits(this.drawContext);
                }
            } else {
                this.drawContext.surfaceShapeTileBuilder.clear();
                this.drawLayers(true);
                this.drawSurfaceRenderables();
                this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);

                if (!this.deferOrderedRendering) {
                    this.drawOrderedRenderables();
                    this.drawScreenRenderables();
                }

                this.drawContext.screenCreditController.drawCredits(this.drawContext);
            }
        };

        WorldWindow.prototype.redrawSurface = function () {
            // Draw the terrain and surface shapes but only where the current stencil buffer is non-zero.
            // The non-zero fragments are from drawing the ordered renderables previously.
            this.drawContext.currentGlContext.enable(this.drawContext.currentGlContext.STENCIL_TEST);
            this.drawContext.currentGlContext.stencilFunc(this.drawContext.currentGlContext.EQUAL, 1, 1);
            this.drawContext.currentGlContext.stencilOp(
                this.drawContext.currentGlContext.KEEP, this.drawContext.currentGlContext.KEEP, this.drawContext.currentGlContext.KEEP);
            this.drawContext.surfaceShapeTileBuilder.clear();
            this.drawLayers(false);
            this.drawSurfaceRenderables();
            this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);
            this.drawContext.currentGlContext.disable(this.drawContext.currentGlContext.STENCIL_TEST);
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.doPick = function () {
            if (this.drawContext.terrain) {
                this.drawContext.terrain.pick(this.drawContext);
            }

            if (!this.drawContext.pickTerrainOnly) {
                if (this.subsurfaceMode && this.hasStencilBuffer) {
                    // Draw the surface and collect the ordered renderables.
                    this.drawContext.currentGlContext.disable(this.drawContext.currentGlContext.STENCIL_TEST);
                    this.drawContext.surfaceShapeTileBuilder.clear();
                    this.drawLayers(true);
                    this.drawSurfaceRenderables();
                    this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);

                    if (!this.deferOrderedRendering) {
                        // Clear the depth and stencil buffers prior to rendering the ordered renderables. This allows
                        // sub-surface renderables to be drawn beneath the terrain. Turn on stenciling to capture the
                        // fragments that ordered renderables draw. The terrain and surface shapes will be subsequently
                        // drawn again, and the stencil buffer will ensure that they are drawn only where they overlap
                        // the fragments drawn by the ordered renderables.
                        this.drawContext.currentGlContext.clear(
                            this.drawContext.currentGlContext.DEPTH_BUFFER_BIT | this.drawContext.currentGlContext.STENCIL_BUFFER_BIT);
                        this.drawContext.currentGlContext.enable(this.drawContext.currentGlContext.STENCIL_TEST);
                        this.drawContext.currentGlContext.stencilFunc(this.drawContext.currentGlContext.ALWAYS, 1, 1);
                        this.drawContext.currentGlContext.stencilOp(
                            this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE);
                        this.drawOrderedRenderables();
                        this.drawContext.terrain.pick(this.drawContext);
                        this.drawScreenRenderables();
                    }
                } else {
                    this.drawContext.surfaceShapeTileBuilder.clear();

                    this.drawLayers(true);
                    this.drawSurfaceRenderables();

                    this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);

                    if (!this.deferOrderedRendering) {
                        this.drawOrderedRenderables();
                        this.drawScreenRenderables();
                    }
                }
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.createTerrain = function () {
            var dc = this.drawContext;
            dc.terrain = this.globe.tessellator.tessellate(dc);
            dc.frameStatistics.setTerrainTileCount(dc.terrain ? dc.terrain.surfaceGeometry.length : 0);
        };

        WorldWindow.prototype.makeCurrent = function (offset) {
            var dc = this.drawContext;
            dc.globe.offset = offset;
            dc.globeStateKey = dc.globe.stateKey;

            switch (offset) {
                case -1:
                    dc.terrain = this.terrainLeft;
                    break;

                case 0:
                    dc.terrain = this.terrainCenter;
                    break;

                case 1:
                    dc.terrain = this.terrainRight;
                    break;
            }
        };

        WorldWindow.prototype.createTerrain2DContiguous = function () {
            var dc = this.drawContext;

            this.terrainCenter = null;
            dc.globe.offset = 0;
            dc.globeStateKey = dc.globe.stateKey;
            if (dc.globe.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates)) {
                this.terrainCenter = dc.globe.tessellator.tessellate(dc);
            }

            this.terrainRight = null;
            dc.globe.offset = 1;
            dc.globeStateKey = dc.globe.stateKey;
            if (dc.globe.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates)) {
                this.terrainRight = dc.globe.tessellator.tessellate(dc);
            }

            this.terrainLeft = null;
            dc.globe.offset = -1;
            dc.globeStateKey = dc.globe.stateKey;
            if (dc.globe.intersectsFrustum(dc.navigatorState.frustumInModelCoordinates)) {
                this.terrainLeft = dc.globe.tessellator.tessellate(dc);
            }
        };

        WorldWindow.prototype.draw2DContiguous = function () {
            var drawing = "";

            if (this.terrainCenter) {
                drawing += " 0 ";
                this.makeCurrent(0);
                this.deferOrderedRendering = this.terrainLeft || this.terrainRight;
                this.doDraw();
            }

            if (this.terrainRight) {
                drawing += " 1 ";
                this.makeCurrent(1);
                this.deferOrderedRendering = this.terrainLeft || this.terrainLeft;
                this.doDraw();
            }

            this.deferOrderedRendering = false;

            if (this.terrainLeft) {
                drawing += " -1 ";
                this.makeCurrent(-1);
                this.doDraw();
            }
            //
            //console.log(drawing);

            if (this.subsurfaceMode && this.hasStencilBuffer) {
                this.deferOrderedRendering = true;

                if (this.terrainCenter) {
                    drawing += " 0 ";
                    this.makeCurrent(0);
                    this.redrawSurface();
                }

                if (this.terrainRight) {
                    drawing += " 1 ";
                    this.makeCurrent(1);
                    this.redrawSurface();
                }

                if (this.terrainLeft) {
                    drawing += " -1 ";
                    this.makeCurrent(-1);
                    this.redrawSurface();
                }
            }

            this.drawScreenRenderables();
        };

        WorldWindow.prototype.pick2DContiguous = function () {
            if (this.terrainCenter) {
                this.makeCurrent(0);
                this.deferOrderedRendering = this.terrainLeft || this.terrainRight;
                this.doPick();
            }

            if (this.terrainRight) {
                this.makeCurrent(1);
                this.deferOrderedRendering = this.terrainLeft || this.terrainLeft;
                this.doPick();
            }

            this.deferOrderedRendering = false;

            if (this.terrainLeft) {
                this.makeCurrent(-1);
                this.doPick();
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.drawLayers = function (accumulateOrderedRenderables) {
            // Draw all the layers attached to this WorldWindow.

            var beginTime = Date.now(),
                dc = this.drawContext,
                layers = dc.layers,
                layer;

            dc.accumulateOrderedRenderables = accumulateOrderedRenderables;

            for (var i = 0, len = layers.length; i < len; i++) {
                layer = layers[i];
                if (layer) {
                    dc.currentLayer = layer;
                    try {
                        layer.render(dc);
                    } catch (e) {
                        Logger.log(Logger.LEVEL_SEVERE, "Error while rendering layer " + layer.displayName + ".\n"
                            + e.toString());
                        // Keep going. Render the rest of the layers.
                    }
                }
            }

            var now = Date.now();
            dc.frameStatistics.layerRenderingTime = now - beginTime;
        };

        /**
         * Adds a specified layer to the end of this WorldWindow.
         * @param {Layer} layer The layer to add. May be null or undefined, in which case this WorldWindow is not modified.
         */
        WorldWindow.prototype.addLayer = function (layer) {
            this.layers.push(layer);
        };

        /**
         * Removes the first instance of a specified layer from this WorldWindow.
         * @param {Layer} layer The layer to remove. May be null or undefined, in which case this WorldWindow is not
         * modified. This WorldWindow is also not modified if the specified layer does not exist in this world
         * window's layer list.
         */
        WorldWindow.prototype.removeLayer = function (layer) {
            if (!layer)
                return;

            var index = -1;
            for (var i = 0, len = this.layers.length; i < len; i++) {
                if (this.layers[i] == layer) {
                    index = i;
                    break;
                }
            }

            if (index >= 0) {
                this.layers.splice(index, 1);
            }
        };

        /**
         * Inserts a specified layer at a specified position in this WorldWindow's layer list.
         * @param {number} index The index at which to insert the layer. May be negative to specify the position
         * from the end of the array.
         * @param {Layer} layer The layer to insert. This WorldWindow's layer list is not changed if the specified
         * layer is null or undefined.
         */
        WorldWindow.prototype.insertLayer = function (index, layer) {
            if (layer) {
                this.layers.splice(index, 0, layer);
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.drawSurfaceRenderables = function () {
            var dc = this.drawContext,
                sr;

            dc.reverseSurfaceRenderables();

            while (sr = dc.popSurfaceRenderable()) {
                try {
                    sr.renderSurface(dc);
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WorldWindow", "drawSurfaceRenderables",
                        "Error while rendering a surface renderable.\n" + e.message);
                    // Keep going. Render the rest of the surface renderables.
                }
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.drawOrderedRenderables = function () {
            var beginTime = Date.now(),
                dc = this.drawContext,
                or;

            dc.sortOrderedRenderables();

            if (this._orderedRenderingFilters) {
                for (var f = 0; f < this._orderedRenderingFilters.length; f++) {
                    this._orderedRenderingFilters[f](this.drawContext);
                }
            }

            dc.orderedRenderingMode = true;

            while (or = dc.popOrderedRenderable()) {
                try {
                    or.renderOrdered(dc);
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WorldWindow", "drawOrderedRenderables",
                        "Error while rendering an ordered renderable.\n" + e.message);
                    // Keep going. Render the rest of the ordered renderables.
                }
            }

            dc.orderedRenderingMode = false;
            dc.frameStatistics.orderedRenderingTime = Date.now() - beginTime;
        };

        WorldWindow.prototype.drawScreenRenderables = function () {
            var dc = this.drawContext,
                or;

            while (or = dc.nextScreenRenderable()) {
                try {
                    or.renderOrdered(dc);
                } catch (e) {
                    Logger.logMessage(Logger.LEVEL_WARNING, "WorldWindow", "drawOrderedRenderables",
                        "Error while rendering a screen renderable.\n" + e.message);
                    // Keep going. Render the rest of the screen renderables.
                }
            }
        };

        // Internal function. Intentionally not documented.
        WorldWindow.prototype.resolveTopPick = function () {
            if (this.drawContext.objectsAtPickPoint.objects.length == 0) {
                return; // nothing picked; avoid calling readPickColor unnecessarily
            }

            // Make a last reading to determine what's on top.

            var pickedObjects = this.drawContext.objectsAtPickPoint,
                pickColor = this.drawContext.readPickColor(this.drawContext.pickPoint),
                topObject = null,
                terrainObject = null;

            if (pickColor) {
                // Find the picked object with the top color code and set its isOnTop flag.
                for (var i = 0, len = pickedObjects.objects.length; i < len; i++) {
                    var po = pickedObjects.objects[i];

                    if (po.isTerrain) {
                        terrainObject = po;
                    }

                    if (po.color.equals(pickColor)) {
                        po.isOnTop = true;
                        topObject = po;

                        if (terrainObject) {
                            break; // no need to search for more than the top object and the terrain object
                        }
                    }
                }

                // In single-pick mode provide only the top-most object and the terrain object, if any.
                if (!this.drawContext.deepPicking) {
                    pickedObjects.clear();
                    if (topObject) {
                        pickedObjects.add(topObject);
                    }
                    if (terrainObject && terrainObject != topObject) {
                        pickedObjects.add(terrainObject);
                    }
                }
            } else {
                pickedObjects.clear(); // nothing drawn at the pick point
            }
        };

        // Internal. Intentionally not documented.
        WorldWindow.prototype.resolveTerrainPick = function () {
            var pickedObjects = this.drawContext.objectsAtPickPoint,
                po;

            // Mark the first picked terrain object as "on top". The picked object list should contain only one entry
            // indicating the picked terrain object, but we iterate over the list contents anyway.
            for (var i = 0, len = pickedObjects.objects.length; i < len; i++) {
                po = pickedObjects.objects[i];
                if (po.isTerrain) {
                    po.isOnTop = true;
                    break;
                }
            }
        };

        // Internal. Intentionally not documented.
        WorldWindow.prototype.resolveRegionPick = function () {
            if (this.drawContext.objectsAtPickPoint.objects.length == 0) {
                return; // nothing picked; avoid calling readPickColors unnecessarily
            }

            // Mark every picked object with a color in the pick buffer as "on top".

            var pickedObjects = this.drawContext.objectsAtPickPoint,
                uniquePickColors = this.drawContext.readPickColors(this.drawContext.pickRectangle),
                po,
                color;

            for (var i = 0, len = pickedObjects.objects.length; i < len; i++) {
                po = pickedObjects.objects[i];
                if (!po) continue;
                var poColor = po.color.toByteString();
                color = uniquePickColors[poColor];
                if (color) {
                    po.isOnTop = true;
                } else if (po.userObject instanceof SurfaceShape) {
                    // SurfaceShapes ALWAYS get added to the pick list, since their rendering is deferred
                    // until the tile they are cached by is rendered. So a SurfaceShape may be in the pick list
                    // but is not seen in the pick rectangle.
                    //
                    // Remove the SurfaceShape that was not visible to the pick rectangle.
                    pickedObjects.objects.splice(i, 1);
                    i -= 1;
                }
            }
        };

        // Internal. Intentionally not documented.
        WorldWindow.prototype.callRedrawCallbacks = function (stage) {
            for (var i = 0, len = this._redrawCallbacks.length; i < len; i++) {
                try {
                    this._redrawCallbacks[i](this, stage);
                } catch (e) {
                    Logger.log(Logger.LEVEL_SEVERE, "Exception calling redraw callback.\n" + e.toString());
                    // Keep going. Execute the rest of the callbacks.
                }
            }
        };

        /**
         * Moves this WorldWindow's navigator to a specified location or position.
         * @param {Location | Position} position The location or position to move the navigator to. If this
         * argument contains an "altitude" property, as {@link Position} does, the end point of the navigation is
         * at the specified altitude. Otherwise the end point is at the current altitude of the navigator.
         *
         * This function uses this WorldWindow's {@link GoToAnimator} property to perform the move. That object's
         * properties can be specified by the application to modify its behavior during calls to this function.
         * It's cancel method can also be used to cancel the move initiated by this function.
         * @param {Function} completionCallback If not null or undefined, specifies a function to call when the
         * animation completes. The completion callback is called with a single argument, this animator.
         * @throws {ArgumentError} If the specified location or position is null or undefined.
         */
        WorldWindow.prototype.goTo = function (position, completionCallback) {
            this.goToAnimator.goTo(position, completionCallback);
        };

        /**
         * Declutters the current ordered renderables with a specified group ID. This function is not called by
         * applications directly. It's meant to be invoked as an ordered rendering filter in this WorldWindow's
         * [orderedRenderingFilters]{@link WorldWindow#orderedRenderingFilters} property.
         * <p>
         * The function operates by setting the target visibility of occluded shapes to 0 and unoccluded shapes to 1.
         * @param {DrawContext} dc The current draw context.
         * @param {Number} groupId The ID of the group to declutter. Must not be null, undefined or 0.
         * @throws {ArgumentError} If the specified group ID is null, undefined or 0.
         */
        WorldWindow.prototype.declutter = function (dc, groupId) {
            if (!groupId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "declutter",
                        "Group ID is null, undefined or 0."));
            }

            // Collect all the declutterables in the specified group.
            var declutterables = [];
            for (var i = 0; i < dc.orderedRenderables.length; i++) {
                var orderedRenderable = dc.orderedRenderables[i].orderedRenderable;
                if (orderedRenderable.declutterGroup === groupId) {
                    declutterables.push(orderedRenderable);
                }
            }

            // Filter the declutterables by determining which are partially occluded. Since the ordered renderable
            // list was already sorted from front to back, the front-most will represent an entire occluded group.
            var rects = [];
            for (var j = 0; j < declutterables.length; j++) {
                var declutterable = declutterables[j],
                    screenBounds = declutterable.screenBounds;

                if (screenBounds && screenBounds.intersectsRectangles(rects)) {
                    declutterable.targetVisibility = 0;
                } else {
                    declutterable.targetVisibility = 1;
                    if (screenBounds) {
                        rects.push(screenBounds);
                    }
                }
            }
        };

        return WorldWindow;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
/**
 * @exports WWMessage
 */
define('util/WWMessage',[],
    function () {
        "use strict";

        /**
         * Create a WWMessage instance.
         * @classdesc Defines a class to hold message information.
         * @param {String} type The message type.
         * @param {{}} source The source of the message.
         * @constructor
         */
        var WWMessage = function(type, source) {

            /**
             * This object's message type.
             * @type {String}
             * @readonly
             */
            this.type = type;

            /**
             * The source object of this message.
             * @type {{}}
             * @readonly
             */
            this.source = source;
        };

        return WWMessage;
    }
);
/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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.
 */
define('WorldWind',[ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not directory name).
        './error/AbstractError',
        './geom/Angle',
        './shapes/Annotation',
        './shapes/AnnotationAttributes',
        './util/measure/AreaMeasurer',
        './error/ArgumentError',
        './layer/AtmosphereLayer',
        './shaders/AtmosphereProgram',
        './shaders/BasicProgram',
        './shaders/BasicTextureProgram',
        './util/BasicTimeSequence',
        './layer/BingAerialLayer',
        './layer/BingAerialWithLabelsLayer',
        './layer/BingRoadsLayer',
        './layer/BingWMSLayer',
        './layer/BMNGLandsatLayer',
        './layer/BMNGLayer',
        './layer/BMNGOneImageLayer',
        './layer/BMNGRestLayer',
        './geom/BoundingBox',
        './gesture/ClickRecognizer',
        './formats/collada/ColladaLoader',
        './util/Color',
        './shapes/Compass',
        './layer/CompassLayer',
        './layer/CoordinatesDisplayLayer',
        './util/Date',
        './layer/DigitalGlobeTiledImageLayer',
        './gesture/DragRecognizer',
        './render/DrawContext',
        './globe/EarthElevationModel',
        './globe/EarthRestElevationModel',
        './globe/ElevationModel',
        './util/Font',
        './util/FrameStatistics',
        './layer/FrameStatisticsLayer',
        './render/FramebufferTexture',
        './render/FramebufferTile',
        './render/FramebufferTileController',
        './geom/Frustum',
        './shapes/GeographicMesh',
        './projections/GeographicProjection',
        './shapes/GeographicText',
        './formats/geojson/GeoJSONGeometry',
        './formats/geojson/GeoJSONGeometryCollection',
        './formats/geojson/GeoJSONGeometryLineString',
        './formats/geojson/GeoJSONGeometryMultiLineString',
        './formats/geojson/GeoJSONGeometryMultiPoint',
        './formats/geojson/GeoJSONGeometryMultiPolygon',
        './formats/geojson/GeoJSONGeometryPoint',
        './formats/geojson/GeoJSONGeometryPolygon',
        './formats/geojson/GeoJSONParser',
        './formats/geotiff/GeoTiffReader',
        './gesture/GestureRecognizer',
        './globe/Globe',
        './globe/Globe2D',
        './util/GoToAnimator',
        './shaders/GpuProgram',
        './cache/GpuResourceCache',
        './shaders/GpuShader',
        './shaders/GroundProgram',
        './util/HashMap',
        './util/HighlightController',
        './formats/kml/util/ImagePyramid',
        './util/ImageSource',
        './render/ImageTile',
        './util/Insets',
        './formats/kml/util/ItemIcon',
        './formats/kml/KmlAbstractView',
        './formats/kml/styles/KmlBalloonStyle',
        './formats/kml/KmlCamera',
        './formats/kml/styles/KmlColorStyle',
        './formats/kml/features/KmlContainer',
        './formats/kml/controls/KmlControls',
        './formats/kml/features/KmlDocument',
        './formats/kml/KmlElements',
        './formats/kml/features/KmlFeature',
        './formats/kml/KmlFile',
        './formats/kml/features/KmlFolder',
        './formats/kml/geom/KmlGeometry',
        './formats/kml/features/KmlGroundOverlay',
        './formats/kml/KmlIcon',
        './formats/kml/styles/KmlIconStyle',
        './formats/kml/styles/KmlLabelStyle',
        './formats/kml/KmlLatLonAltBox',
        './formats/kml/KmlLatLonBox',
        './formats/kml/KmlLatLonQuad',
        './formats/kml/geom/KmlLinearRing',
        './formats/kml/geom/KmlLineString',
        './formats/kml/styles/KmlLineStyle',
        './formats/kml/KmlLink',
        './formats/kml/styles/KmlListStyle',
        './formats/kml/KmlLocation',
        './formats/kml/KmlLod',
        './formats/kml/KmlLookAt',
        './formats/kml/geom/KmlMultiGeometry',
        './formats/kml/features/KmlNetworkLink',
        './formats/kml/KmlObject',
        './formats/kml/KmlOrientation',
        './formats/kml/features/KmlOverlay',
        './formats/kml/features/KmlPhotoOverlay',
        './formats/kml/features/KmlPlacemark',
        './formats/kml/geom/KmlPoint',
        './formats/kml/geom/KmlPolygon',
        './formats/kml/styles/KmlPolyStyle',
        './formats/kml/KmlRegion',
        './formats/kml/features/KmlScreenOverlay',
        './formats/kml/styles/KmlStyle',
        './formats/kml/styles/KmlStyleMap',
        './formats/kml/styles/KmlStyleSelector',
        './formats/kml/styles/KmlSubStyle',
        './formats/kml/KmlTimePrimitive',
        './formats/kml/KmlTimeSpan',
        './formats/kml/KmlTimeStamp',
        './formats/kml/features/KmlTour',
        './formats/kml/geom/KmlTrack',
        './formats/kml/controls/KmlTreeVisibility',
        './layer/LandsatRestLayer',
        './layer/Layer',
        './util/measure/LengthMeasurer',
        './util/Level',
        './util/LevelRowColumnUrlBuilder',
        './util/LevelSet',
        './geom/Line',
        './geom/Location',
        './util/Logger',
        './navigate/LookAtNavigator',
        './geom/Matrix',
        './util/measure/MeasurerUtils',
        './cache/MemoryCache',
        './cache/MemoryCacheListener',
        './layer/MercatorTiledImageLayer',
        './navigate/Navigator',
        './navigate/NavigatorState',
        './util/NominatimGeocoder',
        './error/NotYetImplementedError',
        './util/Offset',
        './layer/OpenStreetMapImageLayer',
        './formats/kml/util/Pair',
        './gesture/PanRecognizer',
        './shapes/Path',
        './util/PeriodicTimeSequence',
        './pick/PickedObject',
        './pick/PickedObjectList',
        './gesture/PinchRecognizer',
        './shapes/Placemark',
        './shapes/PlacemarkAttributes',
        './geom/Plane',
        './shapes/Polygon',
        './util/PolygonSplitter',
        './geom/Position',
        './projections/ProjectionEquirectangular',
        './projections/ProjectionGnomonic',
        './projections/ProjectionMercator',
        './projections/ProjectionPolarEquidistant',
        './projections/ProjectionUPS',
        './projections/ProjectionWgs84',
        './geom/Rectangle',
        './render/Renderable',
        './layer/RenderableLayer',
        './layer/RestTiledImageLayer',
        './gesture/RotationRecognizer',
        './formats/kml/util/Scale',
        './formats/kml/util/Schema',
        './shapes/ScreenImage',
        './shapes/ScreenText',
        './geom/Sector',
        './shapes/ShapeAttributes',
        './formats/shapefile/Shapefile',
        './layer/ShowTessellationLayer',
        './shaders/SkyProgram',
        './layer/StarFieldLayer',
        './shaders/StarFieldProgram',
        './util/SunPosition',
        './shapes/SurfaceImage',
        './shapes/SurfaceCircle',
        './shapes/SurfaceEllipse',
        './shapes/SurfacePolygon',
        './shapes/SurfacePolyline',
        './shapes/SurfaceRectangle',
        './render/SurfaceRenderable',
        './shapes/SurfaceSector',
        './shapes/SurfaceShape',
        './shapes/SurfaceShapeTile',
        './shapes/SurfaceShapeTileBuilder',
        './render/SurfaceTile',
        './render/SurfaceTileRenderer',
        './shaders/SurfaceTileRendererProgram',
        './gesture/TapRecognizer',
        './layer/TectonicPlatesLayer',
        './globe/Terrain',
        './globe/TerrainTile',
        './globe/TerrainTileList',
        './globe/Tessellator',
        './shapes/Text',
        './shapes/TextAttributes',
        './render/TextSupport',
        './render/Texture',
        './render/TextureTile',
        './util/Tile',
        './layer/TiledImageLayer',
        './util/TileFactory',
        './gesture/TiltRecognizer',
        './gesture/Touch',
        './shapes/TriangleMesh',
        './error/UnsupportedOperationError',
        './geom/Vec2',
        './geom/Vec3',
        './layer/ViewControlsLayer',
        './formats/kml/util/ViewVolume',
        './util/WcsTileUrlBuilder',
        './ogc/WfsCapabilities',
        './formats/wkt/Wkt',
        './formats/wkt/WktElements',
        './formats/wkt/geom/WktGeometryCollection',
        './formats/wkt/geom/WktLineString',
        './formats/wkt/geom/WktMultiLineString',
        './formats/wkt/geom/WktMultiPoint',
        './formats/wkt/geom/WktMultiPolygon',
        './formats/wkt/geom/WktObject',
        './formats/wkt/geom/WktPoint',
        './formats/wkt/geom/WktPolygon',
        './formats/wkt/WktTokens',
        './formats/wkt/geom/WktTriangle',
        './formats/wkt/WktType',
        './ogc/wms/WmsCapabilities',
        './layer/WmsLayer',
        './ogc/wms/WmsLayerCapabilities',
        './layer/WmsTimeDimensionedLayer',
        './util/WmsUrlBuilder',
        './ogc/wmts/WmtsCapabilities',
        './layer/WmtsLayer',
        './ogc/wmts/WmtsLayerCapabilities',
        './WorldWindow',
        './util/WWMath',
        './util/WWMessage',
        './util/WWUtil',
        './util/XmlDocument',
        './globe/ZeroElevationModel'],
    function (AbstractError,
              Angle,
              Annotation,
              AnnotationAttributes,
              AreaMeasurer,
              ArgumentError,
              AtmosphereLayer,
              AtmosphereProgram,
              BasicProgram,
              BasicTextureProgram,
              BasicTimeSequence,
              BingAerialLayer,
              BingAerialWithLabelsLayer,
              BingRoadsLayer,
              BingWMSLayer,
              BMNGLandsatLayer,
              BMNGLayer,
              BMNGOneImageLayer,
              BMNGRestLayer,
              BoundingBox,
              ClickRecognizer,
              ColladaLoader,
              Color,
              Compass,
              CompassLayer,
              CoordinatesDisplayLayer,
              DateWW,
              DigitalGlobeTiledImageLayer,
              DragRecognizer,
              DrawContext,
              EarthElevationModel,
              EarthRestElevationModel,
              ElevationModel,
              Font,
              FrameStatistics,
              FrameStatisticsLayer,
              FramebufferTexture,
              FramebufferTile,
              FramebufferTileController,
              Frustum,
              GeographicMesh,
              GeographicProjection,
              GeographicText,
              GeoJSONGeometry,
              GeoJSONGeometryCollection,
              GeoJSONGeometryLineString,
              GeoJSONGeometryMultiLineString,
              GeoJSONGeometryMultiPoint,
              GeoJSONGeometryMultiPolygon,
              GeoJSONGeometryPoint,
              GeoJSONGeometryPolygon,
              GeoJSONParser,
              GeoTiffReader,
              GestureRecognizer,
              Globe,
              Globe2D,
              GoToAnimator,
              GpuProgram,
              GpuResourceCache,
              GpuShader,
              GroundProgram,
              HashMap,
              HighlightController,
              ImagePyramid,
              ImageSource,
              ImageTile,
              Insets,
              ItemIcon,
              KmlAbstractView,
              KmlBalloonStyle,
              KmlCamera,
              KmlColorStyle,
              KmlContainer,
              KmlControls,
              KmlDocument,
              KmlElements,
              KmlFeature,
              KmlFile,
              KmlFolder,
              KmlGeometry,
              KmlGroundOverlay,
              KmlIcon,
              KmlIconStyle,
              KmlLabelStyle,
              KmlLatLonAltBox,
              KmlLatLonBox,
              KmlLatLonQuad,
              KmlLinearRing,
              KmlLineString,
              KmlLineStyle,
              KmlLink,
              KmlListStyle,
              KmlLocation,
              KmlLod,
              KmlLookAt,
              KmlMultiGeometry,
              KmlNetworkLink,
              KmlObject,
              KmlOrientation,
              KmlOverlay,
              KmlPhotoOverlay,
              KmlPlacemark,
              KmlPoint,
              KmlPolygon,
              KmlPolyStyle,
              KmlRegion,
              KmlScreenOverlay,
              KmlStyle,
              KmlStyleMap,
              KmlStyleSelector,
              KmlSubStyle,
              KmlTimePrimitive,
              KmlTimeSpan,
              KmlTimeStamp,
              KmlTour,
              KmlTrack,
              KmlTreeVisibility,
              LandsatRestLayer,
              Layer,
              LengthMeasurer,
              Level,
              LevelRowColumnUrlBuilder,
              LevelSet,
              Line,
              Location,
              Logger,
              LookAtNavigator,
              Matrix,
              MeasurerUtils,
              MemoryCache,
              MemoryCacheListener,
              MercatorTiledImageLayer,
              Navigator,
              NavigatorState,
              NominatimGeocoder,
              NotYetImplementedError,
              Offset,
              OpenStreetMapImageLayer,
              Pair,
              PanRecognizer,
              Path,
              PeriodicTimeSequence,
              PickedObject,
              PickedObjectList,
              PinchRecognizer,
              Placemark,
              PlacemarkAttributes,
              Plane,
              Polygon,
              PolygonSplitter,
              Position,
              ProjectionEquirectangular,
              ProjectionGnomonic,
              ProjectionMercator,
              ProjectionPolarEquidistant,
              ProjectionUPS,
              ProjectionWgs84,
              Rectangle,
              Renderable,
              RenderableLayer,
              RestTiledImageLayer,
              RotationRecognizer,
              Scale,
              Schema,
              ScreenImage,
              ScreenText,
              Sector,
              ShapeAttributes,
              Shapefile,
              ShowTessellationLayer,
              SkyProgram,
              StarFieldLayer,
              StarFieldProgram,
              SunPosition,
              SurfaceImage,
              SurfaceCircle,
              SurfaceEllipse,
              SurfacePolygon,
              SurfacePolyline,
              SurfaceRectangle,
              SurfaceRenderable,
              SurfaceSector,
              SurfaceShape,
              SurfaceShapeTile,
              SurfaceShapeTileBuilder,
              SurfaceTile,
              SurfaceTileRenderer,
              SurfaceTileRendererProgram,
              TapRecognizer,
              TectonicPlatesLayer,
              Terrain,
              TerrainTile,
              TerrainTileList,
              Tessellator,
              Text,
              TextAttributes,
              TextSupport,
              Texture,
              TextureTile,
              Tile,
              TiledImageLayer,
              TileFactory,
              TiltRecognizer,
              Touch,
              TriangleMesh,
              UnsupportedOperationError,
              Vec2,
              Vec3,
              ViewControlsLayer,
              ViewVolume,
              WcsTileUrlBuilder,
              WfsCapabilities,
              Wkt,
              WktElements,
              WktGeometryCollection,
              WktLineString,
              WktMultiLineString,
              WktMultiPoint,
              WktMultiPolygon,
              WktObject,
              WktPoint,
              WktPolygon,
              WktTokens,
              WktTriangle,
              WktType,
              WmsCapabilities,
              WmsLayer,
              WmsLayerCapabilities,
              WmsTimeDimensionedLayer,
              WmsUrlBuilder,
              WmtsCapabilities,
              WmtsLayer,
              WmtsLayerCapabilities,
              WorldWindow,
              WWMath,
              WWMessage,
              WWUtil,
              XmlDocument,
              ZeroElevationModel) {
        "use strict";
        /**
         * This is the top-level WorldWind module. It is global.
         * @exports WorldWind
         * @global
         */
        var WorldWind = {
            /**
             * The WorldWind version number.
             * @default "0.9.0"
             * @constant
             */
            VERSION: "0.9.0",

            // PLEASE KEEP THE ENTRIES BELOW IN ALPHABETICAL ORDER
            /**
             * Indicates an altitude mode relative to the globe's ellipsoid.
             * @constant
             */
            ABSOLUTE: "absolute",

            /**
             * Indicates that a redraw callback has been called immediately after a redraw.
             * @constant
             */
            AFTER_REDRAW: "afterRedraw",

            /**
             * Indicates that a redraw callback has been called immediately before a redraw.
             * @constant
             */
            BEFORE_REDRAW: "beforeRedraw",

            /**
             * The BEGAN gesture recognizer state. Continuous gesture recognizers transition to this state from the
             * POSSIBLE state when the gesture is first recognized.
             * @constant
             */
            BEGAN: "began",

            /**
             * The CANCELLED gesture recognizer state. Continuous gesture recognizers may transition to this state from
             * the BEGAN state or the CHANGED state when the touch events are cancelled.
             * @constant
             */
            CANCELLED: "cancelled",

            /**
             * The CHANGED gesture recognizer state. Continuous gesture recognizers transition to this state from the
             * BEGAN state or the CHANGED state, whenever an input event indicates a change in the gesture.
             * @constant
             */
            CHANGED: "changed",

            /**
             * Indicates an altitude mode always on the terrain.
             * @constant
             */
            CLAMP_TO_GROUND: "clampToGround",

            /**
             * The radius of Earth.
             * @constant
             */
            EARTH_RADIUS: 6371e3,

            /**
             * Indicates the cardinal direction east.
             * @constant
             */
            EAST: "east",

            /**
             * The ENDED gesture recognizer state. Continuous gesture recognizers transition to this state from either
             * the BEGAN state or the CHANGED state when the current input no longer represents the gesture.
             * @constant
             */
            ENDED: "ended",

            /**
             * The FAILED gesture recognizer state. Gesture recognizers transition to this state from the POSSIBLE state
             * when the gesture cannot be recognized given the current input.
             * @constant
             */
            FAILED: "failed",

            /**
             * Indicates a great circle path.
             * @constant
             */
            GREAT_CIRCLE: "greatCircle",

            /**
             * Indicates a linear, straight line path.
             * @constant
             */
            LINEAR: "linear",

            /**
             * Indicates a multi-point shape, typically within a shapefile.
             */
            MULTI_POINT: "multiPoint",

            /**
             * Indicates the cardinal direction north.
             * @constant
             */
            NORTH: "north",

            /**
             * Indicates a null shape, typically within a shapefile.
             * @constant
             */
            NULL: "null",

            /**
             * Indicates that the associated parameters are fractional values of the virtual rectangle's width or
             * height in the range [0, 1], where 0 indicates the rectangle's origin and 1 indicates the corner
             * opposite its origin.
             * @constant
             */
            OFFSET_FRACTION: "fraction",

            /**
             * Indicates that the associated parameters are in units of pixels relative to the virtual rectangle's
             * corner opposite its origin corner.
             * @constant
             */
            OFFSET_INSET_PIXELS: "insetPixels",

            /**
             * Indicates that the associated parameters are in units of pixels relative to the virtual rectangle's
             * origin.
             * @constant
             */
            OFFSET_PIXELS: "pixels",

            /**
             * Indicates a point shape, typically within a shapefile.
             */
            POINT: "point",

            /**
             * Indicates a polyline shape, typically within a shapefile.
             */
            POLYLINE: "polyline",

            /**
             * Indicates a polygon shape, typically within a shapefile.
             */
            POLYGON: "polygon",

            /**
             * The POSSIBLE gesture recognizer state. Gesture recognizers in this state are idle when there is no input
             * event to evaluate, or are evaluating input events to determine whether or not to transition into another
             * state.
             * @constant
             */
            POSSIBLE: "possible",

            /**
             * The RECOGNIZED gesture recognizer state. Discrete gesture recognizers transition to this state from the
             * POSSIBLE state when the gesture is recognized.
             * @constant
             */
            RECOGNIZED: "recognized",

            /**
             * The event name of WorldWind redraw events.
             */
            REDRAW_EVENT_TYPE: "WorldWindRedraw",

            /**
             * Indicates that the related value is specified relative to the globe.
             * @constant
             */
            RELATIVE_TO_GLOBE: "relativeToGlobe",

            /**
             * Indicates an altitude mode relative to the terrain.
             * @constant
             */
            RELATIVE_TO_GROUND: "relativeToGround",

            /**
             * Indicates that the related value is specified relative to the plane of the screen.
             * @constant
             */
            RELATIVE_TO_SCREEN: "relativeToScreen",

            /**
             * Indicates a rhumb path -- a path of constant bearing.
             * @constant
             */
            RHUMB_LINE: "rhumbLine",

            /**
             * Indicates the cardinal direction south.
             * @constant
             */
            SOUTH: "south",

            /**
             * Indicates the cardinal direction west.
             * @constant
             */
            WEST: "west"
        };

        WorldWind['AbstractError'] = AbstractError;
        WorldWind['Angle'] = Angle;
        WorldWind['Annotation'] = Annotation;
        WorldWind['AnnotationAttributes'] = AnnotationAttributes;
        WorldWind['AreaMeasurer'] = AreaMeasurer;
        WorldWind['ArgumentError'] = ArgumentError;
        WorldWind['AtmosphereLayer'] = AtmosphereLayer;
        WorldWind['AtmosphereProgram'] = AtmosphereProgram;
        WorldWind['BasicProgram'] = BasicProgram;
        WorldWind['BasicTextureProgram'] = BasicTextureProgram;
        WorldWind['BasicTimeSequence'] = BasicTimeSequence;
        WorldWind['BingAerialLayer'] = BingAerialLayer;
        WorldWind['BingAerialWithLabelsLayer'] = BingAerialWithLabelsLayer;
        WorldWind['BingRoadsLayer'] = BingRoadsLayer;
        WorldWind['BingWMSLayer'] = BingWMSLayer;
        WorldWind['BMNGLandsatLayer'] = BMNGLandsatLayer;
        WorldWind['BMNGLayer'] = BMNGLayer;
        WorldWind['BMNGOneImageLayer'] = BMNGOneImageLayer;
        WorldWind['BMNGRestLayer'] = BMNGRestLayer;
        WorldWind['BoundingBox'] = BoundingBox;
        WorldWind['ClickRecognizer'] = ClickRecognizer;
        WorldWind['ColladaLoader'] = ColladaLoader;
        WorldWind['Color'] = Color;
        WorldWind['Compass'] = Compass;
        WorldWind['CompassLayer'] = CompassLayer;
        WorldWind['CoordinatesDisplayLayer'] = CoordinatesDisplayLayer;
        WorldWind['DateWW'] = DateWW;
        WorldWind['DigitalGlobeTiledImageLayer'] = DigitalGlobeTiledImageLayer;
        WorldWind['DragRecognizer'] = DragRecognizer;
        WorldWind['DrawContext'] = DrawContext;
        WorldWind['EarthElevationModel'] = EarthElevationModel;
        WorldWind['EarthRestElevationModel'] = EarthRestElevationModel;
        WorldWind['ElevationModel'] = ElevationModel;
        WorldWind['Font'] = Font;
        WorldWind['FrameStatistics'] = FrameStatistics;
        WorldWind['FrameStatisticsLayer'] = FrameStatisticsLayer;
        WorldWind['FramebufferTexture'] = FramebufferTexture;
        WorldWind['FramebufferTile'] = FramebufferTile;
        WorldWind['FramebufferTileController'] = FramebufferTileController;
        WorldWind['Frustum'] = Frustum;
        WorldWind['GeographicMesh'] = GeographicMesh;
        WorldWind['GeographicProjection'] = GeographicProjection;
        WorldWind['GeographicText'] = GeographicText;
        WorldWind['GeoJSONGeometry'] = GeoJSONGeometry;
        WorldWind['GeoJSONGeometryCollection'] = GeoJSONGeometryCollection;
        WorldWind['GeoJSONGeometryLineString'] = GeoJSONGeometryLineString;
        WorldWind['GeoJSONGeometryMultiLineString'] = GeoJSONGeometryMultiLineString;
        WorldWind['GeoJSONGeometryMultiPoint'] = GeoJSONGeometryMultiPoint;
        WorldWind['GeoJSONGeometryMultiPolygon'] = GeoJSONGeometryMultiPolygon;
        WorldWind['GeoJSONGeometryPoint'] = GeoJSONGeometryPoint;
        WorldWind['GeoJSONGeometryPolygon'] = GeoJSONGeometryPolygon;
        WorldWind['GeoJSONParser'] = GeoJSONParser;
        WorldWind['GeoTiffReader'] = GeoTiffReader;
        WorldWind['GestureRecognizer'] = GestureRecognizer;
        WorldWind['Globe'] = Globe;
        WorldWind['Globe2D'] = Globe2D;
        WorldWind['GoToAnimator'] = GoToAnimator;
        WorldWind['GpuProgram'] = GpuProgram;
        WorldWind['GpuResourceCache'] = GpuResourceCache;
        WorldWind['GpuShader'] = GpuShader;
        WorldWind['GroundProgram'] = GroundProgram;
        WorldWind['HashMap'] = HashMap;
        WorldWind['HighlightController'] = HighlightController;
        WorldWind['ImageSource'] = ImageSource;
        WorldWind['ImageTile'] = ImageTile;
        WorldWind['Insets'] = Insets;
        WorldWind['KmlControls'] = KmlControls;
        WorldWind['KmlFile'] = KmlFile;
        WorldWind['KmlTreeVisibility'] = KmlTreeVisibility;
        WorldWind['LandsatRestLayer'] = LandsatRestLayer;
        WorldWind['Layer'] = Layer;
        WorldWind['LengthMeasurer'] = LengthMeasurer;
        WorldWind['Level'] = Level;
        WorldWind['LevelRowColumnUrlBuilder'] = LevelRowColumnUrlBuilder;
        WorldWind['LevelSet'] = LevelSet;
        WorldWind['Line'] = Line;
        WorldWind['Location'] = Location;
        WorldWind['Logger'] = Logger;
        WorldWind['LookAtNavigator'] = LookAtNavigator;
        WorldWind['Matrix'] = Matrix;
        WorldWind['MeasurerUtils'] = MeasurerUtils;
        WorldWind['MemoryCache'] = MemoryCache;
        WorldWind['MemoryCacheListener'] = MemoryCacheListener;
        WorldWind['MercatorTiledImageLayer'] = MercatorTiledImageLayer;
        WorldWind['Navigator'] = Navigator;
        WorldWind['NavigatorState'] = NavigatorState;
        WorldWind['NominatimGeocoder'] = NominatimGeocoder;
        WorldWind['NotYetImplementedError'] = NotYetImplementedError;
        WorldWind['Offset'] = Offset;
        WorldWind['OpenStreetMapImageLayer'] = OpenStreetMapImageLayer;
        WorldWind['PanRecognizer'] = PanRecognizer;
        WorldWind['Path'] = Path;
        WorldWind['PeriodicTimeSequence'] = PeriodicTimeSequence;
        WorldWind['PickedObject'] = PickedObject;
        WorldWind['PickedObjectList'] = PickedObjectList;
        WorldWind['PinchRecognizer'] = PinchRecognizer;
        WorldWind['Placemark'] = Placemark;
        WorldWind['PlacemarkAttributes'] = PlacemarkAttributes;
        WorldWind['Plane'] = Plane;
        WorldWind['Polygon'] = Polygon;
        WorldWind['PolygonSplitter'] = PolygonSplitter;
        WorldWind['Position'] = Position;
        WorldWind['ProjectionEquirectangular'] = ProjectionEquirectangular;
        WorldWind['ProjectionGnomonic'] = ProjectionGnomonic;
        WorldWind['ProjectionMercator'] = ProjectionMercator;
        WorldWind['ProjectionPolarEquidistant'] = ProjectionPolarEquidistant;
        WorldWind['ProjectionUPS'] = ProjectionUPS;
        WorldWind['ProjectionWgs84'] = ProjectionWgs84;
        WorldWind['Rectangle'] = Rectangle;
        WorldWind['Renderable'] = Renderable;
        WorldWind['RenderableLayer'] = RenderableLayer;
        WorldWind['RestTiledImageLayer'] = RestTiledImageLayer;
        WorldWind['RotationRecognizer'] = RotationRecognizer;
        WorldWind['ScreenText'] = ScreenText;
        WorldWind['ScreenImage'] = ScreenImage;
        WorldWind['Sector'] = Sector;
        WorldWind['ShapeAttributes'] = ShapeAttributes;
        WorldWind['Shapefile'] = Shapefile;
        WorldWind['ShowTessellationLayer'] = ShowTessellationLayer;
        WorldWind['SkyProgram'] = SkyProgram;
        WorldWind['StarFieldLayer'] = StarFieldLayer;
        WorldWind['StarFieldProgram'] = StarFieldProgram;
        WorldWind['SunPosition'] = SunPosition;
        WorldWind['SurfaceImage'] = SurfaceImage;
        WorldWind['SurfaceCircle'] = SurfaceCircle;
        WorldWind['SurfaceEllipse'] = SurfaceEllipse;
        WorldWind['SurfacePolygon'] = SurfacePolygon;
        WorldWind['SurfacePolyline'] = SurfacePolyline;
        WorldWind['SurfaceRectangle'] = SurfaceRectangle;
        WorldWind['SurfaceRenderable'] = SurfaceRenderable;
        WorldWind['SurfaceSector'] = SurfaceSector;
        WorldWind['SurfaceShape'] = SurfaceShape;
        WorldWind['SurfaceShapeTile'] = SurfaceShapeTile;
        WorldWind['SurfaceShapeTileBuilder'] = SurfaceShapeTileBuilder;
        WorldWind['SurfaceTile'] = SurfaceTile;
        WorldWind['SurfaceTileRenderer'] = SurfaceTileRenderer;
        WorldWind['SurfaceTileRendererProgram'] = SurfaceTileRendererProgram;
        WorldWind['TapRecognizer'] = TapRecognizer;
        WorldWind['TectonicPlatesLayer'] = TectonicPlatesLayer;
        WorldWind['Terrain'] = Terrain;
        WorldWind['TerrainTile'] = TerrainTile;
        WorldWind['TerrainTileList'] = TerrainTileList;
        WorldWind['Tessellator'] = Tessellator;
        WorldWind['Text'] = Text;
        WorldWind['TextAttributes'] = TextAttributes;
        WorldWind['TextSupport'] = TextSupport;
        WorldWind['Texture'] = Texture;
        WorldWind['TextureTile'] = TextureTile;
        WorldWind['Tile'] = Tile;
        WorldWind['TiledImageLayer'] = TiledImageLayer;
        WorldWind['TileFactory'] = TileFactory;
        WorldWind['TiltRecognizer'] = TiltRecognizer;
        WorldWind['Touch'] = Touch;
        WorldWind['TriangleMesh'] = TriangleMesh;
        WorldWind['UnsupportedOperationError'] = UnsupportedOperationError;
        WorldWind['Vec2'] = Vec2;
        WorldWind['Vec3'] = Vec3;
        WorldWind['ViewControlsLayer'] = ViewControlsLayer;
        WorldWind['WcsTileUrlBuilder'] = WcsTileUrlBuilder;
        WorldWind['WfsCapabilities'] = WfsCapabilities;
        WorldWind['Wkt'] = Wkt;
        WorldWind['WktElements'] = WktElements;
        WorldWind['WktGeometryCollection'] = WktGeometryCollection;
        WorldWind['WktLineString'] = WktLineString;
        WorldWind['WktMultiLineString'] = WktMultiLineString;
        WorldWind['WktMultiPoint'] = WktMultiPoint;
        WorldWind['WktMultiPolygon'] = WktMultiPolygon;
        WorldWind['WktObject'] = WktObject;
        WorldWind['WktPoint'] = WktPoint;
        WorldWind['WktPolygon'] = WktPolygon;
        WorldWind['WktTokens'] = WktTokens;
        WorldWind['WktTriangle'] = WktTriangle;
        WorldWind['WktType'] = WktType;
        WorldWind['WmsCapabilities'] = WmsCapabilities;
        WorldWind['WmsLayer'] = WmsLayer;
        WorldWind['WmsLayerCapabilities'] = WmsLayerCapabilities;
        WorldWind['WmsTimeDimensionedLayer'] = WmsTimeDimensionedLayer;
        WorldWind['WmsUrlBuilder'] = WmsUrlBuilder;
        WorldWind['WmtsCapabilities'] = WmtsCapabilities;
        WorldWind['WmtsLayer'] = WmtsLayer;
        WorldWind['WmtsLayerCapabilities'] = WmtsLayerCapabilities;
        WorldWind['WWMath'] = WWMath;
        WorldWind['WWMessage'] = WWMessage;
        WorldWind['WWUtil'] = WWUtil;
        WorldWind['WorldWindow'] = WorldWindow;
        WorldWind['ZeroElevationModel'] = ZeroElevationModel;

        /**
         * Holds configuration parameters for WorldWind. Applications may modify these parameters prior to creating
         * their first WorldWind objects. Configuration properties are:
         * <ul>
         *     <li><code>gpuCacheSize</code>: A Number indicating the size in bytes to allocate from GPU memory for
         *     resources such as textures, GLSL programs and buffer objects. Default is 250e6 (250 MB).</li>
         *     <li><code>baseUrl</code>: The URL of the directory containing the WorldWind Library and its resources.</li>
         * </ul>
         * @type {{gpuCacheSize: number}}
         */
        WorldWind.configuration = {
            gpuCacheSize: 250e6,
            baseUrl: (WWUtil.worldwindlibLocation()) || (WWUtil.currentUrlSansFilePart() + '/../')
        };

        /**
         * Indicates the Bing Maps key to use when requesting Bing Maps resources.
         * @type {String}
         * @default null
         */
        WorldWind.BingMapsKey = null;

        window.WorldWind = WorldWind;

        return WorldWind;
    }
);
    //Use almond's special top-level, synchronous require to trigger factory
    //functions, get the final module value, and export it as the public
    //value.
    return require('WorldWind');
}));
