Source: Util.js

/**
 * @namespace BKGWebMap.Util
 * @type {object}
 */
BKGWebMap.Util = BKGWebMap.Util || {};

/**
 * Iterates over arrays
 * @param {array} elements - Array that this method is being applied to
 * @param {function} method - Function to be executed for each element
 */
BKGWebMap.Util.each = function (elements, method) {
    if (!elements || !elements.length) {
        return;
    }

    for (var i = 0; i < elements.length; i++) {
        method(i, elements[i]);
    }
};

/**
 * Iterates reverse over arrays
 * @param {array} elements - Array that this method is being applied to
 * @param {function} method - Function to be executed for each element
 */
BKGWebMap.Util.reverseEach = function (elements, method) {
    if (!elements || !elements.length) {
        return;
    }

    for (var i = elements.length - 1; i >= 0; i--) {
        method(i, elements[i]);
    }
};

/**
 * Maps elements of array. It creates a new array with the results of calling a method for every array element. You can pass further static arguments to the method.
 * @param {function} method - Function to be run for each element in the array
 * @param {array} elements - Array that this method is being applied to
 * @return {array}
 */
BKGWebMap.Util.map = function (method, elements) {
    if (!elements || !elements.length) {
        return [];
    }

    // pass further optional arguments
    var staticArgs = (arguments.length === 2) ? [] : Array.prototype.slice.call(arguments, 2, arguments.length);

    var result = [];
    for (var i = 0; i < elements.length; i++) {
        var args = [elements[i]];
        args = args.concat(staticArgs);
        result.push(method.apply(this, args));
    }
    return result;
};

/**
 * Creates a deep copy of an object
 * @param {object} obj - Object to copy
 * @returns {object}
 */
BKGWebMap.Util.deepCopy = function (obj) {
    var newObj = obj;
    if (obj && typeof obj === 'object') {
        newObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
        for (var i in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, i)) {
                newObj[i] = BKGWebMap.Util.deepCopy(obj[i]);
            }
        }
    }
    return newObj;
};

/**
 * Extends an object using properties of a second object. Common properties will be overwritten.
 * @param {object} obj1 - Object to extend.
 * @param {object} obj2 - Object to get new properties.
 * @returns {object}
 */
BKGWebMap.Util.extend = function (obj1, obj2) {
    for (var p in obj2) {
        if (Object.prototype.hasOwnProperty.call(obj2, p)) {
            try {
                // Property in destination object set: update its value.
                if (obj2[p].constructor === Object && (Object.keys(obj2[p]).length > 0)) {
                    obj1[p] = BKGWebMap.Util.extend(obj1[p], obj2[p]);
                } else {
                    obj1[p] = obj2[p];
                }
            } catch (e) {
                // Property in destination object not set: create it and set its value.
                obj1[p] = obj2[p];
            }
        }
    }
    return obj1;
};

/**
 * Checks if an object has a nested property
 * @param {object} obj - The object to test
 * @param {string} propertyPath - Path of property. Syntax: 'prop1.prop2.prop3...'
 * @returns {boolean}
 */
BKGWebMap.Util.hasNestedProperty = function (obj, propertyPath) {
    if (!propertyPath) { return false; }

    var properties = propertyPath.split('.');

    for (var i = 0; i < properties.length; i++) {
        var prop = properties[i];

        if (!obj || !Object.prototype.hasOwnProperty.call(obj, prop)) {
            return false;
        }
        obj = obj[prop];
    }

    return true;
};

/**
 * Get a value from an object using a string path in dot notation
 * @param {object} obj - Object to search.
 * @param {string} propertyPath - String path in dot notation.
 * @returns {object}
 */
BKGWebMap.Util.getObjValueFromStringPath = function (obj, propertyPath) {
    function index(_obj, i) {
        return _obj[i];
    }
    return propertyPath.split('.').reduce(index, obj);
};

/**
 * Generate object with css properties from a string
 * @param {string} cssString - String to parse
 * @returns {object}
 */
