Source: Control/PermaLink.js

/**
 * Create PermaLink Control
 * @type function
 * @returns {function} See parameters of return function in {@link CONTROL_FACTORIES_permaLink}
 */
BKGWebMap.Control.createPermaLink = function () {
    return function (map) {
        var _this = this;

        // Use property 'active' to check later if control is activated. We need to add later the layers.
        this.active = true;

        var layersInHash;
        this.readLayersFromPermaLink = false;

        var mapProjection = map.getView().getProjection().getCode();
        var shouldUpdate = true;

        function replaceMultipleKeys(hash, keys) {
            for (var i = 0; i < keys.length; i++) {
                hash = hash.replace(keys[i], '');
            }
            return hash;
        }

        if (window.location.hash !== '') {
            var keysToReplace = ['#lat=', 'lon=', 'zoom='];
            var hash = replaceMultipleKeys(window.location.hash, keysToReplace);
            var parts = hash.split('&');
            if (parts.length >= 3) {
                var zoom = parseInt(parts[2], 10);
                var center = ol.proj.transform([parseFloat(parts[1]), parseFloat(parts[0])], 'EPSG:4326', mapProjection);
                map.getView().setZoom(zoom);
                map.getView().setCenter(center);
            }
            if (parts.length > 3) {
                _this.readLayersFromPermaLink = true;
                layersInHash = '&' + parts.slice(3).join('&');
            }
        }

        function updatePermalink() {
            if (!shouldUpdate) {
                shouldUpdate = true;
                return;
            }

            var newCenter = map.getView().getCenter();
            var center4326 = ol.proj.transform(newCenter, mapProjection, 'EPSG:4326');
            var lon = center4326[0].toFixed(4);
            var lat = center4326[1].toFixed(4);
            var newZoom = map.getView().getZoom();

            var newHash = '#lat=' + lat + '&lon=' + lon + '&zoom=' + newZoom;
            if (layersInHash) {
                newHash += layersInHash;
            }
            var state = {
                center: newCenter,
                zoom: newZoom,
                layers: layersInHash
            };
            window.history.pushState(state, 'BKG_permaLink', newHash);
        }

        function constructLayersHash(layer) {
            if (layer.getProperties().customLayer || !layer.getProperties().id) {
                return '';
            }
            var id = layer.getProperties().id;
            var visible = layer.getVisible();
            var layerHash = '&' + id + '=' + visible;
            return layerHash;
        }

        // Change layer order and visibility in permalink
        this.changeLayersInPermalink = function () {
            var layersHash = '';
            var singleHash = '';
            map.getLayers().forEach(function (layer) {
                singleHash = constructLayersHash(layer);
                layersHash += singleHash;
                if (layer instanceof ol.layer.Group) {
                    layer.getLayers().forEach(function (layerInGroup) {
                        singleHash = constructLayersHash(layerInGroup);
                        layersHash += singleHash;
                    });
                }
            });
            layersInHash = layersHash;
            updatePermalink();
        };

        // Change layer order in group layer
        function changeGroupOrder(hashParts, groupLayer) {
            var subLayersArray = groupLayer.getLayers().getArray();
            var newSubLayersAray = [];
            var hashPartsTemp;
            for (var i = 1; i < hashParts.length; i++) {
                hashPartsTemp = hashParts[i].split('=');
                for (var k = subLayersArray.length - 1; k >= 0; k--) {
                    if (subLayersArray[k].getProperties().id && subLayersArray[k].getProperties().id === hashPartsTemp[0]) {
                        var visible = (hashPartsTemp[1] === 'true');
                        subLayersArray[k].setVisible(visible);
                        newSubLayersAray.push(subLayersArray[k]);
                        subLayersArray.splice(k, 1);
                    }
                }
            }
            newSubLayersAray = subLayersArray.concat(newSubLayersAray);
            var newCollection = new ol.Collection(newSubLayersAray);
            groupLayer.setLayers(newCollection);
            return groupLayer;
        }

        /**
         * Change layer order using permalink parameteres
         * @param layers
         * @returns {{baseLayers: Array, overlays: Array}}
         */
        this.changeLayersOrder = function (layers) {
            var newLayers = {
                baseLayers: [],
                overlays: []
            };
            var hashParts = layersInHash.split('&');
            var hashPartsTemp;
            var visible;
            for (var i = hashParts.length - 1; i >= 0; i--) {
                hashPartsTemp = hashParts[i].split('=');
                for (var k = layers.overlays.length - 1; k >= 0; k--) {
                    if (layers.overlays[k] && layers.overlays[k].getProperties().id && layers.overlays[k].getProperties().id === hashPartsTemp[0]) {
                        if (layers.overlays[k] instanceof ol.layer.Group) {
                            layers.overlays[k] = changeGroupOrder(hashParts, layers.overlays[k]);
                        }
                        visible = (hashPartsTemp[1] === 'true');
                        layers.overlays[k].setVisible(visible);
                        newLayers.overlays.push(layers.overlays[k]);
                        layers.overlays.splice(k, 1);
                    }
                }

                for (var j = layers.baseLayers.length - 1; j >= 0; j--) {
                    if (layers.baseLayers[j] && layers.baseLayers[j].getProperties().id && layers.baseLayers[j].getProperties().id === hashPartsTemp[0]) {
                        visible = (hashPartsTemp[1] === 'true');
                        layers.baseLayers[j].setVisible(visible);
                        newLayers.baseLayers.push(layers.baseLayers[j]);
                        layers.baseLayers.splice(j, 1);
                    }
                }
            }
            newLayers.overlays = layers.overlays.concat(newLayers.overlays);
            newLayers.baseLayers = layers.baseLayers.concat(newLayers.baseLayers);
            return newLayers;
        };

        map.on('moveend', updatePermalink);

        window.addEventListener('popstate', function (event) {
            if (event.state === null) {
                return;
            }
            map.getView().setCenter(event.state.center);
            map.getView().setZoom(event.state.zoom);
            shouldUpdate = false;
        });
    };
};