293 lines
9.0 KiB
JavaScript
293 lines
9.0 KiB
JavaScript
/* Copyright (c) 2018 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
*/
|
|
|
|
import {DEVICE_PIXEL_RATIO as ol_has_DEVICE_PIXEL_RATIO} from 'ol/has.js'
|
|
import ol_style_Stroke from 'ol/style/Stroke.js'
|
|
import {asString as ol_color_asString} from 'ol/color.js'
|
|
import ol_style_FillPattern from './FillPattern.js'
|
|
|
|
/**
|
|
* @classdesc
|
|
* Stroke style with named pattern
|
|
*
|
|
* @constructor
|
|
* @param {any} options
|
|
* @param {ol.style.Image|undefined} options.image an image pattern, image must be preloaded to draw on first call
|
|
* @param {number|undefined} options.opacity opacity with image pattern, default:1
|
|
* @param {string} options.pattern pattern name (override by image option)
|
|
* @param {ol.colorLike} options.color pattern color
|
|
* @param {ol.style.Fill} options.fill fill color (background)
|
|
* @param {number|Array<number>} options.offset pattern offset for hash/dot/circle/cross pattern
|
|
* @param {number} options.size line size for hash/dot/circle/cross pattern
|
|
* @param {number} options.spacing spacing for hash/dot/circle/cross pattern
|
|
* @param {number|bool} options.angle angle for hash pattern / true for 45deg dot/circle/cross
|
|
* @param {number} options.scale pattern scale
|
|
* @extends {ol.style.Fill}
|
|
* @implements {ol.structs.IHasChecksum}
|
|
* @api
|
|
*/
|
|
var ol_style_StrokePattern = class olstyleStrokePattern extends ol_style_Stroke {
|
|
constructor(options) {
|
|
super(options)
|
|
|
|
options = options || {}
|
|
|
|
var pattern, i;
|
|
var canvas = this.canvas_ = document.createElement('canvas')
|
|
var scale = Number(options.scale) > 0 ? Number(options.scale) : 1
|
|
var ratio = scale * ol_has_DEVICE_PIXEL_RATIO || ol_has_DEVICE_PIXEL_RATIO
|
|
|
|
var ctx = canvas.getContext('2d')
|
|
|
|
if (options.image) {
|
|
options.image.load()
|
|
|
|
var img = options.image.getImage()
|
|
if (img.width) {
|
|
canvas.width = Math.round(img.width * ratio)
|
|
canvas.height = Math.round(img.height * ratio)
|
|
ctx.globalAlpha = typeof (options.opacity) == 'number' ? options.opacity : 1
|
|
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height)
|
|
pattern = ctx.createPattern(canvas, 'repeat')
|
|
} else {
|
|
var self = this
|
|
pattern = [0, 0, 0, 0]
|
|
img.onload = function () {
|
|
canvas.width = Math.round(img.width * ratio)
|
|
canvas.height = Math.round(img.height * ratio)
|
|
ctx.globalAlpha = typeof (options.opacity) == 'number' ? options.opacity : 1
|
|
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height)
|
|
pattern = ctx.createPattern(canvas, 'repeat')
|
|
self.setColor(pattern)
|
|
}
|
|
}
|
|
} else {
|
|
var pat = this.getPattern_(options)
|
|
canvas.width = Math.round(pat.width * ratio)
|
|
canvas.height = Math.round(pat.height * ratio)
|
|
ctx.beginPath()
|
|
if (options.fill) {
|
|
ctx.fillStyle = ol_color_asString(options.fill.getColor())
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
}
|
|
ctx.scale(ratio, ratio)
|
|
ctx.lineCap = "round"
|
|
ctx.lineWidth = pat.stroke || 1
|
|
|
|
ctx.fillStyle = ol_color_asString(options.color || "#000")
|
|
ctx.strokeStyle = ol_color_asString(options.color || "#000")
|
|
if (pat.circles)
|
|
for (i = 0; i < pat.circles.length; i++) {
|
|
var ci = pat.circles[i]
|
|
ctx.beginPath()
|
|
ctx.arc(ci[0], ci[1], ci[2], 0, 2 * Math.PI)
|
|
if (pat.fill)
|
|
ctx.fill()
|
|
if (pat.stroke)
|
|
ctx.stroke()
|
|
}
|
|
|
|
if (!pat.repeat)
|
|
pat.repeat = [[0, 0]]
|
|
|
|
if (pat.char) {
|
|
ctx.font = pat.font || (pat.width) + "px Arial"
|
|
ctx.textAlign = 'center'
|
|
ctx.textBaseline = 'middle'
|
|
if (pat.angle) {
|
|
ctx.fillText(pat.char, pat.width / 4, pat.height / 4)
|
|
ctx.fillText(pat.char, 5 * pat.width / 4, 5 * pat.height / 4)
|
|
ctx.fillText(pat.char, pat.width / 4, 5 * pat.height / 4)
|
|
ctx.fillText(pat.char, 5 * pat.width / 4, pat.height / 4)
|
|
|
|
ctx.fillText(pat.char, 3 * pat.width / 4, 3 * pat.height / 4)
|
|
ctx.fillText(pat.char, -pat.width / 4, -pat.height / 4)
|
|
ctx.fillText(pat.char, 3 * pat.width / 4, -pat.height / 4)
|
|
ctx.fillText(pat.char, -pat.width / 4, 3 * pat.height / 4)
|
|
}
|
|
else
|
|
ctx.fillText(pat.char, pat.width / 2, pat.height / 2)
|
|
}
|
|
|
|
if (pat.lines)
|
|
for (i = 0; i < pat.lines.length; i++)
|
|
for (var r = 0; r < pat.repeat.length; r++) {
|
|
var li = pat.lines[i]
|
|
ctx.beginPath()
|
|
ctx.moveTo(li[0] + pat.repeat[r][0], li[1] + pat.repeat[r][1])
|
|
for (var k = 2; k < li.length; k += 2) {
|
|
ctx.lineTo(li[k] + pat.repeat[r][0], li[k + 1] + pat.repeat[r][1])
|
|
}
|
|
if (pat.fill)
|
|
ctx.fill()
|
|
if (pat.stroke)
|
|
ctx.stroke()
|
|
ctx.save()
|
|
ctx.strokeStyle = 'red'
|
|
ctx.strokeWidth = 0.1
|
|
//ctx.strokeRect(0,0,canvas.width,canvas.height);
|
|
ctx.restore()
|
|
}
|
|
pattern = ctx.createPattern(canvas, 'repeat')
|
|
if (options.offset) {
|
|
var offset = options.offset
|
|
if (typeof (offset) == "number")
|
|
offset = [offset, offset]
|
|
if (offset instanceof Array) {
|
|
var dx = Math.round((offset[0] * ratio))
|
|
var dy = Math.round((offset[1] * ratio))
|
|
// New pattern
|
|
ctx.scale(1 / ratio, 1 / ratio)
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
ctx.translate(dx, dy)
|
|
ctx.fillStyle = pattern
|
|
ctx.fillRect(-dx, -dy, canvas.width, canvas.height)
|
|
pattern = ctx.createPattern(canvas, 'repeat')
|
|
}
|
|
}
|
|
}
|
|
|
|
this.setColor (pattern);
|
|
}
|
|
/**
|
|
* Clones the style.
|
|
* @return {ol_style_StrokePattern}
|
|
*/
|
|
clone() {
|
|
var s = super.clone()
|
|
s.canvas_ = this.canvas_
|
|
return s
|
|
}
|
|
/** Get canvas used as pattern
|
|
* @return {canvas}
|
|
*/
|
|
getImage() {
|
|
return this.canvas_
|
|
}
|
|
/** Get pattern
|
|
* @param {olx.style.FillPatternOption}
|
|
*/
|
|
getPattern_(options) {
|
|
var pat = ol_style_FillPattern.patterns[options.pattern]
|
|
|| ol_style_FillPattern.patterns.dot
|
|
var d = Math.round(options.spacing) || 10
|
|
var size
|
|
// var d2 = Math.round(d/2)+0.5;
|
|
switch (options.pattern) {
|
|
case 'dot':
|
|
case 'circle':
|
|
{
|
|
size = options.size === 0 ? 0 : options.size / 2 || 2
|
|
if (!options.angle) {
|
|
pat.width = pat.height = d
|
|
pat.circles = [[d / 2, d / 2, size]]
|
|
if (options.pattern == 'circle') {
|
|
pat.circles = pat.circles.concat([
|
|
[d / 2 + d, d / 2, size],
|
|
[d / 2 - d, d / 2, size],
|
|
[d / 2, d / 2 + d, size],
|
|
[d / 2, d / 2 - d, size],
|
|
[d / 2 + d, d / 2 + d, size],
|
|
[d / 2 + d, d / 2 - d, size],
|
|
[d / 2 - d, d / 2 + d, size],
|
|
[d / 2 - d, d / 2 - d, size]
|
|
])
|
|
}
|
|
}
|
|
|
|
else {
|
|
d = pat.width = pat.height = Math.round(d * 1.4)
|
|
pat.circles = [[d / 4, d / 4, size], [3 * d / 4, 3 * d / 4, size]]
|
|
if (options.pattern == 'circle') {
|
|
pat.circles = pat.circles.concat([
|
|
[d / 4 + d, d / 4, size],
|
|
[d / 4, d / 4 + d, size],
|
|
[3 * d / 4 - d, 3 * d / 4, size],
|
|
[3 * d / 4, 3 * d / 4 - d, size],
|
|
[d / 4 + d, d / 4 + d, size],
|
|
[3 * d / 4 - d, 3 * d / 4 - d, size]
|
|
])
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case 'tile':
|
|
case 'square':
|
|
{
|
|
size = options.size === 0 ? 0 : options.size / 2 || 2
|
|
if (!options.angle) {
|
|
pat.width = pat.height = d
|
|
pat.lines = [[d / 2 - size, d / 2 - size, d / 2 + size, d / 2 - size, d / 2 + size, d / 2 + size, d / 2 - size, d / 2 + size, d / 2 - size, d / 2 - size]]
|
|
}
|
|
|
|
else {
|
|
pat.width = pat.height = d
|
|
//size *= Math.sqrt(2);
|
|
pat.lines = [[d / 2 - size, d / 2, d / 2, d / 2 - size, d / 2 + size, d / 2, d / 2, d / 2 + size, d / 2 - size, d / 2]]
|
|
}
|
|
if (options.pattern == 'square')
|
|
pat.repeat = [[0, 0], [0, d], [d, 0], [0, -d], [-d, 0], [-d, -d], [d, d], [-d, d], [d, -d]]
|
|
break
|
|
}
|
|
case 'cross':
|
|
{ // Limit angle to 0 | 45
|
|
if (options.angle)
|
|
options.angle = 45
|
|
}
|
|
// fallthrough
|
|
case 'hatch':
|
|
{
|
|
var a = Math.round(((options.angle || 0) - 90) % 360)
|
|
if (a > 180)
|
|
a -= 360
|
|
a *= Math.PI / 180
|
|
var cos = Math.cos(a)
|
|
var sin = Math.sin(a)
|
|
if (Math.abs(sin) < 0.0001) {
|
|
pat.width = pat.height = d
|
|
pat.lines = [[0, 0.5, d, 0.5]]
|
|
pat.repeat = [[0, 0], [0, d]]
|
|
}
|
|
else if (Math.abs(cos) < 0.0001) {
|
|
pat.width = pat.height = d
|
|
pat.lines = [[0.5, 0, 0.5, d]]
|
|
pat.repeat = [[0, 0], [d, 0]]
|
|
if (options.pattern == 'cross') {
|
|
pat.lines.push([0, 0.5, d, 0.5])
|
|
pat.repeat.push([0, d])
|
|
}
|
|
}
|
|
|
|
else {
|
|
var w = pat.width = Math.round(Math.abs(d / sin)) || 1
|
|
var h = pat.height = Math.round(Math.abs(d / cos)) || 1
|
|
if (options.pattern == 'cross') {
|
|
pat.lines = [[-w, -h, 2 * w, 2 * h], [2 * w, -h, -w, 2 * h]]
|
|
pat.repeat = [[0, 0]]
|
|
}
|
|
else if (cos * sin > 0) {
|
|
pat.lines = [[-w, -h, 2 * w, 2 * h]]
|
|
pat.repeat = [[0, 0], [w, 0], [0, h]]
|
|
}
|
|
|
|
else {
|
|
pat.lines = [[2 * w, -h, -w, 2 * h]]
|
|
pat.repeat = [[0, 0], [-w, 0], [0, h]]
|
|
}
|
|
|
|
}
|
|
pat.stroke = options.size === 0 ? 0 : options.size || 4
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
return pat
|
|
}
|
|
}
|
|
|
|
export default ol_style_StrokePattern
|