BKGWebMap.Util.cssParser = function (cssString) {
    var obj = {};
    var propArray = cssString.split(';').filter(function (prop) {
        return (!!prop);
    });
    for (var prop in propArray) {
        propArray[prop] = propArray[prop].split(':');
        obj[propArray[prop][0]] = propArray[prop][1];
    }
    return obj;
};

/**
 * Generate a unique BKG WebMap ID that can be used in DOM elements
 * @returns {string}
 */
BKGWebMap.Util.uniqueId = function () {
    return 'bkgwebmap-' + Math.random().toString(36).substr(2, 16);
};

/**
 * Converts XML to JSON format
 * @param {object} xml - XML as JavaScript object
 * @returns {object}
 */
BKGWebMap.Util.xmlToJson = function (xml) {
    var obj = {};

    if (xml.nodeType === 1) { // element
        // do attributes
        if (xml.attributes.length > 0) {
            obj['@attributes'] = {};
            for (var j = 0; j < xml.attributes.length; j++) {
                var attribute = xml.attributes.item(j);
                obj['@attributes'][attribute.nodeName] = attribute.nodeValue;
            }
        }
    } else if (xml.nodeType === 3) { // text
        obj = xml.nodeValue;
    }

    if (xml.hasChildNodes()) {
        for (var i = 0; i < xml.childNodes.length; i++) {
            var item = xml.childNodes.item(i);
            var nodeName = item.nodeName;
            if (typeof (obj[nodeName]) === 'undefined') {
                obj[nodeName] = BKGWebMap.Util.xmlToJson(item);
            } else {
                if (typeof (obj[nodeName].push) === 'undefined') {
                    var old = obj[nodeName];
                    obj[nodeName] = [];
                    obj[nodeName].push(old);
                }
                obj[nodeName].push(BKGWebMap.Util.xmlToJson(item));
            }
        }
    }
    return obj;
};

/**
 * Generate a Session-Token for secure BKG services
 * @param {string} appID
 * @param {string} appDomain
 * @param {function} callback - Callback function
 * @returns {function}
 */
BKGWebMap.Util.getSessionToken = function (appID, appDomain, callback) {
    var baseURL = BKGWebMap.BASE_URL.replace('http://', 'https://');
    var url = baseURL + 'gdz_getSession?bkg_appid=' + appID + '&domain=' + appDomain;
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);

    xhr.onload = function () {
        if (xhr.status === 200) {
            return callback(xhr.responseText);
        }
    };

    xhr.onerror = function () {
        return callback('');
    };

    xhr.send();
};

/**
 * Determine if we need to create a panel
 * @param {object} controls - Object with controls configuration
 * @returns {boolean}
 */
BKGWebMap.Util.shouldCreatePanel = function (controls) {
    var createPanel = false;
    BKGWebMap.Util.onlyPermaLink = false;
    if (!controls) {
        return createPanel;
    }
    if (typeof controls.tools !== 'object' || controls.tools.constructor !== Object) {
        return createPanel;
    }
    for (var i = 0; i < BKGWebMap.CONTROLS_IN_PANEL.length; i++) {
        if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]]) {
            if (BKGWebMap.CONTROLS_IN_PANEL[i] === 'share') {
                if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div && ((controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport.active) || (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print.active))) {
                    createPanel = true;
                } else if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport.active === false && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print.active === false && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].permaLink && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].permaLink.active === true) {
                    BKGWebMap.Util.onlyPermaLink = true;
                }
            } else if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div) {
                createPanel = true;
            }
        }
    }
    return createPanel;
};

/**
 * Find geometry type of features
 * @param {array} features - Array with OpenLayers features
 * @returns {object}
 */
