496 lines
16 KiB
JavaScript
496 lines
16 KiB
JavaScript
/* Copyright (c) 2015 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
*/
|
|
import ol_style_RegularShape from 'ol/style/RegularShape.js'
|
|
import {asString as ol_color_asString} from 'ol/color.js'
|
|
|
|
/**
|
|
* @classdesc
|
|
* A marker style to use with font symbols.
|
|
*
|
|
* @constructor
|
|
* @param {} options Options.
|
|
* @param {string} [options.color] default #000
|
|
* @param {string} options.glyph the glyph name or a char to display as symbol.
|
|
* The name must be added using the {@link ol.style.FontSymbol.addDefs} function.
|
|
* @param {string} [options.text] a text to display as a glyph
|
|
* @param {string} [options.font] font to use with the text option
|
|
* @param {string} options.form
|
|
* none|circle|poi|bubble|marker|coma|shield|blazon|bookmark|hexagon|diamond|triangle|sign|ban|lozenge|square
|
|
* a form that will enclose the glyph, default none
|
|
* @param {number} options.radius
|
|
* @param {number} options.rotation
|
|
* @param {boolean} options.rotateWithView
|
|
* @param {string} [options.declutterMode] Declutter mode "declutter" | "obstacle" | "none" | undefined
|
|
* @param {number} [options.scale=1]
|
|
* @param {number} [options.opacity=1]
|
|
* @param {number} [options.fontSize=1] size of the font compare to the radius, fontSize greater than 1 will exceed the symbol extent
|
|
* @param {string} [options.fontStyle] the font style (bold, italic, bold italic, etc), default none
|
|
* @param {boolean} options.gradient true to display a gradient on the symbol
|
|
* @param {Array<number>} [options.displacement] to use with ol > 6
|
|
* @param {number} [options.offsetX=0] Horizontal offset in pixels, deprecated use displacement with ol>6
|
|
* @param {number} [options.offsetY=0] Vertical offset in pixels, deprecated use displacement with ol>6
|
|
* @param {_ol_style_Fill_} options.fill
|
|
* @param {_ol_style_Stroke_} options.stroke
|
|
* @extends {ol_style_RegularShape}
|
|
* @implements {ol.structs.IHasChecksum}
|
|
* @api
|
|
*/
|
|
var ol_style_FontSymbol = class olstyleFontSymbol extends ol_style_RegularShape {
|
|
constructor(options) {
|
|
options = options || {};
|
|
var strokeWidth = 0;
|
|
if (options.stroke) {
|
|
strokeWidth = options.stroke.getWidth();
|
|
}
|
|
if (!options.displacement) {
|
|
options.displacement = [options.offsetX || 0, -options.offsetY || 0];
|
|
}
|
|
|
|
super ({
|
|
radius: options.radius,
|
|
fill: options.fill,
|
|
scale: options.scale,
|
|
rotation: options.rotation,
|
|
displacement: options.displacement,
|
|
rotateWithView: options.rotateWithView,
|
|
declutterMode: options.declutterMode,
|
|
});
|
|
|
|
if (typeof (options.opacity) == "number"){
|
|
this.setOpacity(options.opacity);
|
|
}
|
|
this._color = options.color;
|
|
this._fontSize = options.fontSize || 1;
|
|
this._fontStyle = options.fontStyle || '';
|
|
this._stroke = options.stroke;
|
|
this._fill = options.fill;
|
|
this._radius = options.radius - strokeWidth;
|
|
this._form = options.form || "none";
|
|
this._gradient = options.gradient;
|
|
this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0];
|
|
|
|
if (options.glyph){
|
|
this._glyph = this.getGlyph(options.glyph);
|
|
} else {
|
|
this._glyph = this.getTextGlyph(options.text || '', options.font);
|
|
}
|
|
|
|
if (!this.getDisplacement){
|
|
this.getImage();
|
|
}
|
|
this.render();
|
|
}
|
|
/** Static function : add new font defs
|
|
* @param {String|Object} font the font name or a description ({ font: font_name, name: font_name, copyright: '', prefix })
|
|
* @param {Object} glyphs a key / value list of glyph definitions.
|
|
* Each key is the name of the glyph,
|
|
* the value is an object that code the font, the caracter code,
|
|
* the name and a search string for the glyph.
|
|
* { char: the char, code: the char code (if no char), theme: a theme for search puposes, name: the symbol name, search: a search string (separated with ',') }
|
|
*/
|
|
static addDefs(font, glyphs) {
|
|
var thefont = font;
|
|
if (typeof (font) == 'string') {
|
|
thefont = { font: font, name: font, copyright: '' };
|
|
}
|
|
if (!thefont.font || typeof (thefont.font) !== 'string') {
|
|
console.log('bad font def');
|
|
return;
|
|
}
|
|
var fontname = thefont.font;
|
|
ol_style_FontSymbol.defs.fonts[fontname] = thefont;
|
|
for (var i in glyphs) {
|
|
var g = glyphs[i];
|
|
if (typeof (g) === 'string' && (g.length == 1 || g.length == 2)) {
|
|
g = { char: g };
|
|
}
|
|
ol_style_FontSymbol.defs.glyphs[i] = {
|
|
font: thefont.font,
|
|
char: g.char || '' + String.fromCodePoint(g.code) || '',
|
|
theme: g.theme || thefont.name,
|
|
name: g.name || i,
|
|
search: g.search || ''
|
|
};
|
|
}
|
|
}
|
|
/** Clones the style.
|
|
* @return {ol_style_FontSymbol}
|
|
*/
|
|
clone() {
|
|
var g = new ol_style_FontSymbol({
|
|
//glyph: this._glyph,
|
|
text: this._glyph.char,
|
|
font: this._glyph.font,
|
|
color: this._color,
|
|
fontSize: this._fontSize,
|
|
fontStyle: this._fontStyle,
|
|
stroke: this._stroke,
|
|
fill: this._fill,
|
|
radius: this._radius + (this._stroke ? this._stroke.getWidth() : 0),
|
|
form: this._form,
|
|
gradient: this._gradient,
|
|
offsetX: this._offset[0],
|
|
offsetY: this._offset[1],
|
|
opacity: this.getOpacity(),
|
|
rotation: this.getRotation(),
|
|
rotateWithView: this.getRotateWithView(),
|
|
declutterMode: this.getDeclutterMode ? this.getDeclutterMode() : null,
|
|
});
|
|
g.setScale(this.getScale());
|
|
return g;
|
|
}
|
|
/** Get the fill style for the symbol.
|
|
* @return {ol_style_Fill} Fill style.
|
|
* @api
|
|
*/
|
|
getFill() {
|
|
return this._fill;
|
|
}
|
|
/** Get the stroke style for the symbol.
|
|
* @return {_ol_style_Stroke_} Stroke style.
|
|
* @api
|
|
*/
|
|
getStroke() {
|
|
return this._stroke;
|
|
}
|
|
/** Get the glyph definition for the symbol.
|
|
* @param {string|undefined} name a glyph name to get the definition, default return the glyph definition for the style.
|
|
* @return {*}
|
|
* @api
|
|
*/
|
|
getGlyph(name) {
|
|
if (name)
|
|
return ol_style_FontSymbol.defs.glyphs[name] || { font: 'sans-serif', char: name.charAt(0), theme: 'none', name: 'none', search: '' };
|
|
else
|
|
return this._glyph;
|
|
}
|
|
/** Get glyph definition given a text and a font
|
|
* @param {string|undefined} text
|
|
* @param {string} [font] the font for the text
|
|
* @return {*}
|
|
* @api
|
|
*/
|
|
getTextGlyph(text, font) {
|
|
return { font: font || 'sans-serif', char: String(text), theme: 'none', name: 'none', search: '' };
|
|
}
|
|
/**
|
|
* Get the glyph name.
|
|
* @return {string} the name
|
|
* @api
|
|
*/
|
|
getGlyphName() {
|
|
for (var i in ol_style_FontSymbol.defs.glyphs) {
|
|
if (ol_style_FontSymbol.defs.glyphs[i] === this._glyph)
|
|
return i;
|
|
}
|
|
return '';
|
|
}
|
|
/**
|
|
* Get the stroke style for the symbol.
|
|
* @return {_ol_style_Stroke_} Stroke style.
|
|
* @api
|
|
*/
|
|
getFontInfo(glyph) {
|
|
return ol_style_FontSymbol.defs.fonts[glyph.font];
|
|
}
|
|
/**
|
|
* @return {RenderOptions} The render options
|
|
*/
|
|
createRenderOptions() {
|
|
var opt = super.createRenderOptions();
|
|
var stroke = 'none';
|
|
if (this._stroke) {
|
|
stroke = [
|
|
ol_color_asString(this._stroke.getColor() || '#000'),
|
|
this._stroke.getWidth() || 0,
|
|
this._stroke.getLineDash(),
|
|
this._stroke.getLineDashOffset() || 0,
|
|
this._stroke.getLineJoin(),
|
|
this._stroke.getLineCap(),
|
|
this._stroke.getMiterLimit(),
|
|
].join('-')
|
|
}
|
|
opt.fontsymbolOptions = [
|
|
'font-symbol',
|
|
this._color,
|
|
this._fontSize,
|
|
this._fontStyle,
|
|
stroke,
|
|
this._fill ? ol_color_asString(this._fill.getColor() || '') : '',
|
|
this._radius,
|
|
this._form,
|
|
this._gradient,
|
|
(this._offset || []).join(','),
|
|
Object.values(this._glyph||[]).join(',')
|
|
].join('-');
|
|
return opt;
|
|
}
|
|
/**
|
|
* Get the image icon.
|
|
* @param {number} pixelRatio Pixel ratio.
|
|
* @return {HTMLCanvasElement} Image or Canvas element.
|
|
* @api
|
|
*/
|
|
getImage(pixelratio) {
|
|
pixelratio = pixelratio || 1;
|
|
// get canvas
|
|
var canvas = super.getImage(pixelratio);
|
|
|
|
var strokeStyle;
|
|
var strokeWidth = 0;
|
|
|
|
if (this._stroke) {
|
|
strokeStyle = ol_color_asString(this._stroke.getColor());
|
|
strokeWidth = this._stroke.getWidth();
|
|
}
|
|
|
|
/** @type {ol_style_FontSymbol.RenderOptions} */
|
|
var renderOptions = {
|
|
strokeStyle: strokeStyle,
|
|
strokeWidth: strokeWidth,
|
|
size: canvas.width / pixelratio,
|
|
};
|
|
|
|
// draw the circle on the canvas
|
|
var context = (canvas.getContext('2d'));
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
this.drawMarker_(renderOptions, context, 0, 0, pixelratio);
|
|
|
|
// Set anchor / displacement
|
|
if (!this.getDisplacement) {
|
|
var a = this.getAnchor();
|
|
a[0] = canvas.width / 2 - this._offset[0];
|
|
a[1] = canvas.width / 2 - this._offset[1];
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
/**
|
|
* @private
|
|
* @param {ol_style_FontSymbol.RenderOptions} renderOptions
|
|
* @param {CanvasRenderingContext2D} context
|
|
*/
|
|
drawPath_(renderOptions, context) {
|
|
var s = 2 * this._radius + renderOptions.strokeWidth;
|
|
var w = renderOptions.strokeWidth / 2;
|
|
var c = renderOptions.size / 2;
|
|
// Transfo to place the glyph at the right place
|
|
var transfo = { fac: 1, posX: renderOptions.size / 2, posY: renderOptions.size / 2 };
|
|
context.lineJoin = 'round';
|
|
context.lineCap = 'round';
|
|
context.beginPath();
|
|
|
|
// Draw the path with the form
|
|
switch (this._form) {
|
|
case "none": {
|
|
transfo.fac = 1;
|
|
break;
|
|
}
|
|
case "circle":
|
|
case "ban": {
|
|
context.arc(c, c, s / 2, 0, 2 * Math.PI, true);
|
|
break;
|
|
}
|
|
case "poi": {
|
|
context.arc(c, c - 0.4 * this._radius, 0.6 * this._radius, 0.15 * Math.PI, 0.85 * Math.PI, true);
|
|
context.lineTo(c - 0.89 * 0.05 * s, (0.95 + 0.45 * 0.05) * s + w);
|
|
context.arc(c, 0.95 * s + w, 0.05 * s, 0.85 * Math.PI, 0.15 * Math.PI, true);
|
|
transfo = { fac: 0.45, posX: c, posY: c - 0.35 * this._radius };
|
|
break;
|
|
}
|
|
case "bubble": {
|
|
context.arc(c, c - 0.2 * this._radius, 0.8 * this._radius, 0.4 * Math.PI, 0.6 * Math.PI, true);
|
|
context.lineTo(0.5 * s + w, s + w);
|
|
transfo = { fac: 0.7, posX: c, posY: c - 0.2 * this._radius };
|
|
break;
|
|
}
|
|
case "marker": {
|
|
context.arc(c, c - 0.2 * this._radius, 0.8 * this._radius, 0.25 * Math.PI, 0.75 * Math.PI, true);
|
|
context.lineTo(0.5 * s + w, s + w);
|
|
transfo = { fac: 0.7, posX: c, posY: c - 0.2 * this._radius };
|
|
break;
|
|
}
|
|
case "coma": {
|
|
context.moveTo(c + 0.8 * this._radius, c - 0.2 * this._radius);
|
|
context.quadraticCurveTo(0.95 * s + w, 0.75 * s + w, 0.5 * s + w, s + w);
|
|
context.arc(c, c - 0.2 * this._radius, 0.8 * this._radius, 0.45 * Math.PI, 0, false);
|
|
transfo = { fac: 0.7, posX: c, posY: c - 0.2 * this._radius };
|
|
break;
|
|
}
|
|
default: {
|
|
var pts;
|
|
switch (this._form) {
|
|
case "shield": {
|
|
pts = [0.05, 0, 0.95, 0, 0.95, 0.8, 0.5, 1, 0.05, 0.8, 0.05, 0];
|
|
transfo.posY = 0.45 * s + w;
|
|
break;
|
|
}
|
|
case "blazon": {
|
|
pts = [0.1, 0, 0.9, 0, 0.9, 0.8, 0.6, 0.8, 0.5, 1, 0.4, 0.8, 0.1, 0.8, 0.1, 0];
|
|
transfo.fac = 0.8;
|
|
transfo.posY = 0.4 * s + w;
|
|
break;
|
|
}
|
|
case "bookmark": {
|
|
pts = [0.05, 0, 0.95, 0, 0.95, 1, 0.5, 0.8, 0.05, 1, 0.05, 0];
|
|
transfo.fac = 0.9;
|
|
transfo.posY = 0.4 * s + w;
|
|
break;
|
|
}
|
|
case "hexagon": {
|
|
pts = [0.05, 0.2, 0.5, 0, 0.95, 0.2, 0.95, 0.8, 0.5, 1, 0.05, 0.8, 0.05, 0.2];
|
|
transfo.fac = 0.9;
|
|
transfo.posY = 0.5 * s + w;
|
|
break;
|
|
}
|
|
case "diamond": {
|
|
pts = [0.25, 0, 0.75, 0, 1, 0.2, 1, 0.4, 0.5, 1, 0, 0.4, 0, 0.2, 0.25, 0];
|
|
transfo.fac = 0.75;
|
|
transfo.posY = 0.35 * s + w;
|
|
break;
|
|
}
|
|
case "triangle": {
|
|
pts = [0, 0, 1, 0, 0.5, 1, 0, 0];
|
|
transfo.fac = 0.6;
|
|
transfo.posY = 0.3 * s + w;
|
|
break;
|
|
}
|
|
case "sign": {
|
|
pts = [0.5, 0.05, 1, 0.95, 0, 0.95, 0.5, 0.05];
|
|
transfo.fac = 0.7;
|
|
transfo.posY = 0.65 * s + w;
|
|
break;
|
|
}
|
|
case "lozenge": {
|
|
pts = [0.5, 0, 1, 0.5, 0.5, 1, 0, 0.5, 0.5, 0];
|
|
transfo.fac = 0.7;
|
|
break;
|
|
}
|
|
case "square":
|
|
default: {
|
|
pts = [0, 0, 1, 0, 1, 1, 0, 1, 0, 0];
|
|
break;
|
|
}
|
|
}
|
|
for (var i = 0; i < pts.length; i += 2)
|
|
context.lineTo(pts[i] * s + w, pts[i + 1] * s + w);
|
|
}
|
|
}
|
|
|
|
context.closePath();
|
|
return transfo;
|
|
}
|
|
/**
|
|
* @private
|
|
* @param {ol_style_FontSymbol.RenderOptions} renderOptions
|
|
* @param {CanvasRenderingContext2D} context
|
|
* @param {number} x The origin for the symbol (x).
|
|
* @param {number} y The origin for the symbol (y).
|
|
*/
|
|
drawMarker_(renderOptions, context, x, y, pixelratio) {
|
|
var fcolor = this._fill ? this._fill.getColor() : "#000";
|
|
var scolor = this._stroke ? this._stroke.getColor() : "#000";
|
|
if (this._form == "none" && this._stroke && this._fill) {
|
|
scolor = this._fill.getColor();
|
|
fcolor = this._stroke.getColor();
|
|
}
|
|
// reset transform
|
|
context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0);
|
|
|
|
// then move to (x, y)
|
|
context.translate(x, y);
|
|
|
|
var tr = this.drawPath_(renderOptions, context, pixelratio);
|
|
|
|
if (this._fill) {
|
|
if (this._gradient && this._form != "none") {
|
|
var grd = context.createLinearGradient(0, 0, renderOptions.size / 2, renderOptions.size);
|
|
grd.addColorStop(1, ol_color_asString(fcolor));
|
|
grd.addColorStop(0, ol_color_asString(scolor));
|
|
context.fillStyle = grd;
|
|
} else {
|
|
context.fillStyle = ol_color_asString(fcolor);
|
|
}
|
|
context.fill();
|
|
}
|
|
if (this._stroke && renderOptions.strokeWidth) {
|
|
context.strokeStyle = renderOptions.strokeStyle;
|
|
context.lineWidth = renderOptions.strokeWidth;
|
|
context.stroke();
|
|
}
|
|
|
|
// Draw the symbol
|
|
if (this._glyph.char) {
|
|
context.font = this._fontStyle + ' '
|
|
+ (2 * tr.fac * (this._radius) * this._fontSize) + "px "
|
|
+ this._glyph.font;
|
|
context.strokeStyle = context.fillStyle;
|
|
context.lineWidth = renderOptions.strokeWidth * (this._form == "none" ? 2 : 1);
|
|
context.fillStyle = ol_color_asString(this._color || scolor);
|
|
context.textAlign = "center";
|
|
context.textBaseline = "middle";
|
|
var t = this._glyph.char;
|
|
if (renderOptions.strokeWidth && scolor != "transparent")
|
|
context.strokeText(t, tr.posX, tr.posY);
|
|
context.fillText(t, tr.posX, tr.posY);
|
|
}
|
|
|
|
if (this._form == "ban" && this._stroke && renderOptions.strokeWidth) {
|
|
context.strokeStyle = renderOptions.strokeStyle;
|
|
context.lineWidth = renderOptions.strokeWidth;
|
|
var r = this._radius + renderOptions.strokeWidth;
|
|
var d = this._radius * Math.cos(Math.PI / 4);
|
|
context.moveTo(r + d, r - d);
|
|
context.lineTo(r - d, r + d);
|
|
context.stroke();
|
|
}
|
|
}
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
getChecksum() {
|
|
var strokeChecksum = (this._stroke !== null) ? this._stroke.getChecksum() : '-';
|
|
var fillChecksum = (this._fill !== null) ? this._fill.getChecksum() : '-';
|
|
|
|
var recalculate = (this.checksums_ === null)
|
|
|| (strokeChecksum != this.checksums_[1]
|
|
|| fillChecksum != this.checksums_[2]
|
|
|| this._radius != this.checksums_[3]
|
|
|| this._form + "-" + this.glyphs_ != this.checksums_[4]
|
|
);
|
|
|
|
if (recalculate) {
|
|
var checksum = 'c' + strokeChecksum + fillChecksum
|
|
+ ((this._radius !== void 0) ? this._radius.toString() : '-')
|
|
+ this._form + "-" + this.glyphs_;
|
|
this.checksums_ = [checksum, strokeChecksum, fillChecksum, this._radius, this._form + "-" + this.glyphs_];
|
|
}
|
|
|
|
return this.checksums_[0];
|
|
}
|
|
}
|
|
|
|
/** Font defs
|
|
*/
|
|
ol_style_FontSymbol.defs = { 'fonts':{}, 'glyphs':{} };
|
|
|
|
/* Cool stuff to get the image symbol for a style
|
|
* @param {number} ratio pixelratio
|
|
* /
|
|
import ol_style_Image from 'ol/style/Image.js'
|
|
ol_style_Image.prototype.getImagePNG = function(ratio) {
|
|
ratio = ratio || window.devicePixelRatio;
|
|
var canvas = this.getImage(ratio);
|
|
if (canvas) {
|
|
try { return canvas.toDataURL('image/png'); }
|
|
catch(e) { return false; }
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
/* */
|
|
|
|
export default ol_style_FontSymbol
|