diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-11-17 11:17:57 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-11-17 11:17:57 +0100 |
commit | 79359e9cc47bab55a552c35d0951cdc325e0d933 (patch) | |
tree | e18ef952cb057dd04f7f04734a47ab835b6ebe5c | |
parent | 93e740521986afe34c8ade146b95d8f8e95a5fd8 (diff) | |
download | Chimère-79359e9cc47bab55a552c35d0951cdc325e0d933.tar.bz2 Chimère-79359e9cc47bab55a552c35d0951cdc325e0d933.zip |
Add ol3-layerswitcher
-rw-r--r-- | chimere/static/ol3-layerswitcher/README.md | 95 | ||||
-rw-r--r-- | chimere/static/ol3-layerswitcher/ol3-layerswitcher.css | 92 | ||||
-rw-r--r-- | chimere/static/ol3-layerswitcher/ol3-layerswitcher.js | 283 |
3 files changed, 470 insertions, 0 deletions
diff --git a/chimere/static/ol3-layerswitcher/README.md b/chimere/static/ol3-layerswitcher/README.md new file mode 100644 index 0000000..13857b8 --- /dev/null +++ b/chimere/static/ol3-layerswitcher/README.md @@ -0,0 +1,95 @@ +# OpenLayers 3 LayerSwitcher + +Grouped layer list control for an OL3 map. + +All layers should have a `title` property and base layers should have a `type` property set to `base`. Group layers (`ol.layer.Group`) can be used to visually group layers together. See [examples/layerswitcher.js](examples/layerswitcher.js) for usage. + +## Examples + +The examples demonstrate usage and can be viewed online thanks to [RawGit](http://rawgit.com/): + +* [Basic usage](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/layerswitcher.html) + * Create a layer switcher control. Each layer to be displayed in the layer switcher has a `title` property as does each Group; each base map layer has a `type: 'base'` property. +* [Add layer](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/addlayer.html) + * Add a layer to an existing layer group after the layer switcher has been added to the map. +* [Scrolling](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/scroll.html) + * Demonstrate the panel scrolling vertically, control the height of the layer switcher by setting the `max-height` (see [examples/scroll.css](examples/scroll.css)) and it's position relative to the bottom of the map (see the `.layer-switcher.shown` selector in [src/ol3-layerswitcher.css](src/ol3-layerswitcher.css)). + +The source for all examples can be found in [examples](examples). + +## Tests + +To run the tests you'll need to install the dependencies via `npm`. In the root of the repository run: + + npm install + +Then run the tests by opening [test/index.html](test/index.html) in a browser. + +## API + +### `new ol.control.LayerSwitcher(opt_options)` + +OpenLayers 3 Layer Switcher Control. +See [the examples](./examples) for usage. + +#### Parameters: + +|Name|Type|Description| +|:---|:---|:----------| +|`opt_options`|`Object`| Control options, extends olx.control.ControlOptions adding: **`tipLabel`** `String` - the button tooltip. | + +#### Extends + +`ol.control.Control` + +#### Methods + +##### `showPanel()` + +Show the layer panel. + +##### `hidePanel()` + +Hide the layer panel. + +##### `renderPanel()` + +Re-draw the layer panel to represent the current state of the layers. + +##### `setMap(map)` + +Set the map instance the control is associated with. + +###### Parameters: + +|Name|Type|Description| +|:---|:---|:----------| +|`map`|`ol.Map`| The map instance. | + + +##### `(static) ol.control.LayerSwitcher.forEachRecursive(lyr,fn)` + +**Static** Call the supplied function for each layer in the passed layer group +recursing nested groups. + +###### Parameters: + +|Name|Type|Description| +|:---|:---|:----------| +|`lyr`|`ol.layer.Group`| The layer group to start iterating from. | +|`fn`|`function`| Callback which will be called for each `ol.layer.Base` found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach` | + + +##### `(static) ol.control.LayerSwitcher.uuid()` + +Generate a UUID + +## License + +MIT (c) Matt Walker. + +## Also see + +If you find the layer switcher useful you might also like the +[ol3-popup](https://github.com/walkermatt/ol3-popup). + diff --git a/chimere/static/ol3-layerswitcher/ol3-layerswitcher.css b/chimere/static/ol3-layerswitcher/ol3-layerswitcher.css new file mode 100644 index 0000000..088abd9 --- /dev/null +++ b/chimere/static/ol3-layerswitcher/ol3-layerswitcher.css @@ -0,0 +1,92 @@ +.layer-switcher.shown.ol-control { + background-color: transparent; +} + +.layer-switcher.shown.ol-control:hover { + background-color: transparent; +} + +.layer-switcher { + position: absolute; + top: 3.5em; + right: 0.5em; + text-align: left; +} + +.layer-switcher.shown { + bottom: 3em; +} + +.layer-switcher .panel { + padding: 0 1em 0 0; + margin: 0; + border: 4px solid #eee; + border-radius: 4px; + background-color: white; + display: none; + max-height: 100%; + overflow-y: auto; +} + +.layer-switcher.shown .panel { + display: block; +} + +.layer-switcher button { + float: right; + width: 38px; + height: 38px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACE1BMVEX///8A//8AgICA//8AVVVAQID///8rVVVJtttgv98nTmJ2xNgkW1ttyNsmWWZmzNZYxM4gWGgeU2JmzNNr0N1Rwc0eU2VXxdEhV2JqytQeVmMhVmNoydUfVGUgVGQfVGQfVmVqy9hqy9dWw9AfVWRpydVry9YhVmMgVGNUw9BrytchVWRexdGw294gVWQgVmUhVWPd4N6HoaZsy9cfVmQgVGRrytZsy9cgVWQgVWMgVWRsy9YfVWNsy9YgVWVty9YgVWVry9UgVWRsy9Zsy9UfVWRsy9YgVWVty9YgVWRty9Vsy9aM09sgVWRTws/AzM0gVWRtzNYgVWRuy9Zsy9cgVWRGcHxty9bb5ORbxdEgVWRty9bn6OZTws9mydRfxtLX3Nva5eRix9NFcXxOd4JPeINQeIMiVmVUws9Vws9Vw9BXw9BYxNBaxNBbxNBcxdJexdElWWgmWmhjyNRlx9IqXGtoipNpytVqytVryNNrytZsjZUuX210k5t1y9R2zNR3y9V4lp57zth9zdaAnKOGoaeK0NiNpquV09mesrag1tuitbmj1tuj19uktrqr2d2svcCu2d2xwMO63N+7x8nA3uDC3uDFz9DK4eHL4eLN4eIyYnDX5OM5Z3Tb397e4uDf4uHf5uXi5ePi5+Xj5+Xk5+Xm5+Xm6OY6aHXQ19fT4+NfhI1Ww89gx9Nhx9Nsy9ZWw9Dpj2abAAAAWnRSTlMAAQICAwQEBgcIDQ0ODhQZGiAiIyYpKywvNTs+QklPUlNUWWJjaGt0dnd+hIWFh4mNjZCSm6CpsbW2t7nDzNDT1dje5efr7PHy9PT29/j4+Pn5+vr8/f39/f6DPtKwAAABTklEQVR4Xr3QVWPbMBSAUTVFZmZmhhSXMjNvkhwqMzMzMzPDeD+xASvObKePPa+ffHVl8PlsnE0+qPpBuQjVJjno6pZpSKXYl7/bZyFaQxhf98hHDKEppwdWIW1frFnrxSOWHFfWesSEWC6R/P4zOFrix3TzDFLlXRTR8c0fEEJ1/itpo7SVO9Jdr1DVxZ0USyjZsEY5vZfiiAC0UoTGOrm9PZLuRl8X+Dq1HQtoFbJZbv61i+Poblh/97TC7n0neCcK0ETNUrz1/xPHf+DNAW9Ac6t8O8WH3Vp98f5lCaYKAOFZMLyHL4Y0fe319idMNgMMp+zWVSybUed/+/h7I4wRAG1W6XDy4XmjR9HnzvDRZXUAYDFOhC1S/Hh+fIXxen+eO+AKqbs+wAo30zDTDvDxKoJN88sjUzDFAvBzEUGFsnADoIvAJzoh2BZ8sner+Ke/vwECuQAAAABJRU5ErkJggg==') /*logo.png*/; + background-repeat: no-repeat; + background-position: 2px; + background-color: white; + border: none; +} + +.layer-switcher.shown button { + display: none; +} + +.layer-switcher button:focus, .layer-switcher button:hover { + background-color: white; +} + +.layer-switcher ul { + padding-left: 1em; + list-style: none; +} + +.layer-switcher li.group { + padding-top: 5px; +} + +.layer-switcher li.group > label { + font-weight: bold; +} + +.layer-switcher li.layer { + display: table; +} + +.layer-switcher li.layer label, .layer-switcher li.layer input { + display: table-cell; + vertical-align: sub; +} + +.layer-switcher input { + margin: 4px; +} + +.layer-switcher.touch ::-webkit-scrollbar { + width: 4px; +} + +.layer-switcher.touch ::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + border-radius: 10px; +} + +.layer-switcher.touch ::-webkit-scrollbar-thumb { + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); +} diff --git a/chimere/static/ol3-layerswitcher/ol3-layerswitcher.js b/chimere/static/ol3-layerswitcher/ol3-layerswitcher.js new file mode 100644 index 0000000..ee44e2f --- /dev/null +++ b/chimere/static/ol3-layerswitcher/ol3-layerswitcher.js @@ -0,0 +1,283 @@ +/** + * OpenLayers 3 Layer Switcher Control. + * See [the examples](./examples) for usage. + * @constructor + * @extends {ol.control.Control} + * @param {Object} opt_options Control options, extends olx.control.ControlOptions adding: + * **`tipLabel`** `String` - the button tooltip. + */ +ol.control.LayerSwitcher = function(opt_options) { + + var options = opt_options || {}; + + var tipLabel = options.tipLabel ? + options.tipLabel : 'Legend'; + + this.mapListeners = []; + + this.hiddenClassName = 'ol-unselectable ol-control layer-switcher'; + if (ol.control.LayerSwitcher.isTouchDevice_()) { + this.hiddenClassName += ' touch'; + } + this.shownClassName = this.hiddenClassName + ' shown'; + + var element = document.createElement('div'); + element.className = this.hiddenClassName; + + var button = document.createElement('button'); + button.setAttribute('title', tipLabel); + element.appendChild(button); + + this.panel = document.createElement('div'); + this.panel.className = 'panel'; + element.appendChild(this.panel); + ol.control.LayerSwitcher.enableTouchScroll_(this.panel); + + var this_ = this; + + button.onmouseover = function(e) { + this_.showPanel(); + }; + + button.onclick = function(e) { + e = e || window.event; + this_.showPanel(); + e.preventDefault(); + }; + + this_.panel.onmouseout = function(e) { + e = e || window.event; + if (!this_.panel.contains(e.toElement || e.relatedTarget)) { + this_.hidePanel(); + } + }; + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + +}; + +ol.inherits(ol.control.LayerSwitcher, ol.control.Control); + +/** + * Show the layer panel. + */ +ol.control.LayerSwitcher.prototype.showPanel = function() { + if (this.element.className != this.shownClassName) { + this.element.className = this.shownClassName; + this.renderPanel(); + } +}; + +/** + * Hide the layer panel. + */ +ol.control.LayerSwitcher.prototype.hidePanel = function() { + if (this.element.className != this.hiddenClassName) { + this.element.className = this.hiddenClassName; + } +}; + +/** + * Re-draw the layer panel to represent the current state of the layers. + */ +ol.control.LayerSwitcher.prototype.renderPanel = function() { + + this.ensureTopVisibleBaseLayerShown_(); + + while(this.panel.firstChild) { + this.panel.removeChild(this.panel.firstChild); + } + + var ul = document.createElement('ul'); + this.panel.appendChild(ul); + this.renderLayers_(this.getMap(), ul); + +}; + +/** + * Set the map instance the control is associated with. + * @param {ol.Map} map The map instance. + */ +ol.control.LayerSwitcher.prototype.setMap = function(map) { + // Clean up listeners associated with the previous map + for (var i = 0, key; i < this.mapListeners.length; i++) { + this.getMap().unByKey(this.mapListeners[i]); + } + this.mapListeners.length = 0; + // Wire up listeners etc. and store reference to new map + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + var this_ = this; + this.mapListeners.push(map.on('pointerdown', function() { + this_.hidePanel(); + })); + this.renderPanel(); + } +}; + +/** + * Ensure only the top-most base layer is visible if more than one is visible. + * @private + */ +ol.control.LayerSwitcher.prototype.ensureTopVisibleBaseLayerShown_ = function() { + var lastVisibleBaseLyr; + ol.control.LayerSwitcher.forEachRecursive(this.getMap(), function(l, idx, a) { + if (l.get('type') === 'base' && l.getVisible()) { + lastVisibleBaseLyr = l; + } + }); + if (lastVisibleBaseLyr) this.setVisible_(lastVisibleBaseLyr, true); +}; + +/** + * Toggle the visible state of a layer. + * Takes care of hiding other layers in the same exclusive group if the layer + * is toggle to visible. + * @private + * @param {ol.layer.Base} The layer whos visibility will be toggled. + */ +ol.control.LayerSwitcher.prototype.setVisible_ = function(lyr, visible) { + var map = this.getMap(); + lyr.setVisible(visible); + if (visible && lyr.get('type') === 'base') { + // Hide all other base layers regardless of grouping + ol.control.LayerSwitcher.forEachRecursive(map, function(l, idx, a) { + if (l != lyr && l.get('type') === 'base') { + l.setVisible(false); + } + }); + } +}; + +/** + * Render all layers that are children of a group. + * @private + * @param {ol.layer.Base} lyr Layer to be rendered (should have a title property). + * @param {Number} idx Position in parent group list. + */ +ol.control.LayerSwitcher.prototype.renderLayer_ = function(lyr, idx) { + + var this_ = this; + + var li = document.createElement('li'); + + var lyrTitle = lyr.get('title'); + var lyrId = ol.control.LayerSwitcher.uuid(); + + var label = document.createElement('label'); + + if (lyr.getLayers && !lyr.get('combine')) { + + li.className = 'group'; + label.innerHTML = lyrTitle; + li.appendChild(label); + var ul = document.createElement('ul'); + li.appendChild(ul); + + this.renderLayers_(lyr, ul); + + } else { + + li.className = 'layer'; + var input = document.createElement('input'); + if (lyr.get('type') === 'base') { + input.type = 'radio'; + input.name = 'base'; + } else { + input.type = 'checkbox'; + } + input.id = lyrId; + input.checked = lyr.get('visible'); + input.onchange = function(e) { + this_.setVisible_(lyr, e.target.checked); + }; + li.appendChild(input); + + label.htmlFor = lyrId; + label.innerHTML = lyrTitle; + li.appendChild(label); + + } + + return li; + +}; + +/** + * Render all layers that are children of a group. + * @private + * @param {ol.layer.Group} lyr Group layer whos children will be rendered. + * @param {Element} elm DOM element that children will be appended to. + */ +ol.control.LayerSwitcher.prototype.renderLayers_ = function(lyr, elm) { + var lyrs = lyr.getLayers().getArray().slice().reverse(); + for (var i = 0, l; i < lyrs.length; i++) { + l = lyrs[i]; + if (l.get('title')) { + elm.appendChild(this.renderLayer_(l, i)); + } + } +}; + +/** + * **Static** Call the supplied function for each layer in the passed layer group + * recursing nested groups. + * @param {ol.layer.Group} lyr The layer group to start iterating from. + * @param {Function} fn Callback which will be called for each `ol.layer.Base` + * found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach` + */ +ol.control.LayerSwitcher.forEachRecursive = function(lyr, fn) { + lyr.getLayers().forEach(function(lyr, idx, a) { + fn(lyr, idx, a); + if (lyr.getLayers) { + ol.control.LayerSwitcher.forEachRecursive(lyr, fn); + } + }); +}; + +/** + * Generate a UUID + * @returns {String} UUID + * + * Adapted from http://stackoverflow.com/a/2117523/526860 + */ +ol.control.LayerSwitcher.uuid = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); +} + +/** +* @private +* @desc Apply workaround to enable scrolling of overflowing content within an +* element. Adapted from https://gist.github.com/chrismbarr/4107472 +*/ +ol.control.LayerSwitcher.enableTouchScroll_ = function(elm) { + if(ol.control.LayerSwitcher.isTouchDevice_()){ + var scrollStartPos = 0; + elm.addEventListener("touchstart", function(event) { + scrollStartPos = this.scrollTop + event.touches[0].pageY; + }, false); + elm.addEventListener("touchmove", function(event) { + this.scrollTop = scrollStartPos - event.touches[0].pageY; + }, false); + } +}; + +/** + * @private + * @desc Determine if the current browser supports touch events. Adapted from + * https://gist.github.com/chrismbarr/4107472 + */ +ol.control.LayerSwitcher.isTouchDevice_ = function() { + try { + document.createEvent("TouchEvent"); + return true; + } catch(e) { + return false; + } +}; |