Source: ogc/wcs/WcsCoverageDescriptions.js

/*
 * Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @exports WcsCoverageDescriptions
 */
define([
        '../../error/ArgumentError',
        '../../ogc/gml/GmlBoundedBy',
        '../../ogc/gml/GmlDomainSet',
        '../../ogc/gml/GmlRectifiedGrid',
        '../../util/Logger',
        '../../ogc/ows/OwsKeywords',
        '../../geom/Sector'
    ],
    function (ArgumentError,
              GmlBoundedBy,
              GmlDomainSet,
              GmlRectifiedGrid,
              Logger,
              OwsKeywords,
              Sector) {
        "use strict";

        /**
         * Constructs a simple javascript object representation of an OGC WCS Describe Coverage XML response.
         * @alias WcsCoverageDescriptions
         * @constructor
         * @classdesc Represents the common properties of a WCS CoverageDescription document. Common properties are
         * parsed and mapped to a plain javascript object model. Most fields can be accessed as properties named
         * according to their document names converted to camel case. This model supports version 1.0.0 and 2.0.x of the
         * WCS specification. Not all properties are mapped to this representative javascript object model, but the
         * provided XML DOM is maintained in xmlDom property for reference.
         * @param {{}} xmlDom an XML DOM representing the WCS DescribeCoverage document.
         * @throws {ArgumentError} If the specified XML DOM is null or undefined.
         */
        var WcsCoverageDescriptions = function (xmlDom) {
            if (!xmlDom) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsCoverageDescriptions", "constructor", "missingDom"));
            }

            /**
             * The original unmodified XML document. Referenced for use in advanced cases.
             * @type {{}}
             */
            this.xmlDom = xmlDom;

            this.assembleDocument();
        };

        /**
         * Get the bounding Sector for the provided coverage id or name.
         * @param coverageId the coverageId or name
         * @returns {Sector} the bounding Sector
         */
        WcsCoverageDescriptions.prototype.getSector = function (coverageId) {
            if (!coverageId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsCoverageDescriptions", "getSector", "missingId"));
            }
            var coverage = this.getCoverage(coverageId), envelope;

            if (!coverage) {
                return null;
            }

            if (this.version === "1.0.0") {
                envelope = coverage.lonLatEnvelope.pos;
                return new Sector(
                    envelope[0][1],
                    envelope[1][1],
                    envelope[0][0],
                    envelope[1][0]);
            } else if (this.version === "2.0.1" || this.version === "2.0.0") {
                envelope = coverage.boundedBy.envelope;
                return new Sector(
                    envelope.lower[0],
                    envelope.upper[0],
                    envelope.lower[1],
                    envelope.upper[1]);
            }

            return null;
        };

        /**
         * Calculates the resolution of the provided coverage id in degrees.
         * @param coverageId the coverage id or name
         * @returns {number} resolution in degrees
         */
        WcsCoverageDescriptions.prototype.getResolution = function (coverageId) {
            if (!coverageId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsCoverageDescriptions", "getResolution", "missingId"));
            }
            var coverage = this.getCoverage(coverageId), sector = this.getSector(coverageId), xLow, yLow, xHigh, yHigh,
                xRes, yRes;

            if (!coverage) {
                return null;
            }

            if (this.version === "1.0.0") {
                xLow = coverage.domainSet.spatialDomain.rectifiedGrid.limits.low[0];
                yLow = coverage.domainSet.spatialDomain.rectifiedGrid.limits.low[1];
                xHigh = coverage.domainSet.spatialDomain.rectifiedGrid.limits.high[0];
                yHigh = coverage.domainSet.spatialDomain.rectifiedGrid.limits.high[1];
            } else if (this.version === "2.0.1" || this.version === "2.0.0") {
                xLow = coverage.domainSet.rectifiedGrid.limits.low[0];
                yLow = coverage.domainSet.rectifiedGrid.limits.low[1];
                xHigh = coverage.domainSet.rectifiedGrid.limits.high[0];
                yHigh = coverage.domainSet.rectifiedGrid.limits.high[1];
            }

            xRes = sector.deltaLongitude() / (xHigh - xLow);
            yRes = sector.deltaLatitude() / (yHigh - yLow);

            return Math.max(xRes, yRes);
        };

        /**
         * Returns an array of the supported coordinates reference systems for the provided coverage.
         * @param coverageId the coverage id or name
         */
        WcsCoverageDescriptions.prototype.getSupportedCrs = function (coverageId) {
            if (!coverageId) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsCoverageDescriptions", "getSupportedCrs", "missingId"));
            }
            var coverage = this.getCoverage(coverageId), crses = [];

            if (!coverage) {
                return null;
            }

            if (this.version === "1.0.0") {
                return coverage.supportedCrs.requests;
            } else if (this.version === "2.0.1" || this.version === "2.0.0") {
                crses.push(coverage.boundedBy.envelope.srsName);
                return crses;
            }

            return null;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.getCoverage = function (coverageId) {
            for (var i = 0, len = this.coverages.length; i < len; i++) {
                if (coverageId === (this.coverages[i].coverageId || this.coverages[i].name)) {
                    return this.coverages[i];
                }
            }

            return null;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleDocument = function () {
            // Determine version and update sequence
            var root = this.xmlDom.documentElement;

            if (root.localName === "CoverageDescription") {
                this.assembleDocument100(root);
                this.version = "1.0.0";
            } else if (root.localName === "CoverageDescriptions") {
                this.assembleDocument20x(root);
                this.version = root.getAttribute("version") || "2.0.1"; // work around for geoserver bug
            } else {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WcsCapabilities", "assembleDocument", "unsupportedVersion"));
            }
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleDocument100 = function (element) {
            this.version = element.getAttribute("version");

            var children = element.children || element.childNodes;

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "CoverageOffering") {
                    this.coverages = this.coverages || [];
                    this.coverages.push(this.assembleCoverages100(child));
                }
            }
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleDocument20x = function (element) {
            var children = element.children || element.childNodes;

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "CoverageDescription") {
                    this.coverages = this.coverages || [];
                    this.coverages.push(this.assembleCoverages20x(child));
                }
            }
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleCoverages100 = function (element) {
            var children = element.children || element.childNodes, coverage = {};
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "name") {
                    coverage.name = child.textContent;
                } else if (child.localName === "description") {
                    coverage.description = child.textContent;
                } else if (child.localName === "label") {
                    coverage.label = child.textContent;
                } else if (child.localName === "keywords") {
                    // the OWS keywords namespace isn't used but the format is similar
                    coverage.keywords = new OwsKeywords(child).keywords;
                } else if (child.localName === "lonLatEnvelope") {
                    coverage.lonLatEnvelope = this.assembleLonLatEnvelope100(child);
                } else if (child.localName === "supportedCRSs") {
                    coverage.supportedCrs = this.assembleSupportedCrs100(child);
                } else if (child.localName === "supportedFormats") {
                    coverage.supportedFormats = this.assembleSupportedFormats100(child);
                } else if (child.localName === "supportedInterpolations") {
                    coverage.supportedInterpolations = this.assembleSupportedInterpolations100(child);
                } else if (child.localName === "domainSet") {
                    coverage.domainSet = this.assembleDomainSet100(child);
                } else if (child.localName === "rangeSet") {
                    coverage.rangeSet = this.assembleRangeSet100(child);
                }
            }

            return coverage;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleCoverages20x = function (element) {
            var children = element.children || element.childNodes, coverage = {};
            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "CoverageId") {
                    coverage.coverageId = child.textContent;
                } else if (child.localName === "domainSet") {
                    coverage.domainSet = new GmlDomainSet(child);
                } else if (child.localName === "boundedBy") {
                    coverage.boundedBy = new GmlBoundedBy(child);
                } else if (child.localName === "ServiceParameters") {
                    coverage.serviceParameters = this.assembleServiceParameters20x(child);
                } else if (child.localName === "rangeType") {
                    // The information from rangeType is not required for forming a request. Instead of implementing a
                    // complex parser for the SWE DataRecord, a reference to the particular dom element will be provided
                    coverage.rangeType = child;
                }
            }

            return coverage;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleServiceParameters20x = function (element) {
            var children = element.children || element.childNodes, serviceParameters = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "nativeFormat") {
                    serviceParameters.nativeFormat = child.textContent;
                } else if (child.localName === "CoverageSubtype") {
                    serviceParameters.coverageSubtype = child.textContent;
                }
                // TODO CoverageSubtypeParent, Extension
            }

            return serviceParameters;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleLonLatEnvelope100 = function (element) {
            var children = element.children || element.childNodes, latLonEnvelope = {};

            latLonEnvelope.srsName = element.getAttribute("srsName");

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "pos") {
                    latLonEnvelope.pos = latLonEnvelope.pos || [];
                    latLonEnvelope.pos.push(WcsCoverageDescriptions.parseSpacedFloatArray(child.textContent));
                }
            }

            return latLonEnvelope;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleSupportedCrs100 = function (element) {
            var children = element.children || element.childNodes, supportedCrs = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "requestResponseCRSs") {
                    supportedCrs.requests = supportedCrs.requests || [];
                    supportedCrs.requests.push(child.textContent);
                    supportedCrs.responses = supportedCrs.responses || [];
                    supportedCrs.responses.push(child.textContent);
                } else if (child.localName === "requestCRSs") {
                    supportedCrs.requests = supportedCrs.requests || [];
                    supportedCrs.push(child.textContent);
                } else if (child.localName === "responseCRSs") {
                    supportedCrs.responses = supportedCrs.responses || [];
                    supportedCrs.push(child.textContent);
                } else if (child.localName === "NativeCRSs") {
                    supportedCrs.nativeCrs = supportedCrs.nativeCrs || [];
                    supportedCrs.push(child.textContent);
                }
            }

            return supportedCrs;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleSupportedFormats100 = function (element) {
            var children = element.children || element.childNodes, supportedFormats = {};

            supportedFormats.nativeFormat = element.getAttribute("nativeFormat");

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "formats") {
                    supportedFormats.formats = supportedFormats.formats || [];
                    supportedFormats.formats.push(child.textContent);
                }
            }

            return supportedFormats;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleSupportedInterpolations100 = function (element) {
            var children = element.children || element.childNodes, supportedInterpolations = {};

            supportedInterpolations.default = element.getAttribute("default");

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "interpolationMethod") {
                    supportedInterpolations.methods = supportedInterpolations.methods || [];
                    supportedInterpolations.methods.push(child.textContent);
                }
            }

            return supportedInterpolations;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleDomainSet100 = function (element) {
            var children = element.children || element.childNodes, domainSet = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "spatialDomain") {
                    domainSet.spatialDomain = this.assembleSpatialDomain100(child);
                }
            }

            return domainSet;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleSpatialDomain100 = function (element) {
            var children = element.children || element.childNodes, spatialDomain = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "Envelope") {
                    spatialDomain.envelope = this.assembleLonLatEnvelope100(child);
                } else if (child.localName === "RectifiedGrid") {
                    spatialDomain.rectifiedGrid = new GmlRectifiedGrid(child);
                }
            }

            return spatialDomain;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleRangeSet100 = function (element) {
            var children = element.children || element.childNodes, rangeSet = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "RangeSet") {
                    // Jump into the first similarly named element
                    return this.assembleRangeSetElement100(child);
                }
            }
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleRangeSetElement100 = function (element) {
            var children = element.children || element.childNodes, rangeSet = {};

            rangeSet.semantic = element.getAttribute("semantic");
            rangeSet.refSys = element.getAttribute("refSys");
            rangeSet.refSysLable = element.getAttribute("refSysLabel");

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "name") {
                    rangeSet.name = child.textContent;
                } else if (child.localName === "label") {
                    rangeSet.label = child.textContent;
                } else if (child.localName === "axisDescription") {
                    rangeSet.axisDescriptions = this.assembleRangeSetAxisDescription100(child);
                }
            }

            return rangeSet;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleRangeSetAxisDescription100 = function (element) {
            var children = element.children || element.childNodes, axisDescriptions = [];

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "AxisDescription") {
                    axisDescriptions.push(this.assembleRangeSetAxisDescriptionElement100(child));
                }
            }

            return axisDescriptions;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleRangeSetAxisDescriptionElement100 = function (element) {
            var children = element.children || element.childNodes, axisDescription = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "name") {
                    axisDescription.name = child.textContent;
                } else if (child.localName === "label") {
                    axisDescription.label = child.textContent;
                } else if (child.localName === "values") {
                    axisDescription.values = this.assembleRangeSetValues100(child);
                }
            }

            return axisDescription;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.prototype.assembleRangeSetValues100 = function (element) {
            var children = element.children || element.childNodes, values = {};

            for (var c = 0; c < children.length; c++) {
                var child = children[c];

                if (child.localName === "singleValue") {
                    values.singleValue = child.textContent;
                }
                // TODO intervals value type
            }

            return values;
        };

        // Internal. Intentionally not documented.
        WcsCoverageDescriptions.parseSpacedFloatArray = function (line) {
            var result = [], elements = line.split(/\s+/);

            for (var i = 0; i < elements.length; i++) {
                result.push(parseFloat(elements[i]));
            }

            return result;
        };

        return WcsCoverageDescriptions;
    });