Source: util/WWUtil.js

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT 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([
        '../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;
    });