From 576b210657c9b41a92d1b768701a4f7b42b0fce0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 7 Jun 2011 23:49:35 +0000 Subject: [PATCH] making attribution as Terms of Service compliant as we possibly can. p=bartvde (closes #3330) git-svn-id: http://svn.openlayers.org/trunk/openlayers@12070 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/google-ng.html | 1 + lib/OpenLayers/Layer/GoogleNG.js | 172 +++++++++++++++++++++++-------- lib/OpenLayers/Tile/Google.js | 8 +- tests/Layer/GoogleNG.html | 29 ++++-- theme/default/style.css | 4 +- 5 files changed, 157 insertions(+), 57 deletions(-) diff --git a/examples/google-ng.html b/examples/google-ng.html index 2aa9049f7d..79a1260660 100644 --- a/examples/google-ng.html +++ b/examples/google-ng.html @@ -14,6 +14,7 @@ right: inherit; bottom: 3px; line-height: 11px; + font-family: Arial, sans-serif; } diff --git a/lib/OpenLayers/Layer/GoogleNG.js b/lib/OpenLayers/Layer/GoogleNG.js index 1d85396acb..e5192a386c 100644 --- a/lib/OpenLayers/Layer/GoogleNG.js +++ b/lib/OpenLayers/Layer/GoogleNG.js @@ -10,10 +10,7 @@ /** * Class: OpenLayers.Layer.GoogleNG - * Google layer using tiles. Note: Terms of Service - * compliant use requires the map to be configured with an - * control and the attribution placed on or - * near the map. + * Google layer using tiles. * * Inherits from: * - @@ -32,13 +29,11 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { * {String} */ attributionTemplate: '' + - '
' + - '
' + - 'Map data - ${mapData}' + 'Terms of Use
', @@ -54,13 +49,6 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { "terrain": "p" }, - /** - * Property: mapObject - * {google.maps.Map} Shared GMaps instance - will be set on the prototype - * upon instantiation of the 1st GoogleNG layer - */ - mapObject: null, - /** * APIProperty: type * {google.maps.MapTypeId} See @@ -71,7 +59,10 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { /** * Constructor: OpenLayers.Layer.GoogleNG * Create a new GoogleNG layer. Requires the GMaps v3 JavaScript API script - * included in the html document. + * (http://maps.google.com/maps/api/js?v=3.5&sensor=false) loaded in + * the html document. Note: Terms of Service compliant use requires the map + * to be configured with an control and + * the attribution placed on the map. * * Example: * (code) @@ -103,15 +94,15 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs); this.options.numZoomLevels = options.numZoomLevels; - if (!this.mapObject) { - OpenLayers.Layer.GoogleNG.prototype.mapObject = + if (!OpenLayers.Layer.GoogleNG.mapObject) { + OpenLayers.Layer.GoogleNG.mapObject = new google.maps.Map(document.createElement("div")); } - if (this.mapObject.mapTypes[this.type]) { + if (OpenLayers.Layer.GoogleNG.mapObject.mapTypes[this.type]) { this.initLayer(); } else { google.maps.event.addListenerOnce( - this.mapObject, + OpenLayers.Layer.GoogleNG.mapObject, "idle", OpenLayers.Function.bind(this.initLayer, this) ); @@ -124,7 +115,7 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { * Sets layer properties according to the metadata provided by the API */ initLayer: function() { - var mapType = this.mapObject.mapTypes[this.type]; + var mapType = OpenLayers.Layer.GoogleNG.mapObject.mapTypes[this.type]; if (!this.name) { this.setName("Google " + mapType.name); } @@ -171,8 +162,31 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { /** * Method: updateAttribution * Updates the attribution using the + * + * Parameters: + * copyrights - {Object} Object with "m", "k", "h" and "p" properties (see + * ), each holding an array of copyrights. */ - updateAttribution: function() { + updateAttribution: function(copyrights) { + var myCopyrights; + if (this.type == google.maps.MapTypeId.HYBRID) { + // the Copyright Service returns "k" and "m" copyrights for the + // HYBRID layer type. + var candidates = [].concat( + copyrights["h"], copyrights["k"], copyrights["m"] + ); + myCopyrights = []; + for (var i=candidates.length-1; i>=0; --i) { + if (OpenLayers.Util.indexOf(candidates, myCopyrights) == -1) { + myCopyrights.push(candidates[i]); + } + } + } else { + myCopyrights = copyrights[this.mapTypes[this.type]]; + } + var mapData = myCopyrights.length == 0 ? "" : + "Map Data ©" + new Date().getFullYear() + " " + + myCopyrights.join(", ") + " - "; var center = this.map.getCenter(); center && center.transform( this.map.getProjectionObject(), @@ -180,23 +194,23 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { ); var size = this.map.getSize(); this.attribution = OpenLayers.String.format(this.attributionTemplate, { - center: center ? center.lat + "," + center.lon : "", - zoom: this.map.getZoom(), - size: size.w + "x" + size.h, t: this.mapTypes[this.type], - mapType: this.type + zoom: this.map.getZoom(), + center: center.lat + "," + center.lon, + mapType: this.type, + mapData: mapData }); this.map && this.map.events.triggerEvent("changelayer", {layer: this}); }, - + /** * Method: setMap */ setMap: function() { OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments); - - this.updateAttribution(); - this.map.events.register("moveend", this, this.updateAttribution); + this.events.register("moveend", this, + OpenLayers.Layer.GoogleNG.loadCopyrights + ); }, /** @@ -204,7 +218,9 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { */ removeMap: function() { OpenLayers.Layer.XYZ.prototype.removeMap.apply(this, arguments); - this.map.events.unregister("moveend", this, this.updateAttribution); + this.events.unregister("moveend", this, + OpenLayers.Layer.GoogleNG.loadCopyrights + ); }, /** @@ -227,14 +243,86 @@ OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { return obj; }, - /** - * Method: destroy - */ - destroy: function() { - this.map && - this.map.events.unregister("moveend", this, this.updateAttribution); - OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments); - }, - CLASS_NAME: "OpenLayers.Layer.GoogleNG" -}); \ No newline at end of file +}); + +/** + * Property: mapObject + * {google.maps.Map} Shared GMaps instance - will be set upon instantiation of + * the 1st GoogleNG layer + */ +OpenLayers.Layer.GoogleNG.mapObject = null; + +/** + * Function: loadCopyrights + * Using the Google Maps Copyright Service mode (see + * http://mapki.com/wiki/Google_Map_Parameters#Misc) to get the attribution for + * the current map extent. Will be called by each GoogleNG layer instance on + * moveend. + */ +OpenLayers.Layer.GoogleNG.loadCopyrights = function() { + var me = OpenLayers.Layer.GoogleNG.loadCopyrights; + if (me.numLoadingScripts == undefined) { + me.loadingScripts = []; + me.numLoadingScripts = 0; + me.copyrights = {"m": [], "k": [], "h": [], "p": []}; + + // store window scope functions before overwriting them + me.origGAddCopyright = window.GAddCopyright; + me.origGVerify = window.GVerify; + me.origGAppFeatures = window.GAppFeatures; + + // defining window scope functions called by the script that the + // Copyright Service returns + window.GAddCopyright = function() { + var copyright = arguments[7]; + var category = me.copyrights[arguments[0]]; + if (OpenLayers.Util.indexOf(category, copyright) == -1) { + copyright && category.push(copyright); + } + }; + window.GVerify = OpenLayers.Function.True; + window.GAppFeatures = OpenLayers.Function.bind(function() { + me.numLoadingScripts--; + if (me.numLoadingScripts == 0) { + var script; + for (var i=me.loadingScripts.length-1; i>=0; --i) { + script = me.loadingScripts[i][0]; + me.loadingScripts[i][1].updateAttribution(me.copyrights); + script.parentNode.removeChild(script); + } + + // restore original functions + window.GAddCopyright = me.origGAddCopyright; + delete me.origGAddCopyright; + window.GVerify = me.origGVerify; + delete me.origGVerify; + window.GAppFeatures = me.origGAppFeatures; + delete me.origGAppFeatures; + + delete me.loadingScripts; + delete me.numLoadingScripts; + delete me.copyrights; + } + }, this); + } + var mapProj = this.map.getProjectionObject(); + var llProj = new OpenLayers.Projection("EPSG:4326"); + var center = this.map.getCenter().transform(mapProj, llProj); + var extent = this.map.getExtent().transform(mapProj, llProj); + var params = { + spn: extent.getHeight() + "," + extent.getWidth(), + z: this.map.getZoom(), + t: this.mapTypes[this.type], + vp: center.lat + "," + center.lon + }; + var url = "http://maps.google.com/maps?" + + OpenLayers.Util.getParameterString(params); + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + me.numLoadingScripts++; + me.loadingScripts.push([script, this]); + document.getElementsByTagName("head")[0].appendChild(script); +}; + diff --git a/lib/OpenLayers/Tile/Google.js b/lib/OpenLayers/Tile/Google.js index 11686734a6..c5fc651baa 100644 --- a/lib/OpenLayers/Tile/Google.js +++ b/lib/OpenLayers/Tile/Google.js @@ -86,7 +86,9 @@ OpenLayers.Tile.Google = OpenLayers.Class(OpenLayers.Tile, { * {Boolean} Always returns true. */ draw: function() { - var layerType = this.layer.mapObject.mapTypes[this.layer.type]; + var layerType = OpenLayers.Layer.GoogleNG.mapObject.mapTypes[ + this.layer.type + ]; if (layerType && OpenLayers.Tile.prototype.draw.apply(this, arguments)) { var xyz = this.layer.getXYZ(this.bounds); var point = new google.maps.Point(xyz.x, xyz.y); @@ -143,7 +145,9 @@ OpenLayers.Tile.Google = OpenLayers.Class(OpenLayers.Tile, { if (this.node) { this.node.parentNode && this.node.parentNode.removeChild(this.node); - this.layer.mapObject.mapTypes[this.layer.type].releaseTile(this.node); + OpenLayers.Layer.GoogleNG.mapObject.mapTypes[ + this.layer.type + ].releaseTile(this.node); } }, diff --git a/tests/Layer/GoogleNG.html b/tests/Layer/GoogleNG.html index a302450440..946bc9e0d9 100644 --- a/tests/Layer/GoogleNG.html +++ b/tests/Layer/GoogleNG.html @@ -34,11 +34,11 @@ numZoomLevels: 24, initLayer: function() { log[layer2.id] = true; - var origMinZoom = layer2.mapObject.mapTypes[layer2.type].minZoom; + var origMinZoom = OpenLayers.Layer.GoogleNG.mapObject.mapTypes[layer2.type].minZoom; // pretend the API reports a minZoom of 1 - layer2.mapObject.mapTypes[layer2.type].minZoom = 1; + OpenLayers.Layer.GoogleNG.mapObject.mapTypes[layer2.type].minZoom = 1; OpenLayers.Layer.GoogleNG.prototype.initLayer.apply(this, arguments); - layer2.mapObject.mapTypes[layer2.type].minZoom = origMinZoom; + OpenLayers.Layer.GoogleNG.mapObject.mapTypes[layer2.type].minZoom = origMinZoom; } }); map2.addLayer(layer2); @@ -49,7 +49,7 @@ t.eq(log[layer2.id], true, "initLayer called for 2nd layer"); t.eq(layer.numZoomLevels, 10, "numZoomLevels from configuration takes precedence if lower"); - t.eq(layer2.numZoomLevels, layer2.mapObject.mapTypes[layer2.type].maxZoom+1, "numZoomLevels from API takes precedence if lower"); + t.eq(layer2.numZoomLevels, OpenLayers.Layer.GoogleNG.mapObject.mapTypes[layer2.type].maxZoom+1, "numZoomLevels from API takes precedence if lower"); t.eq(layer.restrictedMinZoom, 2, "restrictedMinZoom from configuration takes precedence if higher"); t.eq(layer2.restrictedMinZoom, 1, "restrictedMinZoom from API takes precedence if higher"); @@ -60,18 +60,25 @@ } function test_attribution(t) { - t.plan(3); + t.plan(4); var log = []; map = new OpenLayers.Map("map"); - layer = new OpenLayers.Layer.GoogleNG({type: google.maps.MapTypeId.HYBRID}); + layer = new OpenLayers.Layer.GoogleNG({ + type: google.maps.MapTypeId.HYBRID, + updateAttribution: function(copyrights) { + log.push(copyrights); + OpenLayers.Layer.GoogleNG.prototype.updateAttribution.apply(this, arguments); + } + }); map.addLayer(layer); - map.zoomToMaxExtent(); + map.setCenter(new OpenLayers.LonLat(16, 48).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject()), 5); - t.delay_call(1, function() { - t.ok(layer.attribution.indexOf('olGoogleAttribution hybrid') !== -1, "Attribution has the correct css class"); - t.ok(layer.attribution.indexOf('?ll=0,0&z=0&t=h"') != -1, "maps.google.com link has correct parameters"); - t.ok(layer.attribution.indexOf('¢er=0,0&zoom=0&size=500x550&maptype=hybrid') != -1 , "Attribution has correct map data link"); + t.delay_call(3, function() { + t.eq(log.length, 1, "updateAttribution was called once"); + t.ok(log[0]["m"].length && log[0]["k"].length, "'m' and 'k' copyrights populated for hybrid layer"); + t.ok(layer.attribution.indexOf('olGoogleAttribution hybrid') != -1, "Attribution has the correct css class"); + t.ok(layer.attribution.indexOf('?ll=48,16&z=5&t=h"') != -1, "maps.google.com link has correct parameters"); map.destroy(); }); } diff --git a/theme/default/style.css b/theme/default/style.css index 5db30cafe1..3aeac29c2e 100644 --- a/theme/default/style.css +++ b/theme/default/style.css @@ -356,7 +356,7 @@ div.olControlSaveFeaturesItemInactive { } .olGoogleAttribution.hybrid, .olGoogleAttribution.satellite { - color: #DDD; + color: #EEE; } .olGoogleAttribution { color: #333; @@ -365,7 +365,7 @@ span.olGoogleAttribution a { color: #77C; } span.olGoogleAttribution.hybrid a, span.olGoogleAttribution.satellite a { - color: white; + color: #EEE; } /**