"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Views
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.WmtsCapabilities = exports.WmtsCapability = exports.WmtsConstants = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const internal_1 = require("../../../tile/internal");
var OwsConstants;
(function (OwsConstants) {
    OwsConstants["ABSTRACT_XMLTAG"] = "ows:Abstract";
    OwsConstants["ACCESSCONSTRAINTS_XMLTAG"] = "ows:AccessConstraints";
    OwsConstants["ALLOWEDVALUES_XMLTAG"] = "ows:AllowedValues";
    OwsConstants["BOUNDINGBOX_XMLTAG"] = "ows:BoundingBox";
    OwsConstants["CONSTRAINT_XMLTAG"] = "ows:Constraint";
    OwsConstants["DCP_XMLTAG"] = "ows:DCP";
    OwsConstants["FEES_XMLTAG"] = "ows:Fees";
    OwsConstants["GET_XMLTAG"] = "ows:Get";
    OwsConstants["HTTP_XMLTAG"] = "ows:HTTP";
    OwsConstants["IDENTIFIER_XMLTAG"] = "ows:Identifier";
    OwsConstants["KEYWORDS_XMLTAG"] = "ows:Keywords";
    OwsConstants["KEYWORD_XMLTAG"] = "ows:Keyword";
    OwsConstants["LOWERCORNER_XMLTAG"] = "ows:LowerCorner";
    OwsConstants["OPERATION_XMLTAG"] = "ows:Operation";
    OwsConstants["OPERATIONSMETADATA_XMLTAG"] = "ows:OperationsMetadata";
    OwsConstants["POST_XMLTAG"] = "ows:Post";
    OwsConstants["SERVICEIDENTIFICATION_XMLTAG"] = "ows:ServiceIdentification";
    OwsConstants["SERVICETYPE_XMLTAG"] = "ows:ServiceType";
    OwsConstants["SERVICETYPEVERSION_XMLTAG"] = "ows:ServiceTypeVersion";
    OwsConstants["SUPPORTEDCRS_XMLTAG"] = "ows:SupportedCRS";
    OwsConstants["TITLE_XMLTAG"] = "ows:Title";
    OwsConstants["UPPERCORNER_XMLTAG"] = "ows:UpperCorner";
    OwsConstants["VALUE_XMLTAG"] = "ows:Value";
    OwsConstants["WGS84BOUNDINGBOX_XMLTAG"] = "ows:WGS84BoundingBox";
})(OwsConstants || (OwsConstants = {}));
var XmlConstants;
(function (XmlConstants) {
    // Operations names
    XmlConstants["GETCAPABILITIES"] = "GetCapabilities";
    XmlConstants["GETTILE"] = "GetTile";
    XmlConstants["GETFEATUREINFO"] = "GetFeatureInfo";
    XmlConstants["MATRIXWIDTH_XMLTAG"] = "MatrixWidth";
    XmlConstants["MATRIXHEIGHT_XMLTAG"] = "MatrixHeight";
    XmlConstants["SCALEDENOMINATOR_XMLTAG"] = "ScaleDenominator";
    XmlConstants["TILEHEIGHT_XMLTAG"] = "TileHeight";
    XmlConstants["TILEMATRIX_XMLTAG"] = "TileMatrix";
    XmlConstants["TILEMATRIXSETLINK_XMLTAG"] = "TileMatrixSetLink";
    XmlConstants["RESOURCEURL_XMLTAG"] = "ResourceURL";
    XmlConstants["TILEWIDTH_XMLTAG"] = "TileWidth";
    XmlConstants["TOPLEFTCORNER_XMLTAG"] = "TopLeftCorner";
    XmlConstants["WELLKNOWNSCALESET_XMLTAG"] = "WellKnownScaleSet";
    XmlConstants["CONSTRAINT_NAME_FILTER"] = "Encoding";
    XmlConstants["STYLE_ISDEFAULT"] = "IsDefault";
    XmlConstants["XLINK_HREF"] = "xlink:href";
})(XmlConstants || (XmlConstants = {}));
var WmtsConstants;
(function (WmtsConstants) {
    WmtsConstants[WmtsConstants["GOOGLEMAPS_LEVEL0_SCALE_DENOM"] = 559082264.0287178] = "GOOGLEMAPS_LEVEL0_SCALE_DENOM";
    WmtsConstants["GOOGLEMAPS_COMPATIBLE_WELLKNOWNNAME"] = "googlemapscompatible";
})(WmtsConstants || (exports.WmtsConstants = WmtsConstants = {}));
/**
 * Utility function to extract an element' text content
 * @return An element's text content, default to provided defaultTest value if no text is available.
 * @param url server URL to address the request
 */
