Make all attempts to get a URL for an image go through a single routine, OpenLayers.Util.getImageLocation, which is given the actual image name. This allows users to replace that routine with an enhanced version which can rewrite the full URL, for example to go through the rails asset pipeline when using OpenLayers with rails.
627 lines
19 KiB
JavaScript
627 lines
19 KiB
JavaScript
/* 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/Control.js
|
|
* @requires OpenLayers/Lang.js
|
|
* @requires OpenLayers/Console.js
|
|
*/
|
|
|
|
/**
|
|
* Class: OpenLayers.Control.LayerSwitcher
|
|
* The LayerSwitcher control displays a table of contents for the map. This
|
|
* allows the user interface to switch between BaseLasyers and to show or hide
|
|
* Overlays. By default the switcher is shown minimized on the right edge of
|
|
* the map, the user may expand it by clicking on the handle.
|
|
*
|
|
* To create the LayerSwitcher outside of the map, pass the Id of a html div
|
|
* as the first argument to the constructor.
|
|
*
|
|
* Inherits from:
|
|
* - <OpenLayers.Control>
|
|
*/
|
|
OpenLayers.Control.LayerSwitcher =
|
|
OpenLayers.Class(OpenLayers.Control, {
|
|
|
|
/**
|
|
* APIProperty: roundedCorner
|
|
* {Boolean} If true the Rico library is used for rounding the corners
|
|
* of the layer switcher div, defaults to false. *Deprecated*. Use
|
|
* CSS3's border-radius instead. If this option is set to true the
|
|
* Rico/Corner.js script must be loaded in the page, and therefore
|
|
* listed in the build profile.
|
|
*
|
|
*/
|
|
roundedCorner: false,
|
|
|
|
/**
|
|
* APIProperty: roundedCornerColor
|
|
* {String} The color of the rounded corners, only applies if roundedCorner
|
|
* is true, defaults to "darkblue".
|
|
*/
|
|
roundedCornerColor: "darkblue",
|
|
|
|
/**
|
|
* Property: layerStates
|
|
* {Array(Object)} Basically a copy of the "state" of the map's layers
|
|
* the last time the control was drawn. We have this in order to avoid
|
|
* unnecessarily redrawing the control.
|
|
*/
|
|
layerStates: null,
|
|
|
|
|
|
// DOM Elements
|
|
|
|
/**
|
|
* Property: layersDiv
|
|
* {DOMElement}
|
|
*/
|
|
layersDiv: null,
|
|
|
|
/**
|
|
* Property: baseLayersDiv
|
|
* {DOMElement}
|
|
*/
|
|
baseLayersDiv: null,
|
|
|
|
/**
|
|
* Property: baseLayers
|
|
* {Array(<OpenLayers.Layer>)}
|
|
*/
|
|
baseLayers: null,
|
|
|
|
|
|
/**
|
|
* Property: dataLbl
|
|
* {DOMElement}
|
|
*/
|
|
dataLbl: null,
|
|
|
|
/**
|
|
* Property: dataLayersDiv
|
|
* {DOMElement}
|
|
*/
|
|
dataLayersDiv: null,
|
|
|
|
/**
|
|
* Property: dataLayers
|
|
* {Array(<OpenLayers.Layer>)}
|
|
*/
|
|
dataLayers: null,
|
|
|
|
|
|
/**
|
|
* Property: minimizeDiv
|
|
* {DOMElement}
|
|
*/
|
|
minimizeDiv: null,
|
|
|
|
/**
|
|
* Property: maximizeDiv
|
|
* {DOMElement}
|
|
*/
|
|
maximizeDiv: null,
|
|
|
|
/**
|
|
* APIProperty: ascending
|
|
* {Boolean}
|
|
*/
|
|
ascending: true,
|
|
|
|
/**
|
|
* Constructor: OpenLayers.Control.LayerSwitcher
|
|
*
|
|
* Parameters:
|
|
* options - {Object}
|
|
*/
|
|
initialize: function(options) {
|
|
OpenLayers.Control.prototype.initialize.apply(this, arguments);
|
|
this.layerStates = [];
|
|
|
|
if(this.roundedCorner) {
|
|
OpenLayers.Console.warn('roundedCorner option is deprecated');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: destroy
|
|
*/
|
|
destroy: function() {
|
|
|
|
OpenLayers.Event.stopObservingElement(this.div);
|
|
|
|
OpenLayers.Event.stopObservingElement(this.minimizeDiv);
|
|
OpenLayers.Event.stopObservingElement(this.maximizeDiv);
|
|
|
|
//clear out layers info and unregister their events
|
|
this.clearLayersArray("base");
|
|
this.clearLayersArray("data");
|
|
|
|
this.map.events.un({
|
|
"addlayer": this.redraw,
|
|
"changelayer": this.redraw,
|
|
"removelayer": this.redraw,
|
|
"changebaselayer": this.redraw,
|
|
scope: this
|
|
});
|
|
|
|
OpenLayers.Control.prototype.destroy.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Method: setMap
|
|
*
|
|
* Properties:
|
|
* map - {<OpenLayers.Map>}
|
|
*/
|
|
setMap: function(map) {
|
|
OpenLayers.Control.prototype.setMap.apply(this, arguments);
|
|
|
|
this.map.events.on({
|
|
"addlayer": this.redraw,
|
|
"changelayer": this.redraw,
|
|
"removelayer": this.redraw,
|
|
"changebaselayer": this.redraw,
|
|
scope: this
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Method: draw
|
|
*
|
|
* Returns:
|
|
* {DOMElement} A reference to the DIV DOMElement containing the
|
|
* switcher tabs.
|
|
*/
|
|
draw: function() {
|
|
OpenLayers.Control.prototype.draw.apply(this);
|
|
|
|
// create layout divs
|
|
this.loadContents();
|
|
|
|
// set mode to minimize
|
|
if(!this.outsideViewport) {
|
|
this.minimizeControl();
|
|
}
|
|
|
|
// populate div with current info
|
|
this.redraw();
|
|
|
|
return this.div;
|
|
},
|
|
|
|
/**
|
|
* Method: clearLayersArray
|
|
* User specifies either "base" or "data". we then clear all the
|
|
* corresponding listeners, the div, and reinitialize a new array.
|
|
*
|
|
* Parameters:
|
|
* layersType - {String}
|
|
*/
|
|
clearLayersArray: function(layersType) {
|
|
var layers = this[layersType + "Layers"];
|
|
if (layers) {
|
|
for(var i=0, len=layers.length; i<len ; i++) {
|
|
var layer = layers[i];
|
|
OpenLayers.Event.stopObservingElement(layer.inputElem);
|
|
OpenLayers.Event.stopObservingElement(layer.labelSpan);
|
|
}
|
|
}
|
|
this[layersType + "LayersDiv"].innerHTML = "";
|
|
this[layersType + "Layers"] = [];
|
|
},
|
|
|
|
|
|
/**
|
|
* Method: checkRedraw
|
|
* Checks if the layer state has changed since the last redraw() call.
|
|
*
|
|
* Returns:
|
|
* {Boolean} The layer state changed since the last redraw() call.
|
|
*/
|
|
checkRedraw: function() {
|
|
var redraw = false;
|
|
if ( !this.layerStates.length ||
|
|
(this.map.layers.length != this.layerStates.length) ) {
|
|
redraw = true;
|
|
} else {
|
|
for (var i=0, len=this.layerStates.length; i<len; i++) {
|
|
var layerState = this.layerStates[i];
|
|
var layer = this.map.layers[i];
|
|
if ( (layerState.name != layer.name) ||
|
|
(layerState.inRange != layer.inRange) ||
|
|
(layerState.id != layer.id) ||
|
|
(layerState.visibility != layer.visibility) ) {
|
|
redraw = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return redraw;
|
|
},
|
|
|
|
/**
|
|
* Method: redraw
|
|
* Goes through and takes the current state of the Map and rebuilds the
|
|
* control to display that state. Groups base layers into a
|
|
* radio-button group and lists each data layer with a checkbox.
|
|
*
|
|
* Returns:
|
|
* {DOMElement} A reference to the DIV DOMElement containing the control
|
|
*/
|
|
redraw: function() {
|
|
//if the state hasn't changed since last redraw, no need
|
|
// to do anything. Just return the existing div.
|
|
if (!this.checkRedraw()) {
|
|
return this.div;
|
|
}
|
|
|
|
//clear out previous layers
|
|
this.clearLayersArray("base");
|
|
this.clearLayersArray("data");
|
|
|
|
var containsOverlays = false;
|
|
var containsBaseLayers = false;
|
|
|
|
// Save state -- for checking layer if the map state changed.
|
|
// We save this before redrawing, because in the process of redrawing
|
|
// we will trigger more visibility changes, and we want to not redraw
|
|
// and enter an infinite loop.
|
|
var len = this.map.layers.length;
|
|
this.layerStates = new Array(len);
|
|
for (var i=0; i <len; i++) {
|
|
var layer = this.map.layers[i];
|
|
this.layerStates[i] = {
|
|
'name': layer.name,
|
|
'visibility': layer.visibility,
|
|
'inRange': layer.inRange,
|
|
'id': layer.id
|
|
};
|
|
}
|
|
|
|
var layers = this.map.layers.slice();
|
|
if (!this.ascending) { layers.reverse(); }
|
|
for(var i=0, len=layers.length; i<len; i++) {
|
|
var layer = layers[i];
|
|
var baseLayer = layer.isBaseLayer;
|
|
|
|
if (layer.displayInLayerSwitcher) {
|
|
|
|
if (baseLayer) {
|
|
containsBaseLayers = true;
|
|
} else {
|
|
containsOverlays = true;
|
|
}
|
|
|
|
// only check a baselayer if it is *the* baselayer, check data
|
|
// layers if they are visible
|
|
var checked = (baseLayer) ? (layer == this.map.baseLayer)
|
|
: layer.getVisibility();
|
|
|
|
// create input element
|
|
var inputElem = document.createElement("input");
|
|
inputElem.id = this.id + "_input_" + layer.name;
|
|
inputElem.name = (baseLayer) ? this.id + "_baseLayers" : layer.name;
|
|
inputElem.type = (baseLayer) ? "radio" : "checkbox";
|
|
inputElem.value = layer.name;
|
|
inputElem.checked = checked;
|
|
inputElem.defaultChecked = checked;
|
|
|
|
if (!baseLayer && !layer.inRange) {
|
|
inputElem.disabled = true;
|
|
}
|
|
var context = {
|
|
'inputElem': inputElem,
|
|
'layer': layer,
|
|
'layerSwitcher': this
|
|
};
|
|
OpenLayers.Event.observe(inputElem, "mouseup",
|
|
OpenLayers.Function.bindAsEventListener(this.onInputClick,
|
|
context)
|
|
);
|
|
|
|
// create span
|
|
var labelSpan = document.createElement("span");
|
|
OpenLayers.Element.addClass(labelSpan, "labelSpan");
|
|
if (!baseLayer && !layer.inRange) {
|
|
labelSpan.style.color = "gray";
|
|
}
|
|
labelSpan.innerHTML = layer.name;
|
|
labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
|
|
: "baseline";
|
|
OpenLayers.Event.observe(labelSpan, "click",
|
|
OpenLayers.Function.bindAsEventListener(this.onInputClick,
|
|
context)
|
|
);
|
|
// create line break
|
|
var br = document.createElement("br");
|
|
|
|
|
|
var groupArray = (baseLayer) ? this.baseLayers
|
|
: this.dataLayers;
|
|
groupArray.push({
|
|
'layer': layer,
|
|
'inputElem': inputElem,
|
|
'labelSpan': labelSpan
|
|
});
|
|
|
|
|
|
var groupDiv = (baseLayer) ? this.baseLayersDiv
|
|
: this.dataLayersDiv;
|
|
groupDiv.appendChild(inputElem);
|
|
groupDiv.appendChild(labelSpan);
|
|
groupDiv.appendChild(br);
|
|
}
|
|
}
|
|
|
|
// if no overlays, dont display the overlay label
|
|
this.dataLbl.style.display = (containsOverlays) ? "" : "none";
|
|
|
|
// if no baselayers, dont display the baselayer label
|
|
this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";
|
|
|
|
return this.div;
|
|
},
|
|
|
|
/**
|
|
* Method:
|
|
* A label has been clicked, check or uncheck its corresponding input
|
|
*
|
|
* Parameters:
|
|
* e - {Event}
|
|
*
|
|
* Context:
|
|
* - {DOMElement} inputElem
|
|
* - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
|
|
* - {<OpenLayers.Layer>} layer
|
|
*/
|
|
|
|
onInputClick: function(e) {
|
|
|
|
if (!this.inputElem.disabled) {
|
|
if (this.inputElem.type == "radio") {
|
|
this.inputElem.checked = true;
|
|
this.layer.map.setBaseLayer(this.layer);
|
|
} else {
|
|
this.inputElem.checked = !this.inputElem.checked;
|
|
this.layerSwitcher.updateMap();
|
|
}
|
|
}
|
|
OpenLayers.Event.stop(e);
|
|
},
|
|
|
|
/**
|
|
* Method: onLayerClick
|
|
* Need to update the map accordingly whenever user clicks in either of
|
|
* the layers.
|
|
*
|
|
* Parameters:
|
|
* e - {Event}
|
|
*/
|
|
onLayerClick: function(e) {
|
|
this.updateMap();
|
|
},
|
|
|
|
|
|
/**
|
|
* Method: updateMap
|
|
* Cycles through the loaded data and base layer input arrays and makes
|
|
* the necessary calls to the Map object such that that the map's
|
|
* visual state corresponds to what the user has selected in
|
|
* the control.
|
|
*/
|
|
updateMap: function() {
|
|
|
|
// set the newly selected base layer
|
|
for(var i=0, len=this.baseLayers.length; i<len; i++) {
|
|
var layerEntry = this.baseLayers[i];
|
|
if (layerEntry.inputElem.checked) {
|
|
this.map.setBaseLayer(layerEntry.layer, false);
|
|
}
|
|
}
|
|
|
|
// set the correct visibilities for the overlays
|
|
for(var i=0, len=this.dataLayers.length; i<len; i++) {
|
|
var layerEntry = this.dataLayers[i];
|
|
layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Method: maximizeControl
|
|
* Set up the labels and divs for the control
|
|
*
|
|
* Parameters:
|
|
* e - {Event}
|
|
*/
|
|
maximizeControl: function(e) {
|
|
|
|
// set the div's width and height to empty values, so
|
|
// the div dimensions can be controlled by CSS
|
|
this.div.style.width = "";
|
|
this.div.style.height = "";
|
|
|
|
this.showControls(false);
|
|
|
|
if (e != null) {
|
|
OpenLayers.Event.stop(e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: minimizeControl
|
|
* Hide all the contents of the control, shrink the size,
|
|
* add the maximize icon
|
|
*
|
|
* Parameters:
|
|
* e - {Event}
|
|
*/
|
|
minimizeControl: function(e) {
|
|
|
|
// to minimize the control we set its div's width
|
|
// and height to 0px, we cannot just set "display"
|
|
// to "none" because it would hide the maximize
|
|
// div
|
|
this.div.style.width = "0px";
|
|
this.div.style.height = "0px";
|
|
|
|
this.showControls(true);
|
|
|
|
if (e != null) {
|
|
OpenLayers.Event.stop(e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: showControls
|
|
* Hide/Show all LayerSwitcher controls depending on whether we are
|
|
* minimized or not
|
|
*
|
|
* Parameters:
|
|
* minimize - {Boolean}
|
|
*/
|
|
showControls: function(minimize) {
|
|
|
|
this.maximizeDiv.style.display = minimize ? "" : "none";
|
|
this.minimizeDiv.style.display = minimize ? "none" : "";
|
|
|
|
this.layersDiv.style.display = minimize ? "none" : "";
|
|
},
|
|
|
|
/**
|
|
* Method: loadContents
|
|
* Set up the labels and divs for the control
|
|
*/
|
|
loadContents: function() {
|
|
|
|
//configure main div
|
|
|
|
OpenLayers.Event.observe(this.div, "mouseup",
|
|
OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
|
|
OpenLayers.Event.observe(this.div, "click",
|
|
this.ignoreEvent);
|
|
OpenLayers.Event.observe(this.div, "mousedown",
|
|
OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
|
|
OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
|
|
|
|
// layers list div
|
|
this.layersDiv = document.createElement("div");
|
|
this.layersDiv.id = this.id + "_layersDiv";
|
|
OpenLayers.Element.addClass(this.layersDiv, "layersDiv");
|
|
|
|
this.baseLbl = document.createElement("div");
|
|
this.baseLbl.innerHTML = OpenLayers.i18n("Base Layer");
|
|
OpenLayers.Element.addClass(this.baseLbl, "baseLbl");
|
|
|
|
this.baseLayersDiv = document.createElement("div");
|
|
OpenLayers.Element.addClass(this.baseLayersDiv, "baseLayersDiv");
|
|
|
|
this.dataLbl = document.createElement("div");
|
|
this.dataLbl.innerHTML = OpenLayers.i18n("Overlays");
|
|
OpenLayers.Element.addClass(this.dataLbl, "dataLbl");
|
|
|
|
this.dataLayersDiv = document.createElement("div");
|
|
OpenLayers.Element.addClass(this.dataLayersDiv, "dataLayersDiv");
|
|
|
|
if (this.ascending) {
|
|
this.layersDiv.appendChild(this.baseLbl);
|
|
this.layersDiv.appendChild(this.baseLayersDiv);
|
|
this.layersDiv.appendChild(this.dataLbl);
|
|
this.layersDiv.appendChild(this.dataLayersDiv);
|
|
} else {
|
|
this.layersDiv.appendChild(this.dataLbl);
|
|
this.layersDiv.appendChild(this.dataLayersDiv);
|
|
this.layersDiv.appendChild(this.baseLbl);
|
|
this.layersDiv.appendChild(this.baseLayersDiv);
|
|
}
|
|
|
|
this.div.appendChild(this.layersDiv);
|
|
|
|
if(this.roundedCorner) {
|
|
OpenLayers.Rico.Corner.round(this.div, {
|
|
corners: "tl bl",
|
|
bgColor: "transparent",
|
|
color: this.roundedCornerColor,
|
|
blend: false
|
|
});
|
|
OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
|
|
}
|
|
|
|
// maximize button div
|
|
var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
|
|
this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
|
|
"OpenLayers_Control_MaximizeDiv",
|
|
null,
|
|
null,
|
|
img,
|
|
"absolute");
|
|
OpenLayers.Element.addClass(this.maximizeDiv, "maximizeDiv");
|
|
this.maximizeDiv.style.display = "none";
|
|
OpenLayers.Event.observe(this.maximizeDiv, "click",
|
|
OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
|
|
);
|
|
|
|
this.div.appendChild(this.maximizeDiv);
|
|
|
|
// minimize button div
|
|
var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
|
|
this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
|
|
"OpenLayers_Control_MinimizeDiv",
|
|
null,
|
|
null,
|
|
img,
|
|
"absolute");
|
|
OpenLayers.Element.addClass(this.minimizeDiv, "minimizeDiv");
|
|
this.minimizeDiv.style.display = "none";
|
|
OpenLayers.Event.observe(this.minimizeDiv, "click",
|
|
OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
|
|
);
|
|
|
|
this.div.appendChild(this.minimizeDiv);
|
|
},
|
|
|
|
/**
|
|
* Method: ignoreEvent
|
|
*
|
|
* Parameters:
|
|
* evt - {Event}
|
|
*/
|
|
ignoreEvent: function(evt) {
|
|
OpenLayers.Event.stop(evt);
|
|
},
|
|
|
|
/**
|
|
* Method: mouseDown
|
|
* Register a local 'mouseDown' flag so that we'll know whether or not
|
|
* to ignore a mouseUp event
|
|
*
|
|
* Parameters:
|
|
* evt - {Event}
|
|
*/
|
|
mouseDown: function(evt) {
|
|
this.isMouseDown = true;
|
|
this.ignoreEvent(evt);
|
|
},
|
|
|
|
/**
|
|
* Method: mouseUp
|
|
* If the 'isMouseDown' flag has been set, that means that the drag was
|
|
* started from within the LayerSwitcher control, and thus we can
|
|
* ignore the mouseup. Otherwise, let the Event continue.
|
|
*
|
|
* Parameters:
|
|
* evt - {Event}
|
|
*/
|
|
mouseUp: function(evt) {
|
|
if (this.isMouseDown) {
|
|
this.isMouseDown = false;
|
|
this.ignoreEvent(evt);
|
|
}
|
|
},
|
|
|
|
CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
|
|
});
|