From 275889aa1ad4f117f210e087881fdf6527f5e0ca Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 23 May 2011 21:42:09 +0000 Subject: [PATCH] New GoogleNG layer using tiles from the v3 API's mapType::getTile method. r=bartvde (closes #3312) git-svn-id: http://svn.openlayers.org/trunk/openlayers@12000 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/google-ng.html | 42 ++++++ examples/google-ng.js | 28 ++++ lib/OpenLayers.js | 2 + lib/OpenLayers/Layer/GoogleNG.js | 239 +++++++++++++++++++++++++++++++ lib/OpenLayers/Tile.js | 4 +- lib/OpenLayers/Tile/Google.js | 151 +++++++++++++++++++ tests/Layer/GoogleNG.html | 96 +++++++++++++ tests/Tile/Google.html | 74 ++++++++++ tests/list-tests.html | 2 + theme/default/style.css | 13 ++ 10 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 examples/google-ng.html create mode 100644 examples/google-ng.js create mode 100644 lib/OpenLayers/Layer/GoogleNG.js create mode 100644 lib/OpenLayers/Tile/Google.js create mode 100644 tests/Layer/GoogleNG.html create mode 100644 tests/Tile/Google.html diff --git a/examples/google-ng.html b/examples/google-ng.html new file mode 100644 index 0000000000..5462c84d29 --- /dev/null +++ b/examples/google-ng.html @@ -0,0 +1,42 @@ + + + + + + OpenLayers Google NG Layer Example + + + + + + + + + +

Google NG Layer Example

+
+ Google, api key, apikey +
+

+ Demonstrate use of tiles from the Google Maps v3 API. +

+
+
+

+ If you use OpenLayers.Layer.GoogleNG, the getTile method of the + GMaps v3 API's MapType is used to load tiles. This allows for + better integration than interacting with a whole map generated + by a google.maps.Map instance, as done with + OpenLayers.Layer.Google. See the + google-ng.js source + to see how this is done. +

