From 00d9664b951e1f23dd9c4affe4c1a8a94b709f60 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 20 Jul 2012 17:43:05 +0200 Subject: [PATCH] No longer touching internal GMaps DOM elements. Simple and effective: As soon as a map has a Google layer, the whole map viewport is added as control to the GMap. As soon as no Google layer is visible on the map any more, the map viewport is appended to the map container again. With this change, OpenLayers strictly limits its GMaps integration to the GMaps API. Also note that there are no css overrides for the attribution any more. Instead, controls can now be conditionally positioned differently for Google layer by using the .olForeignContainer selector. --- examples/google-v3.html | 16 --- examples/google-v3.js | 53 +++++---- examples/spherical-mercator.html | 11 +- lib/OpenLayers/Layer/Google/v3.js | 171 ++++++------------------------ lib/OpenLayers/Map.js | 4 +- tests/Layer/Google/v3.html | 89 +++++----------- theme/default/google.css | 8 -- 7 files changed, 92 insertions(+), 260 deletions(-) diff --git a/examples/google-v3.html b/examples/google-v3.html index 4365dafbf8..5c11ae9312 100644 --- a/examples/google-v3.html +++ b/examples/google-v3.html @@ -6,7 +6,6 @@ OpenLayers Google (v3) Layer Example - @@ -30,21 +29,6 @@ spherical mercator projection. See the google-v3.js source to see how this is done. -

- In order to position the Google attribution div in the default - location, you must include the extra theme/default/google.css - stylesheet. -

-

- Note on Google Maps API versioning: - This example uses the "nightly" version of Google Maps - API. This is specified by using v=3 in the - the Google Maps API URL. Production applications should use the - "release" or "frozen" versions of Google Maps - API. See the OpenLayers.Layer.Google.v3 API - docs, and the - Versioning Section - of the Google Maps API docs, for more details.