BKGWebMap.Util.findGeometryType = function (features) {
    var stringsToSearch = [
        {
            string: 'POLYGON',
            name: 'Polygon'
        },
        {
            string: 'POINT',
            name: 'Point'
        },
        {
            string: 'LINE',
            name: 'LineString'
        }
    ];
    var types = [];
    var format = new ol.format.WKT();
    var geometry;
    var wkt;
    var tempType;
    for (var i = 0; i < features.length; i++) {
        geometry = features[i].getGeometry();

        wkt = format.writeGeometry(geometry);
        for (var k = 0; k < stringsToSearch.length; k++) {
            if (wkt.indexOf(stringsToSearch[k].string) !== -1) {
                tempType = stringsToSearch[k].name;
                if (types.indexOf(tempType) === -1) {
                    types.push(tempType);
                }
            }
        }
    }
    return types;
};

/**
 * Counter for layer IDs
 * @type {number}
 */
BKGWebMap.Util.layerId = 0;

/**
 * Generate ID for layers to use in permalink
 * @returns {string}
 */
BKGWebMap.Util.generateLayerId = function () {
    BKGWebMap.Util.layerId++;
    return 'layer_' + BKGWebMap.Util.layerId;
};

// Add remove() if method not exists
if (!('remove' in Element.prototype)) {
    Element.prototype.remove = function () {
        if (this.parentNode) {
            this.parentNode.removeChild(this);
        }
    };
}

/**
 * @namespace BKGWebMap.Util
 * @type {object}
 */
BKGWebMap.Util = BKGWebMap.Util || {};

/**
 * Iterates over arrays
 * @param {array} elements - Array that this method is being applied to
 * @param {function} method - Function to be executed for each element
 */
BKGWebMap.Util.each = function (elements, method) {
    if (!elements || !elements.length) {
        return;
    }

    for (var i = 0; i < elements.length; i++) {
        method(i, elements[i]);
    }
};

/**
 * Iterates reverse over arrays
 * @param {array} elements - Array that this method is being applied to
 * @param {function} method - Function to be executed for each element
 */
BKGWebMap.Util.reverseEach = function (elements, method) {
    if (!elements || !elements.length) {
        return;
    }

    for (var i = elements.length - 1; i >= 0; i--) {
        method(i, elements[i]);
    }
};

/**
 * Maps elements of array. It creates a new array with the results of calling a method for every array element. You can pass further static arguments to the method.
 * @param {function} method - Function to be run for each element in the array
 * @param {array} elements - Array that this method is being applied to
 * @return {array}
 */
BKGWebMap.Util.map = function (method, elements) {
    if (!elements || !elements.length) {
        return [];
    }

    // pass further optional arguments
    var staticArgs = (arguments.length === 2) ? [] : Array.prototype.slice.call(arguments, 2, arguments.length);

    var result = [];
    for (var i = 0; i < elements.length; i++) {
        var args = [elements[i]];
        args = args.concat(staticArgs);
        result.push(method.apply(this, args));
    }
    return result;
};

/**
 * Creates a deep copy of an object
 * @param {object} obj - Object to copy
 * @returns {object}
 */
BKGWebMap.Util.deepCopy = function (obj) {
    var newObj = obj;
    if (obj && typeof obj === 'object') {
        newObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
        for (var i in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, i)) {
                newObj[i] = BKGWebMap.Util.deepCopy(obj[i]);
            }
        }
    }
    return newObj;
};

/**
 * Extends an object using properties of a second object. Common properties will be overwritten.
 * @param {object} obj1 - Object to extend.
 * @param {object} obj2 - Object to get new properties.
 * @returns {object}
 */
BKGWebMap.Util.extend = function (obj1, obj2) {
    for (var p in obj2) {
        if (Object.prototype.hasOwnProperty.call(obj2, p)) {
            try {
                // Property in destination object set: update its value.
                if (obj2[p].constructor === Object && (Object.keys(obj2[p]).length > 0)) {
                    obj1[p] = BKGWebMap.Util.extend(obj1[p], obj2[p]);
                } else {
                    obj1[p] = obj2[p];
                }
            } catch (e) {
                // Property in destination object not set: create it and set its value.
                obj1[p] = obj2[p];
            }
        }
    }
    return obj1;
};

