Source: layer/WmsLayer.js

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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 WmsLayer
 */
define([
        '../error/ArgumentError',
        '../geom/Location',
        '../util/Logger',
        '../util/PeriodicTimeSequence',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../util/WmsUrlBuilder'
    ],
    function (ArgumentError,
              Location,
              Logger,
              PeriodicTimeSequence,
              Sector,
              TiledImageLayer,
              WmsUrlBuilder) {
        "use strict";

        /**
         * Constructs a WMS image layer.
         * @alias WmsLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Displays a WMS image layer.
         * @param {{}} config Specifies configuration information for the layer. Must contain the following
         * properties:
         * <ul>
         *     <li>service: {String} The URL of the WMS server.</li>
         *     <li>layerNames: {String} A comma separated list of the names of the WMS layers to include in this layer.</li>
         *     <li>sector: {Sector} The sector spanned by this layer.</li>
         *     <li>levelZeroDelta: {Location} The level-zero tile delta to use for this layer.</li>
         *     <li>numLevels: {Number} The number of levels to make for this layer.</li>
         *     <li>format: {String} The mime type of the image format to request, e.g., image/png.</li>
         *     <li>size: {Number} The size in pixels of tiles for this layer.</li>
         *     <li>coordinateSystem (optional): {String} The coordinate system to use for this layer, e.g., EPSG:4326.</li>
         *     <li>styleNames (optional): {String} A comma separated list of the styles to include in this layer.</li>
         * </ul>
         * The function [WmsLayer.formLayerConfiguration]{@link WmsLayer#formLayerConfiguration} will create an
         * appropriate configuration object given a {@link WmsLayerCapabilities} object.
         * @param {String} timeString The time parameter passed to the WMS server when imagery is requested. May be
         * null, in which case no time parameter is passed to the server.
         * @throws {ArgumentError} If the specified configuration is null or undefined.
         */
        var WmsLayer = function (config, timeString) {
            if (!config) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "WmsLayer", "constructor", "No configuration specified."));
            }

            var cachePath = config.service + config.layerNames + config.styleNames;
            if (timeString) {
                cachePath = cachePath + timeString;
            }

            TiledImageLayer.call(this, config.sector, config.levelZeroDelta, config.numLevels, config.format,
                cachePath, config.size, config.size);

            this.displayName = config.title;
            this.pickEnabled = false;

            this.urlBuilder = new WmsUrlBuilder(config.service, config.layerNames, config.styleNames, config.version,
                timeString);
            if (config.coordinateSystem) {
                this.urlBuilder.crs = config.coordinateSystem;
            }

            /**
             * The time string passed to this layer's constructor.
             * @type {String}
             * @readonly
             */
            this.timeString = timeString;
        };

        WmsLayer.prototype = Object.create(TiledImageLayer.prototype);

        /**
         * Forms a configuration object for a specified {@link WmsLayerCapabilities} layer description. The
         * configuration object created and returned is suitable for passing to the WmsLayer constructor.
         * <p>
         *     This method also parses any time dimensions associated with the layer and returns them in the
         *     configuration object's "timeSequences" property. This property is a mixed array of Date objects
         *     and {@link PeriodicTimeSequence} objects describing the dimensions found.
         * @param wmsLayerCapabilities {WmsLayerCapabilities} The WMS layer capabilities to create a configuration for.
         * @returns {{}} A configuration object.
         * @throws {ArgumentError} If the specified WMS layer capabilities is null or undefined.
         */
        WmsLayer.formLayerConfiguration = function (wmsLayerCapabilities) {
            var config = {
                title: wmsLayerCapabilities.title,
                version: wmsLayerCapabilities.capability.capsDoc.version
            };

            // Determine the layer's sector.
            var bbox = wmsLayerCapabilities.geographicBoundingBox || wmsLayerCapabilities.latLonBoundingBox;
            if (bbox && bbox.westBoundLongitude) {
                config.sector = new Sector(bbox.southBoundLatitude, bbox.northBoundLatitude,
                    bbox.westBoundLongitude, bbox.eastBoundLongitude);
            } else if (bbox && bbox.minx) {
                config.sector = new Sector(bbox.miny, bbox.maxy, bbox.minx, bbox.maxx);
            } else {
                config.sector = Sector.FULL_SPHERE;
            }

            // Determine level 0 delta.
            config.levelZeroDelta = new Location(36, 36); // TODO: How to determine best delta

            // Determine number of levels.
            config.numLevels = 19; // TODO: How to determine appropriate num levels

            config.size = 256;

            // Assign layer name.
            config.layerNames = wmsLayerCapabilities.name;

            // Determine image format
            var getMapInfo = wmsLayerCapabilities.capability.request.getMap,
                formats = getMapInfo.formats;

            if (formats.indexOf("image/png") >= 0) {
                config.format = "image/png";
            } else if (formats.indexOf("image/jpeg") >= 0) {
                config.format = "image/jpeg";
            } else if (formats.indexOf("image/tiff") >= 0) {
                config.format = "image/tiff";
            } else if (formats.indexOf("image/gif") >= 0) {
                config.format = "image/gif";
            }

            // Determine the GetMap service address.
            config.service = getMapInfo.getUrl;

            // Determine the coordinate system to use.
            var coordinateSystems = wmsLayerCapabilities.crses; // WMS 1.3.0 and greater
            if (!coordinateSystems) {
                coordinateSystems = wmsLayerCapabilities.srses; // WMS 1.1.1 and lower
            }

            if (coordinateSystems) {
                if ((coordinateSystems.indexOf("EPSG:4326") >= 0) || (coordinateSystems.indexOf("epsg:4326") >= 0)) {
                    config.coordinateSystem = "EPSG:4326";
                } else if ((coordinateSystems.indexOf("CRS84") >= 0) || (coordinateSystems.indexOf("CRS:84") >= 0)) {
                    config.coordinateSystem = "CRS:84";
                }
            }

            var dimensions = WmsLayer.parseTimeDimensions(wmsLayerCapabilities);
            if (dimensions && dimensions.length > 0) {
                config.timeSequences = dimensions;
            }

            return config;
        };

        WmsLayer.parseTimeDimensions = function (wmsLayerCapabilities) {
            var dimensions = wmsLayerCapabilities.extents || wmsLayerCapabilities.dimensions,
                parsedDimensions = null;

            if (dimensions) {
                parsedDimensions = [];

                for (var i = 0; i < dimensions.length; i++) {
                    var dimension = dimensions[i];

                    if (dimension.name.toLowerCase() === "time" &&
                        (!dimension.units || dimension.units.toLowerCase() === "iso8601")) {
                        var individualDimensions = dimension.content.split(",");

                        for (var j = 0; j < individualDimensions.length; j++) {
                            var individualDimension = individualDimensions[j],
                                splitDimension = individualDimension.split("/");

                            if (splitDimension.length === 1) {
                                parsedDimensions.push(new Date(individualDimension));
                            } else if (splitDimension.length === 3) {
                                parsedDimensions.push(new PeriodicTimeSequence(individualDimension));
                            }
                        }
                    }
                }
            }

            return parsedDimensions;
        };

        return WmsLayer;
    });