+
+ + diff --git a/examples/google-ng.js b/examples/google-ng.js new file mode 100644 index 0000000000..fa2ba35b25 --- /dev/null +++ b/examples/google-ng.js @@ -0,0 +1,28 @@ +var map; + +function init() { + map = new OpenLayers.Map('map'); + map.addControl(new OpenLayers.Control.LayerSwitcher()); + + var gphy = new OpenLayers.Layer.GoogleNG( + {type: google.maps.MapTypeId.TERRAIN} + ); + var gmap = new OpenLayers.Layer.GoogleNG( + // ROADMAP, the default + ); + var ghyb = new OpenLayers.Layer.GoogleNG( + {type: google.maps.MapTypeId.HYBRID} + ); + var gsat = new OpenLayers.Layer.GoogleNG( + {type: google.maps.MapTypeId.SATELLITE} + ); + + map.addLayers([gphy, gmap, ghyb, gsat]); + + // GoogleNG uses EPSG:900913 as projection, so we have to + // transform our coordinates + map.setCenter(new OpenLayers.LonLat(10.2, 48.9).transform( + new OpenLayers.Projection("EPSG:4326"), + map.getProjectionObject() + ), 5); +} diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index a9061c93b0..82249753f7 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -120,6 +120,7 @@ "OpenLayers/Marker/Box.js", "OpenLayers/Popup.js", "OpenLayers/Tile.js", + "OpenLayers/Tile/Google.js", "OpenLayers/Tile/Image.js", "OpenLayers/Tile/Image/IFrame.js", "OpenLayers/Tile/WFS.js", @@ -152,6 +153,7 @@ "OpenLayers/Layer/Boxes.js", "OpenLayers/Layer/XYZ.js", "OpenLayers/Layer/Bing.js", + "OpenLayers/Layer/GoogleNG.js", "OpenLayers/Layer/TMS.js", "OpenLayers/Layer/TileCache.js", "OpenLayers/Layer/Zoomify.js", diff --git a/lib/OpenLayers/Layer/GoogleNG.js b/lib/OpenLayers/Layer/GoogleNG.js new file mode 100644 index 0000000000..7dc9e986f2 --- /dev/null +++ b/lib/OpenLayers/Layer/GoogleNG.js @@ -0,0 +1,239 @@ +/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * 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. + * + * Inherits from: + * - + */ +OpenLayers.Layer.GoogleNG = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * Property: SUPPORTED_TRANSITIONS + * {Array} An immutable (that means don't change it!) list of supported + * transitionEffect values. This layer type supports none. + */ + SUPPORTED_TRANSITIONS: [], + + /** + * Property: attributionTemplate + * {String} + */ + attributionTemplate: '' + + '
' + + '
' + + 'Map data - ' + + 'Terms of Use
', + + /** + * Property: mapTypes + * {Object} mapping of {google.maps.MapTypeId} to the t param of + * http://maps.google.com/maps? permalinks + */ + mapTypes: { + "roadmap": "m", + "satellite": "k", + "hybrid": "h", + "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 + * http://code.google.com/apis/maps/documentation/javascript/reference.html#MapTypeId + */ + type: null, + + /** + * Constructor: OpenLayers.Layer.GoogleNG + * Create a new GoogleNG layer. Requires the GMaps v3 JavaScript API script + * included in the html document. + * + * Example: + * (code) + * var terrain = new OpenLayers.Layer.GoogleNG({ + * name: "Google Terrain", + * type: google.maps.MapTypeId.TERRAIN + * }); + * (end) + * + * Parameters: + * options - {Object} Configuration properties for the layer. + * + * Required configuration properties: + * type - {google.maps.MapTypeId} The layer identifier. See + * http://code.google.com/apis/maps/documentation/javascript/reference.html#MapTypeId + * for valid types. + * + * Any other documented layer properties can be provided in the config object. + */ + initialize: function(options) { + options = OpenLayers.Util.applyDefaults({ + sphericalMercator: true + }, options); + + if (!options.type) { + options.type = google.maps.MapTypeId.ROADMAP; + } + var newArgs = [options.name, null, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs); + + this.options.numZoomLevels = options.numZoomLevels; + if (!this.mapObject) { + OpenLayers.Layer.GoogleNG.prototype.mapObject = + new google.maps.Map(document.createElement("div")); + } + if (this.mapObject.mapTypes[this.type]) { + this.initLayer(); + } else { + google.maps.event.addListenerOnce( + this.mapObject, + "idle", + OpenLayers.Function.bind(this.initLayer, this) + ); + } + }, + + /** + * Method: initLayer + * + * Sets layer properties according to the metadata provided by the API + */ + initLayer: function() { + var mapType = this.mapObject.mapTypes[this.type]; + if (!this.name) { + this.setName("Google " + mapType.name); + } + + var numZoomLevels = mapType.maxZoom + 1; + if (this.options.numZoomLevels != null) { + numZoomLevels = Math.min(numZoomLevels, this.options.numZoomLevels); + } + var restrictedMinZoom; + if (this.restrictedMinZoom || mapType.minZoom) { + restrictedMinZoom = Math.max( + mapType.minZoom || 0, this.restrictedMinZoom || 0 + ); + } + + this.addOptions({ + restrictedMinZoom: restrictedMinZoom, + numZoomLevels: numZoomLevels, + tileSize: new OpenLayers.Size( + mapType.tileSize.width, mapType.tileSize.height + ) + }); + // redraw to populate tiles with content + this.redraw(); + }, + + /** + * APIMethod: addTile + * Create a tile, initialize it, and add it to the layer div. + * + * Parameters + * bounds - {} + * position - {} + * + * Returns: + * {} The added OpenLayers.Tile.Google + */ + addTile:function(bounds, position) { + return new OpenLayers.Tile.Google( + this, position, bounds, this.tileOptions + ); + }, + + /** + * Method: updateAttribution + * Updates the attribution using the + */ + updateAttribution: function() { + var center = this.map.getCenter(); + center && center.transform( + this.map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326") + ); + 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 + }); + 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); + }, + + /** + * Method: removeMap + */ + removeMap: function() { + OpenLayers.Layer.XYZ.prototype.removeMap.apply(this, arguments); + this.map.events.unregister("moveend", this, this.updateAttribution); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {} An exact clone of this + * + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.GoogleNG(this.options); + } + //get all additions from superclasses + obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + // copy/set any non-init, non-simple values here + 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 diff --git a/lib/OpenLayers/Tile.js b/lib/OpenLayers/Tile.js index 44d268f987..f71de074aa 100644 --- a/lib/OpenLayers/Tile.js +++ b/lib/OpenLayers/Tile.js @@ -103,7 +103,9 @@ OpenLayers.Tile = OpenLayers.Class({ this.position = position.clone(); this.bounds = bounds.clone(); this.url = url; - this.size = size.clone(); + if (size) { + this.size = size.clone(); + } //give the tile a unique id based on its BBOX. this.id = OpenLayers.Util.createUniqueID("Tile_"); diff --git a/lib/OpenLayers/Tile/Google.js b/lib/OpenLayers/Tile/Google.js new file mode 100644 index 0000000000..11686734a6 --- /dev/null +++ b/lib/OpenLayers/Tile/Google.js @@ -0,0 +1,151 @@ +/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/* + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + * @requires OpenLayers/Console.js + * @requires OpenLayers/Lang.js + */ + +/* + * Class: OpenLayers.Tile.Google + * Instances of OpenLayers.Tile.Google are used to manage the tiles created + * by google.maps.MapType (see + * http://code.google.com/apis/maps/documentation/javascript/reference.html#MapType). + * + * Inherits from: + * - + */ +OpenLayers.Tile.Google = OpenLayers.Class(OpenLayers.Tile, { + + /** + * Property: node + * {DOMElement} The tile node from the MapType's getTile method + */ + node: null, + + /** + * Constructor: OpenLayers.Tile.Google + * Constructor for a new instance. + * + * Parameters: + * layer - {} layer that the tile will go in. + * position - {} + * bounds - {} + * options - {Object} + */ + initialize: function(layer, position, bounds, options) { + OpenLayers.Tile.prototype.initialize.apply(this, [ + layer, position, bounds, null, null, options + ]); + }, + + /** + * APIMethod: destroy + * Nullify references to prevent circular references and memory leaks. + */ + destroy:function() { + this.node && this.clear(); + this.node = null; + OpenLayers.Tile.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * + * Parameters: + * obj - {} The tile to be cloned + * + * Returns: + * {} An exact clone of this + */ + clone: function (obj) { + if (obj == null) { + obj = new OpenLayers.Tile.Google(this.layer, + this.position, + this.bounds); + } + + // catch any randomly tagged-on properties + OpenLayers.Util.applyDefaults(obj, this); + + obj.node = null; + + return obj; + }, + + /** + * Method: draw + * Check that a tile should be drawn, and draw it. + * + * Returns: + * {Boolean} Always returns true. + */ + draw: function() { + var layerType = this.layer.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); + + // The hybrid tile consists of two images. For some reason, we have + // to make sure that the satellite image loads first, otherwise we + // occasionally get blank tiles for one of the two images. This is + // done by requesting the tile for just the satellite mapType + // first, before requesting the hybrid one. + //TODO revisit this - it may be a temporary issue with GMaps + var tempTile; + if (this.layer.type === google.maps.MapTypeId.HYBRID) { + tempTile = layerType.getTile(point, xyz.z, document); + } + + this.node = layerType.getTile(point, xyz.z, document); + + this.isLoading = true; + this.events.triggerEvent("loadstart"); + + this.layer.div.appendChild(this.node); + + // We only modify what we need to - we expect the size to be set + // by getTile, and we have a test that will fail if this changes. + OpenLayers.Util.modifyDOMElement( + this.node, null, this.position, null, "absolute" + ); + + // The images inside the node returned from getTile seem to be + // preloaded already, so registering onload events on these images + // won't work. Instead, we trigger the loadend event immediately + // in the next cycle. + window.setTimeout(OpenLayers.Function.bind(function() { + this.isLoading = false; + // check for this.events - we may be destroyed already + this.events && this.events.triggerEvent("loadend"); + + // see hybrid tile issue above + //TODO revisit this - it may be a temporary issue with GMaps + if (tempTile) { + layerType.releaseTile(tempTile); + } + }, this), 0); + } + return true; + }, + + /** + * Method: clear + * Clear the tile of any bounds/position-related data so that it can + * be reused in a new location. To be implemented by subclasses. + */ + clear: function() { + if (this.node) { + this.node.parentNode && + this.node.parentNode.removeChild(this.node); + this.layer.mapObject.mapTypes[this.layer.type].releaseTile(this.node); + } + }, + + CLASS_NAME: "OpenLayers.Tile.Google" +}); diff --git a/tests/Layer/GoogleNG.html b/tests/Layer/GoogleNG.html new file mode 100644 index 0000000000..a302450440 --- /dev/null +++ b/tests/Layer/GoogleNG.html @@ -0,0 +1,96 @@ + + + + + + + +
+
+ + diff --git a/tests/Tile/Google.html b/tests/Tile/Google.html new file mode 100644 index 0000000000..e1710847ba --- /dev/null +++ b/tests/Tile/Google.html @@ -0,0 +1,74 @@ + + + + + + + +
+ + \ No newline at end of file diff --git a/tests/list-tests.html b/tests/list-tests.html index 214c2ffb58..d5d9a8ae10 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -144,6 +144,7 @@
  • Layer/GML.html
  • Layer/Google.html
  • Layer/Google/v3.html
  • +
  • Layer/GoogleNG.html
  • Layer/Grid.html
  • Layer/HTTPRequest.html
  • Layer/Image.html
  • @@ -213,6 +214,7 @@
  • Symbolizer/Raster.html
  • Symbolizer/Text.html
  • Tile.html
  • +
  • Tile/Google.html
  • Tile/Image.html
  • Tile/Image/IFrame.html
  • Tile/WFS.html
  • diff --git a/theme/default/style.css b/theme/default/style.css index ae5b6b4a74..5db30cafe1 100644 --- a/theme/default/style.css +++ b/theme/default/style.css @@ -355,6 +355,19 @@ div.olControlSaveFeaturesItemInactive { color: #333; } +.olGoogleAttribution.hybrid, .olGoogleAttribution.satellite { + color: #DDD; +} +.olGoogleAttribution { + color: #333; +} +span.olGoogleAttribution a { + color: #77C; +} +span.olGoogleAttribution.hybrid a, span.olGoogleAttribution.satellite a { + color: white; +} + /** * Editing and navigation icons. * (using the editing_tool_bar.png sprint image)