import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import GeometryCollection from 'ol/geom/GeometryCollection'
import Circle from 'ol/style/Circle'
import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import { getVectorContext } from 'ol/render'
/**
* Module de gestion d'une couche de dessin permettant les annotations
* @module drawing
*/
const libNamespace = 'drawing'
/**
* Exception lancé si demande de dessin avec la fin d'un autre
*/
class MultiDrawingException extends Error {
constructor () {
super()
this.message = 'An other drawing is not finish'
this.name = 'MultiDrawingException'
}
}
/**
* Service de gestion d'une couche de dessin permettant les annotations
*/
class Drawing {
constructor (viewer, options) {
viewer.DRAWING_LOADED = true
this.ctx = viewer
this.drawingStarted = false
Object.assign(this, options)
}
/** Le style du cercle présent sous le pointeur. */
static pointStyle = new Circle({
radius: 5,
fill: new Fill({
color: [0, 153, 255, 1],
width: 3,
}),
stroke: new Stroke({
color: [255, 255, 255, 1],
width: 2,
}),
})
/**
* Traitement commun au début d'un dessin
*/
validStartDraw () {
if (this.drawingStarted) {
throw new MultiDrawingException()
}
// Désactive les ToolTip et la sélection
if (this.ctx.DATALAYER_LOADED) {
this.ctx.dataLayer.changeMapMode('modify')
}
if (this.ctx.TOOLTIPS_LOADED) {
this.ctx.tooltips.disabledTooltip()
}
this.drawingStarted = true
}
/**
* Traitement commun à la fin d'un dessin
*/
validEndDraw () {
// Réactive les ToolTip et la sélection
if (this.ctx.DATALAYER_LOADED) {
this.ctx.dataLayer.changeMapMode('select')
}
if (this.ctx.TOOLTIPS_LOADED) {
this.ctx.tooltips.enabledTooltip()
}
this.drawingStarted = false
// Vide la fonction d'annulation
this.cancelDraw = () => {}
};
/**
* Permet de dessiner un point et de récupérer la feature
* @param {string | number} id Identifiant de la feature à créer
* @param {Object<string,*>} param Propriétés à ajouter à la feature
* @param {Function} callback Fonction de callback avec en paramètre la feature (ou false si annulé)
* @param {Object} context Context d'appel du callback
*/
drawPoint (id, param, callback, context) {
this.validStartDraw()
let point = null
// Permet de créer le point et de générer le rendu de la carte
const displaySnap = coordinate => {
if (point === null) {
point = new Point(coordinate)
} else {
point.setCoordinates(coordinate)
}
this.ctx.Map.render()
}
// A chaque déplacement du curseur, calcule la position du point
const onPointerMove = ev => {
if (ev.dragging) {
return
}
displaySnap(this.ctx.Map.getEventCoordinate(ev.originalEvent))
}
// Avant la génération de la carte par OpenLayers
const onPostCompose = ev => {
// Si le point existe, l'ajoute sur une couche temporaire
const vectorContext = getVectorContext(ev)
if (point !== null && vectorContext) {
vectorContext.setImageStyle(Drawing.pointStyle)
vectorContext.drawPoint(point)
}
}
// Lors du clique de confirmation de position
const onValidDrawingClick = () => {
this.validEndDraw()
this.ctx.Map.un('pointermove', onPointerMove)
this.ctx.Map.un('postrender', onPostCompose)
this.ctx.Map.un('click', onValidDrawingClick)
if (typeof callback === 'function') {
param = param || {}
param.geometry = point
param.id = id // Pour compatibilité. Devrait être supprimé sinon.
const feature = new Feature(param)
feature.setId(id)
callback.call(context || this, feature)
}
}
// Permet d'annuler le dessin
this.cancelDraw = () => {
this.validEndDraw()
point = null
this.ctx.Map.renderSync()
this.ctx.Map.un('pointermove', onPointerMove)
this.ctx.Map.un('postrender', onPostCompose)
this.ctx.Map.un('click', onValidDrawingClick)
if (typeof callback === 'function') {
callback.call(context || this, false)
}
}
// Lance les fonctions précédente en fonction de l'événement levé
this.ctx.Map.on('pointermove', onPointerMove)
this.ctx.Map.on('postrender', onPostCompose)
this.ctx.Map.on('click', onValidDrawingClick)
}
/**
* Permet de dessiner un point avec accroche sur objet et de récupérer la feature
* @param {string | number} id Identifiant de la feature à créer
* @param {Object} param Propriétés à ajouter à la feature
* @param {Array<ol.Feature>} closestFeatures Liste des features sur lesquelles peut s'accrocher le point
* @param {number} hangsLength Distance en pixel pour l'accroche du point
* @param {Function} callback Fonction de callback avec en paramètre la feature (ou false si annulé)
* @param {Object} context Context d'appel de la fonction
*/
drawPointWithClosest (id, param, closestFeatures, hangsLength, callback, context) {
this.validStartDraw()
let point = null
// Création d'une collection de geometries pour l'accroche
let closestGeometries = []
closestFeatures.forEach(feature => {
closestGeometries.push(feature.getGeometry())
})
closestGeometries = new GeometryCollection(closestGeometries)
// Permet d'afficher le point et de vérifier l'accroche
const displaySnap = coordinate => {
let closestPoint = coordinate
if (closestFeatures.length !== 0) {
// Recherche le point le plus proche
closestPoint = closestGeometries.getClosestPoint(coordinate)
// Récupère les coordonnées du point et du curseur en pixel
const closestPointPx = this.ctx.Map.getPixelFromCoordinate(closestPoint)
const coordinatePx = this.ctx.Map.getPixelFromCoordinate(coordinate)
// Calcule la distance entre les deux points
const closestLength = Math.abs((closestPointPx[0] + closestPointPx[1]) - (coordinatePx[0] + coordinatePx[1]))
// Si superieur à hangsLength, pas d'accroche
if (closestLength > hangsLength) {
closestPoint = coordinate
}
}
if (point === null) {
point = new Point(coordinate)
} else {
point.setCoordinates(coordinate)
}
this.ctx.Map.render()
}
// A chaque déplacement du pointeur
const onPointerMove = ev => {
if (ev.dragging) {
return
}
displaySnap(this.ctx.Map.getEventCoordinate(ev.originalEvent))
}
// Avant le calcule du rendu de la carte
const onPostCompose = ev => {
const vectorContext = getVectorContext(ev)
if (point !== null && vectorContext) {
vectorContext.setImageStyle(Drawing.pointStyle)
vectorContext.drawPoint(point)
}
}
// Au clique de confirmation du point
const onValidDrawingClick = () => {
this.validEndDraw(this)
this.ctx.Map.un('pointermove', onPointerMove)
this.ctx.Map.un('postrender', onPostCompose)
this.ctx.Map.un('click', onValidDrawingClick)
if (typeof callback === 'function') {
param = param || {}
param.geometry = point
param.id = id // Pour compatibilité. Devrait être supprimé sinon.
const feature = new Feature(param)
feature.setId(id)
callback.call(context || this, feature)
}
}
// Permet d'annuler le dessin
this.cancelDraw = () => {
this.validEndDraw(this)
point = null
this.ctx.Map.renderSync()
this.ctx.Map.un('pointermove', onPointerMove)
this.ctx.Map.un('postrender', onPostCompose)
this.ctx.Map.un('click', onValidDrawingClick)
if (typeof callback === 'function') {
callback.call(context || this, false)
}
}
// Lance les fonctions précédente en fonction de l'événement levé
this.ctx.Map.on('pointermove', onPointerMove)
this.ctx.Map.on('postrender', onPostCompose)
this.ctx.Map.on('click', onValidDrawingClick)
}
/**
* Permet d'annuler la création d'un dessin. Lance le callback avec false
*/
cancelDraw () {
// Générée dans les fonctions de dessin
}
}
// Permet d'etendre le module
export default function extendCoreLib (options) {
return function patch (viewer) {
const functions = { }
functions[libNamespace] = new Drawing(viewer, options)
return Object.assign(viewer, functions)
}
}