Source: modules/Snapping.js

  1. /**
  2. * @module modules/Snapping.js
  3. * @name Snapping
  4. * @copyright 2023 3Liz
  5. * @license MPL-2.0
  6. */
  7. import { mainEventDispatcher } from '../modules/Globals.js';
  8. import Edition from './Edition.js';
  9. import { MapRootState } from './state/MapLayer.js';
  10. import { TreeRootState } from './state/LayerTree.js';
  11. /**
  12. * @class
  13. * @name Snapping
  14. */
  15. export default class Snapping {
  16. /**
  17. * Create a snapping instance
  18. * @param {Edition} edition - The edition module
  19. * @param {MapRootState} rootMapGroup - Root map group
  20. * @param {TreeRootState} layerTree - Root tree layer group
  21. * @param {object} lizmap3 - The old lizmap object
  22. */
  23. constructor(edition, rootMapGroup, layerTree, lizmap3) {
  24. this._edition = edition;
  25. this._rootMapGroup = rootMapGroup;
  26. this._layerTree = layerTree;
  27. this._lizmap3 = lizmap3;
  28. this._active = false;
  29. this._snapLayersRefreshable = false;
  30. this._maxFeatures = 1000;
  31. this._restrictToMapExtent = true;
  32. this._config = undefined;
  33. this._snapEnabled = {};
  34. this._snapToggled = {};
  35. this._snapLayers = [];
  36. // Create layer to store snap features
  37. const snapLayer = new OpenLayers.Layer.Vector('snaplayer', {
  38. visibility: false,
  39. styleMap: new OpenLayers.StyleMap({
  40. pointRadius: 2,
  41. fill: false,
  42. stroke: false,
  43. strokeWidth: 3,
  44. strokeColor: 'red',
  45. strokeOpacity: 0.8
  46. })
  47. });
  48. this._lizmap3.map.addLayer(snapLayer);
  49. const snapControl = new OpenLayers.Control.Snapping({
  50. layer: this._edition.editLayer,
  51. targets: [{
  52. layer: snapLayer
  53. }]
  54. });
  55. this._lizmap3.map.addControls([snapControl]);
  56. this._lizmap3.controls['snapControl'] = snapControl;
  57. this._setSnapLayersRefreshable = () => {
  58. if(this._active){
  59. this.snapLayersRefreshable = true;
  60. }
  61. }
  62. this._setSnapLayersVisibility = () => {
  63. if(this._active){
  64. this._snapLayers.forEach((layer)=>{
  65. this._snapEnabled[layer] = this.getLayerTreeVisibility(layer);
  66. })
  67. this._sortSnapLayers();
  68. const config = structuredClone(this._config);
  69. config.snap_layers = this._snapLayers;
  70. config.snap_enabled = this._snapEnabled;
  71. this.config = config;
  72. this.snapLayersRefreshable = true;
  73. // dispatch an event, it might be useful to know when the list of visible layer for snap changed
  74. mainEventDispatcher.dispatch('snapping.layer.visibility.changed');
  75. }
  76. }
  77. this._sortSnapLayers = () => {
  78. let snapLayers = [...this._snapLayers];
  79. let visibleLayers = [];
  80. for (let id in this._snapEnabled) {
  81. if(this._snapEnabled[id]){
  82. let visibileLayer = snapLayers.splice(snapLayers.indexOf(id),1)
  83. visibleLayers = visibleLayers.concat(visibileLayer)
  84. }
  85. }
  86. visibleLayers.sort();
  87. snapLayers.sort();
  88. this._snapLayers = visibleLayers.concat(snapLayers);
  89. }
  90. // Activate snap when a layer is edited
  91. mainEventDispatcher.addListener(
  92. () => {
  93. // Get snapping configuration for edited layer
  94. for (const editionLayer in this._lizmap3.config.editionLayers) {
  95. if (this._lizmap3.config.editionLayers.hasOwnProperty(editionLayer)) {
  96. if (this._lizmap3.config.editionLayers[editionLayer].layerId === this._edition.layerId){
  97. const editionLayerConfig = this._lizmap3.config.editionLayers[editionLayer];
  98. if (editionLayerConfig.hasOwnProperty('snap_layers') && editionLayerConfig.snap_layers.length > 0){
  99. this._snapLayers = [...editionLayerConfig.snap_layers];
  100. this._snapLayers.forEach((layer)=>{
  101. this._snapEnabled[layer] = this.getLayerTreeVisibility(layer);
  102. })
  103. this._snapLayers.forEach((layer)=>{
  104. // on init enable snap by default on visible layers
  105. this._snapToggled[layer] = this.getLayerTreeVisibility(layer);
  106. })
  107. // sorting of layers by name and put disabled layers on bottom of the list
  108. this._sortSnapLayers();
  109. this.config = {
  110. 'snap_layers': this._snapLayers,
  111. 'snap_enabled': this._snapEnabled,
  112. 'snap_on_layers':this._snapToggled,
  113. 'snap_vertices': (editionLayerConfig.hasOwnProperty('snap_vertices') && editionLayerConfig.snap_vertices === 'True') ? true : false,
  114. 'snap_segments': (editionLayerConfig.hasOwnProperty('snap_segments') && editionLayerConfig.snap_segments === 'True') ? true : false,
  115. 'snap_intersections': (editionLayerConfig.hasOwnProperty('snap_intersections') && editionLayerConfig.snap_intersections === 'True') ? true : false,
  116. 'snap_vertices_tolerance': editionLayerConfig.hasOwnProperty('snap_vertices_tolerance') ? editionLayerConfig.snap_vertices_tolerance : 10,
  117. 'snap_segments_tolerance': editionLayerConfig.hasOwnProperty('snap_segments_tolerance') ? editionLayerConfig.snap_segments_tolerance : 10,
  118. 'snap_intersections_tolerance': editionLayerConfig.hasOwnProperty('snap_intersections_tolerance') ? editionLayerConfig.snap_intersections_tolerance : 10
  119. };
  120. }
  121. }
  122. }
  123. }
  124. if (this._config !== undefined){
  125. // Configure snapping
  126. const snapControl = this._lizmap3.controls.snapControl;
  127. // Set edition layer as main layer
  128. snapControl.setLayer(this._edition.editLayer);
  129. snapControl.targets[0].node = this._config.snap_vertices;
  130. snapControl.targets[0].vertex = this._config.snap_intersections;
  131. snapControl.targets[0].edge = this._config.snap_segments;
  132. snapControl.targets[0].nodeTolerance = this._config.snap_vertices_tolerance;
  133. snapControl.targets[0].vertexTolerance = this._config.snap_intersections_tolerance;
  134. snapControl.targets[0].edgeTolerance = this._config.snap_segments_tolerance;
  135. // Listen to moveend event and to layers visibility changes to able data refreshing
  136. this._lizmap3.map.events.register('moveend', this, this._setSnapLayersRefreshable);
  137. this._rootMapGroup.addListener(
  138. this._setSnapLayersVisibility,
  139. ['layer.visibility.changed','group.visibility.changed']
  140. );
  141. }
  142. },
  143. 'edition.formDisplayed'
  144. );
  145. // Clean snap when edition ends
  146. mainEventDispatcher.addListener(
  147. () => {
  148. this.active = false;
  149. this._lizmap3.map.getLayersByName('snaplayer')[0].destroyFeatures();
  150. this.config = undefined;
  151. // Remove listener to moveend event to layers visibility event
  152. this._lizmap3.map.events.unregister('moveend', this, this._setSnapLayersRefreshable);
  153. this._rootMapGroup.removeListener(
  154. this._setSnapLayersVisibility,
  155. ['layer.visibility.changed','group.visibility.changed']
  156. )
  157. },
  158. 'edition.formClosed'
  159. );
  160. }
  161. getSnappingData () {
  162. // Empty snapping layer first
  163. this._lizmap3.map.getLayersByName('snaplayer')[0].destroyFeatures();
  164. // filter only visible layers and toggled layers on the the snap list
  165. const currentSnapLayers = this._snapLayers.filter(
  166. (layerId) => this._snapEnabled[layerId] && this._snapToggled[layerId]
  167. );
  168. // TODO : group aync calls with Promises
  169. for (const snapLayer of currentSnapLayers) {
  170. lizMap.getFeatureData(this._lizmap3.getLayerConfigById(snapLayer)[0], null, null, 'geom', this._restrictToMapExtent, null, this._maxFeatures,
  171. (fName, fFilter, fFeatures) => {
  172. // Transform features
  173. const snapLayerConfig = lizMap.config.layers[fName];
  174. let snapLayerCrs = snapLayerConfig['featureCrs'];
  175. if (!snapLayerCrs) {
  176. snapLayerCrs = snapLayerConfig['crs'];
  177. }
  178. // TODO : use OL 6 instead ?
  179. const gFormat = new OpenLayers.Format.GeoJSON({
  180. ignoreExtraDims: true,
  181. externalProjection: snapLayerCrs,
  182. internalProjection: this._lizmap3.map.getProjection()
  183. });
  184. const tfeatures = gFormat.read({
  185. type: 'FeatureCollection',
  186. features: fFeatures
  187. });
  188. // Add features
  189. this._lizmap3.map.getLayersByName('snaplayer')[0].addFeatures(tfeatures);
  190. });
  191. }
  192. this.snapLayersRefreshable = false;
  193. }
  194. toggle(){
  195. this.active = !this._active;
  196. }
  197. /**
  198. * Getting the layer visibility from the layer tree state
  199. * @param {string} layerId - the layer id
  200. * @returns {boolean} the layer visibility
  201. */
  202. getLayerTreeVisibility(layerId){
  203. let visible = false;
  204. let layerConfig = this._lizmap3.getLayerConfigById(layerId);
  205. if(layerConfig && layerConfig[0]) {
  206. try {
  207. visible = this._layerTree.getTreeLayerByName(layerConfig[0]).visibility
  208. } catch {
  209. visible = false
  210. }
  211. }
  212. return visible;
  213. }
  214. /**
  215. * Getting the layer tile or the layer name for snap layers list
  216. * @param {string} layerId - the layer id
  217. * @returns {string} the layer title or layer name
  218. */
  219. getLayerTitle(layerId){
  220. let layerConfig = this._lizmap3.getLayerConfigById(layerId);
  221. if (layerConfig) {
  222. return layerConfig[1].title || layerConfig[1].name;
  223. }
  224. return "";
  225. }
  226. get snapEnabled(){
  227. return this._snapEnabled;
  228. }
  229. set snapToggled(layerId){
  230. this._snapToggled[layerId] = !this._snapToggled[layerId];
  231. const config = structuredClone(this._config);
  232. config.snap_on_layers = this._snapToggled;
  233. this.config = config;
  234. this.snapLayersRefreshable = true;
  235. }
  236. get snapLayersRefreshable(){
  237. return this._snapLayersRefreshable;
  238. }
  239. set snapLayersRefreshable(refreshable) {
  240. this._snapLayersRefreshable = refreshable;
  241. mainEventDispatcher.dispatch('snapping.refreshable');
  242. }
  243. get active() {
  244. return this._active;
  245. }
  246. set active(active) {
  247. this._active = active;
  248. // (de)activate snap control
  249. if (this._active) {
  250. this.getSnappingData();
  251. this._lizmap3.controls.snapControl.activate();
  252. } else {
  253. // Disable refresh button when snapping is inactive
  254. this.snapLayersRefreshable = false;
  255. this._lizmap3.controls.snapControl.deactivate();
  256. }
  257. // Set snap layer visibility
  258. this._lizmap3.map.getLayersByName('snaplayer')[0].setVisibility(this._active);
  259. mainEventDispatcher.dispatch('snapping.active');
  260. }
  261. get config() {
  262. return this._config;
  263. }
  264. set config(config) {
  265. this._config = config;
  266. mainEventDispatcher.dispatch('snapping.config');
  267. }
  268. }