Greasy Fork is available in English.

Waze GeoPortal DE

Geoportal Overlay für Deutschland

// ==UserScript==
// @name         Waze GeoPortal DE
// @namespace    https://greasyfork.org/de/users/863740-horst-wittlich
// @version      2023.03.24
// @description  Geoportal Overlay für Deutschland
// @author       vertexcode, hiwi234
// @match        https://*.waze.com/editor*
// @match        https://*.waze.com/*/editor*
// @match        https://beta.waze.com/editor*
// @match        https://beta.waze.com/*/editor*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @license      MIT

// ==/UserScript==

// Versions Format
// yyyy.mm.dd

var uOpenLayers;
var uWaze;

function loadWMTSLayer() {
    if (uOpenLayers.Layer.WMTS) {
        return;
    }
    /**
     * Class: OpenLayers.Format.XML.VersionedOGC
     * Base class for versioned formats, i.e. a format which supports multiple
     * versions.
     *
     * To enable checking if parsing succeeded, you will need to define a property
     * called errorProperty on the parser you want to check. The parser will then
     * check the returned object to see if that property is present. If it is, it
     * assumes the parsing was successful. If it is not present (or is null), it will
     * pass the document through an OGCExceptionReport parser.
     *
     * If errorProperty is undefined for the parser, this error checking mechanism
     * will be disabled.
     *
     *
     *
     * Inherits from:
     *  - <OpenLayers.Format.XML>
     */
    uOpenLayers.Format.XML.VersionedOGC = uOpenLayers.Class(uOpenLayers.Format.XML, {

        /**
         * APIProperty: defaultVersion
         * {String} Version number to assume if none found.
         */
        defaultVersion: null,

        /**
         * APIProperty: version
         * {String} Specify a version string if one is known.
         */
        version: null,

        /**
         * APIProperty: profile
         * {String} If provided, use a custom profile.
         */
        profile: null,

        /**
         * APIProperty: allowFallback
         * {Boolean} If a profiled parser cannot be found for the returned version,
         * use a non-profiled parser as the fallback. Application code using this
         * should take into account that the return object structure might be
         * missing the specifics of the profile. Defaults to false.
         */
        allowFallback: false,

        /**
         * Property: name
         * {String} The name of this parser, this is the part of the CLASS_NAME
         * except for "OpenLayers.Format."
         */
        name: null,

        /**
         * APIProperty: stringifyOutput
         * {Boolean} If true, write will return a string otherwise a DOMElement.
         * Default is false.
         */
        stringifyOutput: false,

        /**
         * Property: parser
         * {Object} Instance of the versioned parser.  Cached for multiple read and
         *     write calls of the same version.
         */
        parser: null,

        /**
         * Constructor: OpenLayers.Format.XML.VersionedOGC.
         * Constructor.
         *
         * Parameters:
         * options - {Object} Optional object whose properties will be set on
         *     the object.
         */
        initialize: function (options) {
            uOpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
            var className = this.CLASS_NAME;
            this.name = className.substring(className.lastIndexOf(".") + 1);
        },

        /**
         * Method: getVersion
         * Returns the version to use. Subclasses can override this function
         * if a different version detection is needed.
         *
         * Parameters:
         * root - {DOMElement}
         * options - {Object} Optional configuration object.
         *
         * Returns:
         * {String} The version to use.
         */
        getVersion: function (root, options) {
            var version;
            // read
            if (root) {
                version = this.version;
                if (!version) {
                    version = root.getAttribute("version");
                    if (!version) {
                        version = this.defaultVersion;
                    }
                }
            } else { // write
                version = (options && options.version) ||
                    this.version || this.defaultVersion;
            }
            return version;
        },

        /**
         * Method: getParser
         * Get an instance of the cached parser if available, otherwise create one.
         *
         * Parameters:
         * version - {String}
         *
         * Returns:
         * {<OpenLayers.Format>}
         */
        getParser: function (version) {
            version = version || this.defaultVersion;
            var profile = this.profile ? "_" + this.profile : "";
            if (!this.parser || this.parser.VERSION != version) {
                var format = uOpenLayers.Format[this.name][
                "v" + version.replace(/\./g, "_") + profile
                    ];
                if (!format) {
                    if (profile !== "" && this.allowFallback) {
                        // fallback to the non-profiled version of the parser
                        profile = "";
                        format = uOpenLayers.Format[this.name][
                        "v" + version.replace(/\./g, "_")
                            ];
                    }
                    if (!format) {
                        throw "Can't find a " + this.name + " parser for version " +
                        version + profile;
                    }
                }
                this.parser = new format(this.options);
            }
            return this.parser;
        },

        /**
         * APIMethod: write
         * Write a document.
         *
         * Parameters:
         * obj - {Object} An object representing the document.
         * options - {Object} Optional configuration object.
         *
         * Returns:
         * {String} The document as a string
         */
        write: function (obj, options) {
            var version = this.getVersion(null, options);
            this.parser = this.getParser(version);
            var root = this.parser.write(obj, options);
            if (this.stringifyOutput === false) {
                return root;
            } else {
                return uOpenLayers.Format.XML.prototype.write.apply(this, [root]);
            }
        },

        /**
         * APIMethod: read
         * Read a doc and return an object representing the document.
         *
         * Parameters:
         * data - {String | DOMElement} Data to read.
         * options - {Object} Options for the reader.
         *
         * Returns:
         * {Object} An object representing the document.
         */
        read: function (data, options) {
            if (typeof data == "string") {
                data = uOpenLayers.Format.XML.prototype.read.apply(this, [data]);
            }
            var root = data.documentElement;
            var version = this.getVersion(root);
            this.parser = this.getParser(version);          // Select the parser
            var obj = this.parser.read(data, options);      // Parse the data

            var errorProperty = this.parser.errorProperty || null;
            if (errorProperty !== null && obj[errorProperty] === undefined) {
                // an error must have happened, so parse it and report back
                var format = new uOpenLayers.Format.OGCExceptionReport();
                obj.error = format.read(data);
            }
            obj.version = version;
            return obj;
        },

        CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
    });

    /**
     * Class: OpenLayers.Format.WMTSCapabilities
     * Read WMTS Capabilities.
     *
     * Inherits from:
     *  - <OpenLayers.Format.XML.VersionedOGC>
     */
    uOpenLayers.Format.WMTSCapabilities = uOpenLayers.Class(uOpenLayers.Format.XML.VersionedOGC, {

        /**
         * APIProperty: defaultVersion
         * {String} Version number to assume if none found.  Default is "1.0.0".
         */
        defaultVersion: "1.0.0",

        /**
         * APIProperty: yx
         * {Object} Members in the yx object are used to determine if a CRS URN
         *     corresponds to a CRS with y,x axis order.  Member names are CRS URNs
         *     and values are boolean.  By default, the following CRS URN are
         *     assumed to correspond to a CRS with y,x axis order:
         *
         * * urn:ogc:def:crs:EPSG::4326
         */
        yx: {
            "urn:ogc:def:crs:EPSG::4326": true
        },

        /**
         * Constructor: OpenLayers.Format.WMTSCapabilities
         * Create a new parser for WMTS capabilities.
         *
         * Parameters:
         * options - {Object} An optional object whose properties will be set on
         *     this instance.
         */

        /**
         * APIMethod: read
         * Read capabilities data from a string, and return information about
         * the service (offering and observedProperty mostly).
         *
         * Parameters:
         * data - {String} or {DOMElement} data to read/parse.
         *
         * Returns:
         * {Object} Info about the WMTS Capabilities
         */

        /**
         * APIMethod: createLayer
         * Create a WMTS layer given a capabilities object.
         *
         * Parameters:
         * capabilities - {Object} The object returned from a <read> call to this
         *     format.
         * config - {Object} Configuration properties for the layer.  Defaults for
         *     the layer will apply if not provided.
         *
         * Required config properties:
         * layer - {String} The layer identifier.
         *
         * Optional config properties:
         * matrixSet - {String} The matrix set identifier, required if there is
         *      more than one matrix set in the layer capabilities.
         * style - {String} The name of the style
         * format - {String} Image format for the layer. Default is the first
         *     format returned in the GetCapabilities response.
         * param - {Object} The dimensions values eg: {"Year": "2012"}
         *
         * Returns:
         * {<OpenLayers.Layer.WMTS>} A properly configured WMTS layer.  Throws an
         *     error if an incomplete config is provided.  Returns undefined if no
         *     layer could be created with the provided config.
         */
        createLayer: function (capabilities, config) {
            var layer;

            // confirm required properties are supplied in config
            if (!('layer' in config)) {
                throw new Error("Missing property 'layer' in configuration.");
            }

            var contents = capabilities.contents;

            // find the layer definition with the given identifier
            var layers = contents.layers;
            var layerDef;
            for (var i = 0, ii = contents.layers.length; i < ii; ++i) {
                if (contents.layers[i].identifier === config.layer) {
                    layerDef = contents.layers[i];
                    break;
                }
            }
            if (!layerDef) {
                console.error("No layer with identifier ", config.layer, 'in', contents.layers);
                throw new Error("Layer not found");
            }

            var format = config.format;
            if (!format && layerDef.formats && layerDef.formats.length) {
                format = layerDef.formats[0];
            }

            // find the matrixSet definition
            var matrixSet;
            if (config.matrixSet) {
                matrixSet = contents.tileMatrixSets[config.matrixSet];
            } else if (layerDef.tileMatrixSetLinks.length >= 1) {
                matrixSet = contents.tileMatrixSets[
                    layerDef.tileMatrixSetLinks[0].tileMatrixSet];
            }
            if (!matrixSet) {
                console.error("No matrix set with identifier ", config.matrixSet, 'in', contents.tileMatrixSets);
                throw new Error("matrixSet not found");
            }

            // get the default style for the layer
            var style;
            for (var i = 0, ii = layerDef.styles.length; i < ii; ++i) {
                style = layerDef.styles[i];
                if (style.isDefault) {
                    break;
                }
            }

            var requestEncoding = config.requestEncoding;
            if (!requestEncoding) {
                requestEncoding = "KVP";
                if (capabilities.operationsMetadata.GetTile.dcp.http) {
                    var http = capabilities.operationsMetadata.GetTile.dcp.http;
                    // Get first get method
                    if (http.get[0].constraints) {
                        var constraints = http.get[0].constraints;
                        var allowedValues = constraints.GetEncoding.allowedValues;

                        // The OGC documentation is not clear if we should use
                        // REST or RESTful, ArcGis use RESTful,
                        // and OpenLayers use REST.
                        if (!allowedValues.KVP &&
                            (allowedValues.REST || allowedValues.RESTful)) {
                            requestEncoding = "REST";
                        }
                    }
                }
            }

            var dimensions = [];
            var params = config.params || {};
            // to don't overwrite the changes in the applyDefaults
            delete config.params;
            for (var id = 0, ld = layerDef.dimensions.length; id < ld; id++) {
                var dimension = layerDef.dimensions[id];
                dimensions.push(dimension.identifier);
                if (!params.hasOwnProperty(dimension.identifier)) {
                    params[dimension.identifier] = dimension['default'];
                }
            }

            var projection = config.projection || matrixSet.supportedCRS.replace(
                /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, "$1:$3");
            var units = config.units ||
                (projection === "EPSG:4326" ? "degrees" : "m");

            var resolutions = [];
            for (var mid in matrixSet.matrixIds) {
                if (matrixSet.matrixIds.hasOwnProperty(mid)) {
                    resolutions.push(
                        matrixSet.matrixIds[mid].scaleDenominator * 0.28E-3 /
                        uOpenLayers.METERS_PER_INCH /
                        uOpenLayers.INCHES_PER_UNIT[units]);
                }
            }

            var url;
            if (requestEncoding === "REST" && layerDef.resourceUrls) {
                url = [];
                var resourceUrls = layerDef.resourceUrls,
                    resourceUrl;
                for (var t = 0, tt = layerDef.resourceUrls.length; t < tt; ++t) {
                    resourceUrl = layerDef.resourceUrls[t];
                    if (resourceUrl.format === format && resourceUrl.resourceType === "tile") {
                        url.push(resourceUrl.template);
                    }
                }
            } else {
                var httpGet = capabilities.operationsMetadata.GetTile.dcp.http.get;
                url = [];
                var constraint;
                for (var i = 0, ii = httpGet.length; i < ii; i++) {
                    constraint = httpGet[i].constraints;
                    if (!constraint || (constraint && constraint.GetEncoding.allowedValues[requestEncoding])) {
                        url.push(httpGet[i].url);
                    }
                }
            }

            return new uOpenLayers.Layer.WMTS(
                uOpenLayers.Util.applyDefaults(config, {
                    url: url,
                    requestEncoding: requestEncoding,
                    name: layerDef.title,
                    style: style.identifier,
                    format: format,
                    matrixIds: matrixSet.matrixIds,
                    matrixSet: matrixSet.identifier,
                    projection: projection,
                    units: units,
                    resolutions: config.isBaseLayer === false ? undefined :
                        resolutions,
                    serverResolutions: resolutions,
                    tileFullExtent: matrixSet.bounds,
                    dimensions: dimensions,
                    params: params
                })
            );
        },

        CLASS_NAME: "OpenLayers.Format.WMTSCapabilities"

    });

    /**
     * Class: OpenLayers.Format.OWSCommon
     * Read OWSCommon. Create a new instance with the <OpenLayers.Format.OWSCommon>
     *     constructor.
     *
     * Inherits from:
     *  - <OpenLayers.Format.XML.VersionedOGC>
     */
    uOpenLayers.Format.OWSCommon = uOpenLayers.Class(uOpenLayers.Format.XML.VersionedOGC, {

        /**
         * APIProperty: defaultVersion
         * {String} Version number to assume if none found.  Default is "1.0.0".
         */
        defaultVersion: "1.0.0",

        /**
         * Constructor: OpenLayers.Format.OWSCommon
         * Create a new parser for OWSCommon.
         *
         * Parameters:
         * options - {Object} An optional object whose properties will be set on
         *     this instance.
         */

        /**
         * Method: getVersion
         * Returns the version to use. Subclasses can override this function
         * if a different version detection is needed.
         *
         * Parameters:
         * root - {DOMElement}
         * options - {Object} Optional configuration object.
         *
         * Returns:
         * {String} The version to use.
         */
        getVersion: function (root, options) {
            var version = this.version;
            if (!version) {
                // remember version does not correspond to the OWS version
                // it corresponds to the WMS/WFS/WCS etc. request version
                var uri = root.getAttribute("xmlns:ows");
                // the above will fail if the namespace prefix is different than
                // ows and if the namespace is declared on a different element
                if (uri && uri.substring(uri.lastIndexOf("/") + 1) === "1.1") {
                    version = "1.1.0";
                }
                if (!version) {
                    version = this.defaultVersion;
                }
            }
            return version;
        },

        /**
         * APIMethod: read
         * Read an OWSCommon document and return an object.
         *
         * Parameters:
         * data - {String | DOMElement} Data to read.
         * options - {Object} Options for the reader.
         *
         * Returns:
         * {Object} An object representing the structure of the document.
         */

        CLASS_NAME: "OpenLayers.Format.OWSCommon"
    });


    /**
     * Class: OpenLayers.Format.OWSCommon.v1
     * Common readers and writers for OWSCommon v1.X formats
     *
     * Inherits from:
     *  - <OpenLayers.Format.XML>
     */
    uOpenLayers.Format.OWSCommon.v1 = uOpenLayers.Class(uOpenLayers.Format.XML, {

        /**
         * Property: regExes
         * Compiled regular expressions for manipulating strings.
         */
        regExes: {
            trimSpace: (/^\s*|\s*$/g),
            removeSpace: (/\s*/g),
            splitSpace: (/\s+/),
            trimComma: (/\s*,\s*/g)
        },

        /**
         * Method: read
         *
         * Parameters:
         * data - {DOMElement} An OWSCommon document element.
         * options - {Object} Options for the reader.
         *
         * Returns:
         * {Object} An object representing the OWSCommon document.
         */
        read: function (data, options) {
            options = uOpenLayers.Util.applyDefaults(options, this.options);
            var ows = {};
            this.readChildNodes(data, ows);
            return ows;
        },

        /**
         * Property: readers
         * Contains public functions, grouped by namespace prefix, that will
         *     be applied when a namespaced node is found matching the function
         *     name.  The function will be applied in the scope of this parser
         *     with two arguments: the node being read and a context object passed
         *     from the parent.
         */
        readers: {
            "ows": {
                "Exception": function (node, exceptionReport) {
                    var exception = {
                        code: node.getAttribute('exceptionCode'),
                        locator: node.getAttribute('locator'),
                        texts: []
                    };
                    exceptionReport.exceptions.push(exception);
                    this.readChildNodes(node, exception);
                },
                "ExceptionText": function (node, exception) {
                    var text = this.getChildValue(node);
                    exception.texts.push(text);
                },
                "ServiceIdentification": function (node, obj) {
                    obj.serviceIdentification = {};
                    this.readChildNodes(node, obj.serviceIdentification);
                },
                "Title": function (node, obj) {
                    obj.title = this.getChildValue(node);
                },
                "Abstract": function (node, serviceIdentification) {
                    serviceIdentification["abstract"] = this.getChildValue(node);
                },
                "Keywords": function (node, serviceIdentification) {
                    serviceIdentification.keywords = {};
                    this.readChildNodes(node, serviceIdentification.keywords);
                },
                "Keyword": function (node, keywords) {
                    keywords[this.getChildValue(node)] = true;
                },
                "ServiceType": function (node, serviceIdentification) {
                    serviceIdentification.serviceType = {
                        codeSpace: node.getAttribute('codeSpace'),
                        value: this.getChildValue(node)
                    };
                },
                "ServiceTypeVersion": function (node, serviceIdentification) {
                    serviceIdentification.serviceTypeVersion = this.getChildValue(node);
                },
                "Fees": function (node, serviceIdentification) {
                    serviceIdentification.fees = this.getChildValue(node);
                },
                "AccessConstraints": function (node, serviceIdentification) {
                    serviceIdentification.accessConstraints =
                        this.getChildValue(node);
                },
                "ServiceProvider": function (node, obj) {
                    obj.serviceProvider = {};
                    this.readChildNodes(node, obj.serviceProvider);
                },
                "ProviderName": function (node, serviceProvider) {
                    serviceProvider.providerName = this.getChildValue(node);
                },
                "ProviderSite": function (node, serviceProvider) {
                    serviceProvider.providerSite = this.getAttributeNS(node,
                        this.namespaces.xlink, "href");
                },
                "ServiceContact": function (node, serviceProvider) {
                    serviceProvider.serviceContact = {};
                    this.readChildNodes(node, serviceProvider.serviceContact);
                },
                "IndividualName": function (node, serviceContact) {
                    serviceContact.individualName = this.getChildValue(node);
                },
                "PositionName": function (node, serviceContact) {
                    serviceContact.positionName = this.getChildValue(node);
                },
                "ContactInfo": function (node, serviceContact) {
                    serviceContact.contactInfo = {};
                    this.readChildNodes(node, serviceContact.contactInfo);
                },
                "Phone": function (node, contactInfo) {
                    contactInfo.phone = {};
                    this.readChildNodes(node, contactInfo.phone);
                },
                "Voice": function (node, phone) {
                    phone.voice = this.getChildValue(node);
                },
                "Address": function (node, contactInfo) {
                    contactInfo.address = {};
                    this.readChildNodes(node, contactInfo.address);
                },
                "DeliveryPoint": function (node, address) {
                    address.deliveryPoint = this.getChildValue(node);
                },
                "City": function (node, address) {
                    address.city = this.getChildValue(node);
                },
                "AdministrativeArea": function (node, address) {
                    address.administrativeArea = this.getChildValue(node);
                },
                "PostalCode": function (node, address) {
                    address.postalCode = this.getChildValue(node);
                },
                "Country": function (node, address) {
                    address.country = this.getChildValue(node);
                },
                "ElectronicMailAddress": function (node, address) {
                    address.electronicMailAddress = this.getChildValue(node);
                },
                "Role": function (node, serviceContact) {
                    serviceContact.role = this.getChildValue(node);
                },
                "OperationsMetadata": function (node, obj) {
                    obj.operationsMetadata = {};
                    this.readChildNodes(node, obj.operationsMetadata);
                },
                "Operation": function (node, operationsMetadata) {
                    var name = node.getAttribute("name");
                    operationsMetadata[name] = {};
                    this.readChildNodes(node, operationsMetadata[name]);
                },
                "DCP": function (node, operation) {
                    operation.dcp = {};
                    this.readChildNodes(node, operation.dcp);
                },
                "HTTP": function (node, dcp) {
                    dcp.http = {};
                    this.readChildNodes(node, dcp.http);
                },
                "Get": function (node, http) {
                    if (!http.get) {
                        http.get = [];
                    }
                    var obj = {
                        url: this.getAttributeNS(node, this.namespaces.xlink, "href")
                    };
                    this.readChildNodes(node, obj);
                    http.get.push(obj);
                },
                "Post": function (node, http) {
                    if (!http.post) {
                        http.post = [];
                    }
                    var obj = {
                        url: this.getAttributeNS(node, this.namespaces.xlink, "href")
                    };
                    this.readChildNodes(node, obj);
                    http.post.push(obj);
                },
                "Parameter": function (node, operation) {
                    if (!operation.parameters) {
                        operation.parameters = {};
                    }
                    var name = node.getAttribute("name");
                    operation.parameters[name] = {};
                    this.readChildNodes(node, operation.parameters[name]);
                },
                "Constraint": function (node, obj) {
                    if (!obj.constraints) {
                        obj.constraints = {};
                    }
                    var name = node.getAttribute("name");
                    obj.constraints[name] = {};
                    this.readChildNodes(node, obj.constraints[name]);
                },
                "Value": function (node, allowedValues) {
                    allowedValues[this.getChildValue(node)] = true;
                },
                "OutputFormat": function (node, obj) {
                    obj.formats.push({value: this.getChildValue(node)});
                    this.readChildNodes(node, obj);
                },
                "WGS84BoundingBox": function (node, obj) {
                    var boundingBox = {};
                    boundingBox.crs = node.getAttribute("crs");
                    if (obj.BoundingBox) {
                        obj.BoundingBox.push(boundingBox);
                    } else {
                        obj.projection = boundingBox.crs;
                        boundingBox = obj;
                    }
                    this.readChildNodes(node, boundingBox);
                },
                "BoundingBox": function (node, obj) {
                    // FIXME: We consider that BoundingBox is the same as WGS84BoundingBox
                    // LowerCorner = "min_x min_y"
                    // UpperCorner = "max_x max_y"
                    // It should normally depend on the projection
                    this.readers['ows']['WGS84BoundingBox'].apply(this, [node, obj]);
                },
                "LowerCorner": function (node, obj) {
                    var str = this.getChildValue(node).replace(
                        this.regExes.trimSpace, "");
                    str = str.replace(this.regExes.trimComma, ",");
                    var pointList = str.split(this.regExes.splitSpace);
                    obj.left = pointList[0];
                    obj.bottom = pointList[1];
                },
                "UpperCorner": function (node, obj) {
                    var str = this.getChildValue(node).replace(
                        this.regExes.trimSpace, "");
                    str = str.replace(this.regExes.trimComma, ",");
                    var pointList = str.split(this.regExes.splitSpace);
                    obj.right = pointList[0];
                    obj.top = pointList[1];
                    obj.bounds = new uOpenLayers.Bounds(obj.left, obj.bottom,
                        obj.right, obj.top);
                    delete obj.left;
                    delete obj.bottom;
                    delete obj.right;
                    delete obj.top;
                },
                "Language": function (node, obj) {
                    obj.language = this.getChildValue(node);
                }
            }
        },

        /**
         * Property: writers
         * As a compliment to the readers property, this structure contains public
         *     writing functions grouped by namespace alias and named like the
         *     node names they produce.
         */
        writers: {
            "ows": {
                "BoundingBox": function (options, nodeName) {
                    var node = this.createElementNSPlus(nodeName || "ows:BoundingBox", {
                        attributes: {
                            crs: options.projection
                        }
                    });
                    this.writeNode("ows:LowerCorner", options, node);
                    this.writeNode("ows:UpperCorner", options, node);
                    return node;
                },
                "LowerCorner": function (options) {
                    var node = this.createElementNSPlus("ows:LowerCorner", {
                        value: options.bounds.left + " " + options.bounds.bottom
                    });
                    return node;
                },
                "UpperCorner": function (options) {
                    var node = this.createElementNSPlus("ows:UpperCorner", {
                        value: options.bounds.right + " " + options.bounds.top
                    });
                    return node;
                },
                "Identifier": function (identifier) {
                    var node = this.createElementNSPlus("ows:Identifier", {
                        value: identifier
                    });
                    return node;
                },
                "Title": function (title) {
                    var node = this.createElementNSPlus("ows:Title", {
                        value: title
                    });
                    return node;
                },
                "Abstract": function (abstractValue) {
                    var node = this.createElementNSPlus("ows:Abstract", {
                        value: abstractValue
                    });
                    return node;
                },
                "OutputFormat": function (format) {
                    var node = this.createElementNSPlus("ows:OutputFormat", {
                        value: format
                    });
                    return node;
                }
            }
        },

        CLASS_NAME: "OpenLayers.Format.OWSCommon.v1"

    });

    /**
     * Class: OpenLayers.Format.OWSCommon.v1_1_0
     * Parser for OWS Common version 1.1.0.
     *
     * Inherits from:
     *  - <OpenLayers.Format.OWSCommon.v1>
     */
    uOpenLayers.Format.OWSCommon.v1_1_0 = uOpenLayers.Class(uOpenLayers.Format.OWSCommon.v1, {

        /**
         * Property: namespaces
         * {Object} Mapping of namespace aliases to namespace URIs.
         */
        namespaces: {
            ows: "http://www.opengis.net/ows/1.1",
            xlink: "http://www.w3.org/1999/xlink"
        },

        /**
         * Property: readers
         * Contains public functions, grouped by namespace prefix, that will
         *     be applied when a namespaced node is found matching the function
         *     name.  The function will be applied in the scope of this parser
         *     with two arguments: the node being read and a context object passed
         *     from the parent.
         */
        readers: {
            "ows": uOpenLayers.Util.applyDefaults({
                "ExceptionReport": function (node, obj) {
                    obj.exceptionReport = {
                        version: node.getAttribute('version'),
                        language: node.getAttribute('xml:lang'),
                        exceptions: []
                    };
                    this.readChildNodes(node, obj.exceptionReport);
                },
                "AllowedValues": function (node, parameter) {
                    parameter.allowedValues = {};
                    this.readChildNodes(node, parameter.allowedValues);
                },
                "AnyValue": function (node, parameter) {
                    parameter.anyValue = true;
                },
                "DataType": function (node, parameter) {
                    parameter.dataType = this.getChildValue(node);
                },
                "Range": function (node, allowedValues) {
                    allowedValues.range = {};
                    this.readChildNodes(node, allowedValues.range);
                },
                "MinimumValue": function (node, range) {
                    range.minValue = this.getChildValue(node);
                },
                "MaximumValue": function (node, range) {
                    range.maxValue = this.getChildValue(node);
                },
                "Identifier": function (node, obj) {
                    obj.identifier = this.getChildValue(node);
                },
                "SupportedCRS": function (node, obj) {
                    obj.supportedCRS = this.getChildValue(node);
                }
            }, uOpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])
        },

        /**
         * Property: writers
         * As a compliment to the readers property, this structure contains public
         *     writing functions grouped by namespace alias and named like the
         *     node names they produce.
         */
        writers: {
            "ows": uOpenLayers.Util.applyDefaults({
                "Range": function (range) {
                    var node = this.createElementNSPlus("ows:Range", {
                        attributes: {
                            'ows:rangeClosure': range.closure
                        }
                    });
                    this.writeNode("ows:MinimumValue", range.minValue, node);
                    this.writeNode("ows:MaximumValue", range.maxValue, node);
                    return node;
                },
                "MinimumValue": function (minValue) {
                    var node = this.createElementNSPlus("ows:MinimumValue", {
                        value: minValue
                    });
                    return node;
                },
                "MaximumValue": function (maxValue) {
                    var node = this.createElementNSPlus("ows:MaximumValue", {
                        value: maxValue
                    });
                    return node;
                },
                "Value": function (value) {
                    var node = this.createElementNSPlus("ows:Value", {
                        value: value
                    });
                    return node;
                }
            }, OpenLayers.Format.OWSCommon.v1.prototype.writers["ows"])
        },

        CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_1_0"

    });

    /**
     * Class: OpenLayers.Format.WMTSCapabilities.v1_0_0
     * Read WMTS Capabilities version 1.0.0.
     *
     * Inherits from:
     *  - <OpenLayers.Format.WMTSCapabilities>
     */
    uOpenLayers.Format.WMTSCapabilities.v1_0_0 = uOpenLayers.Class(
        uOpenLayers.Format.OWSCommon.v1_1_0, {

            /**
             * Property: version
             * {String} The parser version ("1.0.0").
             */
            version: "1.0.0",

            /**
             * Property: namespaces
             * {Object} Mapping of namespace aliases to namespace URIs.
             */
            namespaces: {
                ows: "http://www.opengis.net/ows/1.1",
                wmts: "http://www.opengis.net/wmts/1.0",
                xlink: "http://www.w3.org/1999/xlink"
            },

            /**
             * Property: yx
             * {Object} Members in the yx object are used to determine if a CRS URN
             *     corresponds to a CRS with y,x axis order.  Member names are CRS URNs
             *     and values are boolean.  Defaults come from the
             *     <OpenLayers.Format.WMTSCapabilities> prototype.
             */
            yx: null,

            /**
             * Property: defaultPrefix
             * {String} The default namespace alias for creating element nodes.
             */
            defaultPrefix: "wmts",

            /**
             * Constructor: OpenLayers.Format.WMTSCapabilities.v1_0_0
             * Create a new parser for WMTS capabilities version 1.0.0.
             *
             * Parameters:
             * options - {Object} An optional object whose properties will be set on
             *     this instance.
             */
            initialize: function (options) {
                uOpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
                this.options = options;
                var yx = uOpenLayers.Util.extend(
                    {}, uOpenLayers.Format.WMTSCapabilities.prototype.yx
                );
                this.yx = uOpenLayers.Util.extend(yx, this.yx);
            },

            /**
             * APIMethod: read
             * Read capabilities data from a string, and return info about the WMTS.
             *
             * Parameters:
             * data - {String} or {DOMElement} data to read/parse.
             *
             * Returns:
             * {Object} Information about the SOS service.
             */
            read: function (data) {
                if (typeof data == "string") {
                    data = uOpenLayers.Format.XML.prototype.read.apply(this, [data]);
                }
                if (data && data.nodeType == 9) {
                    data = data.documentElement;
                }
                var capabilities = {};
                this.readNode(data, capabilities);
                capabilities.version = this.version;
                return capabilities;
            },

            /**
             * Property: readers
             * Contains public functions, grouped by namespace prefix, that will
             *     be applied when a namespaced node is found matching the function
             *     name.  The function will be applied in the scope of this parser
             *     with two arguments: the node being read and a context object passed
             *     from the parent.
             */
            readers: {
                "wmts": {
                    "Capabilities": function (node, obj) {
                        this.readChildNodes(node, obj);
                    },
                    "Contents": function (node, obj) {
                        obj.contents = {};
                        obj.contents.layers = [];
                        obj.contents.tileMatrixSets = {};
                        this.readChildNodes(node, obj.contents);
                    },
                    "Layer": function (node, obj) {
                        var layer = {
                            styles: [],
                            formats: [],
                            dimensions: [],
                            tileMatrixSetLinks: []
                        };
                        layer.layers = [];
                        this.readChildNodes(node, layer);
                        obj.layers.push(layer);
                    },
                    "Style": function (node, obj) {
                        var style = {};
                        style.isDefault = (node.getAttribute("isDefault") === "true");
                        this.readChildNodes(node, style);
                        obj.styles.push(style);
                    },
                    "Format": function (node, obj) {
                        obj.formats.push(this.getChildValue(node));
                    },
                    "TileMatrixSetLink": function (node, obj) {
                        var tileMatrixSetLink = {};
                        this.readChildNodes(node, tileMatrixSetLink);
                        obj.tileMatrixSetLinks.push(tileMatrixSetLink);
                    },
                    "TileMatrixSet": function (node, obj) {
                        // node could be child of wmts:Contents or wmts:TileMatrixSetLink
                        // duck type wmts:Contents by looking for layers
                        if (obj.layers) {
                            // TileMatrixSet as object type in schema
                            var tileMatrixSet = {
                                matrixIds: []
                            };
                            this.readChildNodes(node, tileMatrixSet);
                            obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet;
                        } else {
                            // TileMatrixSet as string type in schema
                            obj.tileMatrixSet = this.getChildValue(node);
                        }
                    },
                    "TileMatrix": function (node, obj) {
                        var tileMatrix = {
                            supportedCRS: obj.supportedCRS
                        };
                        this.readChildNodes(node, tileMatrix);
                        obj.matrixIds.push(tileMatrix);
                    },
                    "ScaleDenominator": function (node, obj) {
                        obj.scaleDenominator = parseFloat(this.getChildValue(node));
                    },
                    "TopLeftCorner": function (node, obj) {
                        var topLeftCorner = this.getChildValue(node);
                        var coords = topLeftCorner.split(" ");
                        // decide on axis order for the given CRS
                        var yx;
                        if (obj.supportedCRS) {
                            // extract out version from URN
                            var crs = obj.supportedCRS.replace(
                                /urn:ogc:def:crs:(\w+):.+:(\w+)$/,
                                "urn:ogc:def:crs:$1::$2"
                            );
                            yx = !!this.yx[crs];
                        }
                        if (yx) {
                            obj.topLeftCorner = new OpenLayers.LonLat(
                                coords[1], coords[0]
                            );
                        } else {
                            obj.topLeftCorner = new OpenLayers.LonLat(
                                coords[0], coords[1]
                            );
                        }
                    },
                    "TileWidth": function (node, obj) {
                        obj.tileWidth = parseInt(this.getChildValue(node));
                    },
                    "TileHeight": function (node, obj) {
                        obj.tileHeight = parseInt(this.getChildValue(node));
                    },
                    "MatrixWidth": function (node, obj) {
                        obj.matrixWidth = parseInt(this.getChildValue(node));
                    },
                    "MatrixHeight": function (node, obj) {
                        obj.matrixHeight = parseInt(this.getChildValue(node));
                    },
                    "ResourceURL": function (node, obj) {
                        obj.resourceUrl = obj.resourceUrl || {};
                        var resourceType = node.getAttribute("resourceType");
                        if (!obj.resourceUrls) {
                            obj.resourceUrls = [];
                        }
                        var resourceUrl = obj.resourceUrl[resourceType] = {
                            format: node.getAttribute("format"),
                            template: node.getAttribute("template"),
                            resourceType: resourceType
                        };
                        obj.resourceUrls.push(resourceUrl);
                    },
                    // not used for now, can be added in the future though
                    /*"Themes": function(node, obj) {
                obj.themes = [];
                this.readChildNodes(node, obj.themes);
            },
            "Theme": function(node, obj) {
                var theme = {};
                this.readChildNodes(node, theme);
                obj.push(theme);
            },*/
                    "WSDL": function (node, obj) {
                        obj.wsdl = {};
                        obj.wsdl.href = node.getAttribute("xlink:href");
                        // TODO: other attributes of <WSDL> element
                    },
                    "ServiceMetadataURL": function (node, obj) {
                        obj.serviceMetadataUrl = {};
                        obj.serviceMetadataUrl.href = node.getAttribute("xlink:href");
                        // TODO: other attributes of <ServiceMetadataURL> element
                    },
                    "LegendURL": function (node, obj) {
                        obj.legend = {};
                        obj.legend.href = node.getAttribute("xlink:href");
                        obj.legend.format = node.getAttribute("format");
                    },
                    "Dimension": function (node, obj) {
                        var dimension = {values: []};
                        this.readChildNodes(node, dimension);
                        obj.dimensions.push(dimension);
                    },
                    "Default": function (node, obj) {
                        obj["default"] = this.getChildValue(node);
                    },
                    "Value": function (node, obj) {
                        obj.values.push(this.getChildValue(node));
                    }
                },
                "ows": uOpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
            },

            CLASS_NAME: "OpenLayers.Format.WMTSCapabilities.v1_0_0"

        });

    /**
     * Class: OpenLayers.Layer.WMTS
     * Instances of the WMTS class allow viewing of tiles from a service that
     *     implements the OGC WMTS specification version 1.0.0.
     *
     * Inherits from:
     *  - <OpenLayers.Layer.Grid>
     */
    uOpenLayers.Layer.WMTS = uOpenLayers.Class(uOpenLayers.Layer.Grid, {

        /**
         * APIProperty: isBaseLayer
         * {Boolean} The layer will be considered a base layer.  Default is true.
         */
        isBaseLayer: true,

        /**
         * Property: version
         * {String} WMTS version.  Default is "1.0.0".
         */
        version: "1.0.0",

        /**
         * APIProperty: requestEncoding
         * {String} Request encoding.  Can be "REST" or "KVP".  Default is "KVP".
         */
        requestEncoding: "KVP",

        /**
         * APIProperty: url
         * {String|Array(String)} The base URL or request URL template for the WMTS
         * service. Must be provided. Array is only supported for base URLs, not
         * for request URL templates. URL templates are only supported for
         * REST <requestEncoding>.
         */
        url: null,

        /**
         * APIProperty: layer
         * {String} The layer identifier advertised by the WMTS service.  Must be
         *     provided.
         */
        layer: null,

        /**
         * APIProperty: matrixSet
         * {String} One of the advertised matrix set identifiers.  Must be provided.
         */
        matrixSet: null,

        /**
         * APIProperty: style
         * {String} One of the advertised layer styles.  Must be provided.
         */
        style: null,

        /**
         * APIProperty: format
         * {String} The image MIME type.  Default is "image/jpeg".
         */
        format: "image/jpeg",

        /**
         * APIProperty: tileOrigin
         * {<OpenLayers.LonLat>} The top-left corner of the tile matrix in map
         *     units.  If the tile origin for each matrix in a set is different,
         *     the <matrixIds> should include a topLeftCorner property.  If
         *     not provided, the tile origin will default to the top left corner
         *     of the layer <maxExtent>.
         */
        tileOrigin: null,

        /**
         * APIProperty: tileFullExtent
         * {<OpenLayers.Bounds>}  The full extent of the tile set.  If not supplied,
         *     the layer's <maxExtent> property will be used.
         */
        tileFullExtent: null,

        /**
         * APIProperty: formatSuffix
         * {String} For REST request encoding, an image format suffix must be
         *     included in the request.  If not provided, the suffix will be derived
         *     from the <format> property.
         */
        formatSuffix: null,

        /**
         * APIProperty: matrixIds
         * {Array} A list of tile matrix identifiers.  If not provided, the matrix
         *     identifiers will be assumed to be integers corresponding to the
         *     map zoom level.  If a list of strings is provided, each item should
         *     be the matrix identifier that corresponds to the map zoom level.
         *     Additionally, a list of objects can be provided.  Each object should
         *     describe the matrix as presented in the WMTS capabilities.  These
         *     objects should have the propertes shown below.
         *
         * Matrix properties:
         * identifier - {String} The matrix identifier (required).
         * scaleDenominator - {Number} The matrix scale denominator.
         * topLeftCorner - {<OpenLayers.LonLat>} The top left corner of the
         *     matrix.  Must be provided if different than the layer <tileOrigin>.
         * tileWidth - {Number} The tile width for the matrix.  Must be provided
         *     if different than the width given in the layer <tileSize>.
         * tileHeight - {Number} The tile height for the matrix.  Must be provided
         *     if different than the height given in the layer <tileSize>.
         */
        matrixIds: null,

        /**
         * APIProperty: dimensions
         * {Array} For RESTful request encoding, extra dimensions may be specified.
         *     Items in this list should be property names in the <params> object.
         *     Values of extra dimensions will be determined from the corresponding
         *     values in the <params> object.
         */
        dimensions: null,

        /**
         * APIProperty: params
         * {Object} Extra parameters to include in tile requests.  For KVP
         *     <requestEncoding>, these properties will be encoded in the request
         *     query string.  For REST <requestEncoding>, these properties will
         *     become part of the request path, with order determined by the
         *     <dimensions> list.
         */
        params: null,

        /**
         * APIProperty: zoomOffset
         * {Number} If your cache has more levels than you want to provide
         *     access to with this layer, supply a zoomOffset.  This zoom offset
         *     is added to the current map zoom level to determine the level
         *     for a requested tile.  For example, if you supply a zoomOffset
         *     of 3, when the map is at the zoom 0, tiles will be requested from
         *     level 3 of your cache.  Default is 0 (assumes cache level and map
         *     zoom are equivalent).  Additionally, if this layer is to be used
         *     as an overlay and the cache has fewer zoom levels than the base
         *     layer, you can supply a negative zoomOffset.  For example, if a
         *     map zoom level of 1 corresponds to your cache level zero, you would
         *     supply a -1 zoomOffset (and set the maxResolution of the layer
         *     appropriately).  The zoomOffset value has no effect if complete
         *     matrix definitions (including scaleDenominator) are supplied in
         *     the <matrixIds> property.  Defaults to 0 (no zoom offset).
         */
        zoomOffset: 0,

        /**
         * APIProperty: serverResolutions
         * {Array} A list of all resolutions available on the server.  Only set this
         *     property if the map resolutions differ from the server. This
         *     property serves two purposes. (a) <serverResolutions> can include
         *     resolutions that the server supports and that you don't want to
         *     provide with this layer; you can also look at <zoomOffset>, which is
         *     an alternative to <serverResolutions> for that specific purpose.
         *     (b) The map can work with resolutions that aren't supported by
         *     the server, i.e. that aren't in <serverResolutions>. When the
         *     map is displayed in such a resolution data for the closest
         *     server-supported resolution is loaded and the layer div is
         *     stretched as necessary.
         */
        serverResolutions: null,

        /**
         * Property: formatSuffixMap
         * {Object} a map between WMTS 'format' request parameter and tile image file suffix
         */
        formatSuffixMap: {
            "image/png": "png",
            "image/png8": "png",
            "image/png24": "png",
            "image/png32": "png",
            "png": "png",
            "image/jpeg": "jpg",
            "image/jpg": "jpg",
            "jpeg": "jpg",
            "jpg": "jpg"
        },

        /**
         * Property: matrix
         * {Object} Matrix definition for the current map resolution.  Updated by
         *     the <updateMatrixProperties> method.
         */
        matrix: null,

        /**
         * Constructor: OpenLayers.Layer.WMTS
         * Create a new WMTS layer.
         *
         * Example:
         * (code)
         * var wmts = new OpenLayers.Layer.WMTS({
         *     name: "My WMTS Layer",
         *     url: "http://example.com/wmts",
         *     layer: "layer_id",
         *     style: "default",
         *     matrixSet: "matrix_id"
         * });
         * (end)
         *
         * Parameters:
         * config - {Object} Configuration properties for the layer.
         *
         * Required configuration properties:
         * url - {String} The base url for the service.  See the <url> property.
         * layer - {String} The layer identifier.  See the <layer> property.
         * style - {String} The layer style identifier.  See the <style> property.
         * matrixSet - {String} The tile matrix set identifier.  See the <matrixSet>
         *     property.
         *
         * Any other documented layer properties can be provided in the config object.
         */
        initialize: function (config) {

            // confirm required properties are supplied
            var required = {
                url: true,
                layer: true,
                style: true,
                matrixSet: true
            };
            for (var prop in required) {
                if (!(prop in config)) {
                    throw new Error("Missing property '" + prop + "' in layer configuration.");
                }
            }

            config.params = uOpenLayers.Util.upperCaseObject(config.params);
            var args = [config.name, config.url, config.params, config];
            uOpenLayers.Layer.Grid.prototype.initialize.apply(this, args);


            // determine format suffix (for REST)
            if (!this.formatSuffix) {
                this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop();
            }

            // expand matrixIds (may be array of string or array of object)
            if (this.matrixIds) {
                var len = this.matrixIds.length;
                if (len && typeof this.matrixIds[0] === "string") {
                    var ids = this.matrixIds;
                    this.matrixIds = new Array(len);
                    for (var i = 0; i < len; ++i) {
                        this.matrixIds[i] = {identifier: ids[i]};
                    }
                }
            }

        },

        /**
         * Method: setMap
         */
        setMap: function () {
            uOpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
        },

        /**
         * Method: updateMatrixProperties
         * Called when map resolution changes to update matrix related properties.
         */
        updateMatrixProperties: function () {
            this.matrix = this.getMatrix();
            if (this.matrix) {
                if (this.matrix.topLeftCorner) {
                    this.tileOrigin = this.matrix.topLeftCorner;
                }
                if (this.matrix.tileWidth && this.matrix.tileHeight) {
                    this.tileSize = new uOpenLayers.Size(
                        this.matrix.tileWidth, this.matrix.tileHeight
                    );
                }
                if (!this.tileOrigin) {
                    this.tileOrigin = new uOpenLayers.LonLat(
                        this.maxExtent.left, this.maxExtent.top
                    );
                }
                if (!this.tileFullExtent) {
                    this.tileFullExtent = this.maxExtent;
                }
            }
        },

        /**
         * Method: moveTo
         *
         * Parameters:
         * bounds - {<OpenLayers.Bounds>}
         * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
         *     do some init work in that case.
         * dragging - {Boolean}
         */
        moveTo: function (bounds, zoomChanged, dragging) {
            if (zoomChanged || !this.matrix) {
                this.updateMatrixProperties();
            }
            return uOpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
        },

        /**
         * APIMethod: clone
         *
         * Parameters:
         * obj - {Object}
         *
         * Returns:
         * {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS>
         */
        clone: function (obj) {
            if (obj == null) {
                obj = new uOpenLayers.Layer.WMTS(this.options);
            }
            //get all additions from superclasses
            obj = uOpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
            // copy/set any non-init, non-simple values here
            return obj;
        },

        /**
         * Method: getIdentifier
         * Get the current index in the matrixIds array.
         */
        getIdentifier: function () {
            return this.getServerZoom();
        },

        /**
         * Method: getMatrix
         * Get the appropriate matrix definition for the current map resolution.
         */
        getMatrix: function () {
            var matrix;
            if (!this.matrixIds || this.matrixIds.length === 0) {
                matrix = {identifier: this.getIdentifier()};
            } else {
                // get appropriate matrix given the map scale if possible
                if ("scaleDenominator" in this.matrixIds[0]) {
                    // scale denominator calculation based on WMTS spec
                    var denom =
                        uOpenLayers.METERS_PER_INCH *
                        uOpenLayers.INCHES_PER_UNIT[this.units] *
                        this.getServerResolution() / 0.28E-3;
                    var diff = Number.POSITIVE_INFINITY;
                    var delta;
                    for (var i = 0, ii = this.matrixIds.length; i < ii; ++i) {
                        delta = Math.abs(1 - (this.matrixIds[i].scaleDenominator / denom));
                        if (delta < diff) {
                            diff = delta;
                            matrix = this.matrixIds[i];
                        }
                    }
                } else {
                    // fall back on zoom as index
                    matrix = this.matrixIds[this.getIdentifier()];
                }
            }
            return matrix;
        },

        /**
         * Method: getTileInfo
         * Get tile information for a given location at the current map resolution.
         *
         * Parameters:
         * loc - {<OpenLayers.LonLat} A location in map coordinates.
         *
         * Returns:
         * {Object} An object with "col", "row", "i", and "j" properties.  The col
         *     and row values are zero based tile indexes from the top left.  The
         *     i and j values are the number of pixels to the left and top
         *     (respectively) of the given location within the target tile.
         */
        getTileInfo: function (loc) {
            var res = this.getServerResolution();

            var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
            var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h);

            var col = Math.floor(fx);
            var row = Math.floor(fy);

            return {
                col: col,
                row: row,
                i: Math.floor((fx - col) * this.tileSize.w),
                j: Math.floor((fy - row) * this.tileSize.h)
            };
        },

        /**
         * Method: getURL
         *
         * Parameters:
         * bounds - {<OpenLayers.Bounds>}
         *
         * Returns:
         * {String} A URL for the tile corresponding to the given bounds.
         */
        getURL: function (bounds) {
            bounds = this.adjustBounds(bounds);
            var url = "";
            if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) {

                var center = bounds.getCenterLonLat();
                var info = this.getTileInfo(center);
                var matrixId = this.matrix.identifier;
                var dimensions = this.dimensions, params;

                if (uOpenLayers.Util.isArray(this.url)) {
                    url = this.selectUrl([
                        this.version, this.style, this.matrixSet,
                        this.matrix.identifier, info.row, info.col
                    ].join(","), this.url);
                } else {
                    url = this.url;
                }

                if (this.requestEncoding.toUpperCase() === "REST") {
                    params = this.params;
                    if (url.indexOf("{") !== -1) {
                        var template = url.replace(/\{/g, "${");
                        var context = {
                            // spec does not make clear if capital S or not
                            style: this.style, Style: this.style,
                            TileMatrixSet: this.matrixSet,
                            TileMatrix: this.matrix.identifier,
                            TileRow: info.row,
                            TileCol: info.col
                        };
                        if (dimensions) {
                            var dimension, i;
                            for (i = dimensions.length - 1; i >= 0; --i) {
                                dimension = dimensions[i];
                                context[dimension] = params[dimension.toUpperCase()];
                            }
                        }
                        url = uOpenLayers.String.format(template, context);
                    } else {
                        // include 'version', 'layer' and 'style' in tile resource url
                        var path = this.version + "/" + this.layer + "/" + this.style + "/";

                        // append optional dimension path elements
                        if (dimensions) {
                            for (var i = 0; i < dimensions.length; i++) {
                                if (params[dimensions[i]]) {
                                    path = path + params[dimensions[i]] + "/";
                                }
                            }
                        }

                        // append other required path elements
                        path = path + this.matrixSet + "/" + this.matrix.identifier +
                            "/" + info.row + "/" + info.col + "." + this.formatSuffix;

                        if (!url.match(/\/$/)) {
                            url = url + "/";
                        }
                        url = url + path;
                    }
                } else if (this.requestEncoding.toUpperCase() === "KVP") {

                    // assemble all required parameters
                    params = {
                        SERVICE: "WMTS",
                        REQUEST: "GetTile",
                        VERSION: this.version,
                        LAYER: this.layer,
                        STYLE: this.style,
                        TILEMATRIXSET: this.matrixSet,
                        TILEMATRIX: this.matrix.identifier,
                        TILEROW: info.row,
                        TILECOL: info.col,
                        FORMAT: this.format
                    };
                    url = uOpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);

                }
            }
            return url;
        },

        /**
         * APIMethod: mergeNewParams
         * Extend the existing layer <params> with new properties.  Tiles will be
         *     reloaded with updated params in the request.
         *
         * Parameters:
         * newParams - {Object} Properties to extend to existing <params>.
         */
        mergeNewParams: function (newParams) {
            if (this.requestEncoding.toUpperCase() === "KVP") {
                return uOpenLayers.Layer.Grid.prototype.mergeNewParams.apply(
                    this, [uOpenLayers.Util.upperCaseObject(newParams)]
                );
            }
        },

        CLASS_NAME: "OpenLayers.Layer.WMTS"
    });
}

