/* * MarkerManager, v1.0 * Copyright (c) 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * Author: Doug Ricket, others * * Marker manager is an interface between the map and the user, designed * to manage adding and removing many points when the viewport changes. * * * Algorithm: The MM places its markers onto a grid, similar to the map tiles. * When the user moves the viewport, the MM computes which grid cells have * entered or left the viewport, and shows or hides all the markers in those * cells. * (If the users scrolls the viewport beyond the markers that are loaded, * no markers will be visible until the EVENT_moveend triggers an update.) * * In practical consequences, this allows 10,000 markers to be distributed over * a large area, and as long as only 100-200 are visible in any given viewport, * the user will see good performance corresponding to the 100 visible markers, * rather than poor performance corresponding to the total 10,000 markers. * * Note that some code is optimized for speed over space, * with the goal of accommodating thousands of markers. * */ /** * Creates a new MarkerManager that will show/hide markers on a map. * * @constructor * @param {Map} map The map to manage. * @param {Object} opt_opts A container for optional arguments: * {Number} maxZoom The maximum zoom level for which to create tiles. * {Number} borderPadding The width in pixels beyond the map border, * where markers should be display. * {Boolean} trackMarkers Whether or not this manager should track marker * movements. */ function MarkerManager(map, opt_opts) { var me = this; me.map_ = map; me.mapZoom_ = map.getZoom(); me.projection_ = map.getCurrentMapType().getProjection(); opt_opts = opt_opts || {}; me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_; var maxZoom = MarkerManager.DEFAULT_MAX_ZOOM_; if(opt_opts.maxZoom != undefined) { maxZoom = opt_opts.maxZoom; } me.maxZoom_ = maxZoom; me.trackMarkers_ = opt_opts.trackMarkers; var padding; if (typeof opt_opts.borderPadding == "number") { padding = opt_opts.borderPadding; } else { padding = MarkerManager.DEFAULT_BORDER_PADDING_; } // The padding in pixels beyond the viewport, where we will pre-load markers. me.swPadding_ = new GSize(-padding, padding); me.nePadding_ = new GSize(padding, -padding); me.borderPadding_ = padding; me.gridWidth_ = []; me.grid_ = []; me.grid_[maxZoom] = []; me.numMarkers_ = []; me.numMarkers_[maxZoom] = 0; GEvent.bind(map, "moveend", me, me.onMapMoveEnd_); // NOTE: These two closures provide easy access to the map. // They are used as callbacks, not as methods. me.removeOverlay_ = function(marker) { map.removeOverlay(marker); me.shownMarkers_--; }; me.addOverlay_ = function(marker) { map.addOverlay(marker); me.shownMarkers_++; }; me.resetManager_(); me.shownMarkers_ = 0; me.shownBounds_ = me.getMapGridBounds_(); }; // Static constants: MarkerManager.DEFAULT_TILE_SIZE_ = 1024; MarkerManager.DEFAULT_MAX_ZOOM_ = 17; MarkerManager.DEFAULT_BORDER_PADDING_ = 100; MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256; /** * Initializes MarkerManager arrays for all zoom levels * Called by constructor and by clearAllMarkers */ MarkerManager.prototype.resetManager_ = function() { var me = this; var mapWidth = MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE; for (var zoom = 0; zoom <= me.maxZoom_; ++zoom) { me.grid_[zoom] = []; me.numMarkers_[zoom] = 0; me.gridWidth_[zoom] = Math.ceil(mapWidth/me.tileSize_); mapWidth <<= 1; } }; /** * Removes all currently displayed markers * and calls resetManager to clear arrays */ MarkerManager.prototype.clearMarkers = function() { var me = this; me.processAll_(me.shownBounds_, me.removeOverlay_); me.resetManager_(); }; /** * Gets the tile coordinate for a given latlng point. * * @param {LatLng} latlng The geographical point. * @param {Number} zoom The zoom level. * @param {GSize} padding The padding used to shift the pixel coordinate. * Used for expanding a bounds to include an extra padding * of pixels surrounding the bounds. * @return {GPoint} The point in tile coordinates. * */ MarkerManager.prototype.getTilePoint_ = function(latlng, zoom, padding) { var pixelPoint = this.projection_.fromLatLngToPixel(latlng, zoom); return new GPoint( Math.floor((pixelPoint.x + padding.width) / this.tileSize_), Math.floor((pixelPoint.y + padding.height) / this.tileSize_)); }; /** * Finds the appropriate place to add the marker to the grid. * Optimized for speed; does not actually add the marker to the map. * Designed for batch-processing thousands of markers. * * @param {Marker} marker The marker to add. * @param {Number} minZoom The minimum zoom for displaying the marker. * @param {Number} maxZoom The maximum zoom for displaying the marker. */ MarkerManager.prototype.addMarkerBatch_ = function(marker, minZoom, maxZoom) { var mPoint = marker.getPoint(); // Tracking markers is expensive, so we do this only if the // user explicitly requested it when creating marker manager. if (this.trackMarkers_) { GEvent.bind(marker, "changed", this, this.onMarkerMoved_); } var gridPoint = this.getTilePoint_(mPoint, maxZoom, GSize.ZERO); for (var zoom = maxZoom; zoom >= minZoom; zoom--) { var cell = this.getGridCellCreate_(gridPoint.x, gridPoint.y, zoom); cell.push(marker); gridPoint.x = gridPoint.x >> 1; gridPoint.y = gridPoint.y >> 1; } }; /** * Returns whether or not the given point is visible in the shown bounds. This * is a helper method that takes care of the corner case, when shownBounds have * negative minX value. * * @param {Point} point a point on a grid. * @return {Boolean} Whether or not the given point is visible in the currently * shown bounds. */ MarkerManager.prototype.isGridPointVisible_ = function(point) { var me = this; var vertical = me.shownBounds_.minY <= point.y && point.y <= me.shownBounds_.maxY; var minX = me.shownBounds_.minX; var horizontal = minX <= point.x && point.x <= me.shownBounds_.maxX; if (!horizontal && minX < 0) { // Shifts the negative part of the rectangle. As point.x is always less // than grid width, only test shifted minX .. 0 part of the shown bounds. var width = me.gridWidth_[me.shownBounds_.z]; horizontal = minX + width <= point.x && point.x <= width - 1; } return vertical && horizontal; } /** * Reacts to a notification from a marker that it has moved to a new location. * It scans the grid all all zoom levels and moves the marker from the old grid * location to a new grid location. * * @param {Marker} marker The marker that moved. * @param {LatLng} oldPoint The old position of the marker. * @param {LatLng} newPoint The new position of the marker. */ MarkerManager.prototype.onMarkerMoved_ = function(marker, oldPoint, newPoint) { // NOTE: We do not know the minimum or maximum zoom the marker was // added at, so we start at the absolute maximum. Whenever we successfully // remove a marker at a given zoom, we add it at the new grid coordinates. var me = this; var zoom = me.maxZoom_; var changed = false; var oldGrid = me.getTilePoint_(oldPoint, zoom, GSize.ZERO); var newGrid = me.getTilePoint_(newPoint, zoom, GSize.ZERO); while (zoom >= 0 && (oldGrid.x != newGrid.x || oldGrid.y != newGrid.y)) { var cell = me.getGridCellNoCreate_(oldGrid.x, oldGrid.y, zoom); if (cell) { if (me.removeFromArray(cell, marker)) { me.getGridCellCreate_(newGrid.x, newGrid.y, zoom).push(marker); } } // For the current zoom we also need to update the map. Markers that no // longer are visible are removed from the map. Markers that moved into // the shown bounds are added to the map. This also lets us keep the count // of visible markers up to date. if (zoom == me.mapZoom_) { if (me.isGridPointVisible_(oldGrid)) { if (!me.isGridPointVisible_(newGrid)) { me.removeOverlay_(marker); changed = true; } } else { if (me.isGridPointVisible_(newGrid)) { me.addOverlay_(marker); changed = true; } } } oldGrid.x = oldGrid.x >> 1; oldGrid.y = oldGrid.y >> 1; newGrid.x = newGrid.x >> 1; newGrid.y = newGrid.y >> 1; --zoom; } if (changed) { me.notifyListeners_(); } }; /** * Searches at every zoom level to find grid cell * that marker would be in, removes from that array if found. * Also removes marker with removeOverlay if visible. * @param {GMarker} marker The marker to delete. */ MarkerManager.prototype.removeMarker = function(marker) { var me = this; var zoom = me.maxZoom_; var changed = false; var point = marker.getPoint(); var grid = me.getTilePoint_(point, zoom, GSize.ZERO); while (zoom >= 0) { var cell = me.getGridCellNoCreate_(grid.x, grid.y, zoom); if (cell) { me.removeFromArray(cell, marker); } // For the current zoom we also need to update the map. Markers that no // longer are visible are removed from the map. This also lets us keep the count // of visible markers up to date. if (zoom == me.mapZoom_) { if (me.isGridPointVisible_(grid)) { me.removeOverlay_(marker); changed = true; } } grid.x = grid.x >> 1; grid.y = grid.y >> 1; --zoom; } if (changed) { me.notifyListeners_(); } }; /** * Add many markers at once. * Does not actually update the map, just the internal grid. * * @param {Array of Marker} markers The markers to add. * @param {Number} minZoom The minimum zoom level to display the markers. * @param {Number} opt_maxZoom The maximum zoom level to display the markers. */ MarkerManager.prototype.addMarkers = function(markers, minZoom, opt_maxZoom) { var maxZoom = this.getOptMaxZoom_(opt_maxZoom); for (var i = markers.length - 1; i >= 0; i--) { this.addMarkerBatch_(markers[i], minZoom, maxZoom); } this.numMarkers_[minZoom] += markers.length; }; /** * Returns the value of the optional maximum zoom. This method is defined so * that we have just one place where optional maximum zoom is calculated. * * @param {Number} opt_maxZoom The optinal maximum zoom. * @return The maximum zoom. */ MarkerManager.prototype.getOptMaxZoom_ = function(opt_maxZoom) { return opt_maxZoom != undefined ? opt_maxZoom : this.maxZoom_; } /** * Calculates the total number of markers potentially visible at a given * zoom level. * * @param {Number} zoom The zoom level to check. */ MarkerManager.prototype.getMarkerCount = function(zoom) { var total = 0; for (var z = 0; z <= zoom; z++) { total += this.numMarkers_[z]; } return total ; }; /** * Add a single marker to the map. * * @param {Marker} marker The marker to add. * @param {Number} minZoom The minimum zoom level to display the marker. * @param {Number} opt_maxZoom The maximum zoom level to display the marker. */ MarkerManager.prototype.addMarker = function(marker, minZoom, opt_maxZoom) { var me = this; var maxZoom = this.getOptMaxZoom_(opt_maxZoom); me.addMarkerBatch_(marker, minZoom, maxZoom); var gridPoint = me.getTilePoint_(marker.getPoint(), me.mapZoom_, GSize.ZERO); if(me.isGridPointVisible_(gridPoint) && minZoom <= me.shownBounds_.z && me.shownBounds_.z <= maxZoom ) { me.addOverlay_(marker); me.notifyListeners_(); } this.numMarkers_[minZoom]++; }; /** * Returns true if this bounds (inclusively) contains the given point. * @param {Point} point The point to test. * @return {Boolean} This Bounds contains the given Point. */ GBounds.prototype.containsPoint = function(point) { var outer = this; return (outer.minX <= point.x && outer.maxX >= point.x && outer.minY <= point.y && outer.maxY >= point.y); } /** * Get a cell in the grid, creating it first if necessary. * * Optimization candidate * * @param {Number} x The x coordinate of the cell. * @param {Number} y The y coordinate of the cell. * @param {Number} z The z coordinate of the cell. * @return {Array} The cell in the array. */ MarkerManager.prototype.getGridCellCreate_ = function(x, y, z) { var grid = this.grid_[z]; if (x < 0) { x += this.gridWidth_[z]; } var gridCol = grid[x]; if (!gridCol) { gridCol = grid[x] = []; return gridCol[y] = []; } var gridCell = gridCol[y]; if (!gridCell) { return gridCol[y] = []; } return gridCell; }; /** * Get a cell in the grid, returning undefined if it does not exist. * * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_. * * @param {Number} x The x coordinate of the cell. * @param {Number} y The y coordinate of the cell. * @param {Number} z The z coordinate of the cell. * @return {Array} The cell in the array. */ MarkerManager.prototype.getGridCellNoCreate_ = function(x, y, z) { var grid = this.grid_[z]; if (x < 0) { x += this.gridWidth_[z]; } var gridCol = grid[x]; return gridCol ? gridCol[y] : undefined; }; /** * Turns at geographical bounds into a grid-space bounds. * * @param {LatLngBounds} bounds The geographical bounds. * @param {Number} zoom The zoom level of the bounds. * @param {GSize} swPadding The padding in pixels to extend beyond the * given bounds. * @param {GSize} nePadding The padding in pixels to extend beyond the * given bounds. * @return {GBounds} The bounds in grid space. */ MarkerManager.prototype.getGridBounds_ = function(bounds, zoom, swPadding, nePadding) { zoom = Math.min(zoom, this.maxZoom_); var bl = bounds.getSouthWest(); var tr = bounds.getNorthEast(); var sw = this.getTilePoint_(bl, zoom, swPadding); var ne = this.getTilePoint_(tr, zoom, nePadding); var gw = this.gridWidth_[zoom]; // Crossing the prime meridian requires correction of bounds. if (tr.lng() < bl.lng() || ne.x < sw.x) { sw.x -= gw; } if (ne.x - sw.x + 1 >= gw) { // Computed grid bounds are larger than the world; truncate. sw.x = 0; ne.x = gw - 1; } var gridBounds = new GBounds([sw, ne]); gridBounds.z = zoom; return gridBounds; }; /** * Gets the grid-space bounds for the current map viewport. * * @return {Bounds} The bounds in grid space. */ MarkerManager.prototype.getMapGridBounds_ = function() { var me = this; return me.getGridBounds_(me.map_.getBounds(), me.mapZoom_, me.swPadding_, me.nePadding_); }; /** * Event listener for map:movend. * NOTE: Use a timeout so that the user is not blocked * from moving the map. * */ MarkerManager.prototype.onMapMoveEnd_ = function() { var me = this; me.objectSetTimeout_(this, this.updateMarkers_, 0); }; /** * Call a function or evaluate an expression after a specified number of * milliseconds. * * Equivalent to the standard window.setTimeout function, but the given * function executes as a method of this instance. So the function passed to * objectSetTimeout can contain references to this. * objectSetTimeout(this, function() { alert(this.x) }, 1000); * * @param {Object} object The target object. * @param {Function} command The command to run. * @param {Number} milliseconds The delay. * @return {Boolean} Success. */ MarkerManager.prototype.objectSetTimeout_ = function(object, command, milliseconds) { return window.setTimeout(function() { command.call(object); }, milliseconds); }; /** * Refresh forces the marker-manager into a good state. *
    *
  1. If never before initialized, shows all the markers.
  2. *
  3. If previously initialized, removes and re-adds all markers.
  4. *
*/ MarkerManager.prototype.refresh = function() { var me = this; if (me.shownMarkers_ > 0) { me.processAll_(me.shownBounds_, me.removeOverlay_); } me.processAll_(me.shownBounds_, me.addOverlay_); me.notifyListeners_(); }; /** * After the viewport may have changed, add or remove markers as needed. */ MarkerManager.prototype.updateMarkers_ = function() { var me = this; me.mapZoom_ = this.map_.getZoom(); var newBounds = me.getMapGridBounds_(); // If the move does not include new grid sections, // we have no work to do: if (newBounds.equals(me.shownBounds_) && newBounds.z == me.shownBounds_.z) { return; } if (newBounds.z != me.shownBounds_.z) { me.processAll_(me.shownBounds_, me.removeOverlay_); me.processAll_(newBounds, me.addOverlay_); } else { // Remove markers: me.rectangleDiff_(me.shownBounds_, newBounds, me.removeCellMarkers_); // Add markers: me.rectangleDiff_(newBounds, me.shownBounds_, me.addCellMarkers_); } me.shownBounds_ = newBounds; me.notifyListeners_(); }; /** * Notify listeners when the state of what is displayed changes. */ MarkerManager.prototype.notifyListeners_ = function() { GEvent.trigger(this, "changed", this.shownBounds_, this.shownMarkers_); }; /** * Process all markers in the bounds provided, using a callback. * * @param {Bounds} bounds The bounds in grid space. * @param {Function} callback The function to call for each marker. */ MarkerManager.prototype.processAll_ = function(bounds, callback) { for (var x = bounds.minX; x <= bounds.maxX; x++) { for (var y = bounds.minY; y <= bounds.maxY; y++) { this.processCellMarkers_(x, y, bounds.z, callback); } } }; /** * Process all markers in the grid cell, using a callback. * * @param {Number} x The x coordinate of the cell. * @param {Number} y The y coordinate of the cell. * @param {Number} z The z coordinate of the cell. * @param {Function} callback The function to call for each marker. */ MarkerManager.prototype.processCellMarkers_ = function(x, y, z, callback) { var cell = this.getGridCellNoCreate_(x, y, z); if (cell) { for (var i = cell.length - 1; i >= 0; i--) { callback(cell[i]); } } }; /** * Remove all markers in a grid cell. * * @param {Number} x The x coordinate of the cell. * @param {Number} y The y coordinate of the cell. * @param {Number} z The z coordinate of the cell. */ MarkerManager.prototype.removeCellMarkers_ = function(x, y, z) { this.processCellMarkers_(x, y, z, this.removeOverlay_); }; /** * Add all markers in a grid cell. * * @param {Number} x The x coordinate of the cell. * @param {Number} y The y coordinate of the cell. * @param {Number} z The z coordinate of the cell. */ MarkerManager.prototype.addCellMarkers_ = function(x, y, z) { this.processCellMarkers_(x, y, z, this.addOverlay_); }; /** * Use the rectangleDiffCoords function to process all grid cells * that are in bounds1 but not bounds2, using a callback, and using * the current MarkerManager object as the instance. * * Pass the z parameter to the callback in addition to x and y. * * @param {Bounds} bounds1 The bounds of all points we may process. * @param {Bounds} bounds2 The bounds of points to exclude. * @param {Function} callback The callback function to call * for each grid coordinate (x, y, z). */ MarkerManager.prototype.rectangleDiff_ = function(bounds1, bounds2, callback) { var me = this; me.rectangleDiffCoords(bounds1, bounds2, function(x, y) { callback.apply(me, [x, y, bounds1.z]); }); }; /** * Calls the function for all points in bounds1, not in bounds2 * * @param {Bounds} bounds1 The bounds of all points we may process. * @param {Bounds} bounds2 The bounds of points to exclude. * @param {Function} callback The callback function to call * for each grid coordinate. */ MarkerManager.prototype.rectangleDiffCoords = function(bounds1, bounds2, callback) { var minX1 = bounds1.minX; var minY1 = bounds1.minY; var maxX1 = bounds1.maxX; var maxY1 = bounds1.maxY; var minX2 = bounds2.minX; var minY2 = bounds2.minY; var maxX2 = bounds2.maxX; var maxY2 = bounds2.maxY; for (var x = minX1; x <= maxX1; x++) { // All x in R1 // All above: for (var y = minY1; y <= maxY1 && y < minY2; y++) { // y in R1 above R2 callback(x, y); } // All below: for (var y = Math.max(maxY2 + 1, minY1); // y in R1 below R2 y <= maxY1; y++) { callback(x, y); } } for (var y = Math.max(minY1, minY2); y <= Math.min(maxY1, maxY2); y++) { // All y in R2 and in R1 // Strictly left: for (var x = Math.min(maxX1 + 1, minX2) - 1; x >= minX1; x--) { // x in R1 left of R2 callback(x, y); } // Strictly right: for (var x = Math.max(minX1, maxX2 + 1); // x in R1 right of R2 x <= maxX1; x++) { callback(x, y); } } }; /** * Removes value from array. O(N). * * @param {Array} array The array to modify. * @param {any} value The value to remove. * @param {Boolean} opt_notype Flag to disable type checking in equality. * @return {Number} The number of instances of value that were removed. */ MarkerManager.prototype.removeFromArray = function(array, value, opt_notype) { var shift = 0; for (var i = 0; i < array.length; ++i) { if (array[i] === value || (opt_notype && array[i] == value)) { array.splice(i--, 1); shift++; } } return shift; }; function PanoramioLayerCallback(json, panoLayer) { this.panoLayer = panoLayer; var batch = []; for (var i = 0; i < json.photos.length; i++) { var photo = json.photos[i]; if (!panoLayer.ids[photo.photo_id]) { var marker = this.createMarker(photo, panoLayer.markerIcon); panoLayer.mgr.addMarker(marker, 0); panoLayer.ids[photo.photo_id] = "exists"; } } // panoLayer.mgr.addMarkers(batch, 0); panoLayer.mgr.addMarkers(batch, panoLayer.map.getZoom()); panoLayer.mgr.refresh(); } PanoramioLayerCallback.prototype.formImgUrl = function(photoId, imgType) { return 'http://www.panoramio.com/photos/' + imgType + '/' + photoId + '.jpg'; } PanoramioLayerCallback.prototype.formPageUrl = function(photoId) { return 'http://www.panoramio.com/photo/' + photoId; } PanoramioLayerCallback.prototype.createMarker = function(photo, baseIcon) { var me = this; var markerIcon = new GIcon(baseIcon); markerIcon.image = this.formImgUrl(photo.photo_id, "mini_square"); var marker = new GMarker(new GLatLng(photo.latitude, photo.longitude), {icon: markerIcon, title: photo.photo_title}); if (photo.photo_title.length > 33) { photo.photo_title = photo.photo_title.substring(0, 33) + "…"; } var html = "
" + "

" + "Panoramio logo<\/a>

" + "" + "<\/a>" + "
" + "

" + photo.photo_title + "<\/strong><\/a>

" + "

Postad av: " + photo.owner_name + "<\/a>

<\/div>" + "<\/div>"; marker.html = html; GEvent.addListener(marker, "click", function() { me.panoLayer.map.openInfoWindow(marker.getLatLng(), marker.html, {noCloseOnClick: true}); }); return marker; } function PanoramioLayer(map, opt_opts) { var me = this; me.map = map; me.ids = {}; me.mgr = new MarkerManager(map, {maxZoom: 19}); var icon = new GIcon(); icon.image = "http://www.panoramio.com/img/panoramio-marker.png"; icon.shadow = ""; icon.iconSize = new GSize(24, 24); icon.shadowSize = new GSize(22, 22); icon.iconAnchor = new GPoint(9, 9); icon.infoWindowAnchor = new GPoint(9, 0); me.markerIcon = icon; me.enabled = false; GEvent.addListener(map, "moveend", function() { if (me.enabled) { var bounds = map.getBounds(); var southWest = bounds.getSouthWest(); var northEast = bounds.getNorthEast(); me.load(me, {maxy: northEast.lat(), miny: southWest.lat(), maxx: northEast.lng(), minx: southWest.lng()}); } }); } PanoramioLayer.prototype.enable = function() { this.enabled = true; GEvent.trigger(map, "moveend"); } PanoramioLayer.prototype.disable = function() { this.enabled = false; this.mgr.clearMarkers(); this.ids = {}; } PanoramioLayer.prototype.getEnabled = function() { return this.enabled; } PanoramioLayer.prototype.load = function(panoLayer, userOptions) { var options = { order: "popularity", set: "public", from: "0", to: "50", minx: "-180", miny: "-90", maxx: "180", maxy: "90", size: "small" }; for (optionName in userOptions) { if (userOptions.hasOwnProperty(optionName)) { options[optionName] = userOptions[optionName]; } } var url = "http://www.panoramio.com/map/get_panoramas.php?"; var uniqueID = ""; for (optionName in options) { if (options.hasOwnProperty(optionName)) { var optionVal = "" + options[optionName] + ""; url += optionName + "=" + optionVal + "&"; uniqueID += optionVal.replace(/[^\w]+/g,""); } } var callbackName = "PanoramioLayerCallback.loader" + uniqueID; //ask dion eval(callbackName + " = function(json) { var pa = new PanoramioLayerCallback(json, panoLayer);}"); var script = document.createElement('script'); script.setAttribute('src', url + 'callback=' + callbackName); script.setAttribute('id', 'jsonScript'); script.setAttribute('type', 'text/javascript'); document.documentElement.firstChild.appendChild(script); } /* * PanoMapTypeControl Class * Copyright (c) 2007, Google * Author: Pamela Fox, others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This class lets you add a control to the map which mimics GMapTypeControl * and allows for the addition of a traffic button/traffic key. */ /* * Constructor for PanoMapTypeControl */ function PanoMapTypeControl(opt_opts) { this.options = opt_opts || {}; } PanoMapTypeControl.prototype = new GControl(); /** * Is called by GMap2's addOverlay method. Creates the button * and appends to the map div. * @param {GMap2} map The map that has had this PanoMapTypeControl added to it. * @return {DOM Object} Div that holds the control */ PanoMapTypeControl.prototype.initialize = function(map) { var container = document.createElement("div"); var me = this; var panoDiv = me.createButton_("Visa bilder"); panoDiv.style.marginRight = "8px"; GEvent.addDomListener(panoDiv, "click", function() { if (me.panoLayer) { if (me.panoLayer.getEnabled()) { me.panoLayer.disable(); } else { me.panoLayer.enable(); } } else { me.panoLayer = new PanoramioLayer(map); me.panoLayer.enable(); } me.toggleButton_(panoDiv.firstChild, me.panoLayer.getEnabled()); }); me.toggleButton_(panoDiv.firstChild, false); var mapDiv = me.createButton_("Karta"); var satDiv = me.createButton_("Satellit"); var hybDiv = me.createButton_("Hybrid"); var fysDiv = me.createButton_("Fysisk"); me.assignButtonEvent_(mapDiv, map, G_NORMAL_MAP, [satDiv, hybDiv, fysDiv]); me.assignButtonEvent_(satDiv, map, G_SATELLITE_MAP, [mapDiv, hybDiv, fysDiv]); me.assignButtonEvent_(hybDiv, map, G_HYBRID_MAP, [satDiv, mapDiv, fysDiv]); me.assignButtonEvent_(fysDiv, map, G_PHYSICAL_MAP, [satDiv, hybDiv, mapDiv]); GEvent.addListener(map, "maptypechanged", function() { if (map.getCurrentMapType() == G_NORMAL_MAP) { GEvent.trigger(mapDiv, "click"); } else if (map.getCurrentMapType() == G_SATELLITE_MAP) { GEvent.trigger(satDiv, "click"); } else if (map.getCurrentMapType() == G_HYBRID_MAP) { GEvent.trigger(hybDiv, "click"); }else if (map.getCurrentMapType() == G_PHYSICAL_MAP) { GEvent.trigger(fysDiv, "click"); } }); container.appendChild(panoDiv); container.appendChild(fysDiv); container.appendChild(mapDiv); container.appendChild(satDiv); container.appendChild(hybDiv); map.getContainer().appendChild(container); GEvent.trigger(map, "maptypechanged"); return container; } /* * Creates simple buttons with text nodes. * @param {String} text Text to display in button * @return {DOM Object} The div for the button. */ PanoMapTypeControl.prototype.createButton_ = function(text) { var buttonDiv = document.createElement("div"); this.setButtonStyle_(buttonDiv); buttonDiv.style.cssFloat = "left"; buttonDiv.style.styleFloat = "left"; var textDiv = document.createElement("div"); textDiv.appendChild(document.createTextNode(text)); textDiv.style.width = "6em"; buttonDiv.appendChild(textDiv); return buttonDiv; } /* * Assigns events to MapType buttons to change maptype * and toggle button styles correctly for all buttons * when button is clicked. * @param {DOM Object} div Button's div to assign click to * @param {GMap2} Map object to change maptype of. * @param {Object} mapType GMapType to change map to when clicked * @param {Array} otherDivs Array of other button divs to toggle off */ PanoMapTypeControl.prototype.assignButtonEvent_ = function(div, map, mapType, otherDivs) { var me = this; GEvent.addDomListener(div, "click", function() { for (var i = 0; i < otherDivs.length; i++) { me.toggleButton_(otherDivs[i].firstChild, false); } me.toggleButton_(div.firstChild, true); map.setMapType(mapType); }); } /* * Changes style of button to appear on/off depending on boolean passed in. * @param {DOM Object} div Button div to change style of * @param {Boolean} boolCheck Used to decide to use on style or off style */ PanoMapTypeControl.prototype.toggleButton_ = function(div, boolCheck) { div.style.fontWeight = boolCheck ? "bold" : ""; div.style.border = "1px solid white"; var shadows = boolCheck ? ["Top", "Left"] : ["Bottom", "Right"]; for (var j = 0; j < shadows.length; j++) { div.style["border" + shadows[j]] = "1px solid #b0b0b0"; } } /* * Required by GMaps API for controls. * @return {GControlPosition} Default location for control */ PanoMapTypeControl.prototype.getDefaultPosition = function() { return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 7)); } /* * Sets the proper CSS for the given button element. * @param {DOM Object} button Button div to set style for */ PanoMapTypeControl.prototype.setButtonStyle_ = function(button) { button.style.color = "#000000"; button.style.backgroundColor = "white"; button.style.font = "small Arial"; button.style.border = "1px solid black"; button.style.padding = "0px"; button.style.margin= "0px"; button.style.textAlign = "center"; button.style.fontSize = "12px"; button.style.cursor = "pointer"; }