ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
392 lines (366 loc) • 12.8 kB
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).
*
* Photo style for vector features
*/
import ol_style_RegularShape from 'ol/style/RegularShape.js'
import {asString as ol_color_asString} from 'ol/color.js'
import ol_style_Stroke from 'ol/style/Stroke.js'
import ol_style_Fill from 'ol/style/Fill.js'
/**
* @classdesc
* Set Photo style for vector features.
*
* @constructor
* @param {} options
* @param { default | square | circle | anchored | folio } options.kind
* @param {boolean} options.crop crop within square, default is false
* @param {Number} options.radius symbol size
* @param {Number} [options.shadow=0] drop a shadow (the shadow width in pixel)
* @param {string} [options.declutterMode] Declutter mode "declutter" | "obstacle" | "none" | undefined
* @param {ol_style_Stroke} options.stroke
* @param {String} options.src image src
* @param {String} options.crossOrigin The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer.
* @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 {function} [options.onload] callback when image is loaded (to redraw the layer)
* @param {function} [options.onerror] callback when image is on error (not loaded)
* @extends {ol_style_RegularShape}
* @implements {ol.structs.IHasChecksum}
* @api
*/
var ol_style_Photo = class olstylePhoto extends ol_style_RegularShape {
constructor(options) {
options = options || {}
if (!options.displacement){
options.displacement = [options.offsetX || 0, -options.offsetY || 0]
}
var sanchor = (options.kind === "anchored" ? 8 : 0)
var shadow = (Number(options.shadow) || 0)
if (!options.stroke) {
options.stroke = new ol_style_Stroke({ width: 0, color: "#000" })
}
var strokeWidth = options.stroke.getWidth()
if (strokeWidth < 0) strokeWidth = 0;
if (options.kind == 'folio') strokeWidth += 6;
options.stroke.setWidth(strokeWidth)
super({
radius: options.radius + strokeWidth + sanchor / 2 + shadow / 2,
points: 0,
displacement: [options.displacement[0] || 0, (options.displacement[1] || 0) + sanchor],
// No fill to create a hit detection Image (v5) or transparent (v6)
fill: ol_style_RegularShape.prototype.render ? new ol_style_Fill({ color: [0, 0, 0, 0] }) : null,
declutterMode: options.declutterMode,
})
this.sanchor_ = sanchor;
this._shadow = shadow;
// Hack to get the hit detection Image (v4.6.5 ?)
if (!this.getHitDetectionImage) {
var img = super.getImage.call(this)
if (!this.hitDetectionCanvas_) {
for (var i in this) {
if (this[i] && this[i].getContext && this[i] !== img) {
this.hitDetectionCanvas_ = this[i]
break
}
}
}
// Clone canvas for hit detection (old versions)
this.hitDetectionCanvas_ = document.createElement('canvas')
this.hitDetectionCanvas_.width = img.width
this.hitDetectionCanvas_.height = img.height
var hit = this.hitDetectionCanvas_
this.getHitDetectionImage = function () {
return hit
}
}
this._stroke = options.stroke
this._fill = options.fill
this._crop = options.crop
this._crossOrigin = options.crossOrigin
this._kind = options.kind || "default"
this._radius = options.radius
this._src = options.src
this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0]
this._onload = options.onload
this._onerror = options.onerror
if (typeof (options.opacity) == 'number'){
this.setOpacity(options.opacity)
}
if (typeof (options.rotation) == 'number'){
this.setRotation(options.rotation)
}
// Calculate image
this.render();
this.getImage()
}
/** Set photo offset
* @param {ol.pixel} offset
*/
setOffset(offset) {
this._offset = [offset[0] || 0, offset[1] || 0]
this.render()
this.getImage()
}
/**
* Clones the style.
* @return {ol_style_Photo}
*/
clone() {
var i = new ol_style_Photo({
stroke: this._stroke,
fill: this._fill,
shadow: this._shadow,
crop: this._crop,
crossOrigin: this._crossOrigin,
kind: this._kind,
radius: this._radius,
src: this._src,
offsetX: this._offset[0],
offsetY: this._offset[1],
opacity: this.getOpacity(),
rotation: this.getRotation(),
declutterMode: this.getDeclutterMode ? this.getDeclutterMode() : null,
})
i.render()
i.getImage()
return i
}
/**
* Draw the form without the image
* @private
*/
drawBack_(context, color, strokeWidth, pixelratio) {
var shadow = this._shadow
var canvas = context.canvas
context.beginPath()
context.fillStyle = color
context.clearRect(0, 0, canvas.width, canvas.height)
var width = canvas.width / pixelratio
var height = canvas.height / pixelratio
switch (this._kind) {
case 'square': {
context.rect(0, 0, width - shadow, height - shadow)
break
}
case 'circle': {
context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius + strokeWidth, 0, 2 * Math.PI, false)
break
}
case 'folio': {
var offset = 6
strokeWidth -= offset
context.strokeStyle = 'rgba(0,0,0,0.5)'
context.lineWidth = 1
var w = width - shadow - 2 * offset
var a = Math.atan(6 / w)
context.save()
context.rotate(-a)
context.translate(-6, 2)
context.beginPath()
context.rect(offset, offset, w, w)
context.stroke()
context.fill()
context.restore()
context.save()
context.translate(6, -1)
context.rotate(a)
context.beginPath()
context.rect(offset, offset, w, w)
context.stroke()
context.fill()
context.restore()
context.beginPath()
context.rect(offset, offset, w, w)
context.stroke()
break
}
case 'anchored': {
context.roundRect(this.sanchor_ / 2, 0, width - this.sanchor_ - shadow, height - this.sanchor_ - shadow, strokeWidth)
context.moveTo(width / 2 - this.sanchor_ - shadow / 2, height - this.sanchor_ - shadow)
context.lineTo(width / 2 + this.sanchor_ - shadow / 2, height - this.sanchor_ - shadow)
context.lineTo(width / 2 - shadow / 2, height - shadow); break
}
default: {
// roundrect
context.roundRect(0, 0, width - shadow, height - shadow, strokeWidth)
break
}
}
context.closePath()
}
/**
* @return {RenderOptions} The render options
*/
createRenderOptions() {
var opt = super.createRenderOptions()
opt.photoOptions = [
'photo',
this._crossOrigin,
this._crop,
this._src,
this._shadow,
this._kind,
].join('-')
return opt;
}
/**
* Get the image icon.
* @param {number} pixelRatio Pixel ratio.
* @return {HTMLCanvasElement} Image or Canvas element.
* @api
*/
getImage(pixelratio) {
pixelratio = pixelratio || window.devicePixelRatio;
var canvas = super.getImage(pixelratio)
if ((this._gethit || this.img_) && this._currentRatio === pixelratio) return canvas;
// Calculate image at pixel ratio
this._currentRatio = pixelratio;
var strokeStyle
var strokeWidth = 0
if (this._stroke) {
strokeStyle = ol_color_asString(this._stroke.getColor())
strokeWidth = this._stroke.getWidth()
}
// Draw hitdetection image
this._gethit = true
var context = this.getHitDetectionImage().getContext('2d')
context.save()
context.setTransform(1, 0, 0, 1, 0, 0)
this.drawBack_(context, "#000", strokeWidth, 1)
context.fill()
context.restore()
this._gethit = false
// Draw the image
context = canvas.getContext('2d')
context.save()
context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0)
this.drawBack_(context, strokeStyle, strokeWidth, pixelratio)
// Draw a shadow
if (this._shadow) {
context.shadowColor = 'rgba(0,0,0,0.5)'
context.shadowBlur = pixelratio * this._shadow / 2
context.shadowOffsetX = pixelratio * this._shadow / 2
context.shadowOffsetY = pixelratio * this._shadow / 2
}
context.fill()
context.restore()
var self = this
var img = this.img_ = new Image()
if (this._crossOrigin) img.crossOrigin = this._crossOrigin
img.src = this._src
// Draw image
if (img.width) {
self.drawImage_(canvas, img, pixelratio)
} else {
img.onload = function () {
self.drawImage_(canvas, img, pixelratio)
// Force change (?!)
// self.setScale(1);
if (self._onload) self._onload()
}
if (self._onerror) {
img.onerror = function () {
self._onerror()
}
}
}
// Set anchor (ol < 6)
if (!this.getDisplacement) {
var a = this.getAnchor()
a[0] = (canvas.width / pixelratio - this._shadow) / 2 - this._offset[0]
if (this.sanchor_) {
a[1] = canvas.height / pixelratio - this._shadow - this._offset[1]
} else {
a[1] = (canvas.height / pixelratio - this._shadow) / 2 - this._offset[1]
}
}
return canvas
}
/** Returns the photo image
* @returns {HTMLImageElement}
*/
getPhoto() {
return this.img_
}
/**
* Draw an timage when loaded
* @private
*/
drawImage_(canvas, img, pixelratio) {
// Remove the circle on the canvas
var context = (canvas.getContext('2d'))
var strokeWidth = 0
if (this._stroke)
strokeWidth = this._stroke.getWidth()
var size = 2 * this._radius
context.save()
if (ol_style_RegularShape.prototype.render)
context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0)
if (this._kind == 'circle') {
context.beginPath()
context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius, 0, 2 * Math.PI, false)
context.clip()
}
var s, x, y, w, h, sx, sy, sw, sh
// Crop the image to a square vignette
if (this._crop) {
s = Math.min(img.width / size, img.height / size)
sw = sh = s * size
sx = (img.width - sw) / 2
sy = (img.height - sh) / 2
x = y = 0
w = h = size + 1
} else {
// Fit the image to the size
s = Math.min(size / img.width, size / img.height)
sx = sy = 0
sw = img.width
sh = img.height
w = s * sw
h = s * sh
x = (size - w) / 2
y = (size - h) / 2
}
x += strokeWidth + this.sanchor_ / 2
y += strokeWidth
context.drawImage(img, sx, sy, sw, sh, x, y, w, h)
// Draw a circle to avoid aliasing on clip
if (this._kind == 'circle' && strokeWidth) {
context.beginPath()
context.strokeStyle = ol_color_asString(this._stroke.getColor())
context.lineWidth = strokeWidth / 4
context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius, 0, 2 * Math.PI, false)
context.stroke()
}
context.restore()
}
}
/**
* Draws a rounded rectangle using the current state of the canvas.
* Draw a rectangle if the radius is null.
* @param {Number} x The top left x coordinate
* @param {Number} y The top left y coordinate
* @param {Number} width The width of the rectangle
* @param {Number} height The height of the rectangle
* @param {Number} radius The corner radius.
*/
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (!r) {
this.rect(x,y,w,h);
} else {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
this.closePath();
}
return this;
};
export default ol_style_Photo