function geoportal_init() {
    loadWMTSLayer();

    // Define WMTS Layers
    let sources = [
        {
            name: 'GeoDatenZentrum DE',
            unique: '__DrawGeoPortalDE',
            id: 'layer-switcher-geoportal-de',
            source: 'https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml',
            layerName: 'web',
            matrixSet: 'WEBMERCATOR',
            requestEncoding: 'REST'
        },
        {
            name: 'GeoPortal BW',
            unique: '__DrawGeoPortalBW',
            id: 'layer-switcher-geoportal-bw',
            source: 'https://owsproxy.lgl-bw.de/owsproxy/ows/WMTS_LGL-BW_Basiskarte?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities&user=ZentrKomp&password=viewerprod',
            layerName: 'Basiskarte',
            matrixSet: 'GoogleMapsCompatible'
        },
        {
            name: 'GeoPortal NRW',
            unique: '__DrawGeoPortalNRW',
            id: 'layer-switcher-geoportal-nrw',
            source: 'https://www.wmts.nrw.de/geobasis/wmts_nw_dtk/1.0.0/WMTSCapabilities.xml',
            layerName: 'nw_dtk_col',
            matrixSet: 'EPSG_3857_16',
            requestEncoding: 'REST'
        },
        {
            name: 'GeoPortal NRW Overlay',
            unique: '__DrawGeoPortalNRWOverlay',
            id: 'layer-switcher-geoportal-nrw-overlay',
            source: 'https://www.wmts.nrw.de/geobasis/wmts_nw_dop_overlay/1.0.0/WMTSCapabilities.xml',
            layerName: 'nw_dop_overlay',
            matrixSet: 'EPSG_3857_16',
            opacity: 1,
            requestEncoding: 'REST'
        },
        {
            name: 'GeoPortal BY',
            unique: '__DrawGeoPortalBY',
            id: 'layer-switcher-geoportal-by',
            source: 'https://geoservices.bayern.de/wmts/v1/fca0473b-80ad-45c4-aec0-cb16b58f97e0/1.0.0/WMTSCapabilities.xml',
            layerName: 'by_webkarte',
            matrixSet: 'smerc',
            requestEncoding: 'REST'
        }
    ];

    let displayGroupSelector = document.getElementById('layer-switcher-group_display');
    if (displayGroupSelector != null) {
        let displayGroup = displayGroupSelector.parentNode.parentNode.getElementsByTagName("ul")[0];

        for (let source of sources) {
            // Make and add layer

            console.log('request', source);
            GM.xmlHttpRequest({
                method: "GET",
                url: source.source,
                onload: (response) => {
                    var responseXML = response.responseXML;
                    // Inject responseXML into existing Object (only appropriate for XML content).
                    if (!response.responseXML) {
                        responseXML = new DOMParser()
                            .parseFromString(response.responseText, "text/xml");
                    }
                    console.log('load', responseXML);

                    let format = new uOpenLayers.Format.WMTSCapabilities({});
                    var doc = responseXML;
                    var capabilities = format.read(doc);
                    source.layer = format.createLayer(capabilities, {
                        layer: source.layerName,
                        matrixSet: source.matrixSet,
                        format: "image/png",
                        opacity: source.opacity ?? 0.85,
                        isBaseLayer: false,
                        requestEncoding: source.requestEncoding ?? 'KVP'
                    });

                    uWaze.map.addLayer(source.layer);
                    uWaze.map.setLayerIndex(source.layer, 3);
                    console.log(source);

                    // Check if layer was active previously
                    if (localStorage[source.unique]) {
                        source.layer.setVisibility(localStorage[source.unique] == "true");
                    }

                    // Make checkbox and add to "display" section
                    let toggleEntry = document.createElement('li');
                    let checkbox = document.createElement("wz-checkbox");
                    checkbox.id = source.id;
                    checkbox.className = "hydrated";
                    checkbox.disabled = !displayGroupSelector.checked;
                    checkbox.checked = source.layer.getVisibility();
                    checkbox.appendChild(document.createTextNode(source.name));

                    toggleEntry.appendChild(checkbox);
                    displayGroup.appendChild(toggleEntry);

                    checkbox.addEventListener('click', (e) => {
                        source.layer.setVisibility(e.target.checked);
                        localStorage[source.unique] = source.layer.getVisibility();
                    });

                    displayGroupSelector.addEventListener('click', (e) => {
                        source.layer.setVisibility(e.target.checked && checkbox.checked);
                        checkbox.disabled = !e.target.checked;
                        localStorage[source.unique] = source.layer.getVisibility();
                    });
                }
            });
        }
    }
}

function geoportal_bootstrap() {
    console.log('a');
    uWaze = unsafeWindow.W;
    uOpenLayers = unsafeWindow.OpenLayers;
    if (typeof (uOpenLayers) === 'undefined' || typeof (uWaze) === 'undefined' || typeof (uWaze.map) === 'undefined' || document.querySelector('.list-unstyled.togglers .group') === null) {
        setTimeout(geoportal_bootstrap, 500);
    } else {
        geoportal_init();
    }
}

geoportal_bootstrap();