# Source: src/ol/source/bingmapssource.js

    goog.provide('ol.source.BingMaps');

    goog.require('goog.Uri');
    goog.require('goog.array');
    goog.require('goog.asserts');
    goog.require('goog.net.Jsonp');
    goog.require('ol');
    goog.require('ol.Attribution');
    goog.require('ol.TileRange');
    goog.require('ol.TileUrlFunction');
    goog.require('ol.extent');
    goog.require('ol.proj');
    goog.require('ol.source.State');
    goog.require('ol.source.TileImage');
    goog.require('ol.tilegrid.XYZ');

    /**
     * @classdesc
     * Layer source for Bing Maps tile data.
     *
     * @constructor
     * @extends {ol.source.TileImage}
     * @param {olx.source.BingMapsOptions} options Bing Maps options.
     * @todo api
     */
    ol.source.BingMaps = function(options) {

      goog.base(this, {
        crossOrigin: 'anonymous',
        opaque: true,
        projection: ol.proj.get('EPSG:3857'),
        state: ol.source.State.LOADING,
        tileLoadFunction: options.tileLoadFunction
      });

      /**
       * @private
       * @type {string}
       */
      this.culture_ = goog.isDef(options.culture) ? options.culture : 'en-us';

      var protocol = ol.IS_HTTPS ? 'https:' : 'http:';
      var uri = new goog.Uri(
          protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
          options.imagerySet);

      var jsonp = new goog.net.Jsonp(uri, 'jsonp');
      jsonp.send({
        'include': 'ImageryProviders',
        'key': options.key
      }, goog.bind(this.handleImageryMetadataResponse, this));

    };
    goog.inherits(ol.source.BingMaps, ol.source.TileImage);

    /**
     * @const
     * @type {ol.Attribution}
     * @todo api
     */
    ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({
      html: '<a class="ol-attribution-bing-tos" target="_blank" ' +
          'href="http://www.microsoft.com/maps/product/terms.html">' +
          'Terms of Use</a>'
    });

    /**
     * @param {BingMapsImageryMetadataResponse} response Response.
     */
    ol.source.BingMaps.prototype.handleImageryMetadataResponse =
        function(response) {

      if (response.statusCode != 200 ||
          response.statusDescription != 'OK' ||
          response.authenticationResultCode != 'ValidCredentials' ||
          response.resourceSets.length != 1 ||
          response.resourceSets[0].resources.length != 1) {
        this.setState(ol.source.State.ERROR);
        return;
      }

      var brandLogoUri = response.brandLogoUri;
      //var copyright = response.copyright;  // FIXME do we need to display this?
      var resource = response.resourceSets[0].resources[0];

      goog.asserts.assert(resource.imageWidth == resource.imageHeight);
      var tileGrid = new ol.tilegrid.XYZ({
        minZoom: resource.zoomMin,
        maxZoom: resource.zoomMax,
        tileSize: resource.imageWidth
      });
      this.tileGrid = tileGrid;

      var culture = this.culture_;
      var sourceProjection = this.getProjection();
      this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
          tileGrid.createTileCoordTransform(),
          ol.TileUrlFunction.createFromTileUrlFunctions(
              goog.array.map(
                  resource.imageUrlSubdomains,
                  function(subdomain) {
                    var imageUrl = resource.imageUrl
                        .replace('{subdomain}', subdomain)
                        .replace('{culture}', culture);
                    return (
                        /**
                         * @param {ol.TileCoord} tileCoord Tile coordinate.
                         * @param {number} pixelRatio Pixel ratio.
                         * @param {ol.proj.Projection} projection Projection.
                         * @return {string|undefined} Tile URL.
                         */
                        function(tileCoord, pixelRatio, projection) {
                          goog.asserts.assert(ol.proj.equivalent(
                              projection, sourceProjection));
                          if (goog.isNull(tileCoord)) {
                            return undefined;
                          } else {
                            return imageUrl.replace(
                                '{quadkey}', tileCoord.quadKey());
                          }
                        });
                  })));

      if (resource.imageryProviders) {
        var transform = ol.proj.getTransformFromProjections(
            ol.proj.get('EPSG:4326'), this.getProjection());

        var attributions = goog.array.map(
            resource.imageryProviders,
            function(imageryProvider) {
              var html = imageryProvider.attribution;
              /** @type {Object.<string, Array.<ol.TileRange>>} */
              var tileRanges = {};
              goog.array.forEach(
                  imageryProvider.coverageAreas,
                  function(coverageArea) {
                    var minZ = coverageArea.zoomMin;
                    var maxZ = coverageArea.zoomMax;
                    var bbox = coverageArea.bbox;
                    var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]];
                    var extent = ol.extent.applyTransform(
                        epsg4326Extent, transform);
                    var tileRange, z, zKey;
                    for (z = minZ; z <= maxZ; ++z) {
                      zKey = z.toString();
                      tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
                      if (zKey in tileRanges) {
                        tileRanges[zKey].push(tileRange);
                      } else {
                        tileRanges[zKey] = [tileRange];
                      }
                    }
                  });
              return new ol.Attribution({html: html, tileRanges: tileRanges});
            });
        attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
        this.setAttributions(attributions);
      }

      this.setLogo(brandLogoUri);

      this.setState(ol.source.State.READY);

    };