ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
390 lines (378 loc) • 12.1 kB
JavaScript
/* Copyright (c) 2019 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_Style from 'ol/style/Style.js'
import {asString as ol_color_asString} from 'ol/color.js'
import {asArray as ol_color_asArray} from 'ol/color.js'
import {ol_coordinate_dist2d} from '../geom/GeomUtils.js'
/** Flow line style
* Draw LineString with a variable color / width
* NB: the FlowLine style doesn't impress the hit-detection.
* If you want your lines to be sectionable you have to add your own style to handle this.
* (with transparent line: stroke color opacity to .1 or zero width)
* @constructor
* @extends {ol_style_Style}
* @param {Object} options
* @param {boolean} options.visible draw only the visible part of the line, default true
* @param {number|function} options.width Stroke width or a function that gets a feature and the position (beetween [0,1]) and returns current width
* @param {number} options.width2 Final stroke width (if width is not a function)
* @param {number} options.arrow Arrow at start (-1), at end (1), at both (2), none (0), default geta
* @param {ol.colorLike|function} options.color Stroke color or a function that gets a feature and the position (beetween [0,1]) and returns current color
* @param {ol.colorLike} options.color2 Final sroke color if color is nor a function
* @param {ol.colorLike} options.arrowColor Color of arrows, if not defined used color or color2
* @param {string} options.lineCap CanvasRenderingContext2D.lineCap 'butt' | 'round' | 'square', default 'butt'
* @param {number|ol.size} options.arrowSize height and width of the arrow, default 16
* @param {boolean} [options.noOverlap=false] prevent segments overlaping
* @param {number} options.offset0 offset at line start
* @param {number} options.offset1 offset at line end
*/
var ol_style_FlowLine = class olstyleFlowLine extends ol_style_Style {
constructor(options) {
options = options || {}
super({
stroke: options.stroke,
text: options.text,
zIndex: options.zIndex,
geometry: options.geometry
})
this.setRenderer(this._render.bind(this))
// Draw only visible
this._visible = (options.visible !== false)
// Width
if (typeof options.width === 'function') {
this._widthFn = options.width
} else {
this.setWidth(options.width)
}
this.setWidth2(options.width2)
// Color
if (typeof options.color === 'function') {
this._colorFn = options.color
} else {
this.setColor(options.color)
}
this.setColor2(options.color2)
// LineCap
this.setLineCap(options.lineCap)
// Arrow
this.setArrow(options.arrow)
this.setArrowSize(options.arrowSize)
this.setArrowColor(options.arrowColor)
// Offset
this._offset = [0, 0]
this.setOffset(options.offset0, 0)
this.setOffset(options.offset1, 1)
// Overlap
this._noOverlap = options.noOverlap
}
/** Set the initial width
* @param {number} width width, default 0
*/
setWidth(width) {
this._width = width || 0
}
/** Set the final width
* @param {number} width width, default 0
*/
setWidth2(width) {
this._width2 = width
}
/** Get offset at start or end
* @param {number} where 0=start, 1=end
* @return {number} width
*/
getOffset(where) {
return this._offset[where]
}
/** Add an offset at start or end
* @param {number} width
* @param {number} where 0=start, 1=end
*/
setOffset(width, where) {
width = Math.max(0, parseFloat(width))
switch (where) {
case 0: {
this._offset[0] = width
break
}
case 1: {
this._offset[1] = width
break
}
}
}
/** Set the LineCap
* @param {steing} cap LineCap (round or butt), default butt
*/
setLineCap(cap) {
this._lineCap = (cap === 'round' ? 'round' : 'butt')
}
/** Get the current width at step
* @param {ol.feature} feature
* @param {number} step current drawing step beetween [0,1]
* @return {number}
*/
getWidth(feature, step) {
if (this._widthFn)
return this._widthFn(feature, step)
var w2 = (typeof (this._width2) === 'number') ? this._width2 : this._width
return this._width + (w2 - this._width) * step
}
/** Set the initial color
* @param {ol.colorLike} color
*/
setColor(color) {
try {
this._color = ol_color_asArray(color)
} catch (e) {
this._color = [0, 0, 0, 1]
}
}
/** Set the final color
* @param {ol.colorLike} color
*/
setColor2(color) {
try {
this._color2 = ol_color_asArray(color)
} catch (e) {
this._color2 = null
}
}
/** Set the arrow color
* @param {ol.colorLike} color
*/
setArrowColor(color) {
try {
this._acolor = ol_color_asString(color)
} catch (e) {
this._acolor = null
}
}
/** Get the current color at step
* @param {ol.feature} feature
* @param {number} step current drawing step beetween [0,1]
* @return {string}
*/
getColor(feature, step) {
if (this._colorFn)
return ol_color_asString(this._colorFn(feature, step))
var color = this._color
var color2 = this._color2 || this._color
return 'rgba(' +
+Math.round(color[0] + (color2[0] - color[0]) * step) + ','
+ Math.round(color[1] + (color2[1] - color[1]) * step) + ','
+ Math.round(color[2] + (color2[2] - color[2]) * step) + ','
+ (color[3] + (color2[3] - color[3]) * step)
+ ')'
}
/** Get arrow
*/
getArrow() {
return this._arrow
}
/** Set arrow
* @param {number} n -1 | 0 | 1 | 2, default: 0
*/
setArrow(n) {
this._arrow = parseInt(n)
if (this._arrow < -1 || this._arrow > 2)
this._arrow = 0
}
/** getArrowSize
* @return {ol.size}
*/
getArrowSize() {
return this._arrowSize || [16, 16]
}
/** setArrowSize
* @param {number|ol.size} size
*/
setArrowSize(size) {
if (Array.isArray(size))
this._arrowSize = size
else if (typeof (size) === 'number')
this._arrowSize = [size, size]
}
/** drawArrow
* @param {CanvasRenderingContext2D} ctx
* @param {ol.coordinate} p0
* @param ol.coordinate} p1
* @param {number} width
* @param {number} ratio pixelratio
* @private
*/
drawArrow(ctx, p0, p1, width, ratio) {
var asize = this.getArrowSize()[0] * ratio
var l = ol_coordinate_dist2d(p0, p1)
var dx = (p0[0] - p1[0]) / l
var dy = (p0[1] - p1[1]) / l
width = Math.max(this.getArrowSize()[1] / 2, width / 2) * ratio
ctx.beginPath()
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p0[0] - asize * dx + width * dy, p0[1] - asize * dy - width * dx)
ctx.lineTo(p0[0] - asize * dx - width * dy, p0[1] - asize * dy + width * dx)
ctx.lineTo(p0[0], p0[1])
ctx.fill()
}
/** Renderer function
* @param {Array<ol.coordinate>} geom The pixel coordinates of the geometry in GeoJSON notation
* @param {ol.render.State} e The olx.render.State of the layer renderer
*/
_render(geom, e) {
if (e.geometry.getType() === 'LineString') {
var i, g, p, ctx = e.context
// Get geometry used at drawing
if (!this._visible) {
var a = e.pixelRatio / e.resolution
var cos = Math.cos(e.rotation)
var sin = Math.sin(e.rotation)
g = e.geometry.getCoordinates()
var dx = geom[0][0] - g[0][0] * a * cos - g[0][1] * a * sin
var dy = geom[0][1] - g[0][0] * a * sin + g[0][1] * a * cos
geom = []
for (i = 0; p = g[i]; i++) {
geom[i] = [
dx + p[0] * a * cos + p[1] * a * sin,
dy + p[0] * a * sin - p[1] * a * cos,
p[2]
]
}
}
var asize = this.getArrowSize()[0] * e.pixelRatio
ctx.save()
// Offsets
if (this.getOffset(0))
this._splitAsize(geom, this.getOffset(0) * e.pixelRatio)
if (this.getOffset(1))
this._splitAsize(geom, this.getOffset(1) * e.pixelRatio, true)
// Arrow 1
if (geom.length > 1 && (this.getArrow() === -1 || this.getArrow() === 2)) {
p = this._splitAsize(geom, asize)
if (this._acolor)
ctx.fillStyle = this._acolor
else
ctx.fillStyle = this.getColor(e.feature, 0)
this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 0), e.pixelRatio)
}
// Arrow 2
if (geom.length > 1 && this.getArrow() > 0) {
p = this._splitAsize(geom, asize, true)
if (this._acolor)
ctx.fillStyle = this._acolor
else
ctx.fillStyle = this.getColor(e.feature, 1)
this.drawArrow(ctx, p[0], p[1], this.getWidth(e.feature, 1), e.pixelRatio)
}
// Split into
var geoms = this._splitInto(geom, 255, 2)
var k = 0
var nb = geoms.length
// Draw
ctx.lineJoin = 'round'
ctx.lineCap = this._lineCap || 'butt'
if (geoms.length > 1) {
for (k = 0; k < geoms.length; k++) {
var step = k / nb
g = geoms[k]
ctx.lineWidth = this.getWidth(e.feature, step) * e.pixelRatio
ctx.strokeStyle = this.getColor(e.feature, step)
ctx.beginPath()
ctx.moveTo(g[0][0], g[0][1])
for (i = 1; p = g[i]; i++) {
ctx.lineTo(p[0], p[1])
}
ctx.stroke()
}
}
ctx.restore()
}
}
/** Split extremity at
* @param {ol.geom.LineString} geom
* @param {number} asize
* @param {boolean} end start=false or end=true, default false (start)
*/
_splitAsize(geom, asize, end) {
var p, p1, p0
var dl, d = 0
if (end)
p0 = geom.pop()
else
p0 = geom.shift()
p = p0
while (geom.length) {
if (end)
p1 = geom.pop()
else
p1 = geom.shift()
dl = ol_coordinate_dist2d(p, p1)
if (d + dl > asize) {
p = [p[0] + (p1[0] - p[0]) * (asize - d) / dl, p[1] + (p1[1] - p[1]) * (asize - d) / dl]
dl = ol_coordinate_dist2d(p, p0)
if (end) {
geom.push(p1)
geom.push(p)
geom.push([p[0] + (p0[0] - p[0]) / dl, p[1] + (p0[1] - p[1]) / dl])
} else {
geom.unshift(p1)
geom.unshift(p)
geom.unshift([p[0] + (p0[0] - p[0]) / dl, p[1] + (p0[1] - p[1]) / dl])
}
break
}
d += dl
p = p1
}
return [p0, p]
}
/** Split line geometry into equal length geometries
* @param {Array<ol.coordinate>} geom
* @param {number} nb number of resulting geometries, default 255
* @param {number} nim minimum length of the resulting geometries, default 1
*/
_splitInto(geom, nb, min) {
var i, p
var dt = this._noOverlap ? 1 : .9
// Split geom into equal length geoms
var geoms = []
var dl, l = 0
for (i = 1; p = geom[i]; i++) {
l += ol_coordinate_dist2d(geom[i - 1], p)
}
var length = Math.max(min || 2, l / (nb || 255))
var p0 = geom[0]
l = 0
var g = [p0]
i = 1
p = geom[1]
while (i < geom.length) {
var dx = p[0] - p0[0]
var dy = p[1] - p0[1]
dl = Math.sqrt(dx * dx + dy * dy)
if (l + dl > length) {
var d = (length - l) / dl
g.push([
p0[0] + dx * d,
p0[1] + dy * d
])
geoms.push(g)
p0 = [
p0[0] + dx * d * dt,
p0[1] + dy * d * dt
]
g = [p0]
l = 0
} else {
l += dl
p0 = p
g.push(p0)
i++
p = geom[i]
}
}
geoms.push(g)
return geoms
}
}
export default ol_style_FlowLine