import * as Extent from 'ol/extent'
import * as Easing from 'ol/easing'
import { Style, Stroke, Fill, Circle, Text } from 'ol/style'
import * as Condition from 'ol/events/condition'
import { unByKey } from 'ol/Observable'
import Feature from 'ol/Feature'
import Collection from 'ol/Collection'
import * as Tilegrid from 'ol/tilegrid'
import Layer from 'ol/layer/Layer'
import VectorTile from 'ol/layer/VectorTile'
import VectorLayer from 'ol/layer/Vector'
import Cluster from 'ol/source/Cluster'
import VectorSource from 'ol/source/Vector'
import { Select, Draw, Modify, Translate, DragBox } from 'ol/interaction'
import { Point, LineString } from 'ol/geom'
import GeoJSON from 'ol/format/GeoJSON'
import * as proj from 'ol/proj'
import { getVectorContext } from 'ol/render'
import geojsonvt from 'geojson-vt'
import Zonal from '../interaction/zonal'
import Measure from '../interaction/Measure'
import CustomDrawTouch from '../interaction/CustomDrawTouch'
import CustomDraw from '../interaction/CustomDraw'
import * as MapviewerServices from '../tools/mapviewer-services'
import IsRequired from '../utils/IsRequired'
import { getPointForGeometry } from '../tools/services/centroid'
import ControlModifyTools from '../control/ControlModifyTools'
import CustomHeatmap from '../layer/CustomHeatMap'
import { bbox as bboxStrategy, tile as tileStrategy } from 'ol/loadingstrategy'
import { createXYZ } from 'ol/tilegrid'
import Guid from '../utils/Guid'
import { isSplitPossible, splitFeature } from '../tools/services/featureSplit'
import { geoJsonToFeature } from '../tools/services/geojsons'
import { getZoomForScale } from '../services/scales-service'
import { getWfsVectorSource } from '../utils/wfs-utils'
/**
* @typedef {Object} Mapviewer
*/
/**
* Module de gestion des couches vectorielles de données
* @module dataLayer
*/
/**
* @typedef {String} MapMode Liste des modes de carte disponibles
*/
/**
* @typedef {String} SelectMode
*/
const libNamespace = 'dataLayer'
const STYLE_NONE = [
new Style({
display: 'none',
}),
]
const STYLE_CLUSTER_LINK = [
new Style({
stroke: new Stroke({
color: '#FFF',
width: 1,
}),
}),
]
const CACHE_CLUSTER = {}
const DEFAULT_CLUSTER_STYLE_FUNCTION = function ({ features, cluster, resolution, zoom }) {
if (!CACHE_CLUSTER[features.length]) {
CACHE_CLUSTER[features.length] = [
new Style({
image: new Circle({
radius: 20,
stroke: new Stroke({
color: '#FAFAFA',
width: 2,
}),
fill: new Fill({
color: '#9400d3',
}),
}),
text: new Text({
text: `${features.length}`,
font: 'bold 16px Roboto',
offsetY: 2,
fill: new Fill({
color: '#FAFAFA',
}),
}),
}),
]
}
return CACHE_CLUSTER[features.length]
}
const DATA_LAYER_TYPE = 'data'
/** Service de couche vectorielle */
class DataLayer {
/** Nom des propriétés systeme dans une feature */
propertiesName = {
// Propriété définissant si la feature est visible
VISIBLE_FEATURE: '__gmapv_visible',
// Propriété définissant si la feature est selectionnée
SELECTED_FEATURE: '__gmapv_located',
// Propriété définissant si la feature est activée
ACTIVATED_FEATURE: '__gmapv_activated',
// Fonction de filtre de cluster
CLUSTER_FILTER: '__gmapv_clusterFilter',
// Propriété pour la géométrie alternative un style
STYLE_GEOMETRY_POINT: '__gmapv_style_geometry_point',
// propriété pour sauvegarder les tags
TAG_FEATURE: '__gmapv_tags',
}
/** Nom des tags systeme dans les tags */
systemTagName = {
HOVERED_FEATURE: '__gmapv_hovered',
}
/** Permet d'exclure des layers de la selection */
excludeLayers = []
/** types de géométries sélectionnable en mode rectangle */
geometrieTypesSelectableInRectangularMode = []
/** peut-on sélectionner les features en mode rectangle ? */
selectHiddenFeatureInRectangularMode = false
createStyle = null // Style pour la création
/** Calques survolable */
hoverableLayer = []
/** Styles pour chaques calques */
_layersStyle = {}
/** instance du viewer
* @type Mapviewer
*/
ctx = null
/**
*
* @param {Mapviewer} viewer
* @param {Object} options options génériques du datalayer
*/
constructor (viewer, options) {
viewer.DATALAYER_LOADED = true
/** @type Mapviewer */
this.ctx = viewer
/** @type {SelectMode} */
this.selectMode = this.setSelectMode(DataLayer.SelectModes.REPLACE)
Object.assign(this, options)
this.created()
}
// permet de mapper les interaction select sur nos styles
_styleSelectedFeature () {
return (feature, resolution) => {
// Dans le cas d'un cluster, on applique le style du layer dominant
if (feature.get('features')) {
// Récupère tous les identifiants de layers
const layersId = feature.get('features')
.map(feature => feature.layerSourceId_)
.flat()
// On récupère le layer avec le plus d'occurrence
const layerId = layersId
.sort((a, b) =>
layersId.filter(v => v === a).length -
layersId.filter(v => v === b).length)
.pop()
// Retourne le style pour le layer dominant
return this._layersStyle[layerId](feature, resolution)
}
if (feature.get(this.propertiesName.VISIBLE_FEATURE) === false) {
return false
}
// On va merger toutes les fonction de style de chaque layer où la feature
// est présente. Ce cas peut se présenter si on a un layer de polygon et
// un layer de point alors que la feature est la même pour les deux
// layers. Il faudra s'assurer que le style contienne la geometry sur
// laquelle appliquer le filtre (côté client)
return feature.layerSourceId_
.filter(id => this.isVisible(id) && this._layersStyle[id] && !this.excludeLayers.includes(id))
.map(id => this._layersStyle[id](feature, resolution))
.flat()
.filter(style => style)
}
}
/**
* Fonction lancée à l'instanciation du module
*/
created () {
/** @type {Collection<Feature>} */
this._currentSelection = new Collection()
// Definition de l'interaction de selection propre au module dataLayer
this._selectInteraction = new Select({
condition: Condition.click,
toggleCondition: (evt) => {
return (this.selectMode === DataLayer.SelectModes.TOGGLE && Condition.click(evt)) ||
(this.selectMode === DataLayer.SelectModes.REPLACE && Condition.shiftKeyOnly(evt))
},
addCondition: (evt) => {
return this.selectMode === DataLayer.SelectModes.ADD && Condition.click(evt)
},
// removeCondition: (evt) => { return false/* this.selectMode === DataLayer.SelectModes.ADD */ },
// On ne récupère que les layers de données non exclus
layers: layer => {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
return layerId && this.layerExist(layerId) && this.isSelectableLayer(layer)
},
style: this._styleSelectedFeature(),
hitTolerance: this.ctx.hitTolerance,
features: this._currentSelection,
})
// On retransmet l'event hors de gmapv sous un aurtes nom
this._selectInteraction.on('select', ev => this._dispatchSelectEvent(ev))
this._zonalSelectInteraction = new Zonal({
// On ne récupère que les layers de données non exclus
layers: layer => {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
return layerId && this.layerExist(layerId) && this.isSelectableLayer(layer)
},
style: this._styleSelectedFeature(),
features: this._currentSelection,
filter: (feature) => { return feature.getGeometry().getType() === 'Point' && feature.get(this.propertiesName.VISIBLE_FEATURE) !== false },
viewer: this.ctx,
})
this._zonalSelectInteraction.on('select', ev => this._dispatchSelectEvent(ev))
// a DragBox interaction used to select features by drawing boxes
this._dragBoxInteraction = new DragBox({})
// clear selection when drawing a new box and when clicking on the map
this._dragBoxInteraction.on('boxstart', () => {
this._dragBoxInteraction.once('boxend', ev => {
onDrawSelection.call(this, {
geometry: this._dragBoxInteraction.getGeometry(),
filter: feature => this.isSelectableInRectangularMode(feature),
includeHidden: this.selectHiddenFeatureInRectangularMode,
})
})
})
this._polygonSelectInteraction = new Draw({
type: 'Polygon',
})
this._polygonSelectInteraction.on('drawstart', () => {
this._polygonSelectInteraction.once('drawend', ev => {
onDrawSelection.call(this, { geometry: ev.feature.getGeometry() })
})
})
function onDrawSelection (options) {
const polygon = options?.geometry
const filter = options.filter ? options.filter : () => true
const includeHidden = options?.includeHidden || false
if (!polygon) {
console.warn('onDrawSelection appelé sans géométrie')
return
}
const extent = polygon.getExtent()
const featuresInExtent = this.getFeaturesInExtent(extent, {
layers: this.getSelectableLayers(),
filter,
includeHidden,
})
const nextSelection = featuresInExtent.filter(feature =>
MapviewerServices.intersectsGeometry(polygon, feature.getGeometry()))
// ancienne selection
const lastSelection = this.getCurrentSelection()
let selected = []
let deselected = []
switch (this.selectMode) {
case DataLayer.SelectModes.TOGGLE:
selected = nextSelection.filter(feature => !lastSelection.includes(feature))
deselected = lastSelection.filter(feature => nextSelection.includes(feature))
break
case DataLayer.SelectModes.ADD:
selected = nextSelection.filter(feature => !lastSelection.includes(feature))
break
case DataLayer.SelectModes.REPLACE:
default:
selected = nextSelection
deselected = lastSelection.filter(feature => !nextSelection.includes(feature))
}
// modifie la sélection OL
deselected.forEach(feature => this._currentSelection.remove(feature))
selected.forEach(feature => this._currentSelection.push(feature))
// Dispatch l'event vers l'exterieur
this._dispatchSelectEvent({ selected, deselected })
}
// Définition de l'interaction de modification
this._modifyInteraction = new Modify({
features: this._currentSelection,
})
// Gestion de l'evenement de modification de feature
this._modifyInteraction.on('modifyend', ev => {
this.ctx.dispatchEvent('change:features', {
features: ev.features.getArray().slice(),
})
})
// Définition de l'interaction de drag & drop (translate)
this._dragAndDropInteraction = new Translate({
layers: (layer) => {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
const isDraggable = this._dragAndDropInteraction.get('draggableLayerList').includes(layerId)
return isDraggable
},
active: false,
})
// Passer une liste vide au debut
this._dragAndDropInteraction.set('draggableLayerList', [])
// Gestion de l'evenement de drag & drop de feature
this._dragAndDropInteraction.on('translatestart', evt => {
const common = {
evenType: evt.type,
coordinate: evt.coordinate,
startCoordinate: evt.startCoordinate,
}
// Si des features sont superposées
evt.features.forEach((feature) => {
const rtn = {
...common,
originalGeometry: feature.getGeometry(),
modifiedFeature: feature,
toolId: this.ctx.commonLayer.getCurrentToolId(),
}
this.ctx.dispatchEvent('modifyfeature:beforeModify', rtn)
})
})
this._dragAndDropInteraction.on('translateend', evt => {
const common = {
evenType: evt.type,
coordinate: evt.coordinate,
startCoordinate: evt.startCoordinate,
}
// Si des features sont superposées
evt.features.forEach((feature) => {
const rtn = {
...common,
originalGeometry: feature.getGeometry(),
modifiedFeature: feature,
toolId: this.ctx.commonLayer.getCurrentToolId(),
}
this.ctx.dispatchEvent('modifyfeature:modify', rtn)
})
})
this._drawPointTouchInteraction = this.ctx.isDesktop
? new CustomDraw({
active: false,
type: 'Point',
stopClick: true,
viewer: this.ctx,
})
: new CustomDrawTouch({
active: false,
type: 'Point',
viewer: this.ctx,
})
this._drawPointTouchInteraction.on('drawend', ev => {
this.ctx.dispatchEvent('drawend', ev)
})
this._drawLineStringTouchInteraction = this.ctx.isDesktop
? new CustomDraw({
active: false,
type: 'LineString',
stopClick: true,
viewer: this.ctx,
})
: new CustomDrawTouch({
active: false,
type: 'LineString',
viewer: this.ctx,
})
this._drawLineStringTouchInteraction.on('drawend', ev => {
this.ctx.dispatchEvent('drawend', ev)
})
this._drawPolygonTouchInteraction = this.ctx.isDesktop
? new CustomDraw({
active: false,
type: 'Polygon',
stopClick: true,
viewer: this.ctx,
})
: new CustomDrawTouch({
active: false,
type: 'Polygon',
viewer: this.ctx,
})
this._drawPolygonTouchInteraction.on('drawend', ev => {
this.ctx.dispatchEvent('drawend', ev)
})
this.ctx.on('change:padding', ({ padding }) => {
this._drawPointTouchInteraction.setPadding(padding)
this._drawLineStringTouchInteraction.setPadding(padding)
this._drawPolygonTouchInteraction.setPadding(padding)
})
this._measureLineInteraction = new Measure({
mode: 'length',
viewer: this.ctx,
active: false,
})
this._measureLineInteraction.on('measureend', event => this.ctx.dispatchEvent('measureend', event))
this._measureAreaInteraction = new Measure({
mode: 'area',
viewer: this.ctx,
active: false,
})
this._measureAreaInteraction.on('measureend', event => this.ctx.dispatchEvent('measureend', event))
// Mode de selection
this.ctx.commonLayer.createMapMode({
name: 'select',
interactions: this._selectInteraction,
isDefault: true,
onactive: () => {
this.setSelectMode(DataLayer.SelectModes.REPLACE)
},
})
// Mode de selection multiples
this.ctx.commonLayer.createMapMode({
name: 'multiselect',
interactions: this._selectInteraction,
onactive: () => {
this.setSelectMode(DataLayer.SelectModes.TOGGLE)
},
})
// Mode de selection rectangulaire
this.ctx.commonLayer.createMapMode({
name: 'rectangular',
interactions: [this._selectInteraction, this._dragBoxInteraction],
})
// Mode de selection zonale
this.ctx.commonLayer.createMapMode({
name: 'zonal',
interactions: this._zonalSelectInteraction,
})
// Mode de selection polygonale
this.ctx.commonLayer.createMapMode({
name: 'polygon',
interactions: this._polygonSelectInteraction,
})
// Mode de modification
this.ctx.commonLayer.createMapMode({
name: 'modify',
interactions: [this._selectInteraction, this._modifyInteraction],
})
// Mode de drad & drop des features d'un layer
this.ctx.commonLayer.createMapMode({
name: 'dragAndDrop',
interactions: [this._dragAndDropInteraction],
oninactive: () => {
// passer une liste vide pour desactiver les layers
this._dragAndDropInteraction.set('draggableLayerList', [])
},
})
// Mode de modification tactile
this.ctx.commonLayer.createMapMode({
name: 'modifyPointTouch',
interactions: this._modifyPointTouchInteraction,
onactive: () => {
const selection = this.getCurrentSelection()
if (selection.length === 1) {
this._modifyPointTouchInteraction.setFeature(selection[0])
}
},
})
// Mode de mesure de ligne
this.ctx.commonLayer.createMapMode({
name: 'measureLine',
interactions: this._measureLineInteraction,
onactive: () => {
this._measureLineInteraction.startMeasurement()
},
})
// Mode de mesure de surface
this.ctx.commonLayer.createMapMode({
name: 'measureArea',
interactions: this._measureAreaInteraction,
onactive: () => {
this._measureAreaInteraction.startMeasurement()
},
})
// Mode de dessin de point
this.ctx.commonLayer.createMapMode({
name: '_drawPointTouch',
interactions: this._drawPointTouchInteraction,
})
// Mode de dessin de ligne tactile
this.ctx.commonLayer.createMapMode({
name: '_drawLineStringTouch',
interactions: this._drawLineStringTouchInteraction,
})
// Mode de dessin de polygon tactile
this.ctx.commonLayer.createMapMode({
name: '_drawPolygonTouch',
interactions: this._drawPolygonTouchInteraction,
})
// fonction au changement de view
this.ctx.getMap().on('change:view', this.onViewChange.bind(this))
}
/**
* Mets à jours les propriétés OpenLayer ou Karteis Mapviewer d'un calque
* @param {ol.layer} layer Calque OpenLayer
* @param {Object} options Options possibles pour les calques
*/
onViewChange (event) {
// lorsque l'on change de view, si la projection a évoluée, on reprojete les données
const oldProjection = event.oldValue.getProjection().getCode()
const newProjection = event.target.getView().getProjection().getCode()
if (!oldProjection || !newProjection || oldProjection === newProjection) {
// pas de srid, ou le même, rien a faire
return
}
// sinon on reprojete les géométrie du srid existant vers le nouveau
this.getLayers().forEach(layer => {
const layerInfos = this.viewer.commonLayer.getLayerInfos(layer)
const source = this.getSourceLayer(layerInfos.id)
source.getFeatures().forEach((feature) => {
// on peut avoir mit la même feature dans plusieurs calques, on sauvegarde le fait d'avoir changé sa projection
if (feature.getGeometry() !== null && feature.get(this.viewer.commonLayer.propertiesName.GEOMETRY_PROJECTION) !== newProjection) {
feature.getGeometry().transform(oldProjection, newProjection)
feature.set(this.viewer.commonLayer.propertiesName.GEOMETRY_PROJECTION, newProjection, true)
}
})
})
}
// Est-on dans un mode qui empêche de faire du survol de feature
hasNonHoverableInteractionInProgress () {
return ['_drawPointTouch', '_drawLineStringTouch', '_drawPolygonTouch', 'measureArea', 'measureLine'].includes(this.ctx.commonLayer.getCurrentMapMode()?.name)
}
/**
* Permet de mettre à plat les features
* Récupère que les features fonctionnelle même s'il
* y a des features de cluster en paramètre
*
* @param {Feature} features Liste des features
*/
flattenFeatures (features) {
return features.map(feature => {
const clusterizedFeatures = feature.get('features')
if (!clusterizedFeatures) {
return feature
}
if (clusterizedFeatures.length) {
// TODO: Revoir le mode de selection pour ce cas là
const layerId = clusterizedFeatures[0].layerSourceId_[0]
return this.getSelectableFeatures(
feature,
this.getLayer(layerId),
)
}
return null
}).flat()
}
/**
* Dispatch un event MapViewer à partir d'un event OL
*
* @param {ol.SelectEvent} event Evenement de sélection Openlayers
* @private
*/
_dispatchSelectEvent ({ selected = [], deselected = [], mapBrowserEvent }) {
// S'assure de la non présence de cluster
const flatDeselected = this.flattenFeatures(deselected)
const flatSelected = this.flattenFeatures(selected)/* .filter((featSelected) => {
const selectedId = featSelected.getId()
return !flatDeselected.find((featUnselected) => featUnselected.getId() === selectedId)
}) */
// retire une feature désélectionné de la liste sélectionné (arrive avec les clusters)
flatSelected.forEach(feature => feature.set(this.propertiesName.SELECTED_FEATURE, true))
flatDeselected.forEach(feature => feature.set(this.propertiesName.SELECTED_FEATURE, false))
// Dispatch l'event de MapViewer
this.ctx.dispatchEvent('change:selection', {
selected: flatSelected.slice(),
deselected: flatDeselected.slice(),
selection: this._currentSelection.getArray().slice(),
coordinate: mapBrowserEvent && mapBrowserEvent.coordinate,
toolId: this.ctx.commonLayer.getCurrentToolId(),
})
}
/**
* Permet de savoir si le layer est sélectionnable actuellement
*
* @param {ol.Layer} layer Layer
* @returns {Boolean} True si le layer est sélectionnable
*/
isSelectableLayer (layer) {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
return layer.getVisible() &&
!this.excludeLayers.includes(layerId)
}
/**
* Permet de savoir si le layer est survolable actuellement
*
* @param {ol.Layer} layer Layer
* @returns {Boolean} True si le layer est survolable
*/
isHoverableLayer (layer) {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
return layer.getVisible() &&
this.hoverableLayer.includes(layerId)
}
/**
* Retourne les layers actuellement sélectionnable
*
* @returns {Array<ol.Layer>} Liste des layers sélectionnable
*/
getSelectableLayers () {
return this.getLayers().filter(layer => this.isSelectableLayer(layer))
}
/**
* Permet de récuéprer les features réellement sélectionnées à partir
* d'une feature. Retourne toujours un tableau
* Nécessaire sur un cluster pour récupérer le sous-ensemble
*
* @param {Feature} feature Feature normal ou issue d'un cluster
* @param {ol.Layer} layer Layer de la feature
* @param {number} resolution Resolution de la carte
* @param {bool} includeHidden Inclure les feature cachée?
* @returns {Array<Feature>} Liste des features fonctionnelles visibles
*/
getSelectableFeatures (feature, layer, resolution = this.ctx.Map.getView().getResolution(), includeHidden = false) {
// Dans le cas d'une feature de cluster à destructurer
// NDHE: Si on à sélectionné des feature via leur cluster puis que l'on a zoomé
// isCluster n'est plus sur une source cluster..
if (/* this.isCluster(layer) && */ feature.get('features')) {
// Récupère le filtre de cluster
const clusterFilter = layer.get(this.propertiesName.CLUSTER_FILTER)
// Filtre les features visibles
return clusterFilter(feature.get('features'), resolution)
}
// Evaluation de la visiblité si nécessaire
if (this.isVisibleFeature({ feature, resolution, includeHidden })) {
return [feature]
}
return []
}
/**
* Permet de récupérer des features selectionnables présentes dans une extent
*
* @param {ol.Extent} extent Extent de l'extraction
* @param {object} options Options
* @param {Array<ol.Layer>|undefined} options.layers Liste des layers dans lesquelle rechercher. Par défaut, tout les layers de données
* @param {number|undefined} options.resolution Résolution de la carte. Par defaut, celle de la carte principale.
* @param {Function|undefined} options.filter Permet de filtrer les features retournées. La fonction prend une feature en argument et retourne un boolean. Par défaut, pas de filtre
* @returns {Array<Feature>} Liste des features dans l'extent
*/
getFeaturesInExtent (extent, {
layers = this.getLayers(),
resolution = this.ctx.Map.getView().getResolution(),
filter = () => true,
includeHidden = false,
} = {}) {
return layers.reduce((allFeatures, layer) => {
layer.getSource().forEachFeatureInExtent(extent, feature => {
const selectableFeatures = this.getSelectableFeatures(feature, layer, resolution, includeHidden)
allFeatures.push(...selectableFeatures.filter(filter))
})
return allFeatures
}, [])
}
/**
* Permet de récupérer la selection courrante
*
* @return {Array<Feature>} Tableau des features sélectionnées
*/
getCurrentSelection () {
return this._currentSelection.getArray().slice()
}
/**
* Permet d'effacer l'ensemble de la selection actuelle
*/
clearCurrentSelection () {
// Récupère les features à retirer et vide la selection
const selection = this._currentSelection.getArray().slice()
this._currentSelection.clear()
// Dispatch l'event
this._dispatchSelectEvent({
selected: [],
deselected: selection,
})
}
/**
* Permet de désactiver la selection sur un layer
* Si des features du layer sont sélectionnées, elles ne seront pas déselectionnées par défaut
*
* @param {string | number} idLayer Identifiant du layer
*/
excludeLayerSelection (idLayer) {
if (this.layerExist(idLayer) && this.excludeLayers.indexOf(idLayer) === -1) {
this.excludeLayers.push(idLayer)
}
}
/**
* Permet d'autoriser la sélection sur un layer
*
* @param {string | number} idLayer Identifiant du layer
*/
includeLayerSelection (idLayer) {
if (this.layerExist(idLayer) && this.excludeLayers.indexOf(idLayer) !== -1) {
this.excludeLayers.splice(this.excludeLayers.indexOf(idLayer), 1)
}
}
/**
* Le calque génère-t-il des events au survol d'une feature?
* @param {string | number} idLayer Identifiant du layer
* @param {Boolean} hoverable
*/
setHoverableLayer (idLayer, hoverable) {
if (hoverable && this.layerExist(idLayer) && this.hoverableLayer.indexOf(idLayer) === -1) {
this.hoverableLayer.push(idLayer)
} else if (!hoverable && this.layerExist(idLayer) && this.hoverableLayer.indexOf(idLayer) !== -1) {
this.hoverableLayer.splice(this.hoverableLayer.indexOf(idLayer), 1)
}
}
/**
* Autorise ou non la sélection sur un layer
* pour une plage de zoom donnée
* en fonction du zoom courant
*
* @param {Object} options Options
* @param {String|Number} options.idLayer Identifiant du layer
* @param {Number|undefined} options.minZoom Contrainte sur le niveau de zoom minimal. Optionnelle.
* @param {Number|undefined} options.maxZoom Contrainte sur le niveau de zoom maximal. Optionnelle.
* @param {Number} options.currentZoom Niveau de zoom actuel de la carte.
*/
setLayerSelectionAtZoomLevel ({ idLayer, minZoom, maxZoom, currentZoom }) {
if (currentZoom >= (minZoom || 0) && currentZoom <= (maxZoom || Infinity)) {
this.includeLayerSelection(idLayer)
} else {
this.excludeLayerSelection(idLayer)
}
/* if ((minZoom && currentZoom < minZoom) || (maxZoom && currentZoom > maxZoom)) {
this.excludeLayerSelection(idLayer)
} else {
this.includeLayerSelection(idLayer)
} */
}
/**
* Permet d'ajouter une ou plusieurs features à la selection actuelle
*
* @param {Feature | Array<Feature>} features Liste des
* features à ajouter
* @param {ol.mapBrowserEvent} mapBrowserEvent Permet d'attacher un
* evenement du navigateur à la selection
*/
addFeaturesSelection (features, mapBrowserEvent) {
// Force to array
features = !features || Array.isArray(features) ? features : [features]
const realFeatures = features.filter(feature => {
// Si layerSourceId_ n'existe pas, l'instance n'a jamais été ajoutée sur la carte.
const isMapInstance = !!feature.layerSourceId_
if (!isMapInstance) {
console.warn('L\'instance de la feature n\'a jamais été ajoutée à la carte. Elle ne peut pas être sélectionnée.', feature)
}
return isMapInstance
})
if (realFeatures && realFeatures.length) {
// Récupère la selection courante et les nouvelles à ajouter
const current = this.getCurrentSelection()
const selected = realFeatures.filter(item => current.indexOf(item) < 0).slice()
if (selected.length) {
// Ajoute les nouvelles features et lance un event natif
this._currentSelection.extend(selected)
this._dispatchSelectEvent({ selected, mapBrowserEvent })
}
}
}
/**
* Permet de retirer une ou plusieurs features de la selection actuelle
*
* @param {Feature | Array<Feature>} features Liste des
* features à retirer
* @param {ol.mapBrowserEvent} mapBrowserEvent Permet d'atacher un
* evenement du navigateur à la selection
*/
removeFeaturesSelection (features, mapBrowserEvent) {
// Force to array
features = !features || Array.isArray(features) ? features : [features]
if (features && features.length) {
// Récupère la selection courante et les features à retirer
const current = this.getCurrentSelection()
const deselected = features.filter(item => current.indexOf(item) !== -1).slice()
if (deselected.length) {
// Supprime les features demandé et lève un event natif
deselected.forEach(item => this._currentSelection.remove(item))
this._dispatchSelectEvent({ deselected, mapBrowserEvent })
}
}
}
/**
* Permet de changer le mode de la carte
* @param {MapMode} mode Mode de la carte
*/
changeMapMode (mapMode, toolId = null) {
// Appel de la nouvelle méthode standard
this.ctx.commonLayer.setMapMode(mapMode, { toolId })
if (this._cancelDrawing) {
this._cancelDrawing()
}
}
/**
* Permet de retourner la liste des couches de données
*
* @return {array<string>} Liste des ids de layer de données
*/
getLayersId () {
return this.ctx.commonLayer.getLayersId(DATA_LAYER_TYPE)
}
/**
* Permet de retourner les couches de données
*
* @return {array<ol.Layer>} Liste des layers de données
*/
getLayers () {
return this.ctx.commonLayer.getLayers(DATA_LAYER_TYPE)
}
/**
* Permet de savoir si un layer existe
*
* @param {string} id Id du layer
* @return {boolean} Si le layer existe
*/
layerExist (id) {
return this.ctx.commonLayer.layerExist(id, DATA_LAYER_TYPE)
}
/**
* Permet de retourner un layer
*
* @param {string} id Id du layer
* @return {ol.layer.Vector} Layer recherché
*/
getLayer (id) {
return this.ctx.commonLayer.getLayer(id, DATA_LAYER_TYPE)
}
/**
* Permet de savoir si le layer est un layer de données
*
* @param {ol.Layer|String|Number} layer Layer ou identifiant de layer
* @returns {Boolean} True si c'est un layer de données
*/
isDataLayer (layer) {
if (layer instanceof Layer) {
return this.getLayers().includes(layer)
}
return this.layerExist(layer)
}
/**
* Permet de récupérer une source depuis un id de layer
*
* @param {string} id Id du layer
* @return {ol.source.Vector} Source vector du layer
*/
getSourceLayer (id) {
const l = this.getLayer(id)
if (this.isCluster(l)) {
return l.getSource().getSource()
} else {
return l.getSource()
}
}
/**
* Permet de rechercher une feature dans une source à l'aide de son id
*
* @param {string | number | Array<string | number>} idFeatures Identifiant de la/des features
* @param {ol.source.Vector} layer Source du layer
* @return {array<Feature>} Features correspondant aux ids
*/
findFeatures (idsFeatures, idLayer) {
// Si pas de layer, pas de recherche !
if (!this.layerExist(idLayer)) {
return []
}
// Récupère la source du layer
const source = this.getSourceLayer(idLayer)
// Si ce n'est pas un array, transforme en array de longueur 1
if (!Array.isArray(idsFeatures)) {
idsFeatures = [idsFeatures]
}
const res = []
// Parcours le tableau pour rechercher
source.getFeatures().forEach(feature => {
if (idsFeatures.indexOf(feature.getId()) !== -1) {
res.push(feature)
}
})
return res
}
/**
* Permet de rechercher une feature dans tous les layers vectoriels à l'aide de son id
*
* @param {string | number | Array<string | number>} idFeatures Identifiant de la/des features
* @return {array<Feature>} Features correspondant aux ids
*/
findFeaturesInAllLayers (idsFeatures) {
let results = []
this.getLayers()
.forEach((layer) => {
const layerId = layer.get(this.commonLayer.propertiesName.ID_LAYER)
results = results.concat(this.findFeatures(idsFeatures, layerId))
})
return results
}
/**
* Permet de masquer une feature
*
* @param {Feature | string | number} feature Feature Openlayers à masquer ou son identifiant
* @param {string | undefined} idLayer Id du layer. Uniquement si feature est un identifiant
* @param {boolean} silent Update without triggering an event.
*/
hideFeature (feature, idLayer, silent) {
// Si c'est un identifiant, recherche la feature
if (feature.constructor !== Feature) {
feature = this.findFeatures(feature, idLayer)[0]
}
silent = silent === true
// Change la propriété pour la masquer
if (feature.constructor === Feature) {
feature.set(this.propertiesName.VISIBLE_FEATURE, false, silent)
}
}
/**
* Permet de masquer toutes les features d'un layer
*
* @param {string} idLayer Id du layer
* @param {boolean} silent Update without triggering an event.
*/
hideAllFeatures (idLayer, silent) {
// Si le layer n'existe pas, annule la suite du traitement
if (!this.layerExist(idLayer)) {
return
}
this.getSourceLayer(idLayer).getFeatures().forEach(feature => {
this.hideFeature(feature, null, silent)
})
}
/**
* Permet d'afficher une feature
*
* @param {Feature | string | number} feature Feature Openlayers à afficher ou son identifiant
* @param {string | undefined} idLayer Id du layer. Uniquement si feature est un identifiant
* @param {boolean=} silent Update without triggering an event.
*/
showFeature (feature, idLayer, silent) {
// Si c'est un identifiant, recherche la feature
if (feature.constructor !== Feature) {
feature = this.findFeatures(feature, idLayer)[0]
}
silent = silent === true
// Change la propriété pour l'afficher
if (feature.constructor === Feature) {
feature.set(this.propertiesName.VISIBLE_FEATURE, true, silent)
}
}
/**
* Permet d'afficher toutes les features d'un layer
*
* @param {string} idLayer Id du layer
* @param {boolean} silent Update without triggering an event.
*/
showAllFeatures (idLayer, silent) {
// Si le layer n'existe pas, annule la suite du traitement
if (!this.layerExist(idLayer)) {
return
}
this.getSourceLayer(idLayer).getFeatures().forEach(feature => {
this.showFeature(feature, null, silent)
})
}
translateFeature (feature) {
const collect = new Collection([feature])
const translate = new Translate({
features: collect,
})
this.ctx.Map.addInteraction(translate)
return translate
}
cancelTranslate (feature) {
feature.setGeometry(feature.initialGeometry)
return feature
}
stopTranslate (translate) {
this.ctx.Map.removeInteraction(translate)
}
/**
* Permet de déterminer si un layer est un cluster
*
* @param {ol.Layer} layer Layer dont on recherche le type
* @return {boolean} True si layer est un cluster
*/
isCluster (layer) {
return layer.getSource().constructor === Cluster
};
/**
* Permet de recalculer un layer (style, position, ...)
* @param {string} idLayer Id du layer
*/
refreshCluster (idLayer) {
if (this.layerExist(idLayer) && this.isCluster(this.getLayer(idLayer))) {
this.getLayer(idLayer).getSource().refresh()
}
};
/**
* Permet de recalculer un layer
* @param {string} idLayer Id du layer
*/
refreshLayer (idLayer) {
this.ctx.commonLayer.refreshLayer(idLayer, DATA_LAYER_TYPE)
};
/**
* Permet d'indiquer à un layer qu'il a changé
* @param {string} idLayer Id du layer
*/
changedLayer (idLayer) {
this.ctx.commonLayer.changedLayer(idLayer, DATA_LAYER_TYPE)
}
linkFeatureToSource_ (feature, idLayer) {
if (!feature.layerSourceId_) {
feature.layerSourceId_ = []
}
if (feature.layerSourceId_.indexOf(idLayer) === -1) {
feature.layerSourceId_.push(idLayer)
}
}
unlinkFeatureToSource_ (feature, idLayer) {
if (feature.layerSourceId_) {
feature.layerSourceId_.filter(item => item !== idLayer)
}
}
/**
* renvoi les layers auquels la feature à été ajoutée
* @param {Feature} feature Feature OpenLayer
* @returns identifiant du layer
*/
getFeatureLinkedLayers (feature) {
return feature.layerSourceId_
}
/**
* Permet de retourner un tableau avec tous les
* identifiants des features qu'il contient
*
* @param {string} idLayer Id du layer
* @return {array<string> | array<number>} Tableau des identifiants
*/
getIdsFeatures (idLayer) {
if (!this.layerExist(idLayer)) {
return
}
const res = []
this.getSourceLayer(idLayer).getFeatures().forEach(feature => {
res.push(feature.getId())
})
return res
}
/**
* Retourne l'ensemble des features d'un layer
* @param {string|Layer} layer Identifiant ou instance du layer
* @return {Array<Feature>} Liste des features du layer
*/
getFeatures (layer) {
// Dans le cas où l'instance est directement passé
if (layer instanceof Layer && this.isDataLayer(layer)) {
return this.isCluster(layer)
? layer.getSource().getSource().getFeatures()
: layer.getSource().getFeatures()
}
// Dans le cas où un identifiant est passé
return this.layerExist(layer) ? this.getSourceLayer(layer).getFeatures() : []
}
/**
* Retourne une feature d'un layer
* @param {string} idFeature Id de la feature
* @param {string} idLayer Id du layer
* @return {Feature} Feature du layer
*/
getFeature (idFeature, idLayer) {
return this.getFeatures(idLayer).find(feature => feature.getId() === idFeature)
}
/**
* Permet d'ajouter des features openLayers dans un layer
*
* @param {array<Feature>} features Tableau des features à ajouter
* @param {string} idLayer Id du layer
* @param {boolean} overwrite True si les features déjà existantes doivent-être écrase. Par défaut false
*/
addFeatures (features, idLayer, overwrite) {
if (!this.layerExist(idLayer) || !features.length) {
return
}
const source = this.getSourceLayer(idLayer)
const listIds = this.getIdsFeatures(idLayer)
const toAdd = []
const toRemove = []
features.forEach(feature => {
if (listIds.indexOf(feature.getId()) < 0) {
toAdd.push(feature)
} else if (overwrite) {
toRemove.push(feature)
toAdd.push(feature)
}
})
source.removeFeatures(toRemove)
source.addFeatures(toAdd)
this.refreshCluster(idLayer)
}
/**
* Ajoute des features a partir d'un GeoJSON
* @param {Object} geojsonObject Object au format {@link https://fr.wikipedia.org/wiki/GeoJSON GeoJSON} valide
* @param {string} idLayer Id du layer contenant la feature
* @param {string} originProjection projection d'origine des feature, sinon on suppose qu'elles sont dans le systeme de la carte
*/
addGeojsonFeatures (geojsonObject, idLayer, originProjection = null) {
const features = geoJsonToFeature(geojsonObject, originProjection, originProjection ? this.ctx.getMap().getView().getProjection() : null)
this.addFeatures(features, idLayer)
}
/**
* Permet de supprimer une feature d'un layer
*
* @param {Feature | string | number} feature La feature Openlayers à supprimer
* @param {string} idLayer Id du layer contenant la feature
*/
removeFeature (feature, idLayer) {
if (!this.layerExist(idLayer)) {
return
}
if (feature.constructor !== Feature) {
feature = this.findFeatures(feature, idLayer)
feature = feature.length ? feature[0] : false
}
if (feature && feature.constructor === Feature) {
this.getSourceLayer(idLayer).removeFeature(feature)
this.refreshCluster(idLayer)
this.removeFeaturesSelection([feature])
this.ctx.dispatchEvent('deletefeature:delete', { deletedFeature: feature })
}
}
/**
* Permet de mettre à jour une feature d'un layer
*
* @param {Feature | string | number} olFeature La feature Openlayers à mettre à jour
* @param {string} idLayer Id du layer contenant la feature
* @param {Object} geojsonFeature Données de la feature en geojson
* @param {Object} geojsonFeature.properties Les propriétés mises à jour
* @param {Object} geojsonFeature.geometry La géométrie à mettre à jour
*/
updateFeature (feature, idLayer, geojsonFeature) {
if (!this.layerExist(idLayer)) {
return
}
if (feature.constructor !== Feature) {
feature = this.findFeatures(feature, idLayer)
feature = feature.length ? feature[0] : false
}
if (feature && feature.constructor === Feature) {
const properties = (geojsonFeature && geojsonFeature.properties) || {}
const geometry = (geojsonFeature && geojsonFeature.geometry) || null
const olGeometry = geometry ? (new GeoJSON()).readGeometry(geometry) : null
feature.setProperties(properties)
feature.setGeometry(olGeometry)
this.refreshCluster(idLayer)
}
}
/**
* Permet de supprimer un ensemble de features d'un layer
*
* @param {array<string | number | Feature>} features Tableau des features à supprimer
* @param {string} idLayer Id du layer contenant les features
*/
removeFeatures (features, idLayer) {
if (!this.layerExist(idLayer) || !features.length) {
return
}
// optimisation, si toutes les features sont de type olFeature, on passe par la méthode native
if (features.every(feature => feature.constructor === Feature)) {
this.getSourceLayer(idLayer).removeFeatures(features)
return
}
features.forEach(feature => {
this.removeFeature(feature, idLayer)
})
}
/**
* Permet d'effacer toutes les features d'un layer
*
* @param {string} idLayer Id du layer à vider
*/
clearLayer (idLayer) {
if (this.layerExist(idLayer)) {
// Supprime le lien avec les features
this.getFeatures(idLayer).forEach(feature => {
this.unlinkFeatureToSource_(feature, idLayer)
})
this.getSourceLayer(idLayer).clear()
}
}
/**
* Permet de supprimer un layer
*
* @param {string} idLayer Id du layer à supprimer
* @fires removeLayer Lancé avant la suppression
* @fires removedLayer Lancé après la suppression
*/
removeLayer (idLayer) {
// Supprime les labels si présents
if (this.ctx.LABELFEATURE_LOADED) {
this.ctx.labelFeature.detachLayer(idLayer)
}
// Supprime le lien avec les features
this.getFeatures(idLayer).forEach(feature => {
this.unlinkFeatureToSource_(feature, idLayer)
})
// Supprime le layer
this.ctx.commonLayer.removeLayer(idLayer, DATA_LAYER_TYPE)
// Supprime la référence du style du layer
if (this._layersStyle[idLayer]) {
delete this._layersStyle[idLayer]
}
}
/**
* Permet de masquer un layer
*
* @param {string} id Id du layer à masquer
*/
hideLayer (id) {
this.ctx.commonLayer.hideLayer(id, DATA_LAYER_TYPE)
}
/**
* Permet d'afficher un layer
*
* @param {string} id Id du layer à afficher
*/
showLayer (id) {
this.ctx.commonLayer.showLayer(id, DATA_LAYER_TYPE)
}
/**
* Permet de monter le layer d'un niveau dans l'ordre d'affichage
*
* @param {string} id Id du layer
*/
upLayer (id) {
return this.ctx.commonLayer.upLayer(id, DATA_LAYER_TYPE)
}
/**
* Permet de descendre le layer d'un nieau dans l'ordre d'affichage
*
* @param {string} id Id du layer
*/
downLayer (id) {
return this.ctx.commonLayer.downLayer(id, DATA_LAYER_TYPE)
}
/**
* Permet de connaitre la visibilité d'un layer (en fonction de min/max zoom, propriété "visible" & group "visible")
*
* @param {string} id Identifiant du layer
* @return {boolean} Visibilité
*/
isVisible (id) {
return this.ctx.commonLayer.isVisible(id, DATA_LAYER_TYPE)
}
/**
* Animation flash d'une feature pendant une durée donnée
* Point: crée un cercle rouge dont le rayon augmente autour de la feature
* LineString, Polygon: crée une bordure rouge dont la taille augmente
* @param {Feature} feature
* @param {Number} duration durée de l'animation en ms
*/
animateFeature (feature, duration) {
const start = new Date().getTime()
let listenerKey = null
const layerSourceId = feature.layerSourceId_[0]
const featureLayer = this.getLayer(layerSourceId)
// récupère les morceaux de geom hétéroclite et les anime une a une
let geometrys = []
if (feature.getGeometry()) {
switch (feature.getGeometry().getType()) {
case 'Point':
case 'LineString':
case 'Polygon':
geometrys = [feature.getGeometry()]
break
case 'GeometryCollection':
geometrys = feature.getGeometry().getGeometries().map((geometry) => geometry)
break
case 'MultiPoint':
geometrys = feature.getGeometry().getCoordinates().map((coordinates) => new Point(coordinates))
break
case 'MultiLineString':
geometrys = feature.getGeometry().getLineStrings().map((lineString) => lineString)
break
case 'MultiPolygon':
geometrys = feature.getGeometry().getPolygons().map((polygon) => polygon)
break
}
}
function animate (event) {
const vectorContext = getVectorContext(event)
const frameState = event.frameState
const elapsed = frameState.time - start
const elapsedRatio = elapsed / duration
// radius will be 5 at start and 30 at end.
const radius = Easing.easeOut(elapsedRatio) * 25 + 5
const opacity = Easing.easeOut(1 - elapsedRatio)
const strokeWidth = Easing.easeOut(elapsedRatio) * 25 + 0.1
geometrys.forEach((geometry) => {
let style = null
switch (geometry.getType()) {
case 'Point':
case 'MultiPoint':
style = new Style({
image: new Circle({
radius,
stroke: new Stroke({
color: 'rgba(255, 0, 0, ' + opacity + ')',
width: 5 + opacity,
}),
}),
})
break
case 'LineString':
case 'MultiLineString':
case 'Polygon':
case 'MultiPolygon':
case 'GeometryCollection':
style = new Style({
stroke: new Stroke({
color: 'rgba(255, 0, 0, ' + opacity + ')',
width: strokeWidth,
}),
})
break
default:
console.warn('[datalayer-animate] Type de geometry non valide')
}
if (style) {
vectorContext.setStyle(style)
vectorContext.drawGeometry(geometry)
}
})
// si pas de style (défault on risquer la boucle infinie..)
if (elapsed > duration) {
unByKey(listenerKey)
return
}
// tell OpenLayers to continue postrender animation
this.ctx.Map.render()
}
if (geometrys.length > 0) {
listenerKey = featureLayer.on('postrender', animate.bind(this))
}
}
/**
* Animation flash d'un ensemble de features pendant une durée donnée
* @param {Array<Feature>} features Features
* @param {Number} duration durée de l'animation en ms
*/
animateFeatures (features, duration) {
if (!features || features.length < 1) {
return
}
features.forEach(feature => {
this.animateFeature(feature, duration)
})
}
/**
* Permet de center sur un groupe de feature
* @param {Array<Feature>} features Features devant apparaitre dans le zoom
* @param {boolean} noFly Permet de désactiver l'animation de déplacement
* @param {Object} options Permet de passer des options d'ajustement au composant ol.View (voir ol.View.fit)
*/
centerOnFeatures (features, noFly, options) {
if (!features || features.length < 1) {
return
}
const extentMax = Extent.createEmpty()
// Parcours les features
features.forEach(feature => {
if (feature.getGeometry()) {
Extent.extend(extentMax, feature.getGeometry().getExtent())
}
})
if (Extent.isEmpty(extentMax)) {
return
}
// Ne se déplace que s'il existe une extent max (non infinie)
if (extentMax && !(
extentMax[0] === Infinity ||
extentMax[1] === Infinity ||
extentMax[2] === -Infinity ||
extentMax[3] === -Infinity)) {
// Si noFly, déplace la carte sans annimation
if (noFly) {
this.ctx.zoomToExtent(extentMax, options)
} else { // Sinon utilise une animation de MapViewer
this.ctx.flyToExtent(extentMax, options)
}
}
}
/**
* Permet de récupérer l'extent englobant tous les layers demandés
*
* @param {Array<string>} idLayers Ids des layers
*/
getLayersExtent (idLayers) {
idLayers = !Array.isArray(idLayers) ? [idLayers] : idLayers
let extentMax = null
// Recherche les layers passé en paramètre
idLayers.forEach(id => {
if (this.layerExist(id) && this.isDataLayer(id)) {
// Si aucune extent n'est récupérer, applique celle du layer dirrectement
if (!extentMax) {
extentMax = this.getSourceLayer(id).getExtent()
} else { // sinon, cumule l'extent du layer avec l'extent global
Extent.extend(extentMax, this.getSourceLayer(id).getExtent())
}
}
})
return extentMax
}
/**
* Permet de centrer sur toutes les features d'un layer.
* Centre sur un niveau de zoom exact adapté pour voir l'ensemble des features et avoir un fond de plan net
*
* @param {string} idLayer Id du layer
* @param {boolean | undefined} noFly Permet de centrer sur le layer sans animation. Par défaut 'false'
* @param {Object} options Permet de passer des options d'ajustement au composant ol.View (voir ol.View.fit)
*/
centerOnLayers (idLayers, noFly, options) {
const extentMax = this.getLayersExtent(idLayers)
// Ne se déplace que s'il existe une extent max (non infinie)
if (extentMax && !(
extentMax[0] === Infinity ||
extentMax[1] === Infinity ||
extentMax[2] === -Infinity ||
extentMax[3] === -Infinity)) {
// Si noFly, se déplace à l'extent sans annimation
if (noFly) {
this.ctx.zoomToExtent(extentMax, options)
} else { // Sinon utilise une annimation de déplacement
this.ctx.flyToExtent(extentMax, options)
}
}
}
/**
* Permet de retourner le style d'une feature
*/
_applyStyleToFeature ({ style, feature, resolution }) {
// effectu les traitement de la feature:
const zoom = this.ctx.Map.getView().getZoomForResolution(resolution)
const selected = !!feature.get(this.propertiesName.SELECTED_FEATURE)
const activated = !!feature.get(this.propertiesName.ACTIVATED_FEATURE)
const tags = feature.get(this.propertiesName.TAG_FEATURE) || {}
const hovered = !!tags[this.systemTagName.HOVERED_FEATURE]
// si on a une macro-fonction englobante, on l'execute
let stylesToApply = typeof style === 'function'
? style({
feature,
resolution,
zoom,
selected,
activated,
hovered,
tags,
})
: style
// Transforme un style unitaire, fonction ou tableau de tout ca en tableau de style
stylesToApply = (Array.isArray(stylesToApply) ? stylesToApply : [stylesToApply]).flatMap((styleToApply) => {
// si le style est une fonction on l'exectute
styleToApply = typeof styleToApply === 'function'
? styleToApply({
feature,
resolution,
zoom,
selected,
activated,
hovered,
tags,
})
: styleToApply
// pour chaque style du tableau on détermine si on a besoin de créer d'une géométry alternative que l'on va coller sur la feature comme "propriété"
return styleToApply
})
// Force un style vide si pas de style
return stylesToApply.length > 0 ? stylesToApply : STYLE_NONE
}
isVisibleFeature ({ feature, includeHidden, evaluateStyles = true, filter, resolution }) {
// Si on souaite inclure les features masquées
if (!includeHidden && feature.get(this.propertiesName.VISIBLE_FEATURE) === false) {
return false
}
const zoom = this.ctx.Map.getView().getZoomForResolution(resolution)
const selected = !!feature.get(this.propertiesName.SELECTED_FEATURE)
const activated = !!feature.get(this.propertiesName.ACTIVATED_FEATURE)
const tags = feature.get(this.propertiesName.TAG_FEATURE) || {}
const hovered = !!tags[this.systemTagName.HOVERED_FEATURE]
// Si on filtre les features du cluster
if (filter && !filter({ feature, resolution, zoom, selected, activated, hovered })) {
return false
}
// Si on souhaite évaluer les styles pour contraindre l'affichage
if (evaluateStyles) {
return feature.layerSourceId_ && feature.layerSourceId_
.filter(layerId => this._layersStyle[layerId])
.some(layerId => {
const styles = this._layersStyle[layerId](feature, resolution, {
bypassCluster: true,
})
return styles && (!Array.isArray(styles) || styles.length)
})
}
return true
}
/**
* Permet de filtrer les features en fonction de leur visibilité
*/
filterVisibleFeatures ({ features, includeHidden, evaluateStyles = true, filter, resolution }) {
return features.filter(feature => this.isVisibleFeature({
feature,
includeHidden,
evaluateStyles,
filter,
resolution,
}))
}
/**
* Construit une fonction de filtre de visiblité pour ne pas avoir
* à conserver les informations (filtre, ...)
*/
buildFilterVisibleFeatures ({ includeHidden, evaluateStyles = true, filter }) {
return (features, resolution) => this.filterVisibleFeatures({
features,
includeHidden,
evaluateStyles,
filter,
resolution,
})
}
/**
* Permet de retourner une fonction de style pour un layer de données
*/
_getDataLayerStyleFunction ({ style, cluster, layer }) {
const clusterStyle = cluster && (cluster.style || DEFAULT_CLUSTER_STYLE_FUNCTION)
const clusterFilter = cluster && this.buildFilterVisibleFeatures({
includeHidden: cluster.includeHidden,
filter: cluster.filter,
})
layer.set(this.propertiesName.CLUSTER_FILTER, clusterFilter)
return (feature, resolution, { bypassCluster } = {}) => {
// Si la feature est masquée
if (feature.get(this.propertiesName.VISIBLE_FEATURE) === false) {
return STYLE_NONE
}
if (!bypassCluster && cluster && this.isCluster(layer)) {
const clusterizedFeatures = feature.get('features')
// On évalue une feature qui n'est pas un cluster
if (!clusterizedFeatures) {
return STYLE_NONE
}
const visibleFeatures = clusterFilter(clusterizedFeatures, resolution)
// Cas d'un cluster vide (sous ensemble non visible)
if (visibleFeatures.length === 0) {
return STYLE_NONE
}
// Cas d'une représentation avec le style du cluster
if (visibleFeatures.length > 1 || cluster.groupFeaturesAlone) {
return typeof clusterStyle === 'function'
? clusterStyle({
features: visibleFeatures,
cluster: feature, // pour garder une compatibilité surement inutilisée
feature, // les fonction de styles.js attendent "feature"
resolution,
zoom: this.ctx.Map.getView().getZoomForResolution(resolution),
})
: clusterStyle
}
// Une seule feature, donc pas de cluster (style de la feature)
return this._applyStyleToFeature({
style,
feature: visibleFeatures[0],
resolution,
})
}
return this._applyStyleToFeature({ style, feature, resolution })
}
}
_getExplodedClusterStyleFunction ({ style }) {
return (feature, resolution) => {
// Si la feature est masquée
if (feature.get(this.propertiesName.VISIBLE_FEATURE) === false) {
return STYLE_NONE
}
// Style du trait entre une feature et le cluster
if (feature.get('selectclusterlink')) {
return STYLE_CLUSTER_LINK
}
// Style d'une feature
if (feature.get('selectclusterfeature')) {
return this._applyStyleToFeature({
style,
feature: feature.get('feature'),
resolution,
})
}
}
}
getFeaturesAtPixel (pixel) {
const features = []
this.ctx.Map.forEachFeatureAtPixel(pixel, (feature, layer) =>
features.push({ feature, layer }))
return features
}
_buildExplodedClusterLayer ({ mainLayer, style, zIndex }) {
return new VectorLayer({
id: `explodedLayer ${mainLayer.getId()}`,
parentLayer: mainLayer,
zIndex,
updateWhileAnimating: true,
updateWhileInteracting: true,
source: new VectorSource({
features: new Collection(),
}),
style: this._getExplodedClusterStyleFunction({ style }),
})
}
_buildDetailedCluster ({ cluster, layer }) {
const features = cluster.get('features')
if (features < 2) {
return null
}
// Rayon de la vue explosée
const pointRadius = 100
// Nombre maximal de features dans la vue explosée
const circleMaxObjects = 8
// Centre de la vue explosée (emplacement du cluster)
const center = cluster.getGeometry().getCoordinates()
// Nombre d'unités par pixel
const pixelSize = this.ctx.Map.getView().getResolution()
// Nombre de points dans la vue explosée
const maxOnCircle = Math.min(features.length, circleMaxObjects)
// Rayon en unité de mesure de la carte
const radius = pointRadius * pixelSize * (0.5 * maxOnCircle / 4)
// Pour chaque point de la vue explosée
for (let i = 0; i < maxOnCircle; ++i) {
// Récupère l'angle pour positionner la feature sur le cercle
let angle = 2 * Math.PI * i / maxOnCircle
// Cas particulier pour 2 et 4 features
if (maxOnCircle === 2 || maxOnCircle === 4) {
angle += Math.PI / 4
}
// Coordonnées de l'emplacement de la feature détaillée
const point = [
center[0] + radius * Math.sin(angle),
center[1] + radius * Math.cos(angle),
]
// Création de la feature détaillée
layer.getSource().addFeature(new Feature({
selectclusterfeature: true,
feature: features[i],
geometry: new Point(point),
}))
// Création de la ligne qui relie la feature détaillée au cluster
layer.getSource().addFeature(new Feature({
selectclusterlink: true,
geometry: new LineString([center, point]),
}))
}
}
_refreshClusterVisibility ({ layer, clusterSource, defaultSource, constraintZoom, currentZoom }) {
// On met à jour la source du layer si nécessaire
if (currentZoom <= constraintZoom && layer.getSource() !== clusterSource) {
layer.setSource(clusterSource)
}
if (currentZoom > constraintZoom && layer.getSource() !== defaultSource) {
layer.setSource(defaultSource)
}
}
/**
* Permet de créer un layer de données
*
* @param {Object} options Options
*
* @param {String|Number} options.layerId Identifiant du layer
* @param {String?} options.idGroup Nom du groupe de calque
* @param {Array<Feature>|undefined} options.features Features à inséréer. Optionnel.
* @param {ol.style.Style|Array<ol.style.Style>|ol.style.StyleFunction|undefined} options.style Style du layer. Optionnel.
* @param {Boolean|Object|undefined} options.selectable Activer la sélection sur le layer. Par défaut true.
* @param {Number} options.selectable.minZoom Zoom minimal pour être sélectionnable (exclusif). Optionnels.
* @param {Number} options.selectable.maxZoom Zoom maximal pour être sélectionnable (inclusif). Optionnels.
* @param {Number} options.selectable.minScale echelle minimal pour être sélectionnable (exclusif). Optionnels. prévaut sur options.selectable.maxZoom.
* @param {Number} options.selectable.maxScale echelle maximal pour être sélectionnable (inclusif). Optionnels. prévaut sur options.selectable.minZoom.
* @param {Boolean|undefined} [options.hoverable=false] Activer le mode survol de ce calque. Par défaut false. Il faudra lancer le service de survol sur Core pour que cela soit prit en compte (setFeatureHover)
* @param {Number|undefined} options.minZoom Zoom minimal pour afficher le layer (exclusif)
* @param {Number|undefined} options.maxZoom Zoom maximal pour afficher le layer (inclusif)
* @param {Number|undefined} options.minResolution Résolution minimale pour afficher le layer (inclusif). Non utilisée si maxZoom défini
* @param {Number|undefined} options.maxResolution Résolution maximale pour afficher le layer (exclusif). Non utilisée si minZoom défini
* @param {(Number|undefined)?} options.zIndex Si le zIndex est présent, celui-ci prévaut sur la position du layer sur la carte. Optionnel.
* @param {String} options.attributions Attribution de la carte. Optionnel.
*
* @param {Object?} options.cluster Options pour définir un layer de type Cluster. Optionnel.
* @param {ol.style.Style|Array<ol.style.Style>|ol.style.StyleFunction|undefined} options.cluster.style Style des points de cluster. Optionnel.
* @param {Number|undefined} options.cluster.distance Distance de regroupement. Par défaut 50.
* @param {Boolean|undefined} options.cluster.exploded Possibilité de voir une vue éclatée d'un groupe. Par défaut false. @TODO_EXPLODED: Non testée.
* @param {Boolean|undefined} options.cluster.groupFeaturesAlone Les groupe d'une features seront représentés sous forme de groupe. Par défaut false.
* @param {Boolean|undefined} options.cluster.includeNoStyle Inclure dans les groupes les features sans style. Par défaut false.
* @param {Boolean|undefined} options.cluster.includeHidden Inclure dans les groupes les features masquée. Par défaut false.
* @param {Boolean|undefined} options.cluster.zoom Permet d'afficher le cluster uniquement jusqu'à un certain niveau de zoom. Par défaut, toujours visible.
*
* @param {Object?} options.label Affichage de labels sur la carte. Optionnel.
* @param {Function} options.label.templateFunction Fonction de template pour générer un label. Prends une feature et la résolution en entrée et retourne un template HTML. Optionnel.
* @param {Number} options.label.minZoom Zoom minimal pour afficher les labels. Optionnels.
* @param {Number} options.label.maxZoom Zoom maximal pour afficher les labels. Optionnels.
* @param {Number} options.label.minScale echelle minimal pour afficher les labels. Optionnels. prévaut sur options.label.maxZoom.
* @param {Number} options.label.maxScale echelle maximal pour afficher les labels. Optionnels. prévaut sur options.label.minZoom.
*
* @param {Object?} options.wfs Option de chargement des données via un service wfs
* @param {String} options.wfs.url Url du service wfs
* @param {string} options.wfs.geometryName nom du champs geométrie du service (défaut geo)
* @param {Object} options.wfs.params paramètre du service wfs
* @param {string} options.wfs.params.typename Nom de la couche du service WFS à exploiter
* @param {string} [options.wfs.params.srsname=EPSG:3857] projection de retour des données
* @param {string} [options.wfs.params.version=1.1.1] version du service WFS
* @param {string} options.wfs.params.* autres paramètres du service WFS
* @param {string} [options.wfs.projection=EPSG:3857] projection de la bbox pour interroger le service
* @param {string} [options.wfs.geometryName=geometry] nom de la géométrie pour interroger la bbox
* @param {boolean} [options.wfs.bboxStrategy=false] Utiliser une stratégie de chargement par bbox (true) ou par tuile (false). Par défaut false
* @param {boolean | Object} [options.wfs.tileStrategy=false] Utiliser une stratégie de chargemen par tuile (false). Par défaut true
* @param {boolean | Object} [options.wfs.tileStrategy.tileSize=256] Taille des tuiles pour la stratégie par tuile
* @param {string} options.wfs.cqlFilter filtre cql
* @param {Object<string,(string|Array)>} options.wfs.filters objet represant des filtres a générer (exemple {code_insee:[9420,8521]}). Ignoré si cqlFilter est défini
*
* @param {string} options.title titre pour affichage dans layermanager
* @param {boolean} options.displayInLayerSwitcher pour afficher ou non dans layermanager
*
* @param {Object?} options.ui (deprecated) élement pour l'IHM si on utilise la layervisibilitybar
* @param {string} options.ui.title tooltip du calque
* @param {Element|string} options.ui.html contenu html du bouton a afficher
* @param {string} options.ui.icon icone si html non saisie (utilisé comme <i class='icon'/>)
*
* @param {LegendOption[]?} options.legends Options de légende (voir examples)
* @param {loadingStrategy?} options.loadingStrategy stratégie de chargement de carte avec un loader personnalisé
*/
addDataLayer ({
idGroup = null,
layerId = IsRequired('layerId'),
features = [],
// TODO Vérifier à quoi servait et ce qu'était defaultStyleFunction
style = null,
selectable = true,
hoverable = false,
minZoom,
maxZoom,
minResolution,
maxResolution,
minScale,
maxScale,
zIndex,
attributions = '',
cluster,
label,
wfs,
ui,
title = null,
displayInLayerSwitcher = true,
legends = null,
loadingStrategy = null,
}) {
// Verifie que le layer n'existe pas
if (this.layerExist(layerId)) {
throw new Error(`Layer ${layerId} already exists`)
}
// les notions de zoom & scale/résolutions sont inversées (max zoom = proche du sol)
// options minScale? on la convertie en zoom
if (minScale) {
maxZoom = getZoomForScale(this.ctx.Map.getView(), minScale)
}
// options maxScale? on la convertie en zoom
if (maxScale) {
minZoom = getZoomForScale(this.ctx.Map.getView(), maxScale)
}
// converti les scales pour les label
if (label && label.minScale) {
label.maxZoom = getZoomForScale(this.ctx.Map.getView(), label.minScale)
}
if (label && label.maxScale) {
label.minZoom = getZoomForScale(this.ctx.Map.getView(), label.maxScale)
}
// converti les scales pour les selectable
if (selectable && selectable.minScale) {
selectable.maxZoom = getZoomForScale(this.ctx.Map.getView(), selectable.minScale)
}
if (selectable && selectable.maxScale) {
selectable.minZoom = getZoomForScale(this.ctx.Map.getView(), selectable.maxScale)
}
const olEvents = []
/* si l'utilisateur a renseigné une url de service wfs,
on configure une strategy bbox ou tuile
et wfsSource sera notre source de vecteurs */
const wfsSource = getWfsVectorSource(wfs, { attributions })
const defaultSource = wfsSource ||
(loadingStrategy // l'utilisateur veut appliquer un loader spécifique
? new VectorSource({
attributions,
strategy: loadingStrategy?.bboxStrategy ? bboxStrategy : tileStrategy(createXYZ({ tileSize: loadingStrategy?.tileStrategy?.tileSize || 256 })),
loader: async function (extent, resolution, projection, success, failure) {
try {
const features = await loadingStrategy.loader(extent, resolution, projection)
this.addFeatures(features)
success(features)
} catch (error) {
console.error('[datalayer-loadingStrategy]', error)
this.removeLoadedExtent(extent)
// si on est pas dans le scope on peut faire ceci:
failure()
}
},
})
: new VectorSource({ features, attributions })) /* Chargement classique des données */
defaultSource.on('addfeature', (evt) => {
this.linkFeatureToSource_(evt.feature, layerId)
})
defaultSource.on('removefeature', (evt) => {
this.unlinkFeatureToSource_(evt.feature, layerId)
})
// Création du layer principal
title = title || ui?.title // compatibilité ControlLayerVisibility et layerSwitcher
const mainLayer = new VectorLayer({
title,
[this.commonLayer.propertiesName.GROUP_LAYER]: idGroup,
[this.commonLayer.propertiesName.ID_LAYER]: layerId,
source: defaultSource,
minZoom,
maxZoom,
minResolution,
maxResolution,
zIndex,
})
mainLayer.set('displayInLayerSwitcher', displayInLayerSwitcher)
// Ajout de la fonction de style si présente
mainLayer.setStyle(this._getDataLayerStyleFunction({
style,
cluster,
layer: mainLayer,
}))
// Ajoute une référence au layer dans la feature
features.forEach(feature => {
this.linkFeatureToSource_(feature, layerId)
})
// Garde une référence dans le cas d'une fonction de style
if (mainLayer.getStyleFunction()) {
this._layersStyle[layerId] = mainLayer.getStyleFunction()
}
// Création de la source pour le cluster
if (cluster) {
// Création de la source du cluster
const clusterSource = new Cluster({
distance: cluster.distance || 50,
source: defaultSource,
geometryFunction: feature => getPointForGeometry(feature.getGeometry()),
})
// Si le cluster est contraint par le niveau de zoom
if (cluster.zoom) {
this._refreshClusterVisibility({
layer: mainLayer,
clusterSource,
defaultSource,
constraintZoom: cluster.zoom,
currentZoom: this.ctx.Map.getView().getZoom(),
})
// Traitement à réaliser lors du changement de résolution
olEvents.push(this.ctx.Map.getView().on('change:resolution', ({ target: view }) => {
this._refreshClusterVisibility({
layer: mainLayer,
clusterSource,
defaultSource,
constraintZoom: cluster.zoom,
currentZoom: view.getZoom(),
})
}))
} else {
mainLayer.setSource(clusterSource)
}
}
// Création de la vue explosée du cluster
// TODO: Exploded Force la désactivation tant que c'est pas testé !!!
if (cluster && cluster.exploded && !cluster.exploded) {
// Gestion de la vue explosée
const explodedLayer = this._buildExplodedClusterLayer({
mainLayer,
style,
zIndex,
})
// Ajoute le layer à la carte
this.ctx.Map.addLayer(explodedLayer)
// Traitement à réaliser lors d'un clic sur la carte
olEvents.push(this.ctx.Map.on('click', ev => {
const clickedFeatures = this.getFeaturesAtPixel(ev.pixel)
const detailedFeatures = clickedFeatures.some(({ feature, layer }) =>
layer === explodedLayer || feature.get('selectclusterfeature'))
// Dans le cas d'un clic sur une feature détaillée
if (detailedFeatures.length) {
return null
}
// Dans tous les autres cas, ou retire la vue explosée
explodedLayer.getSource().clear()
// Cherche si un cluster est cliqué
const clickedCluster = clickedFeatures.find(({ layer }) => layer === mainLayer)
if (clickedCluster) {
this._buildDetailedCluster({
cluster: clickedCluster,
layer: explodedLayer,
})
}
}))
// Traitement à réaliser lors d'un changement de résolution
olEvents.push(this.ctx.Map.getView().on('change:resolution', () => {
// Vide la vue explosée à chaque changement de zoom
explodedLayer.getSource().clear()
}))
// Si le layer source change, actualise la vue explosée
mainLayer.on('change', () => explodedLayer.getSource().refresh())
// Traitement à réaliser lors de la suppression du layer de données
this.ctx.once(`removedLayer:${layerId}`, () => {
// Supprime le layer de la vue explosée des clusters
this.ctx.Map.removeLayer(explodedLayer)
})
}
// Ajout du layer de données sur la carte OpenLayers
this.ctx.commonLayer.addLayer(mainLayer, DATA_LAYER_TYPE, ui)
// Si on souhaite utiliser le module de label sur ce layer
if (this.ctx.LABELFEATURE_LOADED && label && label.templateFunction) {
this.ctx.labelFeature.attachLayer({
idLayer: layerId,
templateFunction: label.templateFunction,
minZoom: label.minZoom,
maxZoom: label.maxZoom,
})
}
// Cas d'un layer non sélectionnable
if (!selectable) {
this.excludeLayerSelection(layerId)
}
// Si le calque est hoverable
this.setHoverableLayer(layerId, hoverable)
// il y a des options pour générer une légende?
if (legends) {
this.ctx.legendManager.addLegends(mainLayer, legends)
}
// Cas d'une sélection contrainte par le niveau de zoom
if (selectable.minZoom || selectable.maxZoom) {
// Initialise l'état en fonction du zoom actuel
this.setLayerSelectionAtZoomLevel({
idLayer: layerId,
minZoom: selectable.minZoom,
maxZoom: selectable.maxZoom,
currentZoom: this.ctx.Map.getView().getZoom(),
})
// On observe le changement de résolution pour mettre à jour l'état de sélection
olEvents.push(this.ctx.Map.getView().on('change:resolution', ev => {
const currentZoom = this.ctx.Map.getView().getZoom()
this.setLayerSelectionAtZoomLevel({
idLayer: layerId,
minZoom: selectable.minZoom,
maxZoom: selectable.maxZoom,
currentZoom,
})
}))
}
// Traitement à réaliser lors de la suppression du layer
this.ctx.once(`removedLayer:${layerId}`, () => {
// Retire les events de la Map et la View liés au layer
olEvents.forEach(unByKey)
})
};
geojsonvtReplacer (key, value) {
if (value && value.geometry) {
let type
const rawType = value.type
const geometry = value.geometry
if (rawType === 1) {
type = geometry.length === 1 ? 'Point' : 'MultiPoint'
} else if (rawType === 2) {
type = geometry.length === 1 ? 'LineString' : 'MultiLineString'
} else if (rawType === 3) {
type = geometry.length === 1 ? 'Polygon' : 'MultiPolygon'
}
return {
type: 'Feature',
id: value.id,
geometry: {
type,
coordinates: geometry.length === 1 ? geometry : [geometry],
},
properties: value.tags,
}
} else {
return value
}
};
/**
* Permet de définir une couche de données vectoriel
* Cette couche permet de charger un nombre important de données
* @param {Object} options Paramètres pour limport { SelectModes } from './data-layer';
* @param {string} options.id Id du layerimport Mapviewer from './../core/core';
* @param {string | undefined} options.attributions Définition des attributions propre au layer. Vide par défaut
* @param {ol.style.Style | Array.<ol.style.Style> | ol.style.StyleFunction | undefined} options.style Tableau des styles à appliquer au features ou fonction de style openlayers
* @param {boolean | undefined} options.selectable Autoriser la selection sur le layer. Activée par défaut
* @param {Object | undefined} options.geojson Geojson en EPSG:4326 des données. Permet de créer le layer plus rapidement que les features. options.geojson ou options.features requis
* @param {Array<Feature> | undefined} options.features Features à charger. Préférer l'utilisation de options.geojson. options.geojson ou options.features requis
*/
addVectorDataLayer (options) {
// Définition des arguments par défaut
const args = {
attributions: '',
// TODO Vérifier à quoi servait et ce qu'était defaultStyleFunction
style: null,
selectable: true,
ui: null,
}
// Ajoute les arguments en paramètres dans les arguments par défauts
Object.assign(args, options)
// Le layer ne peut pas avoir le même id qu'un layer existant
if (this.layerExist(args.id)) {
throw new Error('Layer ' + args.id + ' already exixts')
}
if (!args.features && !args.geojson) {
throw new Error('Data are required for ' + args.id)
}
// Un tableau contenant des features est requis pour créer le layer
if (args.features && (!Array.isArray(args.features) || !args.features.length)) {
throw new Error('Features to be an array for layer ' + args.id)
}
let geojson = args.geojson
if (!geojson) {
// Convertie les features en GeoJSON pour les passer à geojson-vt
geojson = JSON.parse(MapviewerServices.featureToGeoJson(args.features, 'EPSG:3857', 'EPSG:4326'))
}
// Indexation des features sur les tuiles
const tileIndex = geojsonvt(geojson, { extent: 4096, debug: 0 })
const tilePixels = new proj.Projection({ code: 'TILE_PIXELS', units: 'tile-pixels' })
/*
* Si une feature chevauche deux tuiles, elles est représenté par deux
* features. On doit donc posséder un objet permettant de connaitres les
* features selectionnées qui sera utilisé par la fonction de style
*/
let selectedFeatures = {}
// Création de la source vectorielle
const vectorSource = new VectorTile({
format: new GeoJSON(),
tileGrid: Tilegrid.createXYZ(),
// tilePixelRatio: 16,
// incompatible OL > 6.14, voir https://github.com/openlayers/openlayers/releases/tag/v6.15.0 si probleme
tileLoadFunction: tile => {
// Récupère les informations de la tuile
const format = tile.getFormat()
const tileCoord = tile.getTileCoord()
// Récupère les features à afficher sur la tuile
const data = tileIndex.getTile(tileCoord[0], tileCoord[1], tileCoord[2])
// Définition du loader de la tuile
tile.setLoader((extent, resolution, projection) => {
// Création des features à afficher
const features = format.readFeatures(
JSON.stringify({
type: 'FeatureCollection',
features: data ? data.features : [],
}, this.geojsonvtReplacer),
{
extent,
featureProjection: tilePixels,
})
tile.setFeatures(features)
})
},
url: 'data',
})
// Création du layer vectoriel
const vectorLayer = new VectorTile({
source: vectorSource,
[this.commonLayer.propertiesName.ID_LAYER]: args.id,
zIndex: args.zIndex,
})
// Définition du style pour la source
vectorLayer.setStyle((feature, resolution) => {
const stylesNone = [
new Style({
display: 'none',
}),
]
// Si la feature est masquée
if (feature.get(this.propertiesName.VISIBLE_FEATURE) !== undefined && feature.get(this.propertiesName.VISIBLE_FEATURE) === false) {
return stylesNone
}
if (typeof args.style === 'function') {
return args.style({
feature,
resolution,
selected: selectedFeatures[feature.getId()],
zoom: this.ctx.Map.getView().getZoomForResolution(resolution),
})
} else {
return args.style
}
})
// Ajout du layer
this.ctx.commonLayer.addLayer(vectorLayer, DATA_LAYER_TYPE, args.ui)
// Si selectable à false, desactive la selection sur le layer
if (!args.selectable) {
this.excludeLayerSelection(args.id)
}
// Permet la gestion de la selection pour les couches vectorielles
this.ctx.on('change:selection', ev => {
selectedFeatures = {}
const selection = ev.currentSelection[args.id]
// Si des données sont selectionnées sur le layer
if (selection) {
selection.forEach(feature => {
// Determine les features selectionnées
selectedFeatures[feature.getId()] = true
})
}
// Indique au layer qu'il a changé
vectorLayer.changed()
})
};
/**
* Permet de créer un layer de données sous forme de HeatMap
*
* @param {Object} options Options pour la création
* @param {string | undefined} options.weightProperties Propriété du poid de la feature
* @param {string} options.id Id du layer à créer
* @param {string} options.features Features à ajouter au layer
* @param {number | undefined} options.zIndex zIndex du layer sur la carte
*/
addHeatMapLayer ({
id,
features,
zIndex,
weightProperties = 'weight',
ui,
displayInLayerSwitcher = true,
}) {
this.ctx.commonLayer.addLayer(new CustomHeatmap({
source: new VectorSource({ features }),
[this.commonLayer.propertiesName.ID_LAYER]: id,
weight: weightProperties,
zIndex,
displayInLayerSwitcher,
}), DATA_LAYER_TYPE, ui)
}
/**
* Permet de passer en mode de création pour dessiner une feature
* et l'ajouter automatiquement sur le layer
* Nouvelle signature afin d'étendre plus facilement les fonctionnalités
*
* @param {string | number | object} idFeature IdFeature pour version deprecated ou Options pour la création
* @param {string | number} options.idFeature Identifiant de la feature à créer
* @param {string} options.idLayer Identifiant du layer
* @param {string} options.geometryType Type de géométrie à dessiner
* @param {Object} options.properties Propriétés de la feature
* @param {object} options.digitalizeOptions options de l'outil de digitalisation
* @param {object} options.createTemplates templates de création de géométries
* @param {string} options.toolId Outil qui demande la création
* @param {Boolean} options.autoValidate Validation automatique (implicite si boite outil masquée)
* @param {Boolean} options.hideToolbox Masquer la boite à outil (impossible pour multigéométrie)
* @param {Boolean} options.hideGPS Masquer l'option GPS sur les outils d'ajout
* @param {Array} options.modifyTools Outils de modifications disponibles ['move','rotate','scale','modifyVertex','removeVertex']
* @param {Object} options.createToolOptions Options pour la popup d'outils (pas encore utilisé)
* @return {Promise} Ajoute la feature au calque idLayer et retourne un objet étendu avec la feature une fois créer
*
* @example
* Ajoute la feature au calque idLayer et retourne un objet étendu avec la feature une fois créer
* mapviewer.dataLayer.createFeature( {
* "geometryType": "GeometryCollection", // type de geometrie a créer (type geojson)
* "idLayer": "features-EQU", // calque ou créer la geométrie
* "idFeature" : "efefef-1234",
* "properties" : {test:1},
* "toolId": "create-EQU", // sera renvoyé lors de la levé de l'event createfeature:create
* "digitalizeOptions": { // options de l'outil de digitalisation
* "showConstructionPoint": true,
* "maxPoints" : 5,
* "showConstructionLine" : true,
* },
* autoValidate: false,
* hideToolbox: false,
* modifyTools: ['move','rotate','scale','modifyVertex','removeVertex'],
* hideGPS: [],
* "createTemplates": { // templates de création de géométries
* "features": [{ // geojson features
* "type": "Feature",
* "geometry": {
* "type": "MultiPolygon",
* "coordinates": [coordonnées de l objet]
* },
* "properties": { // propriétés recopiées sur la feature openlayers créées (si elle n'existe pas déjà)
* "name": "Assainissement - Arbre"
* },
* "display": { // libellé affiché dans l'ihm de création
* "label": "Assainissement - Arbre"
* }
* }, {
* "type": "Feature",
* "geometry": {
* "type": "MultiPolygon",
* "coordinates": [coordonnées de l objet]
* },
* "properties": {
* "name": "Assainissement - Bac graisse"
* },
* "display": {
* "label": "Assainissement - Bac graisse"
* }
* },
* ]
* }
* }
* }
* ]
*})
*/
createFeature (idFeature, idLayer, geometryType, properties = {}, padding) {
if (typeof idFeature !== 'object') {
console.warn('[deprecated] ancienne signature de la méthode, utiliser createFeature({idFeature,idLayer,type,properties,padding,onFeature,useLRS,toolId})')
return this.createFeature({ idFeature, idLayer, geometryType, properties, padding })
}
// on est en train de create ou modif, si c'est le cas on stoppe avant
if (this._cancelModifyDrawing) {
this._cancelModifyDrawing()
}
const options = idFeature
const CREATE_MODE = {
Point: 'Point',
LineString: 'LineString',
Polygon: 'Polygon',
// pour les multigéométrie on lance la création comme une modification de feature vide
GeometryCollection: 'GeometryCollection',
MultiPoint: 'MultiPoint',
MultiLineString: 'MultiLineString',
MultiPolygon: 'MultiPolygon',
}
return new Promise((resolve, reject) => {
// On s'assure de l'existance du layer
if (!this.layerExist(options.idLayer)) {
return reject(new Error('layerNotFound'))
}
// Si pas d'id de feature ou qu'il existe déjà sur le layer
if (!options.idFeature || this.findFeatures(options.idFeature, options.idLayer).length) {
return reject(new Error('FeatureAlreadyExist'))
}
// On s'assure qu'il y a un mode de création pour le type demandé
if (!CREATE_MODE[options.geometryType]) {
return reject(new Error(`invalidTypeFeature ${options.geometryType}`))
}
// on créée une feature vide que l'on envoit a l'outil de modification
const modifyFeature = new Feature()
const modifyOptions = {
...options,
feature: modifyFeature,
}
this.modifyFeature(modifyOptions).then((result) => {
if (!result) {
resolve(null)
return
}
result.modifiedFeature.setProperties(options.properties)
result.modifiedFeature.setId(options.idFeature)
// ajoute la feature au calque correspondant
this.addFeatures([result.modifiedFeature], options.idLayer, true)
// previent l'appli qu'on a fini (par exemple audit re-traite les feature créée)
const rtn = {
originalGeometry: result.modifiedFeature.getGeometry(),
createdFeature: result.modifiedFeature,
toolId: options.toolId,
}
this.ctx.dispatchEvent('createfeature:create', rtn)
resolve(rtn)
}).catch((err) => {
this.ctx.horizontalToolbar.setVisible(true)
console.error('[datalayer-createFeature] Error : ', err)
reject(err)
}) // renvoi d'une erreur du modifyfeature
// on créée une feature vide que l'on envoit a l'outil de modification
}).catch((err) => {
this.ctx.horizontalToolbar.setVisible(true)
console.error(err)
throw (err)
})
}
/**
* Permet de passer en mode de modification pour dessiner une feature
* et la modifier automatiquement sur le layer
* Nouvelle signature afin d'étendre plus facilement les fonctionnalités
*
* @param {string | number | object} idFeature IdFeature pour version deprecated ou Options pour la création
* @param {string | number} options.idFeature Identifiant de la feature à créer
* @param {string} options.idLayer Identifiant du layer
* @param {string} options.geometryType Type de géométrie à dessiner
* @param {object} options.properties Propriétés de la feature
* @param {object} options.digitalizeOptions options de l'outil de digitalisation
* @param {object} options.createTemplates templates de création de géométries
* @param {string} options.toolId Outil qui demande la création
* @param {Boolean} options.autoValidate Validation automatique (implicite si boite outil masquée)
* @param {Boolean} options.hideToolbox Masquer la boite à outil (impossible pour multigéométrie)
* @param {Boolean} options.hideGPS Masquer l'option GPS sur les outils d'ajout
* @param {Array} options.modifyTools Outils de modifications disponibles ['move','rotate','scale','modifyVertex','removeVertex']
* @param {Object} options.createToolOptions Options pour la popup d'outils (pas encore utilisé)
* @return {Promise} Ajoute la feature au calque idLayer et retourne un objet étendu avec la feature une fois créer
*
* @example
* Ajoute la feature au calque idLayer et retourne un objet étendu avec la feature une fois créer
* mapviewer.dataLayer.modifyFeature( {
* "type": "GeometryCollection", // type de geometrie a créer (type geojson)
* "feature": olFeature, si non présent recherche la feature avec les paramètres idLayer/idFeature
* "idLayer": "features-EQU", // calque ou se trouve la geométrie
* "idFeature" : "efefef-1234",
* "properties" : {test:1},
* "toolId": "create-EQU", // sera renvoyé lors de la levé de l'event createfeature:create
* "digitalizeOptions": { // options de l'outil de digitalisation
* "showConstructionPoint": true,
* "maxPoints" : 5,
* "showConstructionLine" : true,
* },
* autoValidate: false,
* hideToolbox: false,
* modifyTools: ['move','rotate','scale','modifyVertex','removeVertex'],
* hideGPS: [],
* "createTemplates": { // templates de création de géométries
* "features": [{ // geojson features
* "type": "Feature",
* "geometry": {
* "type": "MultiPolygon",
* "coordinates": [coordonnées de l objet]
* },
* "properties": { // propriétés recopiées sur la feature openlayers créées (si elle n'existe pas déjà)
* "name": "Assainissement - Arbre"
* },
* "display": { // libellé affiché dans l'ihm de création
* "label": "Assainissement - Arbre"
* }
* }, {
* "type": "Feature",
* "geometry": {
* "type": "MultiPolygon",
* "coordinates": [coordonnées de l objet]
* },
* "properties": {
* "name": "Assainissement - Bac graisse"
* },
* "display": {
* "label": "Assainissement - Bac graisse"
* }
* },
* ]
* }
* }
* }
* ]
*})
*/
modifyFeature (options) {
// on est en train de create ou modif, si c'est le cas on stoppe avant
if (this._cancelModifyDrawing) {
this._cancelModifyDrawing()
}
let _modifyControl = null
const cleanAfterModify = () => {
if (_modifyControl) { this.ctx.Map.removeControl(_modifyControl) }
this.changeMapMode('select', 'select-single')
this.ctx.horizontalToolbar.setVisible(true)
this._cancelModifyDrawing = null
_modifyControl = null
}
return new Promise((resolve, reject) => {
if (!options.feature) {
// Si pas d'id de feature ou qu'il existe déjà sur le layer
if (options.idFeature && options.idLayer) {
const features = this.findFeatures(options.idFeature, options.idLayer)
options.feature = features.length === 1 ? features[0] : null
}
}
if (!options.feature) {
return reject(new Error(`'[datalayer-modifyFeature] modifyFeatureNotFound ${options.idFeature} - ${options.idLayer}`))
}
this.ctx.horizontalToolbar.setVisible(false)
// pour les bouton d'annulation et de validation de la boite de dialogue modify
this._cancelModifyDrawing = () => {
// console.log('cancel modify drawing')
cleanAfterModify()
resolve(null)
}
const _validateModifyDrawing = (feature) => {
// console.log('validate modify drawing: ', feature)
cleanAfterModify()
// clone et nettoi la feature avant de la renvoyer
const id = feature.getId()
feature = feature.clone()
feature.setId(id)
Object.values(this.propertiesName).forEach((propertie) => {
feature.unset(propertie)
})
// previent l'appli qu'on a fini (par exemple audit re-traite les feature créée)
const rtn = {
originalGeometry: feature.getGeometry(),
modifiedFeature: feature,
toolId: options.toolId,
}
this.ctx.dispatchEvent('modifyfeature:modify', rtn)
resolve(rtn)
}
const createToolOptions = {
...(options?.createToolOptions ? options.createToolOptions : {}), // si on a passé des options cosmétique pour l'outil de création on l'utilise
feature: options.feature,
geometryType: options.geometryType,
toolId: options.toolId,
viewer: this.ctx,
digitalizeOptions: options.digitalizeOptions || {},
createTemplates: options.createTemplates || {},
title: options.title || null,
autoValidate: options.autoValidate,
hideToolbox: options.hideToolbox,
modifyTools: options.modifyTools,
hideGPS: options.hideGPS,
// la boite d'outils de création ne fait qu'utiliser des outils venant de datalayer, on fournit les méthodes et interractions
methods: {
cancel: this._cancelModifyDrawing,
validate: _validateModifyDrawing,
},
mapModes: {
Point: '_drawPointTouch',
LineString: '_drawLineStringTouch',
Polygon: '_drawPolygonTouch',
},
}
_modifyControl = new ControlModifyTools(createToolOptions)
this.ctx.Map.addControl(_modifyControl)
}).catch((err) => {
console.error(err)
// dans tout les cas on fait le ménage
cleanAfterModify()
throw (err)
})
}
/**
* Effectue l'union de feature et génère les évènements utiles
* @param {Object} options Options de l'outil
* @param {Array<Feature>} options.features - Liste des features a fusionner
* @param {string | number} [options.idFeature] - Identifiant de la feature à créer (ou guid)
* @param {string} [options.idLayer] - Identifiant du layer (ou layer de la première feature)
* @param {toolId} [options.toolId='tool-union'] - Outil qui demande la création
* @param {Boolean} [options.updateLayer = true] - On supprime les anciennes features et on ajout la nouvelle
* @param {Boolean} [options.contiguonsPolygon = false] - les polygones doivent être contigue? Sinon peut créer des multipolygone
* @param {Boolean} [options.copyProperties = false] - copie les propriétées de la première feature
* @returns {Feature} features fusionnées
*/
unionFeature (options = {}) {
const { features, contiguonsPolygon = false, updateLayer = true, toolId = 'tool-union', copyProperties = false } = options
let { idLayer, idFeature } = options
if (!features) {
throw new Error('\'[datalayer-unionFeature] paramètre features manquant')
// return reject(new Error(`'[datalayer-unionFeature] paramètre features manqnuant`))
}
if (!MapviewerServices.FeatureUnion.isUnionPossible(features, contiguonsPolygon)) {
throw new Error('\'[datalayer-unionFeature] ces features ne peuvent pas être fusionnées', features)
}
// si on indique pas de layer, on utilise le layer de la feature
idLayer = idLayer || features[0]?.layerSourceId_?.[0]
idFeature = idFeature || Guid()
const newFeature = MapviewerServices.FeatureUnion.union(features)
if (copyProperties) {
// const { geometry, ...properties } = features[0].getProperties()
const forbiddenProperties = Object.values(this.propertiesName).concat('geometry')
const properties = Object.fromEntries(Object.entries(features[0].getProperties())
.filter(([key]) => !forbiddenProperties.includes(key)))
newFeature.setProperties(properties)
}
newFeature.setId(idFeature)
newFeature.set('merged', true)
const rtn = {
originalGeometry: newFeature.getGeometry(),
createdFeature: newFeature,
toolId,
}
// on veut maj le calque
// on supprime les anciennes feature, on ajoute la nouvelle
if (updateLayer) {
this.removeFeatures(features, idLayer)
this.addFeatures([newFeature], idLayer, true)
this.ctx.dispatchEvent('createfeature:create', rtn)
}
rtn.deletedFeatures = features
this.ctx.dispatchEvent('createfeature:union', rtn)
return rtn
}
async splitFeature (options = {}) {
const { feature, updateLayer = true, toolId = 'tool-split', copyProperties = false } = options
let { idLayer } = options
if (!feature) {
throw new Error('\'[datalayer-splitFeature] paramètre feature manquant')
}
if (!isSplitPossible(feature)) {
throw new Error('\'[datalayer-splitFeature] cette feature ne peut pas être découpés', feature)
}
// si on indique pas de layer, on utilise le layer de la feature
idLayer = idLayer || feature?.layerSourceId_?.[0]
// idFeature = idFeature || Guid()
const newFeatures = await splitFeature({ feature, viewer: this.ctx, toolId })
if (!newFeatures) {
throw new Error('\'[datalayer-splitFeature] Opération annulée')
}
if (copyProperties) {
const forbiddenProperties = Object.values(this.propertiesName).concat('geometry')
const properties = Object.fromEntries(Object.entries(feature.getProperties())
.filter(([key]) => !forbiddenProperties.includes(key)))
// const { geometry, ...properties } = feature.getProperties()
newFeatures.forEach(newFeature => newFeature.setProperties(properties))
}
newFeatures.forEach((newFeature, index) => {
newFeature.setId(Guid())
newFeature.setProperties({
splitted: true,
parentId: feature.getId(),
index,
})
})
const rtn = {
createdFeatures: newFeatures,
toolId,
}
// on veut maj le calque
// on supprime les anciennes feature, on ajoute la nouvelle
if (updateLayer) {
this.removeFeature(feature, idLayer)
this.addFeatures(newFeatures, idLayer, true)
newFeatures.forEach(newFeature => {
this.ctx.dispatchEvent('createfeature:create',
{
originalGeometry: newFeature.getGeometry(),
createdFeature: newFeature,
toolId,
})
})
}
rtn.deletedFeature = feature
this.ctx.dispatchEvent('createfeature:split', rtn)
return rtn
// return
}
startDragAndDropFeatures (mapMode, layerList) {
this._dragAndDropInteraction.set('draggableLayerList', layerList)
this.changeMapMode(mapMode)
}
stopDragAndDropFeatures () {
this.ctx.commonLayer.disableCurrentMapMode()
}
/**
* Ajout d'un tag sur une feature
* @param {Feature} feature Feature OpenLayer à activer
* @param {String} tag At tag a ajouter a la feature
* @param {Object} options options
* @param {boolean} options.noRefresh Permet de ne par recharger la carte (changement non visible).
*/
addTagToOlFeature (feature, tag, { noRefresh } = {}) {
const currentFeatureTags = feature.get(this.propertiesName.TAG_FEATURE) || {}
currentFeatureTags[tag] = true
feature.set(this.propertiesName.TAG_FEATURE, currentFeatureTags, true)
if (!noRefresh) {
feature.changed()
}
}
removeTagToOlFeature (feature, tag, { noRefresh } = {}) {
if (this.hasTag(feature, tag)) {
const currentFeatureTags = feature.get(this.propertiesName.TAG_FEATURE) || {}
delete currentFeatureTags[tag]
feature.set(this.propertiesName.TAG_FEATURE, currentFeatureTags, true)
if (!noRefresh) {
feature.changed()
}
}
}
hasTag (feature, tag) {
if (!feature) { return false }
return feature.get(this.propertiesName.TAG_FEATURE)?.[tag] || false
}
addTagToFeature (idFeature, idLayer, tag, { noRefresh } = {}) {
const feature = this.getFeature(idFeature, idLayer)
if (feature) {
this.addTagToOlFeature(feature, tag, { noRefresh })
}
}
removeTagToFeature (idFeature, idLayer, tag, { noRefresh } = {}) {
const feature = this.getFeature(idFeature, idLayer)
if (feature) {
this.removeTagToOlFeature(feature, tag, { noRefresh })
}
}
/**
* Activer une feature OpenLayers
* @param {Feature} feature Feature OpenLayer à activer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
activeOlFeature (feature, { noRefresh } = {}) {
feature.set(this.propertiesName.ACTIVATED_FEATURE, true, noRefresh)
}
/**
* Desactiver une feature OpenLayers
* @param {Feature} feature Feature OpenLayer à desactiver
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
desactiveOlFeature (feature, { noRefresh } = {}) {
feature.set(this.propertiesName.ACTIVATED_FEATURE, false, noRefresh)
}
/**
* Activer des features OpenLayers
* @param {Array<Feature>} features Feature OpenLayer à activer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
activeOlFeatures (features, { noRefresh } = {}) {
features.forEach(item => this.activeOlFeature(item, { noRefresh }))
}
/**
* Desactiver des features OpenLayers
* @param {Array<Feature>} features Feature OpenLayer à desactiver
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
desactiveOlFeatures (features, { noRefresh } = {}) {
features.forEach(item => this.desactiveOlFeature(item, { noRefresh }))
}
/**
* Activer une feature
* @param {*} idFeature Identifiant de la feature à activer
* @param {string} idLayer Identifiant du layer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
activeFeature (idFeature, idLayer, { noRefresh } = {}) {
const feature = this.getFeature(idFeature, idLayer)
if (feature) {
this.activeOlFeature(feature, { noRefresh })
}
}
/**
* Desactiver une feature
* @param {*} idFeature Identifiant de la feature à desactiver
* @param {string} idLayer Identifiant du layer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
desactiveFeature (idFeature, idLayer, { noRefresh } = {}) {
const feature = this.getFeature(idFeature, idLayer)
if (feature) {
this.desactiveOlFeature(feature, { noRefresh })
}
}
/**
* Activer des features
* @param {Array<*>} idFeature Identifiants des features à activer
* @param {string} idLayer Identifiant du layer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
activeFeatures (idFeatures, idLayer, { noRefresh } = {}) {
// Créer une map id/features
// Plus performant qu'un filter/includes
const featuresMap = this.getFeatures(idLayer).reduce((acc, feature) => {
acc[feature.getId()] = feature
return acc
}, {})
// Récuère les features à partir des ids
const features = idFeatures.map(id => featuresMap[id]).filter(item => item)
this.activeOlFeatures(features, { noRefresh })
}
/**
* Desactiver des features
* @param {Array<*>} idFeature Identifiants des features à desactiver
* @param {string} idLayer Identifiant du layer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
desactiveFeatures (idFeatures, idLayer, { noRefresh } = {}) {
// Créer une map id/features
// Plus performant qu'un filter/includes
const featuresMap = this.getFeatures(idLayer).reduce((acc, feature) => {
acc[feature.getId()] = feature
return acc
}, {})
// Récuère les features à partir des ids
const features = idFeatures.map(id => featuresMap[id]).filter(item => item)
this.desactiveOlFeatures(features, { noRefresh: true })
if (!noRefresh && this.layerExist(idLayer)) {
this.getSourceLayer(idLayer).changed()
}
}
/**
* Descativer toutes les features
* Si idLayer, on ne désactive que les features de ce layer
* @param {string|undefined} idLayer Identifiant du layer
* @param {boolean} noRefresh Permet de ne par recharger la carte (changement non visible).
*/
desactiveAllFeatures ({ idLayer, noRefresh } = {}) {
if (idLayer) {
this.desactiveOlFeatures(this.getFeatures(idLayer), { noRefresh: true })
} else {
this.getLayers().forEach(layer =>
this.desactiveOlFeatures(this.getFeatures(layer), { noRefresh: true }))
}
if (!noRefresh && this.layerExist(idLayer)) {
this.getSourceLayer(idLayer).changed()
}
}
/**
* Options pour le mode de sélection bbox
* @param {Object} options
*/
setRectangularModeOptions (options) {
if (options && options.include) {
this.setgeometrieTypesSelectableInRectangularMode(options.include)
}
// sélection-t-on les features invisibles dans ce mode?
this.selectHiddenFeatureInRectangularMode = options && options.selectHiddenFeature !== false
}
/**
* garde les géométries de type de features filtrées en mode rectangulaire
* @param {Array<string>} geometries
*/
setgeometrieTypesSelectableInRectangularMode (geometries) {
if (geometries && Array.isArray(geometries)) {
this.geometrieTypesSelectableInRectangularMode = geometries
} else {
this.geometrieTypesSelectableInRectangularMode = []
}
}
/**
* Teste si la feature doit être sélectionnée lors d'une selection rectangulaire
* @param {Object} feature
*/
isSelectableInRectangularMode (feature) {
return (!this.geometrieTypesSelectableInRectangularMode.length ||
this.geometrieTypesSelectableInRectangularMode.includes(feature.getGeometry().getType())) &&
(feature.get(this.propertiesName.VISIBLE_FEATURE) !== false || this.selectHiddenFeatureInRectangularMode)
}
/**
* change le mode de sélection
* @param {SelectModes} mode mode de sélection @link DataLayer.SelectModes
*/
setSelectMode (mode) {
this.selectMode = mode
this.ctx.dispatchEvent('change:selectMode', { selectMode: mode })
}
/**
* Renvoit le mode de sélection actuel
* @returns {SelectModes}
*/
getSelectMode () {
return this.selectMode
}
/** instance du viewer
* @type Mapviewer
*/
get viewer () {
return this.ctx
}
/** Fonction commonLayer */
get commonLayer () {
return this.viewer.commonLayer
}
}
/**
* @enum {MapMode}
*/
DataLayer.MapModes = {
/**
* Mode de modification de features
*/
modify: 'modify',
/**
* Mode de sélection cumulant les features
*/
multiselect: 'multiselect',
/**
* Mode de sélection simple
*/
select: 'select',
/**
* Mode de sélection par tracé d'une zone rectangulaire
*/
rectangular: 'rectangular',
/**
* Mode de sélection de features contenues dans une autre
*/
zonal: 'zonal',
/**
* Mode de sélection via le tracé d'un polygon sur la carte
*/
polygon: 'polygon',
/**
* Mode de mesure de distances
*/
measureLine: 'measureLine',
/**
* Mode de mesure de surfaces
*/
measureArea: 'measureArea',
/**
* Mode drag & drop sur layer
*/
dragAndDrop: 'dragAndDrop',
}
/**
* Liste des modes de sélection disponibles
* @enum {SelectMode}
*/
DataLayer.SelectModes = {
/**
* la sélection remplace la précédente (mode par défaut)
*/
REPLACE: 'replace',
/**
* la re-selection d'un élement le déselectionne
*/
TOGGLE: 'toggle',
/**
* Mode de sélection additive
*/
ADD: 'add',
}
export const SelectModes = DataLayer.SelectModes
// Permet d'etendre le module
export default function extendCoreLib (options) {
return function patch (viewer) {
const functions = { }
functions[libNamespace] = new DataLayer(viewer, options)
return Object.assign(viewer, functions)
}
}