const getElementTextContent = (elem, qualifiedName, defaultText) => {
    const tmpElem = elem.getElementsByTagName(qualifiedName);
    if (tmpElem.length > 0) {
        return tmpElem[0].textContent ?? defaultText;
    }
    else
        return defaultText;
};
/** Encapsulation of the capabilities for an WMTS server
 */
var WmtsCapability;
(function (WmtsCapability) {
    class ServiceIdentification {
        abstract;
        accessConstraints;
        fees;
        serviceType;
        serviceTypeVersion;
        title;
        keywords;
        constructor(elem) {
            this.abstract = getElementTextContent(elem, OwsConstants.ABSTRACT_XMLTAG);
            this.serviceType = getElementTextContent(elem, OwsConstants.SERVICETYPE_XMLTAG);
            this.serviceTypeVersion = getElementTextContent(elem, OwsConstants.SERVICETYPEVERSION_XMLTAG);
            this.title = getElementTextContent(elem, OwsConstants.TITLE_XMLTAG);
            const keywords = elem.getElementsByTagName(OwsConstants.KEYWORDS_XMLTAG);
            if (keywords.length > 0) {
                const keyword = keywords[0].getElementsByTagName(OwsConstants.KEYWORD_XMLTAG);
                this.keywords = [];
                for (const keyworkElem of keyword) {
                    const keyWordText = keyworkElem.textContent;
                    if (keyWordText)
                        this.keywords.push(keyWordText);
                }
            }
            this.accessConstraints = getElementTextContent(elem, OwsConstants.ACCESSCONSTRAINTS_XMLTAG);
            this.fees = getElementTextContent(elem, OwsConstants.FEES_XMLTAG);
        }
    }
    WmtsCapability.ServiceIdentification = ServiceIdentification;
    class OperationMetadata {
        _getCapabilities;
        get getCapabilities() { return this._getCapabilities; }
        _getFeatureInfo;
        get getFeatureInfo() { return this._getFeatureInfo; }
        _getTile;
        get getTile() { return this._getTile; }
        readOperation(op) {
            const nameAttr = op.attributes.getNamedItem("name");
            if (!nameAttr)
                return;
            if (nameAttr.textContent === XmlConstants.GETCAPABILITIES) {
                this._getCapabilities = new Operation(op);
            }
            else if (nameAttr.textContent === XmlConstants.GETTILE) {
                this._getTile = new Operation(op);
            }
            else if (nameAttr.textContent === XmlConstants.GETFEATUREINFO) {
                this._getFeatureInfo = new Operation(op);
            }
        }
        constructor(elem) {
            const operation = elem.getElementsByTagName(OwsConstants.OPERATION_XMLTAG);
            if (operation.length > 0) {
                for (const op of operation) {
                    this.readOperation(op);
                }
            }
        }
    }
    WmtsCapability.OperationMetadata = OperationMetadata;
    class HttpDcp {
        url;
        constraintName;
        // For simplicity of use we create a 'static' encoding property instead of having
        // a generic constraint data model.
        // We make sure the constraint name is 'encoding' related.
        encoding;
        constructor(elem) {
            const url = elem.getAttribute(XmlConstants.XLINK_HREF);
            if (url)
                this.url = url ?? "";
            const constraint = elem.getElementsByTagName(OwsConstants.CONSTRAINT_XMLTAG);
            if (constraint.length > 0) {
                this.constraintName = constraint[0].getAttribute("name") ?? "";
                if (this.constraintName?.endsWith(XmlConstants.CONSTRAINT_NAME_FILTER)) {
                    const allowedValues = constraint[0].getElementsByTagName(OwsConstants.ALLOWEDVALUES_XMLTAG);
                    if (allowedValues.length > 0) {
                        this.encoding = getElementTextContent(allowedValues[0], OwsConstants.VALUE_XMLTAG);
                    }
                }
            }
        }
    }
    WmtsCapability.HttpDcp = HttpDcp;
    class Operation {
        name;
        _getDcpHttp;
        get getDcpHttp() { return this._getDcpHttp; }
        _postDcpHttp;
        get postDcpHttp() { return this._postDcpHttp; }
        constructor(elem) {
            const name = elem.getAttribute("name");
            if (name)
                this.name = name;
            const dcp = elem.getElementsByTagName(OwsConstants.DCP_XMLTAG);
            if (!dcp || dcp.length === 0)
                return;
            const dcpHttp = dcp[0].getElementsByTagName(OwsConstants.HTTP_XMLTAG);
            if (!dcpHttp || dcpHttp.length === 0)
                return;
            const get = dcpHttp[0].getElementsByTagName(OwsConstants.GET_XMLTAG);
            if (get.length > 0) {
                this._getDcpHttp = [];
                for (const getItem of get) {
                    this._getDcpHttp?.push(new HttpDcp(getItem));
                }
            }
            const post = dcpHttp[0].getElementsByTagName(OwsConstants.POST_XMLTAG);
            if (post.length > 0) {
                this._postDcpHttp = [];
                for (const postItem of post) {
                    this._postDcpHttp?.push(new HttpDcp(postItem));
                }
            }
        }
    }
    WmtsCapability.Operation = Operation;
    class Contents {
        layers = [];
        tileMatrixSets = [];
        constructor(elem) {
            // Layers
            const layer = elem.getElementsByTagName("Layer");
            if (layer) {
                for (const layerElem of layer)
                    this.layers.push(new Layer(layerElem));
            }
            // TileMatrixSet
            const tms = elem.querySelectorAll("Contents > TileMatrixSet");
            if (tms) {
                for (const tmsElem of tms)
                    this.tileMatrixSets.push(new TileMatrixSet(tmsElem));
            }
        }
        getGoogleMapsCompatibleTileMatrixSet() {
            const googleMapsTms = [];
            this.tileMatrixSets.forEach((tms) => {
                if (tms.wellKnownScaleSet?.toLowerCase().includes(WmtsConstants.GOOGLEMAPS_COMPATIBLE_WELLKNOWNNAME))
                    googleMapsTms.push(tms);
                // In case wellKnownScaleSet was not been set properly, infer from scaleDenominator
                // Note: some servers are quite inaccurate in their scale values, hence I used a delta value of 1.
                else if (tms.tileMatrix.length > 0
                    && Math.abs(tms.tileMatrix[0].scaleDenominator - WmtsConstants.GOOGLEMAPS_LEVEL0_SCALE_DENOM) < 1
                    && (tms.supportedCrs.includes("3857") || tms.supportedCrs.includes("900913")))
                    googleMapsTms.push(tms);
            });
            return googleMapsTms;
        }
        getEpsg4326CompatibleTileMatrixSet() {
            return this.tileMatrixSets.filter((tms) => tms.supportedCrs.includes("4326"));
        }
    }
    WmtsCapability.Contents = Contents;
    class Style {
        isDefault = false;
        title;
        identifier;
        // TODO: LegendURL
        constructor(elem) {
            if (!elem)
                return;
            const isDefault = elem.getAttribute("isDefault");
            if (isDefault)
                this.isDefault = isDefault.toLowerCase() === "true";
            this.title = getElementTextContent(elem, OwsConstants.TITLE_XMLTAG);
            this.identifier = getElementTextContent(elem, OwsConstants.IDENTIFIER_XMLTAG);
        }
    }
    WmtsCapability.Style = Style;
    class BoundingBox {
        crs;
        range;
        constructor(elem) {
            this.crs = elem.getAttribute("crs") ?? undefined;
            const lowerCorner = getElementTextContent(elem, OwsConstants.LOWERCORNER_XMLTAG);
            const upperCorner = getElementTextContent(elem, OwsConstants.UPPERCORNER_XMLTAG);
            if (lowerCorner && upperCorner) {
                const lowerCornerArray = lowerCorner?.split(" ").map((x) => +x);
                const upperCornerArray = upperCorner?.split(" ").map((x) => +x);
                if (lowerCornerArray && lowerCornerArray.length === 2 && upperCornerArray && upperCornerArray.length === 2)
                    this.range = core_geometry_1.Range2d.createXYXY(lowerCornerArray[0], lowerCornerArray[1], upperCornerArray[0], upperCornerArray[1]);
            }
        }
    }
    WmtsCapability.BoundingBox = BoundingBox;
    class TileMatrixSetLimits {
        limits;
        tileMatrix;
        constructor(elem) {
            this.tileMatrix = getElementTextContent(elem, "TileMatrix");
            const minTileRow = getElementTextContent(elem, "MinTileRow");
            const maxTileRow = getElementTextContent(elem, "MaxTileRow");
            const minTileCol = getElementTextContent(elem, "MinTileCol");
            const maxTileCol = getElementTextContent(elem, "MaxTileCol");
            if (minTileRow !== undefined && maxTileRow !== undefined && minTileCol !== undefined && maxTileCol)
                this.limits = core_geometry_1.Range2d.createXYXY(Number(minTileCol), Number(minTileRow), Number(maxTileCol), Number(maxTileRow));
        }
    }
    WmtsCapability.TileMatrixSetLimits = TileMatrixSetLimits;
    class TileMatrixSetLink {
        tileMatrixSet;
        tileMatrixSetLimits = new Array();
        constructor(elem) {
            this.tileMatrixSet = (0, core_bentley_1.expectDefined)(getElementTextContent(elem, "TileMatrixSet", ""));
            const tileMatrixLimitsRoot = elem.getElementsByTagName("TileMatrixSetLimits");
            if (tileMatrixLimitsRoot.length > 0) {
                const tileMatrixLimits = tileMatrixLimitsRoot[0].getElementsByTagName("TileMatrixLimits");
                for (const tmsl of tileMatrixLimits) {
                    this.tileMatrixSetLimits.push(new TileMatrixSetLimits(tmsl));
                }
            }
        }
    }
    WmtsCapability.TileMatrixSetLink = TileMatrixSetLink;
    class ResourceURL {
        format;
        resourceType;
        template;
        constructor(elem) {
            this.format = elem.getAttribute("format") ?? "";
            this.resourceType = elem.getAttribute("resourceType") ?? "";
            this.template = elem.getAttribute("template") ?? "";
        }
    }
    WmtsCapability.ResourceURL = ResourceURL;
    class TileMatrixSet {
        identifier;
        title;
        abstract;
        supportedCrs;
        wellKnownScaleSet;
        tileMatrix = [];
        constructor(elem) {
            const identifier = getElementTextContent(elem, OwsConstants.IDENTIFIER_XMLTAG);
            if (identifier)
                this.identifier = identifier;
            else
                throw new Error("No Identifier found.");
            this.title = getElementTextContent(elem, OwsConstants.TITLE_XMLTAG);
            this.abstract = getElementTextContent(elem, OwsConstants.ABSTRACT_XMLTAG);
            const supportedCrs = getElementTextContent(elem, OwsConstants.SUPPORTEDCRS_XMLTAG);
            if (supportedCrs)
                this.supportedCrs = supportedCrs;
            else
                throw new Error("No supported CRS found.");
            this.wellKnownScaleSet = (0, core_bentley_1.expectDefined)(getElementTextContent(elem, XmlConstants.WELLKNOWNSCALESET_XMLTAG, ""));
            // TileMatrix:
            // TileMatrix is mandatory on TileMatrixSet, if it doesn't exists, something is OFF with the capability.
            const tileMatrix = elem.getElementsByTagName(XmlConstants.TILEMATRIX_XMLTAG);
            if (tileMatrix.length === 0)
                throw new Error("No matrix set link found for WMTS layer");
            for (const tm of tileMatrix) {
                this.tileMatrix.push(new TileMatrix(tm));
            }
        }
    }
    WmtsCapability.TileMatrixSet = TileMatrixSet;
    class TileMatrix {
        identifier;
        title;
        abstract;
        scaleDenominator;
        topLeftCorner;
        tileWidth;
        tileHeight;
        matrixWidth;
        matrixHeight;
        constructor(elem) {
            const identifier = getElementTextContent(elem, OwsConstants.IDENTIFIER_XMLTAG, "");
            if (identifier)
                this.identifier = identifier;
            else
                throw new Error("No Identifier found.");
            this.title = getElementTextContent(elem, OwsConstants.TITLE_XMLTAG);
            this.abstract = getElementTextContent(elem, OwsConstants.ABSTRACT_XMLTAG);
            // Scale denominator
            const scaleDenom = getElementTextContent(elem, XmlConstants.SCALEDENOMINATOR_XMLTAG, "");
            if (!scaleDenom)
                throw new Error("No scale denominator found on TileMatrix.");
            this.scaleDenominator = +scaleDenom;
            // Top left corner
            const topLeftCorner = getElementTextContent(elem, XmlConstants.TOPLEFTCORNER_XMLTAG, "")?.split(" ").map((x) => +x);
            if (topLeftCorner?.length !== 2)
                throw new Error("No TopLeftCorner found on TileMatrix.");
            this.topLeftCorner = core_geometry_1.Point2d.create(topLeftCorner[0], topLeftCorner[1]);
            // Tile Width
            const tileWidth = getElementTextContent(elem, XmlConstants.TILEWIDTH_XMLTAG);
            if (!tileWidth)
                throw new Error("No tile width found on TileMatrix.");
            this.tileWidth = +tileWidth;
            // Tile Height
            const tileHeight = getElementTextContent(elem, XmlConstants.TILEHEIGHT_XMLTAG);
            if (!tileHeight)
                throw new Error("No tile height found on TileMatrix.");
            this.tileHeight = +tileHeight;
            // Matrix Width
            const matrixWidth = getElementTextContent(elem, XmlConstants.MATRIXWIDTH_XMLTAG);
            if (!matrixWidth)
                throw new Error("No tile width found on TileMatrix.");
            this.matrixWidth = +matrixWidth;
            // Matrix Height
            const matrixHeight = getElementTextContent(elem, XmlConstants.MATRIXHEIGHT_XMLTAG);
            if (!matrixHeight)
                throw new Error("No tile height found on TileMatrix.");
            this.matrixHeight = +matrixHeight;
        }
    }
    WmtsCapability.TileMatrix = TileMatrix;
    class Layer {
        identifier;
        title;
        abstract;
        format;
        wsg84BoundingBox;
        boundingBox;
        styles = [];
        tileMatrixSetLinks = [];
        resourceUrls = [];
        constructor(elem) {
            const identifier = getElementTextContent(elem, OwsConstants.IDENTIFIER_XMLTAG, "");
            if (identifier)
                this.identifier = identifier;
            else
                throw new Error("No Identifier found.");
            this.title = getElementTextContent(elem, OwsConstants.TITLE_XMLTAG);
            this.format = getElementTextContent(elem, "Format");
            // BoundingBox
            const boundingBox = elem.getElementsByTagName(OwsConstants.BOUNDINGBOX_XMLTAG);
            if (boundingBox.length > 0)
                this.boundingBox = new BoundingBox(boundingBox[0]);
            let lowerCornerArray, upperCornerArray;
            const bbox = elem.getElementsByTagName(OwsConstants.WGS84BOUNDINGBOX_XMLTAG);
            if (bbox.length > 0) {
                lowerCornerArray = getElementTextContent(bbox[0], OwsConstants.LOWERCORNER_XMLTAG)?.split(" ").map((x) => +x);
                upperCornerArray = getElementTextContent(bbox[0], OwsConstants.UPPERCORNER_XMLTAG)?.split(" ").map((x) => +x);
            }
            if (lowerCornerArray?.length === 2 && upperCornerArray?.length === 2)
                this.wsg84BoundingBox = internal_1.MapCartoRectangle.fromDegrees(lowerCornerArray[0], lowerCornerArray[1], upperCornerArray[0], upperCornerArray[1]);
            // If we could not initialized WSG84 bounding box, attempt to initialized it from Bounding Box
            if (!this.wsg84BoundingBox && (this.boundingBox?.crs?.includes("EPSG:4326") || this.boundingBox?.crs?.includes("CRS:84"))) {
                const range = this.boundingBox.range;
                if (range)
                    this.wsg84BoundingBox = internal_1.MapCartoRectangle.fromDegrees(range.low.x, range.low.y, range.high.x, range.high.y);
                else
                    this.wsg84BoundingBox = internal_1.MapCartoRectangle.createMaximum();
            }
            // Style
            const style = elem.getElementsByTagName("Style");
            if (style.length > 0) {
                for (const styleElem of style)
                    this.styles.push(new Style(styleElem));
            }
            // TileMatrixSetLink
            // TileMatrixSetLink is mandatory on Layer, if it doesn't exists, something is OFF with the capability.
            const tileMatrixSetLink = elem.getElementsByTagName(XmlConstants.TILEMATRIXSETLINK_XMLTAG);
            if (tileMatrixSetLink.length === 0)
                throw new Error("No matrix set link found for WMTS layer");
            for (const tmsl of tileMatrixSetLink)
                this.tileMatrixSetLinks.push(new TileMatrixSetLink(tmsl));
            // ResourceURL are optional.  It can be repeated for different resource types.
            const resourceUrls = elem.getElementsByTagName(XmlConstants.RESOURCEURL_XMLTAG);
            for (const url of resourceUrls)
                this.resourceUrls.push(new ResourceURL(url));
        }
    }
    WmtsCapability.Layer = Layer;
})(WmtsCapability || (exports.WmtsCapability = WmtsCapability = {}));
class WmtsCapabilities {
    static _capabilitiesCache = new Map();
    version;
    serviceIdentification;
    contents;
    operationsMetadata;
    constructor(xmlDoc) {
        const capabilities = xmlDoc.getElementsByTagName("Capabilities");
        if (capabilities.length !== 0) {
            const capability = capabilities[0];
            this.version = capability.getAttribute("version") ?? undefined;
            // Service Identification
            const serviceIdentification = capability.getElementsByTagName(OwsConstants.SERVICEIDENTIFICATION_XMLTAG);
            if (serviceIdentification.length > 0)
                this.serviceIdentification = new WmtsCapability.ServiceIdentification(serviceIdentification[0]);
            // Operations metadata
            const operationsMetadata = capability.getElementsByTagName(OwsConstants.OPERATIONSMETADATA_XMLTAG);
            if (operationsMetadata.length > 0)
                this.operationsMetadata = new WmtsCapability.OperationMetadata(operationsMetadata[0]);
            // Contents
            const content = capability.getElementsByTagName("Contents");
            if (content.length > 0)
                this.contents = new WmtsCapability.Contents(content[0]);
        }
    }
    static createFromXml(xmlCapabilities) {
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(xmlCapabilities, "text/xml");
        return new WmtsCapabilities(xmlDoc);
    }
    static async create(url, credentials, ignoreCache, queryParams) {
        if (!ignoreCache) {
            const cached = WmtsCapabilities._capabilitiesCache.get(url);
            if (cached !== undefined)
                return cached;
        }
        const tmpUrl = new URL(internal_1.WmsUtilities.getBaseUrl(url));
        tmpUrl.searchParams.append("request", "GetCapabilities");
        tmpUrl.searchParams.append("service", "WMTS");
        if (queryParams) {
            Object.keys(queryParams).forEach((paramKey) => {
                if (!tmpUrl.searchParams.has(paramKey))
                    tmpUrl.searchParams.append(paramKey, queryParams[paramKey]);
            });
        }
        const xmlCapabilities = await internal_1.WmsUtilities.fetchXml(tmpUrl.toString(), credentials);
        if (!xmlCapabilities)
            return undefined;
        const capabilities = WmtsCapabilities.createFromXml(xmlCapabilities);
        if (capabilities)
            WmtsCapabilities._capabilitiesCache.set(url, capabilities);
        return capabilities;
    }
}
exports.WmtsCapabilities = WmtsCapabilities;
//# sourceMappingURL=WmtsCapabilities.js.map