/**
 * Checks if an object has a nested property
 * @param {object} obj - The object to test
 * @param {string} propertyPath - Path of property. Syntax: 'prop1.prop2.prop3...'
 * @returns {boolean}
 */
BKGWebMap.Util.hasNestedProperty = function (obj, propertyPath) {
    if (!propertyPath) { return false; }

    var properties = propertyPath.split('.');

    for (var i = 0; i < properties.length; i++) {
        var prop = properties[i];

        if (!obj || !Object.prototype.hasOwnProperty.call(obj, prop)) {
            return false;
        }
        obj = obj[prop];
    }

    return true;
};

/**
 * Get a value from an object using a string path in dot notation
 * @param {object} obj - Object to search.
 * @param {string} propertyPath - String path in dot notation.
 * @returns {object}
 */
BKGWebMap.Util.getObjValueFromStringPath = function (obj, propertyPath) {
    function index(_obj, i) {
        return _obj[i];
    }
    return propertyPath.split('.').reduce(index, obj);
};

/**
 * Generate object with css properties from a string
 * @param {string} cssString - String to parse
 * @returns {object}
 */
BKGWebMap.Util.cssParser = function (cssString) {
    var obj = {};
    var propArray = cssString.split(';').filter(function (prop) {
        return (!!prop);
    });
    for (var prop in propArray) {
        propArray[prop] = propArray[prop].split(':');
        obj[propArray[prop][0]] = propArray[prop][1];
    }
    return obj;
};

/**
 * Generate a unique BKG WebMap ID that can be used in DOM elements
 * @returns {string}
 */
BKGWebMap.Util.uniqueId = function () {
    return 'bkgwebmap-' + Math.random().toString(36).substr(2, 16);
};

/**
 * Converts XML to JSON format
 * @param {object} xml - XML as JavaScript object
 * @returns {object}
 */
BKGWebMap.Util.xmlToJson = function (xml) {
    var obj = {};

    if (xml.nodeType === 1) { // element
        // do attributes
        if (xml.attributes.length > 0) {
            obj['@attributes'] = {};
            for (var j = 0; j < xml.attributes.length; j++) {
                var attribute = xml.attributes.item(j);
                obj['@attributes'][attribute.nodeName] = attribute.nodeValue;
            }
        }
    } else if (xml.nodeType === 3) { // text
        obj = xml.nodeValue;
    }

    if (xml.hasChildNodes()) {
        for (var i = 0; i < xml.childNodes.length; i++) {
            var item = xml.childNodes.item(i);
            var nodeName = item.nodeName;
            if (typeof (obj[nodeName]) === 'undefined') {
                obj[nodeName] = BKGWebMap.Util.xmlToJson(item);
            } else {
                if (typeof (obj[nodeName].push) === 'undefined') {
                    var old = obj[nodeName];
                    obj[nodeName] = [];
                    obj[nodeName].push(old);
                }
                obj[nodeName].push(BKGWebMap.Util.xmlToJson(item));
            }
        }
    }
    return obj;
};

/**
 * Generate a Session-Token for secure BKG services
 * @param {string} appID
 * @param {string} appDomain
 * @param {function} callback - Callback function
 * @returns {function}
 */
BKGWebMap.Util.getSessionToken = function (appID, appDomain, callback) {
    var baseURL = BKGWebMap.BASE_URL.replace('http://', 'https://');
    var url = baseURL + 'gdz_getSession?bkg_appid=' + appID + '&domain=' + appDomain;
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);

    xhr.onload = function () {
        if (xhr.status === 200) {
            return callback(xhr.responseText);
        }
    };

    xhr.onerror = function () {
        return callback('');
    };

    xhr.send();
};

/**
 * Determine if we need to create a panel
 * @param {object} controls - Object with controls configuration
 * @returns {boolean}
 */
