diff --git a/css/ol.css b/css/ol.css
index f22a2b9c1b..448735abf7 100644
--- a/css/ol.css
+++ b/css/ol.css
@@ -24,6 +24,19 @@
position: absolute;
border: 2px solid red;
}
+.ol-logo {
+ bottom: 0;
+ left: 0;
+ padding: 2px;
+ position: absolute;
+}
+.ol-logo ul {
+ margin: 0;
+}
+.ol-logo ul li {
+ display: inline;
+ list-style: none;
+}
.ol-scale-line {
background: rgba(0,60,136,0.3);
border-radius: 4px;
diff --git a/src/objectliterals.exports b/src/objectliterals.exports
index 593cae744f..ce9a64c542 100644
--- a/src/objectliterals.exports
+++ b/src/objectliterals.exports
@@ -49,9 +49,15 @@
@exportObjectLiteral ol.control.DefaultsOptions
@exportObjectLiteralProperty ol.control.DefaultsOptions.attribution boolean|undefined
@exportObjectLiteralProperty ol.control.DefaultsOptions.attributionOptions ol.control.AttributionOptions|undefined
+@exportObjectLiteralProperty ol.control.DefaultsOptions.logo boolean|undefined
+@exportObjectLiteralProperty ol.control.DefaultsOptions.logoOptions ol.control.LogoOptions|undefined
@exportObjectLiteralProperty ol.control.DefaultsOptions.zoom boolean|undefined
@exportObjectLiteralProperty ol.control.DefaultsOptions.zoomOptions ol.control.ZoomOptions|undefined
+@exportObjectLiteral ol.control.LogoOptions
+@exportObjectLiteralProperty ol.control.LogoOptions.map ol.Map|undefined
+@exportObjectLiteralProperty ol.control.LogoOptions.target Element|undefined
+
@exportObjectLiteral ol.control.ScaleLineOptions
@exportObjectLiteralProperty ol.control.ScaleLineOptions.map ol.Map|undefined
@exportObjectLiteralProperty ol.control.ScaleLineOptions.minWidth number|undefined
@@ -145,6 +151,7 @@
@exportObjectLiteral ol.source.SourceOptions
@exportObjectLiteralProperty ol.source.SourceOptions.attributions Array.
|undefined
@exportObjectLiteralProperty ol.source.SourceOptions.extent ol.Extent|undefined
+@exportObjectLiteralProperty ol.source.SourceOptions.logo string|undefined
@exportObjectLiteralProperty ol.source.SourceOptions.projection ol.ProjectionLike
@exportObjectLiteral ol.source.StamenOptions
diff --git a/src/ol/control/defaults.js b/src/ol/control/defaults.js
index 90980c182b..95fa4d9f12 100644
--- a/src/ol/control/defaults.js
+++ b/src/ol/control/defaults.js
@@ -2,6 +2,7 @@ goog.provide('ol.control.defaults');
goog.require('goog.array');
goog.require('ol.control.Attribution');
+goog.require('ol.control.Logo');
goog.require('ol.control.Zoom');
@@ -25,6 +26,14 @@ ol.control.defaults = function(opt_options, opt_controls) {
controls.push(new ol.control.Attribution(attributionControlOptions));
}
+ var logoControl = goog.isDef(options.logo) ?
+ options.logo : true;
+ if (logoControl) {
+ var logoControlOptions = goog.isDef(options.logoOptions) ?
+ options.logoOptions : undefined;
+ controls.push(new ol.control.Logo(logoControlOptions));
+ }
+
var zoomControl = goog.isDef(options.zoom) ?
options.zoom : true;
if (zoomControl) {
diff --git a/src/ol/control/logocontrol.js b/src/ol/control/logocontrol.js
new file mode 100644
index 0000000000..69897eaef1
--- /dev/null
+++ b/src/ol/control/logocontrol.js
@@ -0,0 +1,129 @@
+goog.provide('ol.control.Logo');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.events');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('ol.FrameState');
+goog.require('ol.MapEvent');
+goog.require('ol.MapEventType');
+goog.require('ol.control.Control');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.control.Control}
+ * @param {ol.control.LogoOptions=} opt_options Options.
+ */
+ol.control.Logo = function(opt_options) {
+
+ var options = goog.isDef(opt_options) ? opt_options : {};
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL);
+
+ var element = goog.dom.createDom(goog.dom.TagName.DIV, {
+ 'class': 'ol-logo ' + ol.CSS_CLASS_UNSELECTABLE
+ }, this.ulElement_);
+
+ goog.base(this, {
+ element: element,
+ map: options.map,
+ target: options.target
+ });
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.renderedVisible_ = true;
+
+ /**
+ * @private
+ * @type {Object.}
+ */
+ this.logoElements_ = {};
+
+ /**
+ * @private
+ * @type {?number}
+ */
+ this.postrenderListenKey_ = null;
+
+};
+goog.inherits(ol.control.Logo, ol.control.Control);
+
+
+/**
+ * @param {ol.MapEvent} mapEvent Map event.
+ */
+ol.control.Logo.prototype.handleMapPostrender = function(mapEvent) {
+ this.updateElement_(mapEvent.frameState);
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.control.Logo.prototype.setMap = function(map) {
+ if (!goog.isNull(this.postrenderListenKey_)) {
+ goog.events.unlistenByKey(this.postrenderListenKey_);
+ this.postrenderListenKey_ = null;
+ }
+ goog.base(this, 'setMap', map);
+ if (!goog.isNull(map)) {
+ this.postrenderListenKey_ = goog.events.listen(
+ map, ol.MapEventType.POSTRENDER, this.handleMapPostrender, false, this);
+ }
+};
+
+
+/**
+ * @param {?ol.FrameState} frameState Frame state.
+ * @private
+ */
+ol.control.Logo.prototype.updateElement_ = function(frameState) {
+
+ if (goog.isNull(frameState)) {
+ if (this.renderedVisible_) {
+ goog.style.showElement(this.element, false);
+ this.renderedVisible_ = false;
+ }
+ return;
+ }
+
+ var logo;
+ var logos = frameState.logos;
+ var logoElements = this.logoElements_;
+
+ for (logo in logoElements) {
+ if (!(logo in logos)) {
+ goog.dom.removeNode(logoElements[logo]);
+ delete logoElements[logo];
+ }
+ }
+
+ var image, logoElement;
+ for (logo in logos) {
+ if (!(logo in logoElements)) {
+ image = new Image();
+ image.src = logo;
+ logoElement = goog.dom.createElement(goog.dom.TagName.LI);
+ logoElement.appendChild(image);
+ goog.dom.appendChild(this.ulElement_, logoElement);
+ logoElements[logo] = logoElement;
+ }
+ }
+
+ var renderVisible = !goog.object.isEmpty(logos);
+ if (this.renderedVisible_ != renderVisible) {
+ goog.style.showElement(this.element, renderVisible);
+ this.renderedVisible_ = renderVisible;
+ }
+
+};
diff --git a/src/ol/framestate.js b/src/ol/framestate.js
index cffecf36b4..5ab8fdb9a7 100644
--- a/src/ol/framestate.js
+++ b/src/ol/framestate.js
@@ -26,6 +26,7 @@ goog.require('ol.layer.LayerState');
* focus: ol.Coordinate,
* layersArray: Array.,
* layerStates: Object.,
+ * logos: Object.,
* pixelToCoordinateMatrix: goog.vec.Mat4.Number,
* postRenderFunctions: Array.,
* size: ol.Size,
diff --git a/src/ol/map.js b/src/ol/map.js
index 56b0f12355..b2f1a29b9f 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -754,6 +754,7 @@ ol.Map.prototype.renderFrame_ = function(time) {
focus: goog.isNull(this.focus_) ? view2DState.center : this.focus_,
layersArray: layersArray,
layerStates: layerStates,
+ logos: {},
pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_,
postRenderFunctions: [],
size: size,
diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js
index b1ff6ba2ec..deff997d11 100644
--- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js
@@ -115,5 +115,6 @@ ol.renderer.canvas.ImageLayer.prototype.renderFrame =
0);
this.updateAttributions(frameState.attributions, image.getAttributions());
+ this.updateLogos(frameState, imageSource);
}
};
diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js
index 31e1714ee9..e700cba2ee 100644
--- a/src/ol/renderer/canvas/canvastilelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js
@@ -286,6 +286,7 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame =
this.manageTilePyramid(frameState, tileSource, tileGrid, projection, extent,
z, tileLayer.getPreload());
this.scheduleExpireCache(frameState, tileSource);
+ this.updateLogos(frameState, tileSource);
var transform = this.transform_;
goog.vec.Mat4.makeIdentity(transform);
diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js
index 265edd5f03..bc07ae2776 100644
--- a/src/ol/renderer/dom/domimagelayerrenderer.js
+++ b/src/ol/renderer/dom/domimagelayerrenderer.js
@@ -109,6 +109,7 @@ ol.renderer.dom.ImageLayer.prototype.renderFrame =
this.setTransform(transform);
this.updateAttributions(frameState.attributions, image.getAttributions());
+ this.updateLogos(frameState, imageSource);
}
};
diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js
index a953658797..e2cff9f1f2 100644
--- a/src/ol/renderer/dom/domtilelayerrenderer.js
+++ b/src/ol/renderer/dom/domtilelayerrenderer.js
@@ -221,6 +221,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame =
this.manageTilePyramid(frameState, tileSource, tileGrid, projection, extent,
z, tileLayer.getPreload());
this.scheduleExpireCache(frameState, tileSource);
+ this.updateLogos(frameState, tileSource);
};
diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js
index 7a6f4e416f..72b3db2bbd 100644
--- a/src/ol/renderer/layerrenderer.js
+++ b/src/ol/renderer/layerrenderer.js
@@ -14,6 +14,7 @@ goog.require('ol.TileState');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerProperty');
goog.require('ol.layer.LayerState');
+goog.require('ol.source.Source');
goog.require('ol.source.TileSource');
@@ -220,6 +221,19 @@ ol.renderer.Layer.prototype.updateAttributions =
};
+/**
+ * @protected
+ * @param {ol.FrameState} frameState Frame state.
+ * @param {ol.source.Source} source Source.
+ */
+ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
+ var logo = source.getLogo();
+ if (goog.isDef(logo)) {
+ frameState.logos[logo] = true;
+ }
+};
+
+
/**
* @protected
* @param {Object.>} usedTiles Used tiles.
diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js
index 8a9e8b68e1..e4658636ca 100644
--- a/src/ol/renderer/webgl/webglimagelayerrenderer.js
+++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js
@@ -137,6 +137,7 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame =
this.texture = texture;
this.updateAttributions(frameState.attributions, image.getAttributions());
+ this.updateLogos(frameState, imageSource);
}
};
diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js
index da9a405349..2b3b082b3f 100644
--- a/src/ol/renderer/webgl/webgltilelayerrenderer.js
+++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js
@@ -282,6 +282,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
}
}, this);
this.scheduleExpireCache(frameState, tileSource);
+ this.updateLogos(frameState, tileSource);
var texCoordMatrix = this.texCoordMatrix;
goog.vec.Mat4.makeIdentity(texCoordMatrix);
diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js
index 3e28a73bea..a141f9fd4f 100644
--- a/src/ol/source/bingmapssource.js
+++ b/src/ol/source/bingmapssource.js
@@ -147,6 +147,8 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse =
});
this.setAttributions(attributions);
+ this.setLogo(brandLogoUri);
+
this.ready_ = true;
this.dispatchLoadEvent();
diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js
index 8935dc541d..ca0657344f 100644
--- a/src/ol/source/imagesource.js
+++ b/src/ol/source/imagesource.js
@@ -16,6 +16,7 @@ goog.require('ol.source.Source');
* @typedef {{attributions: (Array.|undefined),
* crossOrigin: (null|string|undefined),
* extent: (null|ol.Extent|undefined),
+ * logo: (string|undefined),
* projection: ol.ProjectionLike,
* resolutions: (Array.|undefined),
* imageUrlFunction: (ol.ImageUrlFunctionType|
@@ -36,6 +37,7 @@ ol.source.ImageSource = function(options) {
goog.base(this, {
attributions: options.attributions,
extent: options.extent,
+ logo: options.logo,
projection: options.projection
});
diff --git a/src/ol/source/imagetilesource.js b/src/ol/source/imagetilesource.js
index 9df28a9328..ed8a65b688 100644
--- a/src/ol/source/imagetilesource.js
+++ b/src/ol/source/imagetilesource.js
@@ -19,6 +19,7 @@ goog.require('ol.tilegrid.TileGrid');
* @typedef {{attributions: (Array.|undefined),
* crossOrigin: (null|string|undefined),
* extent: (ol.Extent|undefined),
+ * logo: (string|undefined),
* opaque: (boolean|undefined),
* projection: ol.ProjectionLike,
* tileGrid: (ol.tilegrid.TileGrid|undefined),
@@ -38,6 +39,7 @@ ol.source.ImageTileSource = function(options) {
goog.base(this, {
attributions: options.attributions,
extent: options.extent,
+ logo: options.logo,
opaque: options.opaque,
projection: options.projection,
tileGrid: options.tileGrid
diff --git a/src/ol/source/mapquestsource.js b/src/ol/source/mapquestsource.js
index 43ef01ce50..10827beff2 100644
--- a/src/ol/source/mapquestsource.js
+++ b/src/ol/source/mapquestsource.js
@@ -15,8 +15,7 @@ ol.source.MapQuestOSM = function() {
var attributions = [
new ol.Attribution(
'Tiles Courtesy of ' +
- 'MapQuest ' +
- '
'),
+ 'MapQuest'),
new ol.Attribution(
'Data © ' +
'OpenStreetMap ' +
@@ -27,6 +26,7 @@ ol.source.MapQuestOSM = function() {
goog.base(this, {
attributions: attributions,
crossOrigin: 'anonymous',
+ logo: 'http://developer.mapquest.com/content/osm/mq_logo.png',
opaque: true,
maxZoom: 28,
url: 'http://otile{1-4}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg'
@@ -46,8 +46,7 @@ ol.source.MapQuestOpenAerial = function() {
var attributions = [
new ol.Attribution(
'Tiles Courtesy of ' +
- 'MapQuest ' +
- '
'),
+ 'MapQuest'),
new ol.Attribution(
'Portions Courtesy NASA/JPL-Caltech and ' +
'U.S. Depart. of Agriculture, Farm Service Agency')
@@ -56,6 +55,7 @@ ol.source.MapQuestOpenAerial = function() {
goog.base(this, {
attributions: attributions,
crossOrigin: 'anonymous',
+ logo: 'http://developer.mapquest.com/content/osm/mq_logo.png',
maxZoom: 18,
opaque: true,
url: 'http://oatile{1-4}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg'
diff --git a/src/ol/source/source.js b/src/ol/source/source.js
index 84dcbee6bd..8d4733a742 100644
--- a/src/ol/source/source.js
+++ b/src/ol/source/source.js
@@ -39,6 +39,12 @@ ol.source.Source = function(sourceOptions) {
this.attributions_ = goog.isDef(sourceOptions.attributions) ?
sourceOptions.attributions : null;
+ /**
+ * @private
+ * @type {string|undefined}
+ */
+ this.logo_ = sourceOptions.logo;
+
};
goog.inherits(ol.source.Source, goog.events.EventTarget);
@@ -67,6 +73,14 @@ ol.source.Source.prototype.getExtent = function() {
};
+/**
+ * @return {string|undefined} Logo.
+ */
+ol.source.Source.prototype.getLogo = function() {
+ return this.logo_;
+};
+
+
/**
* @return {ol.Projection} Projection.
*/
@@ -103,6 +117,14 @@ ol.source.Source.prototype.setExtent = function(extent) {
};
+/**
+ * @param {string|undefined} logo Logo.
+ */
+ol.source.Source.prototype.setLogo = function(logo) {
+ this.logo_ = logo;
+};
+
+
/**
* @param {ol.Projection} projection Projetion.
*/
diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js
index 6ef41ebbdb..6680e3bb43 100644
--- a/src/ol/source/tilesource.js
+++ b/src/ol/source/tilesource.js
@@ -14,6 +14,7 @@ goog.require('ol.tilegrid.TileGrid');
/**
* @typedef {{attributions: (Array.|undefined),
* extent: (ol.Extent|undefined),
+ * logo: (string|undefined),
* opaque: (boolean|undefined),
* projection: ol.ProjectionLike,
* tileGrid: (ol.tilegrid.TileGrid|undefined)}}
@@ -32,6 +33,7 @@ ol.source.TileSource = function(tileSourceOptions) {
goog.base(this, {
attributions: tileSourceOptions.attributions,
extent: tileSourceOptions.extent,
+ logo: tileSourceOptions.logo,
projection: tileSourceOptions.projection
});
diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js
index 612e6eadc4..186aaa5344 100644
--- a/src/ol/source/xyzsource.js
+++ b/src/ol/source/xyzsource.js
@@ -19,6 +19,7 @@ goog.require('ol.tilegrid.XYZ');
* @typedef {{attributions: (Array.|undefined),
* crossOrigin: (string|undefined),
* extent: (ol.Extent|undefined),
+ * logo: (string|undefined),
* maxZoom: number,
* projection: (ol.Projection|undefined),
* tileUrlFunction: (ol.TileUrlFunctionType|undefined),
@@ -105,6 +106,7 @@ ol.source.XYZ = function(xyzOptions) {
attributions: xyzOptions.attributions,
crossOrigin: xyzOptions.crossOrigin,
extent: xyzOptions.extent,
+ logo: xyzOptions.logo,
projection: projection,
tileGrid: tileGrid,
tileUrlFunction: tileUrlFunction