diff --git a/examples/google-v3.js b/examples/google-v3.js index 4d4cd205df..e81c6a4c22 100644 --- a/examples/google-v3.js +++ b/examples/google-v3.js @@ -1,35 +1,34 @@ var map; function init() { - map = new OpenLayers.Map('map'); + map = new OpenLayers.Map('map', { + projection: 'EPSG:3857', + layers: [ + new OpenLayers.Layer.Google( + "Google Physical", + {type: google.maps.MapTypeId.TERRAIN} + ), + new OpenLayers.Layer.Google( + "Google Streets", // the default + {numZoomLevels: 20} + ), + new OpenLayers.Layer.Google( + "Google Hybrid", + {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} + ), + new OpenLayers.Layer.Google( + "Google Satellite", + {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} + ) + ], + center: new OpenLayers.LonLat(10.2, 48.9) + // Google.v3 uses web mercator as projection, so we have to + // transform our coordinates + .transform('EPSG:4326', 'EPSG:3857'), + zoom: 5 + }); map.addControl(new OpenLayers.Control.LayerSwitcher()); - var gphy = new OpenLayers.Layer.Google( - "Google Physical", - {type: google.maps.MapTypeId.TERRAIN} - ); - var gmap = new OpenLayers.Layer.Google( - "Google Streets", // the default - {numZoomLevels: 20} - ); - var ghyb = new OpenLayers.Layer.Google( - "Google Hybrid", - {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} - ); - var gsat = new OpenLayers.Layer.Google( - "Google Satellite", - {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} - ); - - map.addLayers([gphy, gmap, ghyb, gsat]); - - // Google.v3 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); - // add behavior to html var animate = document.getElementById("animate"); animate.onclick = function() { diff --git a/examples/spherical-mercator.html b/examples/spherical-mercator.html index b2835701d8..443ba3bb86 100644 --- a/examples/spherical-mercator.html +++ b/examples/spherical-mercator.html @@ -17,6 +17,10 @@ right: inherit; width: 400px; } + /* conditionally position control differently for Google Maps */ + .olForeignContainer div.olControlMousePosition { + bottom: 28px; + } #map { height: 512px; } @@ -38,7 +42,12 @@

-
+
+

Note that maps with Google layers are a special case, because we + cannot control the position of the attribution. To conditionally + position controls differently for Google layers, prepend the + css selector with .olForeignContainer.

+
- * (end) - * - * but that development code should use the latest 'nightly' version, so that any - * problems can be dealt with as soon as they arise, and before they affect the production, 'frozen', code. - * - * Note, however, that frozen versions are retired as part of Google's release - * cycle, and once this happens, you will get the next version, in the example above, 3.8 once 3.7 is retired. - * - * This version supports 3.7. - * - * * Note that this layer configures the google.maps.map object with the * "disableDefaultUI" option set to true. Using UI controls that the Google * Maps API provides is not supported by the OpenLayers API. @@ -98,18 +69,13 @@ OpenLayers.Layer.Google.v3 = { ++cache.count; } else { // this is the first Google layer for this map - - var container = this.map.viewPortDiv; - var div = document.createElement("div"); - div.id = this.map.id + "_GMapContainer"; - div.style.position = "absolute"; - div.style.width = "100%"; - div.style.height = "100%"; - container.appendChild(div); - - // create GMap and shuffle elements + // create GMap var center = this.map.getCenter(); - mapObject = new google.maps.Map(div, { + var container = document.createElement('div'); + container.className = "olForeignContainer"; + container.style.width = '100%'; + container.style.height = '100%'; + mapObject = new google.maps.Map(container, { center: center ? new google.maps.LatLng(center.lat, center.lon) : new google.maps.LatLng(0, 0), @@ -130,87 +96,17 @@ OpenLayers.Layer.Google.v3 = { count: 1 }; OpenLayers.Layer.Google.cache[this.map.id] = cache; - this.repositionListener = google.maps.event.addListenerOnce( - mapObject, - "center_changed", - OpenLayers.Function.bind(this.repositionMapElements, this) - ); } this.mapObject = mapObject; this.setGMapVisibility(this.visibility); }, - /** - * Method: repositionMapElements - * - * Waits until powered by and terms of use elements are available and then - * moves them so they are clickable. - */ - repositionMapElements: function() { - - // This is the first time any Google layer in this mapObject has been - // made visible. The mapObject needs to know the container size. - google.maps.event.trigger(this.mapObject, "resize"); - - var div = this.mapObject.getDiv().firstChild; - if (!div || div.childNodes.length < 3) { - this.repositionTimer = window.setTimeout( - OpenLayers.Function.bind(this.repositionMapElements, this), - 250 - ); - return false; - } - - var cache = OpenLayers.Layer.Google.cache[this.map.id]; - var container = this.map.viewPortDiv; - - // move the ToS and branding stuff up to the container div - // depends on value of zIndex, which is not robust - for (var i=div.children.length-1; i>=0; --i) { - if (div.children[i].style.zIndex == 1000001) { - var termsOfUse = div.children[i]; - container.appendChild(termsOfUse); - termsOfUse.style.zIndex = "1100"; - termsOfUse.style.bottom = ""; - termsOfUse.className = "olLayerGoogleCopyright olLayerGoogleV3"; - termsOfUse.style.display = ""; - cache.termsOfUse = termsOfUse; - } - if (div.children[i].style.zIndex == 1000000) { - var poweredBy = div.children[i]; - container.appendChild(poweredBy); - poweredBy.style.zIndex = "1100"; - poweredBy.style.bottom = ""; - poweredBy.className = "olLayerGooglePoweredBy olLayerGoogleV3 gmnoprint"; - poweredBy.style.display = ""; - cache.poweredBy = poweredBy; - } - if (div.children[i].style.zIndex == 10000002) { - container.appendChild(div.children[i]); - } - } - - this.setGMapVisibility(this.visibility); - - }, - /** * APIMethod: onMapResize */ onMapResize: function() { if (this.visibility) { google.maps.event.trigger(this.mapObject, "resize"); - } else { - var cache = OpenLayers.Layer.Google.cache[this.map.id]; - if (!cache.resized) { - var layer = this; - google.maps.event.addListenerOnce(this.mapObject, "tilesloaded", function() { - google.maps.event.trigger(layer.mapObject, "resize"); - layer.moveTo(layer.map.getCenter(), layer.map.getZoom()); - delete cache.resized; - }); - } - cache.resized = true; } }, @@ -238,27 +134,34 @@ OpenLayers.Layer.Google.v3 = { } var container = this.mapObject.getDiv(); if (visible === true) { - this.mapObject.setMapTypeId(type); - container.style.left = ""; - if (cache.termsOfUse && cache.termsOfUse.style) { - cache.termsOfUse.style.left = ""; - cache.termsOfUse.style.display = ""; - cache.poweredBy.style.display = ""; + if (!cache.gmapHasViewport) { + this.map.div.removeChild(this.map.viewPortDiv); + this.mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(this.map.viewPortDiv); + cache.gmapHasViewport = true; + this.map.div.appendChild(container); + google.maps.event.trigger(this.mapObject, 'resize'); } + this.mapObject.setMapTypeId(type); cache.displayed = this.id; } else { - delete cache.displayed; - container.style.left = "-9999px"; - if (cache.termsOfUse && cache.termsOfUse.style) { - cache.termsOfUse.style.display = "none"; - // move ToU far to the left in addition to setting - // display to "none", because at the end of the GMap - // load sequence, display: none will be unset and ToU - // would be visible after loading a map with a google - // layer that is initially hidden. - cache.termsOfUse.style.left = "-9999px"; - cache.poweredBy.style.display = "none"; + if (cache.gmapHasViewport) { + this.map.div.removeChild(container); + this.map.div.appendChild( + this.mapObject.controls[google.maps.ControlPosition.TOP_LEFT].pop() + ); + delete cache.gmapHasViewport; + + // restore all styles of the viewPortDiv, in case GMaps + // changes any. At the time of writing this, it definitely + // changes the position. + OpenLayers.Util.modifyDOMElement( + this.map.viewPortDiv, null, null, null, 'relative', + null, 'hidden' + ); + this.map.viewPortDiv.style.width = "100%"; + this.map.viewPortDiv.style.height = "100%"; } + delete cache.displayed; } } }, @@ -448,20 +351,6 @@ OpenLayers.Layer.Google.v3 = { */ getMapObjectPixelFromXY: function(x, y) { return new google.maps.Point(x, y); - }, - - /** - * APIMethod: destroy - * Clean up this layer. - */ - destroy: function() { - if (this.repositionListener) { - google.maps.event.removeListener(this.repositionListener); - } - if (this.repositionTimer) { - window.clearTimeout(this.repositionTimer); - } - OpenLayers.Layer.Google.prototype.destroy.apply(this, arguments); } }; diff --git a/lib/OpenLayers/Map.js b/lib/OpenLayers/Map.js index 5f4cf565a7..50eb3bc8c9 100644 --- a/lib/OpenLayers/Map.js +++ b/lib/OpenLayers/Map.js @@ -772,8 +772,8 @@ OpenLayers.Map = OpenLayers.Class({ } this.layers = null; } - if (this.viewPortDiv) { - this.div.removeChild(this.viewPortDiv); + if (this.viewPortDiv && this.viewPortDiv.parentNode) { + this.viewPortDiv.parentNode.removeChild(this.viewPortDiv); } this.viewPortDiv = null; diff --git a/tests/Layer/Google/v3.html b/tests/Layer/Google/v3.html index a90f8f46c1..10da99aea4 100644 --- a/tests/Layer/Google/v3.html +++ b/tests/Layer/Google/v3.html @@ -170,68 +170,6 @@ t.eq(satellite.div.style.display, "block", "Satellite layer is visible."); } - function test_Layer_Google_setGMapVisibility(t) { - t.plan(3); - - var map = new OpenLayers.Map('map'); - var gmap = new OpenLayers.Layer.Google("Google Streets"); - var dummy = new OpenLayers.Layer("Dummy", {isBaseLayer: true}); - map.addLayers([dummy, gmap]); - map.zoomToMaxExtent(); - - // In v3, the terms of use and powered by elements are not available - // until the layer loads. This can occur before the layer is visible, - // but we don't try to access these elements until after the layer is - // made visible for the first time. - var cache = OpenLayers.Layer.Google.cache[map.id]; - t.ok(!cache.termsOfUse, "termsOfUse is not yet cached"); - t.ok(!cache.poweredBy, "poweredBy is not yet cached"); - - var called = 0; - var original = gmap.repositionMapElements; - gmap.repositionMapElements = function() { - ++called; - original.apply(gmap, arguments); - } - - map.setBaseLayer(gmap); - t.delay_call(4, function() { - t.ok(called > 0, "repositionMapElements called"); - map.destroy(); - }); - } - - function test_Layer_Google_setGMapVisibility_allOverlays(t) { - t.plan(3); - - var map = new OpenLayers.Map('map', {allOverlays: true}); - var gmap = new OpenLayers.Layer.Google("Google Streets", {visibility: false}); - var dummy = new OpenLayers.Layer("Dummy"); - map.addLayers([gmap, dummy]); - map.zoomToMaxExtent(); - - // In v3, the terms of use and powered by elements are not available - // until the layer loads. This can occur before the layer is visible, - // but we don't try to access these elements until after the layer is - // made visible for the first time. - var cache = OpenLayers.Layer.Google.cache[map.id]; - t.ok(!cache.termsOfUse, "termsOfUse is not yet cached"); - t.ok(!cache.poweredBy, "poweredBy is not yet cached"); - - var called = 0; - var original = gmap.repositionMapElements; - gmap.repositionMapElements = function() { - ++called; - original.apply(gmap, arguments); - } - - gmap.setVisibility(true); - t.delay_call(2, function() { - t.ok(called > 0, "repositionMapElements called"); - map.destroy(); - }); - } - function test_allOverlays_invisible(t) { t.plan(1); @@ -319,10 +257,9 @@ t.plan(2); var origPrecision = OpenLayers.Util.DEFAULT_PRECISION; - // GMaps v3 seems to use a default precision of 13, which is lower - // than what we use in OpenLayers. + // Our default precision is very high - millimeters should be enough. // See http://trac.osgeo.org/openlayers/ticket/3059 - OpenLayers.Util.DEFAULT_PRECISION = 13; + OpenLayers.Util.DEFAULT_PRECISION = 12; var map = new OpenLayers.Map("map"); @@ -370,6 +307,28 @@ } + function test_moveViewportDiv(t) { + t.plan(2); + + var map = new OpenLayers.Map('map', { + projection: 'EPSG:3857', + center: [0, 0], + zoom: 1 + }); + var gmap = new OpenLayers.Layer.Google(); + map.addLayer(gmap); + + t.delay_call(1, function() { + t.ok(map.viewPortDiv.parentNode !== map.div, 'viewport moved inside GMaps'); + + var osm = new OpenLayers.Layer.OSM(); + map.addLayer(osm); + map.setBaseLayer(osm); + + t.ok(map.viewPortDiv.parentNode === map.div, 'viewport moved back'); + }); + } + diff --git a/theme/default/google.css b/theme/default/google.css index 3ee757c478..1b748ef3e5 100644 --- a/theme/default/google.css +++ b/theme/default/google.css @@ -3,15 +3,7 @@ bottom: 2px; left: auto; } -.olLayerGoogleV3.olLayerGoogleCopyright { - bottom: 0px; - right: 0px !important; -} .olLayerGooglePoweredBy { left: 2px; bottom: 2px; } -.olLayerGoogleV3.olLayerGooglePoweredBy { - bottom: 0px !important; -} -