BKGWebMap.Util.shouldCreatePanel = function (controls) {
    var createPanel = false;
    BKGWebMap.Util.onlyPermaLink = false;
    if (!controls) {
        return createPanel;
    }
    if (typeof controls.tools !== 'object' || controls.tools.constructor !== Object) {
        return createPanel;
    }
    for (var i = 0; i < BKGWebMap.CONTROLS_IN_PANEL.length; i++) {
        if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]]) {
            if (BKGWebMap.CONTROLS_IN_PANEL[i] === 'share') {
                if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div && ((controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport.active) || (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print.active))) {
                    createPanel = true;
                } else if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].jsonExport.active === false && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].print.active === false && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].permaLink && controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].permaLink.active === true) {
                    BKGWebMap.Util.onlyPermaLink = true;
                }
            } else if (controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].active && !controls.tools[BKGWebMap.CONTROLS_IN_PANEL[i]].div) {
                createPanel = true;
            }
        }
    }
    return createPanel;
};

/**
 * Find geometry type of features
 * @param {array} features - Array with OpenLayers features
 * @returns {object}
 */
BKGWebMap.Util.findGeometryType = function (features) {
    var stringsToSearch = [
        {
            string: 'POLYGON',
            name: 'Polygon'
        },
        {
            string: 'POINT',
            name: 'Point'
        },
        {
            string: 'LINE',
            name: 'LineString'
        }
    ];
    var types = [];
    var format = new ol.format.WKT();
    var geometry;
    var wkt;
    var tempType;
    for (var i = 0; i < features.length; i++) {
        geometry = features[i].getGeometry();

        wkt = format.writeGeometry(geometry);
        for (var k = 0; k < stringsToSearch.length; k++) {
            if (wkt.indexOf(stringsToSearch[k].string) !== -1) {
                tempType = stringsToSearch[k].name;
                if (types.indexOf(tempType) === -1) {
                    types.push(tempType);
                }
            }
        }
    }
    return types;
};

/**
 * Counter for layer IDs
 * @type {number}
 */
BKGWebMap.Util.layerId = 0;

/**
 * Generate ID for layers to use in permalink
 * @returns {string}
 */
BKGWebMap.Util.generateLayerId = function () {
    BKGWebMap.Util.layerId++;
    return 'layer_' + BKGWebMap.Util.layerId;
};

// Add remove() if method not exists
if (!('remove' in Element.prototype)) {
    Element.prototype.remove = function () {
        if (this.parentNode) {
            this.parentNode.removeChild(this);
        }
    };
}

// Polyfill CustomEvent() in IE
(function () {
    if (typeof window.CustomEvent === 'function') return false;

    function CustomEvent(event, params) {
        params = params || { bubbles: false, cancelable: false, detail: undefined };
        var evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
    }

    CustomEvent.prototype = window.Event.prototype;

    window.CustomEvent = CustomEvent;
}());


/**
 * Returns the extent of the given layer. Depending on the layer type the
 * access to the extent may vary.
 *
 * @param {ol.layer.Layer} layer - the layer from which to retrieve the extent
 * @param {ol.proj.Projection} projection - optional target projection for layer
 * @returns {ol.Extent}
 */
BKGWebMap.Util.getLayerExtent = function (layer, projection) {
    var extent;
    if (layer instanceof ol.layer.Group) {
        extent = ol.extent.createEmpty();
        layer.getLayers().forEach(function (subLayer) {
            var subLayerExtent = BKGWebMap.Util.getLayerExtent(subLayer, projection);
            ol.extent.extend(extent, subLayerExtent);
        });
        return extent;
    }

    // layer has no source. e.g. EMPTY
    if (layer.getSource() == null) { return ol.extent.createEmpty(); }

    if (layer instanceof ol.layer.Vector) {
        extent = layer.getSource().getExtent();
    } else if (layer.extent && layer.extent instanceof Array) {
        extent = layer.extent;
    }

    var srcProjection = layer.getSource().getProjection();
    if (projection && srcProjection != null) {
        extent = ol.proj.transformExtent(extent, srcProjection, projection);
    }

    return extent;
};