1/* eslint-disable func-names */ 2/* eslint-disable no-param-reassign */ 3/* eslint-disable no-loop-func */ 4/* eslint-disable no-plusplus */ 5/* eslint-disable no-unused-expressions */ 6/* eslint-disable no-underscore-dangle */ 7/* eslint-disable no-shadow */ 8/* global proj4, ol, $ */ 9(function (window) { 10 function sitnLayers() { 11 const sitnLayers = {}; 12 const _extent = [2420000, 1030000, 2900000, 1350000]; 13 const _crs = 'EPSG:2056'; 14 const _WMTSurl = 'https://sitn.ne.ch/web_getcapabilities/WMTSGetCapabilities95.xml'; 15 const _WMSurl = 'https://sitn.ne.ch/mapserv_proxy?ogcserver=source+for+image%2Fpng'; 16 const _drawSource = new ol.source.Vector(); 17 const _markerColor = '#8959A8'; 18 const _markerSource = new ol.source.Vector(); 19 const _sitnBaseLayers = { 20 plan_ville: 'Plan de ville', 21 plan_cadastral: 'Plan cadastral', 22 ortho: 'Images aériennes', 23 }; 24 // A dictionnary containing named layers in the form of { layer_name: ol.layer.Image(), } 25 const _sitnWMSLayers = {}; 26 let _buttons = []; 27 let _baselayers = []; 28 let _wmslayers = []; 29 let _target; 30 let _selectTarget; 31 let _drawSimpleGeom = false; 32 let _markerImage = '/typo3conf/ext/address/Resources/Public/Medias/marker.svg'; 33 let _drawColor = 'cornflowerblue'; 34 let _drawFillColor; 35 let _drawWidth = 4; 36 let _pointStyle = 'circle'; 37 let _searchColor = 'red'; 38 let _map = false; 39 let _minZoom = 0; 40 let _maxZoom = 28; 41 42 sitnLayers.sitnCurrentBaseLayer = new ol.layer.Tile(); 43 sitnLayers.sitnDrawLayer = new ol.layer.Vector({ 44 source: _drawSource, 45 }); 46 47 // projection 48 proj4.defs(_crs, 49 '+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333' 50 + ' +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel ' 51 + '+towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs'); 52 ol.proj.proj4.register(proj4); 53 const _projection = new ol.proj.Projection({ 54 code: _crs, 55 extent: _extent, 56 }); 57 58 sitnLayers._createMarkerStyle = function (color) { 59 return new ol.style.Icon({ 60 anchor: [0.5, 1], 61 color, 62 opacity: 1, 63 src: _markerImage, 64 }); 65 }; 66 // TODO: get out source from here when base layer selection will be implemented 67 sitnLayers.setSource = function (layerName) { 68 $.ajax(_WMTSurl).then((response) => { 69 const _parser = new ol.format.WMTSCapabilities(); 70 const _result = _parser.read(response); 71 sitnLayers.WMTSOptions = ol.source.WMTS.optionsFromCapabilities( 72 _result, { 73 layer: layerName, 74 matrixSet: _crs, 75 }, 76 ); 77 const source = new ol.source.WMTS(sitnLayers.WMTSOptions); 78 sitnLayers.sitnCurrentBaseLayer.setSource(source); 79 }); 80 }; 81 82 sitnLayers.view = new ol.View({ 83 projection: _projection, 84 center: ol.proj.fromLonLat([6.80, 47.05], _projection), 85 zoom: 4, 86 }); 87 88 /** 89 * Toogles multiple geometry mode, if it's set to simple geometry 90 * and multiple geometries were already drawn -> only keeps the last drawn geometry 91 */ 92 sitnLayers.setSimpleGeom = function (value) { 93 _drawSimpleGeom = value; 94 if (_drawSimpleGeom && _drawSource.getFeatures().length > 1) { 95 const lastFeature = _drawSource.getFeatures().slice(-1)[0]; 96 _drawSource.clear(); 97 _drawSource.addFeature(lastFeature); 98 } 99 }; 100 101 /** 102 * Sets color and width to draw layer 103 */ 104 sitnLayers.setDrawStyle = function (options) { 105 _drawColor = ol.color.asArray(options.drawColor || _drawColor); 106 _drawWidth = options.drawWidth || _drawWidth; 107 _pointStyle = options.pointStyle || _pointStyle; 108 const _drawFillColor = [ 109 _drawColor[0], 110 _drawColor[1], 111 _drawColor[2], 112 _drawColor[3] / 2, 113 ]; 114 115 let _imageStyle; 116 if (_pointStyle === 'marker') { 117 _imageStyle = this._createMarkerStyle(_drawColor); 118 } else { 119 _imageStyle = new ol.style.Circle({ 120 fill: new ol.style.Fill({ color: _drawFillColor }), 121 stroke: new ol.style.Stroke({ color: _drawColor, width: _drawWidth / 2 }), 122 radius: 5, 123 }); 124 } 125 sitnLayers.sitnDrawLayer.setStyle(new ol.style.Style({ 126 fill: new ol.style.Fill({ color: _drawFillColor }), 127 stroke: new ol.style.Stroke({ color: _drawColor, width: _drawWidth }), 128 image: _imageStyle, 129 })); 130 }; 131 132 sitnLayers.createMap = function (options) { 133 _buttons = options.buttons; 134 _baselayers = options.baseLayers; 135 _wmslayers = options.wmslayers; 136 _target = options.target; 137 _selectTarget = options.selectTarget; 138 _drawSimpleGeom = options.drawSimpleGeom; // controls wether or not an user can draw multiple geometries 139 _drawColor = options.drawColor || _drawColor; 140 _drawWidth = options.drawWidth || _drawWidth; 141 _pointStyle = options.pointStyle || _pointStyle; 142 _markerImage = options.markerImage || _markerImage; 143 _searchColor = options.searchColor || _searchColor; 144 _minZoom = options.minZoom || _minZoom; 145 _maxZoom = options.maxZoom || _maxZoom; 146 _drawFillColor; 147 let _mainbar; 148 sitnLayers.setDrawStyle({}); 149 150 if (_wmslayers) {
151 _wmslayers.forEach((layername) => { 152 _sitnWMSLayers[layername] = new ol.layer.Image({ 153 extent: _extent, 154 source: new ol.source.ImageWMS({ 155 url: _WMSurl, 156 params: { LAYERS: layername }, 157 serverType: 'mapserver', 158 }), 159 }); 160 }); 161 } 162 _map = new ol.Map({ 163 target: _target, 164 layers: [ 165 sitnLayers.sitnCurrentBaseLayer, 166 ...Object.values(_sitnWMSLayers), // insert array of wms layers 167 sitnLayers.sitnDrawLayer, 168 new ol.layer.Vector({ source: _markerSource }), 169 ], 170 view: sitnLayers.view, 171 }); 172 sitnLayers.view.setZoom(options.minZoom || 4); 173 sitnLayers.view.setMinZoom(_minZoom); 174 sitnLayers.view.setMaxZoom(_maxZoom); 175 176 /** 177 * Creates select options, sets first baseLayer as default 178 * or sets plan_ville if no _baselayers are defined 179 */ 180 if (_baselayers) { 181 $.each(_baselayers, (key, value) => { 182 $(`#${_selectTarget}`) 183 .append(new Option(_sitnBaseLayers[value], value)); 184 }); 185 $(`#${_selectTarget}`).change(() => { 186 const newSource = $(`#${_selectTarget} option:selected`).val(); 187 sitnLayers.setSource(newSource); 188 }); 189 this.setSource(_baselayers[0]); 190 } else { 191 this.setSource('plan_ville'); 192 } 193 194 /** 195 * Will set up button bar with editing activated by default 196 */ 197 if (_buttons) { 198 _mainbar = new ol.control.Bar(); 199 _map.addControl(_mainbar); 200 _mainbar.setPosition('top-left'); 201 const editbar = new ol.control.Bar({ 202 toggleOne: true, // one control active at the same time 203 group: false, // group controls together 204 }); 205 _mainbar.addControl(editbar); 206 207 // Edit control bar 208 if (_buttons.indexOf('edit') !== -1) { 209 // Add selection tool: 210 // 1- a toggle control with a select interaction 211 // 2- an option bar to delete / get information on the selected feature 212 const sbar = new ol.control.Bar(); 213 const selectCtrl = new ol.control.Toggle({ 214 html: '<small class="fas fa-mouse-pointer"></small>', 215 title: 'Select', 216 interaction: new ol.interaction.Select(), 217 bar: sbar, 218 autoActivate: true, 219 active: true, 220 }); 221 222 sbar.addControl(new ol.control.TextButton({ 223 html: '<i class="fas fa-trash"></i>', 224 title: 'Effacer', 225 handleClick() { 226 const features = selectCtrl.getInteraction().getFeatures(); 227 // if (!features.getLength()) console.log('Select an object first...'); 228 // else console.log(`${features.getLength()} object(s) deleted.`); 229 let i = 0; 230 do { 231 _drawSource.removeFeature(features.item(i)); 232 i += 1; 233 } while (features.item(i)); 234 selectCtrl.getInteraction().getFeatures().clear(); 235 }, 236 })); 237 238 editbar.addControl(selectCtrl); 239 } 240 241 // Add editing tools 242 if (_buttons.indexOf('createPoint') !== -1) { 243 let active = true; 244 if (_buttons.indexOf('edit') !== -1) { 245 active = false; 246 } 247 const pedit = new ol.control.Toggle({ 248 html: '<small class="fas fa-map-pin"></small>', 249 title: 'Point', 250 active, 251 interaction: new ol.interaction.Draw({ 252 type: 'Point', 253 source: _drawSource, 254 }), 255 }); 256 editbar.addControl(pedit); 257 } 258 if (_buttons.indexOf('createLineString') !== -1) { 259 const ledit = new ol.control.Toggle( 260 { 261 html: '<small class="fas fa-check fa-rotate-270 fa-flip-vertical"></small>', 262 title: 'LineString', 263 interaction: new ol.interaction.Draw({ 264 type: 'LineString', 265 source: _drawSource, 266 // Count inserted points 267 geometryFunction(coordinates, geometry) { 268 let newGeom; 269 if (geometry) {
270 geometry.setCoordinates(coordinates); 271 newGeom = geometry; 272 } else newGeom = new ol.geom.LineString(coordinates); 273 this.nbpts = newGeom.getCoordinates().length; 274 return newGeom; 275 }, 276 }), 277 // Options bar associated with the control 278 bar: new ol.control.Bar( 279 { 280 controls: [new ol.control.TextButton({ 281 html: '<i class="fas fa-undo"></i>', 282 title: 'Revenir en arrière', 283 handleClick() { 284 if (ledit.getInteraction().nbpts > 1) ledit.getInteraction().removeLastPoint(); 285 }, 286 }), 287 new ol.control.TextButton({ 288 html: '<i class="fas fa-check"></i>', 289 title: 'Terminer la construction', 290 handleClick() { // Prevent null objects on finishDrawing 291 if (ledit.getInteraction().nbpts > 2) ledit.getInteraction().finishDrawing(); 292 }, 293 }), 294 ], 295 }, 296 ), 297 }, 298 ); 299 editbar.addControl(ledit); 300 } 301 302 if (_buttons.indexOf('createPolygon') !== -1) { 303 const fedit = new ol.control.Toggle( 304 { 305 html: '<small class="fas fa-play fa-rotate-270"></small>', 306 title: 'Polygon', 307 interaction: new ol.interaction.Draw({ 308 type: 'Polygon', 309 source: _drawSource, 310 // Count inserted points 311 geometryFunction(coordinates, geometry) { 312 this.nbpts = coordinates[0].length; 313 let newGeom = geometry; 314 if (geometry) { 315 newGeom.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); 316 } else newGeom = new ol.geom.Polygon(coordinates); 317 return newGeom; 318 }, 319 }), 320 // Options bar associated with the control 321 bar: new ol.control.Bar( 322 { 323 controls: [new ol.control.TextButton({ 324 html: '<i class="fas fa-undo"></i>', 325 title: 'Revenir en arrière', 326 handleClick() { 327 if (fedit.getInteraction().nbpts > 1) fedit.getInteraction().removeLastPoint(); 328 }, 329 }), 330 new ol.control.TextButton({ 331 html: '<i class="fas fa-check"></i>', 332 title: 'Terminer la construction', 333 handleClick() { // Prevent null objects on finishDrawing 334 if (fedit.getInteraction().nbpts > 3) fedit.getInteraction().finishDrawing(); 335 }, 336 }), 337 ], 338 }, 339 ), 340 }, 341 ); 342 editbar.addControl(fedit); 343 } 344 345 // If this option is enabled, then the user can only draw simple geometries 346 _drawSource.on('addfeature', (event) => { 347 if (_drawSimpleGeom && _drawSource.getFeatures().length > 1) { 348 _drawSource.clear(); 349 _drawSource.addFeature(event.feature); 350 } 351 }); 352 } 353 }; 354 355 sitnLayers.getLayerByName = function (layerName) { 356 return _sitnWMSLayers[layerName]; 357 }; 358 359 /** 360 * Loads WKT and adds it to _drawsource 361 * @param {String} wkt 362 */ 363 sitnLayers.loadWKT = function (wkt) { 364 const format = new ol.format.WKT({ 365 splitCollection: true, 366 }); 367 const data = wkt; 368 const features = format.readFeatures(data, { 369 dataProjection: _crs, 370 featureProjection: _crs, 371 }); 372 _drawSource.addFeatures(features); 373 }; 374 375 /** 376 * remove WKT 377 * 378 */ 379 sitnLayers.removeWKT = function () { 380 _drawSource.clear(); 381 }; 382 383 /** 384 * Exports _drawSource as WKT 385 */ 386 sitnLayers.getWKTData = function () { 387 const features = _drawSource.getFeatures(); 388 if (features) { 389 const wktData = new ol.format.WKT().writeFeatures(features); 390 return wktData; 391 } 392 return ''; 393 }; 394 395 /** 396 * Exports _drawsource as GeoJSON 397 */ 398 sitnLayers.getGeoJSONData = function () { 399 const features = _drawSource.getFeatures(); 400 if (features) { 401 const jsonData = new ol.format.GeoJSON().writeFeatures(features); 402 return jsonData; 403 } 404 return ''; 405 }; 406 407 /** 408 * Adds a marker based on coordinates: an array of 2 numbers 409 * and clears another existing maker before. The color is optional 410 */ 411 sitnLayers.addMarker = function (coordinates, color) { 412 _markerSource.clear(); 413 const marker = new ol.Feature({ 414 geometry: new ol.geom.Point(coordinates), 415 }); 416 marker.setStyle(new ol.style.Style({ 417 image: this._createMarkerStyle(color || _markerColor), 418 })); 419 420 _markerSource.addFeature(marker); 421 422 423 424 425 }; 426 427 /** 428 * Updates marker position based 429 * on coordinates: an array of 2 numbers 430 */ 431 sitnLayers.updateMarker = function (coordinates) { 432 sitnLayers.addMarker(coordinates); 433 }; 434 435 /** 436 * Removes marker 437 */ 438 sitnLayers.removeMarker = function () { 439 _markerSource.clear(); 440 }; 441 442 /** 443 * Exports _drawSource as WKT 444 */ 445 sitnLayers.refresh = function () { 446 _map.updateSize(); 447 }; 448 449 /** 450 * Exports _drawSource as WKT 451 */ 452 sitnLayers.addOverlay = function (o) { 453 _map.addOverlay(o); 454 }; 455 /** 456 * Recenters the map based 457 * on coordinates: an array of 2 numbers 458 */ 459 sitnLayers.recenterMap = function (coordinates, zoomLevel) { 460 if (_map) { 461 const view = _map.getView(); 462 view.setCenter(coordinates); 463 view.setZoom(zoomLevel); 464 } 465 }; 466 467 /** 468 * Gets center point of the extent an optionnaly recenters de map 469 */ 470 sitnLayers.getCenterPoint = function (recenter) { 471 let center = [0, 0]; 472 let extent; 473 if (_drawSource.getFeatures().length > 0) { 474 extent = _drawSource.getExtent(); 475 center = ol.extent.getCenter(extent); 476 } else if (_drawSource.getFeatures.length === 0 && _markerSource.getFeatures().length > 0) { 477 extent = _markerSource.getExtent(); 478 center = ol.extent.getCenter(_markerSource.getExtent()); 479 } 480 if (recenter && center[0] !== 0) { 481 if (Math.round(extent[0]) === Math.round(extent[2])) { 482 sitnLayers.recenterMap(center, 14); 483 } else { 484 _map.getView().fit(extent); 485 } 486 } 487 return center; 488 }; 489 490 sitnLayers.searchBox = function (config) { 491 let inputclasses; 492 let resultclasses; 493 let headerclasses; 494 let itemclasses; 495 const searchLayer = new ol.layer.Vector({ 496 source: new ol.source.Vector(), 497 style: new ol.style.Style({ 498 fill: new ol.style.Fill({ color: 'yellow' }), 499 stroke: new ol.style.Stroke({ color: _searchColor, width: 8 }), 500 image: new ol.style.RegularShape({ 501 fill: new ol.style.Fill({ color: 'yellow' }), 502 stroke: new ol.style.Stroke({ color: _searchColor, width: 4 }), 503 points: 4, 504 radius: 10, 505 radius2: 0, 506 angle: Math.PI / 4, 507 }), 508 }), 509 opacity: 0.5, 510 }); 511 512 if (_map) { 513 _map.addLayer(searchLayer); 514 } 515 let width = 'auto'; 516 517 if (config.width) { 518 width = config.width; 519 } 520 521 if (config) { 522 inputclasses = config.inputclasses || ['']; 523 resultclasses = config.resultclasses || ['sitn-search-result']; 524 headerclasses = config.headerclasses || ['']; 525 itemclasses = config.itemclasses || ['']; 526 } 527 528 // alias list for category names 529 const catClean = { 530 neophytes: 'Plantes invasives', 531 search_satac: 'N° SATAC', 532 search_entree_sortie: 'Entrée/sortie autoroute', 533 rt16_giratoires: 'Giratoires', 534 batiments_ofs: 'Bâtiments regBL et n° egid', 535 axe_mistra: 'Routes et axes', 536 search_arrets_tp: 'Arrêts transports publics', 537 ImmeublesCantonHistorique: 'Biens-fonds historiques', 538 point_interet: "Points d'intérêt", 539 axe_rue: 'Axes et rues', 540 nom_local_lieu_dit: 'Noms locaux et lieux-dits', 541 search_cours_eau: "Cours d'eau", 542 ImmeublesCanton: 'Biens-fonds', 543 search_fo_administrations: 'Administrations forestières', 544 search_uap_publique: "Unité d'aménagement publique", 545 adresses_sitn: 'Adresses', 546 localite: 'Localité', 547 search_fo09: 'Secours en forêt', 548 search_conc_hydr: 'Concessions hydrauliques', 549 }; 550
551 // create required divs 552 document.getElementById(config.target).innerHTML = `<input id=placeInput type=text class="${ 553 inputclasses.join(' ')}"/>` 554 + `<ol id="selectable" class=" ${resultclasses.join(' ') 555 }"></ol>`; 556 $(`#${config.target}`).width(width); 557 558 // start setting up events 559 $('#placeInput').val('Recherche un lieu ou un objet géographique'); 560 561 $('#placeInput').focusin(() => { 562 $('#placeInput').val(''); 563 searchLayer.getSource().clear(); 564 }); 565 566 $('#placeInput').focusout(() => { 567 $('#selectable').hide(); 568 }); 569 570 $('#selectable').hide(); 571 // stop setting up events 572 573 // start get data from server 574 let inputTerm = ''; 575 let recCopy = ''; 576 577 $('#placeInput').keyup(() => { 578 inputTerm = $('#placeInput').val(); 579 if (inputTerm.length > 2) { 580 $.ajax({ 581 url: 'https://sitn.ne.ch/search', 582 crossDomain: true, 583 data: { 584 limit: 20, 585 query: inputTerm, 586 }, 587 success(rec) { 588 // get the results 589 if (rec.features.length === 0) { 590 document.getElementById('selectable').innerHTML = 'Aucun résultat'; 591 $('#selectable').show(); 592 return; 593 } 594 // process results: group by categories 595 const itLength = rec.features.length; 596 const cat = []; 597 // get categories 598 for (let i = 0; i < itLength; i++) { 599 cat.push(rec.features[i].properties.layer_name); 600 } 601 // sort categories in ascending order and keep only unique 602 cat.sort(); 603 const catUnique = []; 604 catUnique.push(cat[0]); 605 for (let i = 1; i < cat.length; i++) { 606 if (cat[i] !== cat[i - 1]) { 607 catUnique.push(cat[i]); 608 } 609 } 610 // group the features by categories and fill the ordered list used for place selection 611 recCopy = $.extend(true, {}, rec); 612 let index = 0; 613 let listItems = ''; 614 let catTest; let 615 catName; 616 for (let i = 0; i < catUnique.length; i++) { 617 catTest = catUnique[i]; 618 $.each(catClean, (key, val) => { 619 if (catTest === key) { 620 catName = val; 621 } 622 }); 623 listItems += `<div class="${headerclasses.join(' ')}"><b>${catName}</b></div>`; 624 for (let j = 0; j < itLength; j++) { 625 if (catUnique[i].toUpperCase() === rec.features[j].properties.layer_name.toUpperCase()) { 626 recCopy.features[index] = rec.features[j]; 627 listItems += `<li class="${itemclasses.join(' ')}">${ 628 rec.features[j].properties.label}</li>`; 629 index += 1; 630 } 631 } 632 } 633 document.getElementById('selectable').innerHTML = listItems; 634 $('#selectable').show(); 635 }, 636 error() { 637 $('#placeInput').val('Ãchec de la recherche'); 638 }, 639 }); 640 } else if (!inputTerm) { 641 searchLayer.getSource().clear(); 642 } 643 }); 644 // stop get data from server 645 646 // fill selectable list and zoom to selected feature 647 $(() => { 648 $('#selectable').selectable({ 649 stop() { 650 const result = $('#select-result').empty(); 651 $('.ui-selected', this).each(function () { 652 const index = $('#selectable li').index(this); 653 if (index !== -1) { 654 $('#placeInput').val(recCopy.features[index].properties.label); 655 if (_map) { 656 const geojsonFormat = new ol.format.GeoJSON(); 657 searchLayer.getSource().clear(); 658 const nGeom = recCopy.features[index].geometry.coordinates.length; 659 // geometry is not empty 660 if (nGeom !== 0) { 661 searchLayer.getSource().addFeatures(geojsonFormat.readFeatures(recCopy.features[index])); 662 } else if (recCopy.features[index].bbox.length > 1) { // geometry is empty 663 const point = new ol.geom.Point(recCopy.features[index].bbox[0], recCopy.features[index].bbox[1]); 664 const pointFeature = new ol.Feature(point); 665 searchLayer.addFeatures(pointFeature); 666 } 667 if (recCopy.features[index].geometry.type === 'Point') { 668 _map.getView().fit(recCopy.features[index].bbox); 669 _map.getView().setZoom(12); 670 } else { 671 _map.getView().fit(recCopy.features[index].bbox); 672 } 673 } 674 result.append(` #${index + 1}`); 675 $('#selectable').hide(); 676 } 677 }); 678 }, 679 }); 680 }); 681 682 const css = ' \n' 683 + '.sitn-search-result { \n' 684 + ' position: absolute; \n' 685 + ' top: 100%; \n' 686 + ' left: 0; \n' 687 + ' z-index: 1000; \n' 688 + ' float: left; \n' 689 + ' min-width: 10rem; \n' 690 + ' padding: .5rem 0; \n' 691 + ' margin: .125rem 0 0; \n' 692 + ' text-align: left; \n' 693 + ' list-style: none; \n' 694 + ' background-color: #fff; \n' 695 + ' background-clip: padding-box; \n' 696 + '}'; 697 const head = document.head || document.getElementsByTagName('head')[0]; 698 const style = document.createElement('style'); 699 700 head.appendChild(style); 701 702 style.type = 'text/css'; 703 style.appendChild(document.createTextNode(css)); 704 }; 705 return sitnLayers; 706 } 707 708 window.SitnMap = sitnLayers; 709}(window));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.