622 lines
20 KiB
JavaScript
622 lines
20 KiB
JavaScript
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.style.Style> | 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
|
|
}
|
|
/** Get title Style
|
|
* @returns {ol_style_Text}
|
|
*/
|
|
getTitleStyle() {
|
|
return this._titleStyle
|
|
}
|
|
/** 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.style.Style> | 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'
|
|
|
|
const stroke = this._titleStyle.getStroke();
|
|
if (stroke) {
|
|
ctx.lineWidth = stroke.getWidth() || 1;
|
|
ctx.strokeStyle = stroke.getColor() || '#000';
|
|
ctx.lineJoin = 'round';
|
|
ctx.strokeText(this.getTitle(), canvas.width / ratio / 2, height / 2);
|
|
}
|
|
|
|
const fill = this._titleStyle.getFill();
|
|
if (fill) {
|
|
ctx.fillStyle = fill.getColor() || '#000';
|
|
}
|
|
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<list.length; l++) {
|
|
var li = list[l].cloneNode();
|
|
li.innerHTML = list[l].innerHTML;
|
|
table.appendChild(li);
|
|
nb++;
|
|
}
|
|
offsetY += r.getHeight();
|
|
}
|
|
} else {
|
|
if (r instanceof ol_legend_Image) {
|
|
// Title
|
|
if (r.get('title')) {
|
|
table.appendChild(this._title.getElement([width, height], function (b) {
|
|
this.dispatchEvent({
|
|
type: 'select',
|
|
index: -1,
|
|
symbol: b,
|
|
item: this._title
|
|
})
|
|
}.bind(this)))
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont()
|
|
if (/\bcenter\b/.test(r.get('className'))) {
|
|
ctx.textAlign = 'center'
|
|
this._drawText(ctx, r.get('title'), canvas.width / ratio / 2, offsetY + height / 2)
|
|
} else {
|
|
this._drawText(ctx, r.get('title'), margin, offsetY + height / 2)
|
|
}
|
|
offsetY += height;
|
|
}
|
|
// Image
|
|
var img = r.getImage()
|
|
try {
|
|
ctx.drawImage(img, 0,0,img.naturalWidth, img.naturalHeight, 0, offsetY * ratio, r.getWidth() * ratio, r.getHeight() * ratio)
|
|
} catch(e) { /* ok */ }
|
|
offsetY += r.getHeight();
|
|
} else {
|
|
var item = r.getProperties()
|
|
var h = item.height || height;
|
|
ctx.textAlign = 'left'
|
|
if (item.feature || item.typeGeom) {
|
|
canvas = this.getLegendImage(item, canvas, offsetY)
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont()
|
|
|
|
const stroke = r.get('textStyle') ? r.get('textStyle').getStroke() : this._textStyle.getStroke();
|
|
if (stroke) {
|
|
ctx.lineWidth = stroke.getWidth() || 0;
|
|
ctx.strokeStyle = stroke.getColor() || '#000';
|
|
ctx.lineJoin = 'round';
|
|
}
|
|
|
|
// Handle fill
|
|
const fill = r.get('textStyle') ? r.get('textStyle').getFill() : this._textStyle.getFill();
|
|
ctx.fillStyle = fill ? fill.getColor() || '#000' : '#000';
|
|
this._drawText(ctx, r.get('title'), width + margin, offsetY + h / 2)
|
|
} else {
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont()
|
|
if (/\bcenter\b/.test(item.className)) {
|
|
ctx.textAlign = 'center'
|
|
this._drawText(ctx, r.get('title'), canvas.width / ratio / 2, offsetY + h / 2)
|
|
} else {
|
|
this._drawText(ctx, r.get('title'), margin, offsetY + h / 2)
|
|
}
|
|
}
|
|
offsetY += h;
|
|
}
|
|
table.appendChild(r.getElement([width, height], function (b) {
|
|
this.dispatchEvent({
|
|
type: 'select',
|
|
index: i,
|
|
symbol: b,
|
|
item: r
|
|
})
|
|
}.bind(this)))
|
|
nb++;
|
|
}
|
|
}.bind(this))
|
|
|
|
this.set('items', nb, true)
|
|
this.dispatchEvent({
|
|
type: 'items',
|
|
nb: nb
|
|
})
|
|
|
|
// Done
|
|
if (!opt_silent) {
|
|
this.dispatchEvent({
|
|
type: 'refresh',
|
|
width: width,
|
|
height: (this._items.length + 1) * height
|
|
})
|
|
}
|
|
}
|
|
/** Calculate the legend height
|
|
* @return {number}
|
|
*/
|
|
getHeight() {
|
|
// default item height
|
|
var margin = this.get('margin')
|
|
var hitem = this.get('lineHeight') || this.get('size')[1] + 2 * margin
|
|
var height = this.getTitle() ? hitem : 0;
|
|
this._items.forEach(function (r) {
|
|
if (r instanceof ol_legend_Legend) {
|
|
if (!r._layer || r._layer.getVisible()) {
|
|
height += r.getHeight()
|
|
}
|
|
} else if (r instanceof ol_legend_Image) {
|
|
if (r.get('title')) height += hitem;
|
|
height += r.getHeight()
|
|
} else {
|
|
if (r.get('height')) height += r.get('height') + 2 * margin;
|
|
else height += hitem
|
|
}
|
|
})
|
|
return height
|
|
}
|
|
/** Calculate the legend height
|
|
* @return {number}
|
|
*/
|
|
getWidth() {
|
|
var canvas = this.getCanvas()
|
|
var ctx = canvas.getContext('2d')
|
|
var margin = this.get('margin');
|
|
var width = this.get('size')[0] + 2 * margin
|
|
|
|
ctx.font = this._titleStyle.getFont()
|
|
var textWidth = this._measureText(ctx, this.getTitle('title')).width
|
|
this._items.forEach(function (r) {
|
|
if (r instanceof ol_legend_Legend) {
|
|
if (!r._layer || r._layer.getVisible()) {
|
|
textWidth = Math.max(textWidth, r.getWidth())
|
|
}
|
|
} else if (r instanceof ol_legend_Image) {
|
|
textWidth = Math.max(textWidth, r.getWidth())
|
|
if (r.get('title')) {
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont()
|
|
textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width)
|
|
}
|
|
} else {
|
|
if (r.get('feature') || r.get('typeGeom')) {
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._textStyle.getFont()
|
|
textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width + width)
|
|
} else {
|
|
ctx.font = r.get('textStyle') ? r.get('textStyle').getFont() : this._titleStyle.getFont()
|
|
textWidth = Math.max(textWidth, this._measureText(ctx, r.get('title')).width)
|
|
}
|
|
}
|
|
}.bind(this))
|
|
return textWidth + 2 * margin
|
|
}
|
|
/** Get the image for a style
|
|
* @param {olLegendItemOptions} item
|
|
* @param {Canvas|undefined} canvas a canvas to draw in, if none create one
|
|
* @param {int|undefined} offsetY Y offset to draw in canvas, default 0
|
|
* @return {CanvasElement}
|
|
*/
|
|
getLegendImage(options, canvas, offsetY) {
|
|
options = options || {};
|
|
var size = this.get('size');
|
|
return ol_legend_Legend.getLegendImage({
|
|
className: options.className,
|
|
feature: options.feature,
|
|
typeGeom: options.typeGeom,
|
|
style: options.style || this._style,
|
|
properties: options.properties,
|
|
margin: options.margin || this.get('margin'),
|
|
size: [ options.width || size[0], options.height || size[1]],
|
|
lineHeight: options.lineHeight || this.get('lineHeight'),
|
|
onload: function() {
|
|
// Force refresh
|
|
this.refresh();
|
|
}.bind(this)
|
|
}, canvas, offsetY);
|
|
}
|
|
}
|
|
export default ol_legend_Legend
|