import ol_Object from 'ol/Object.js' import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js' import ol_Collection from 'ol/Collection.js' import {DEVICE_PIXEL_RATIO as ol_has_DEVICE_PIXEL_RATIO} from 'ol/has.js' import {toContext as ol_render_toContext} from 'ol/render.js' import {asString as ol_color_asString} from 'ol/color.js' import ol_Feature from 'ol/Feature.js' import ol_geom_Point from 'ol/geom/Point.js' import ol_geom_LineString from 'ol/geom/LineString.js'; import ol_geom_Polygon from 'ol/geom/Polygon.js' import {extend as ol_extent_extend} from 'ol/extent.js' import ol_ext_element from '../util/element.js' import ol_style_Text from 'ol/style/Text.js' import ol_style_Fill from 'ol/style/Fill.js' import ol_legend_Item from './Item.js' import ol_legend_Image from './Image.js' /** Legend class to draw features in a legend element * @constructor * @fires select * @fires refresh * @param {*} options * @param {String} options.title Legend title * @param {number} [options.maxWidth] maximum legend width * @param {ol.size} [options.size] Size of the symboles in the legend, default [40, 25] * @param {number} [options.margin=10] Size of the symbole's margin, default 10 * @param { ol.layer.Base } [layer] layer associated with the legend * @param { ol.style.Text} [options.textStyle='16px sans-serif'] a text style for the legend, default 16px sans-serif * @param { ol.style.Text} [options.titleStyle='bold 16px sans-serif'] a text style for the legend title, default textStyle + bold * @param { ol.style.Style | Array | ol.StyleFunction | undefined } options.style a style or a style function to use with features */ var ol_legend_Legend = class ollegendLegend extends ol_Object { constructor(options) { super() options = options || {} // Handle item collection this._items = new ol_Collection() var listeners = [] var tout this._items.on('add', function (e) { listeners.push({ item: e.element, on: e.element.on('change', function () { this.refresh() }.bind(this)) }) if (tout) { clearTimeout(tout) tout = null } tout = setTimeout(function () { this.refresh() }.bind(this), 0) }.bind(this)) this._items.on('remove', function (e) { for (var i = 0; i < listeners; i++) { if (e.element === listeners[i].item) { ol_Observable_unByKey(listeners[i].on) listeners.splice(i, 1) break } } if (tout) { clearTimeout(tout) tout = null } tout = setTimeout(function () { this.refresh() }.bind(this), 0) }.bind(this)) // List item element this._listElement = ol_ext_element.create('UL', { className: 'ol-legend' }) // Legend canvas this._canvas = document.createElement('canvas') // Set layer this.setLayer(options.layer) // Properties this.set('maxWidth', options.maxWidth, true) this.set('size', options.size || [40, 25], true) this.set('margin', options.margin === 0 ? 0 : options.margin || 10, true) this._textStyle = options.textStyle || new ol_style_Text({ font: '16px sans-serif', fill: new ol_style_Fill({ color: '#333' }), backgroundFill: new ol_style_Fill({ color: 'rgba(255,255,255,.8)' }) }) this._title = new ol_legend_Item({ title: options.title || '', className: 'ol-title' }) if (options.titleStyle) { this._titleStyle = options.titleStyle } else { this._titleStyle = this._textStyle.clone() this._titleStyle.setFont('bold ' + this._titleStyle.getFont()) } this.setStyle(options.style) if (options.items instanceof Array) { options.items.forEach(function (item) { this.addItem(item) }.bind(this)) } this.refresh() } /** Get a symbol image for a given legend item * @param {olLegendItemOptions} item * @param {Canvas|undefined} canvas a canvas to draw in, if none creat one * @param {int|undefined} offsetY Y offset to draw in canvas, default 0 */ static getLegendImage(item, canvas, offsetY) { item = item || {} if (typeof (item.margin) === 'undefined'){ item.margin = 10 } var size = item.size || [40, 25] if (item.width) size[0] = item.width if (item.heigth) size[1] = item.heigth item.onload = item.onload || function () { setTimeout(function () { ol_legend_Legend.getLegendImage(item, canvas, offsetY) }, 100) } var width = size[0] + 2 * item.margin var height = item.lineHeight || (size[1] + 2 * item.margin) var ratio = item.pixelratio || ol_has_DEVICE_PIXEL_RATIO if (!canvas) { offsetY = 0 canvas = document.createElement('canvas') canvas.width = width * ratio canvas.height = height * ratio } var ctx = canvas.getContext('2d') ctx.save() var vectorContext = ol_render_toContext(ctx, { pixelRatio: ratio }) var typeGeom = item.typeGeom var style var feature = item.feature if (!feature && typeGeom) { if (/Point/.test(typeGeom)){ feature = new ol_Feature(new ol_geom_Point([0, 0])) } else if (/LineString/.test(typeGeom)) { feature = new ol_Feature(new ol_geom_LineString([0, 0])) } else { feature = new ol_Feature(new ol_geom_Polygon([[0, 0]])) } if (item.properties) { feature.setProperties(item.properties) } } if (feature) { style = feature.getStyle() if (typeof (style) === 'function') style = style(feature) if (!style) { style = typeof (item.style) === 'function' ? item.style(feature) : item.style || [] } typeGeom = feature.getGeometry().getType() } else { style = [] } if (!(style instanceof Array)) style = [style] var cx = width / 2 var cy = height / 2 var sx = size[0] / 2 var sy = size[1] / 2 var i, s // Get point offset if (typeGeom === 'Point') { var extent = null for (i = 0; s = style[i]; i++) { var img = s.getImage() // Refresh legend on image load if (img) { var imgElt = img.getPhoto ? img.getPhoto() : img.getImage() // Check image is loaded if (imgElt && imgElt instanceof HTMLImageElement && !imgElt.naturalWidth) { if (typeof (item.onload) === 'function') { imgElt.addEventListener('load', function () { setTimeout(function () { item.onload() }, 100) }) } img.load() } // Check anchor to center the image if (img.getAnchor) { var anchor = img.getAnchor() if (anchor) { var si = img.getSize() var dx = anchor[0] - si[0] var dy = anchor[1] - si[1] if (!extent) { extent = [dx, dy, dx + si[0], dy + si[1]] } else { ol_extent_extend(extent, [dx, dy, dx + si[0], dy + si[1]]) } } } } } if (extent) { cx = cx + (extent[2] + extent[0]) / 2 cy = cy + (extent[3] + extent[1]) / 2 } } // Draw image cy += offsetY || 0 for (i = 0; s = style[i]; i++) { vectorContext.setStyle(s) ctx.save() var geom switch (typeGeom) { case ol_geom_Point: case 'Point': case 'MultiPoint': { geom = new ol_geom_Point([cx, cy]) break } case ol_geom_LineString: case 'LineString': case 'MultiLineString': { // Clip lines ctx.rect(item.margin * ratio, 0, size[0] * ratio, canvas.height) ctx.clip() geom = new ol_geom_LineString([[cx - sx, cy], [cx + sx, cy]]) break } case ol_geom_Polygon: case 'Polygon': case 'MultiPolygon': { geom = new ol_geom_Polygon([[[cx - sx, cy - sy], [cx + sx, cy - sy], [cx + sx, cy + sy], [cx - sx, cy + sy], [cx - sx, cy - sy]]]) break } } // Geometry function? if (s.getGeometryFunction()) { geom = s.getGeometryFunction()(new ol_Feature(geom)) } vectorContext.drawGeometry(geom) ctx.restore() } ctx.restore() return canvas } /** Set legend title * @param {string} title */ setTitle(title) { this._title.setTitle(title) this.refresh() } /** Get legend title * @returns {string} */ getTitle() { return this._title.get('title') } /** Set the layer associated with the legend * @param {ol.layer.Layer} [layer] */ setLayer(layer) { if (this._layerListener) ol_Observable_unByKey(this._layerListener) this._layer = layer; if (layer) { this._layerListener = layer.on('change:visible', function() { this.refresh(); }.bind(this)) } else { this._layerListener = null; } } /** Get text Style * @returns {ol_style_Text} */ getTextStyle() { return this._textStyle } /** Set legend size * @param {ol.size} size */ set(key, value, opt_silent) { super.set(key, value, opt_silent) if (!opt_silent) this.refresh() } /** Get legend list element * @returns {Element} */ getListElement() { return this._listElement } /** Get legend canvas * @returns {HTMLCanvasElement} */ getCanvas() { return this._canvas } /** Set the style * @param { ol.style.Style | Array | ol.StyleFunction | undefined } style a style or a style function to use with features */ setStyle(style) { this._style = style this.refresh() } /** Add a new item to the legend * @param {olLegendItemOptions|ol_legend_Item} item */ addItem(item) { if (item instanceof ol_legend_Legend) { this._items.push(item) item.on('refresh', function() { this.refresh(true) }.bind(this)) } else if (item instanceof ol_legend_Item || item instanceof ol_legend_Image) { this._items.push(item) } else { this._items.push(new ol_legend_Item(item)) } } /** Remove an item at index * @param {ol_legend_Item} item */ removeItem(item) { this._items.remove(item) } /** Remove an item at index * @param {number} index */ removeItemAt(index) { this._items.removeAt(index) } /** Get item collection * @param {ol_Collection} */ getItems() { return this._items } /** Draw legend text * @private */ _drawText(ctx, text, x, y) { ctx.save() ctx.scale(ol_has_DEVICE_PIXEL_RATIO, ol_has_DEVICE_PIXEL_RATIO) text = text || '' var txt = text.split('\n') if (txt.length === 1) { ctx.fillText(text, x, y) } else { ctx.textBaseline = 'bottom' ctx.fillText(txt[0], x, y) ctx.textBaseline = 'top' ctx.fillText(txt[1], x, y) } ctx.restore() } /** Draw legend text * @private */ _measureText(ctx, text) { var txt = (text || '').split('\n') if (txt.length === 1) { return ctx.measureText(text) } else { var m1 = ctx.measureText(txt[0]) var m2 = ctx.measureText(txt[1]) return { width: Math.max(m1.width, m2.width), height: m1.height + m2.height } } } /** Refresh the legend */ refresh(opt_silent) { var table = this._listElement if (!table) return; table.innerHTML = '' var margin = this.get('margin') var width = this.get('size')[0] + 2 * margin var height = this.get('lineHeight') || this.get('size')[1] + 2 * margin var canvas = this.getCanvas() var ctx = canvas.getContext('2d') ctx.textAlign = 'left' ctx.textBaseline = 'middle' var ratio = ol_has_DEVICE_PIXEL_RATIO // Canvas size var w = Math.min(this.getWidth(), this.get('maxWidth') || Infinity); var h = this.getHeight() canvas.width = w * ratio canvas.height = h * ratio canvas.style.height = h + 'px' ctx.textBaseline = 'middle' ctx.fillStyle = ol_color_asString(this._textStyle.getFill().getColor()) // Add Title if (this.getTitle()) { table.appendChild(this._title.getElement([width, height], function (b) { this.dispatchEvent({ type: 'select', index: -1, symbol: b, item: this._title }) }.bind(this))) ctx.font = this._titleStyle.getFont() ctx.textAlign = 'center' this._drawText(ctx, this.getTitle(), canvas.width / ratio / 2, height / 2) } // Add items var offsetY = 0; if (this.getTitle()) offsetY = height; var nb = 0; this._items.forEach(function (r, i) { if (r instanceof ol_legend_Legend) { if ((!r._layer || r._layer.getVisible()) && r.getCanvas().height) { ctx.drawImage(r.getCanvas(), 0, offsetY * ratio) var list = r._listElement.querySelectorAll('li') for (var l=0; l