UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

219 lines (218 loc) 9.45 kB
import DragAndDrop from 'ol/interaction/DragAndDrop.js'; import { GPX, GeoJSON, IGC, KML, TopoJSON } from 'ol/format.js'; import { Vector as VectorLayer } from 'ol/layer.js'; import { Vector as VectorSource } from 'ol/source.js'; import LayerLocalFile from '../../../models/layers/layerlocalfile'; import GroupLayer from '../../../models/layers/grouplayer'; import StateManager from '../../../tools/state/statemanager'; import { extend, intersects } from 'ol/extent'; import I18nManager from '../../../tools/i18n/i18nmanager'; import UserInteractionManager from '../../../tools/state/userInteractionManager'; import { v4 as uuidv4 } from 'uuid'; class LocalFileManager { constructor(map) { Object.defineProperty(this, "map", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "supportedFileFormats", { enumerable: true, configurable: true, writable: true, value: [GPX, GeoJSON, IGC, new KML({ extractStyles: true }), TopoJSON] }); Object.defineProperty(this, "supportedFileExtensions", { enumerable: true, configurable: true, writable: true, value: ['gpx', 'geojson', 'igc', 'kml', 'topojson', 'json'] }); Object.defineProperty(this, "stateManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "i18nManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "userInteractionManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "activeLayers", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "layerGroup", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "layerGroupProxy", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.map = map; this.name = `localFileManager-${uuidv4()}`; this.stateManager = StateManager.getInstance(); this.i18nManager = I18nManager.getInstance(); this.userInteractionManager = UserInteractionManager.getInstance(); this.registerEvents(); // Add drag n drop interaction to add local files const dragAndDropInteraction = this.createInteraction(); this.map.addInteraction(dragAndDropInteraction); } registerEvents() { this.userInteractionManager.registerListener('map.drop', false, this.name); this.stateManager.subscribe('layers.layersList', (oldLayers, newLayers) => { if (oldLayers?.includes(this.layerGroup) && !newLayers?.includes(this.layerGroup)) { // Group was deleted in tree, cleanup references delete this.layerGroupProxy; delete this.layerGroup; } }); } createInteraction() { // Handle dropping of unsupported files this.map.getViewport().addEventListener('drop', (event) => this.handleUnsupportedFiles(event)); const dragAndDropInteraction = new DragAndDrop({ // @ts-expect-error ol Format types formatConstructors: this.supportedFileFormats }); dragAndDropInteraction.on('addfeatures', (e) => { if (!this.userInteractionManager.canListenerExecute('map.drop', this.name)) return; if (!this.layerGroup) { this.layerGroup = new GroupLayer(0, 'Local Files', 0, { isDefaultChecked: true, isDefaultExpanded: true }); } // Check if all features can be displayed in the current map maximum extent // This will also approximately validate if the SRID is correct const featureType = e.file.name.replace('.', '_'); const acceptableFeatures = this.validateAndCompleteFeatures(featureType, e.features); // Create Layer const layer = new LayerLocalFile(e.file, acceptableFeatures.features, acceptableFeatures.globalExtent); layer.parent = this.layerGroup; if (e.features && e.features.length > acceptableFeatures.features.length) { // Some features are outer extent layer.hasError = true; layer.errorMessage = `Only ${acceptableFeatures.features.length} features among ${e.features.length} could be loaded. Verify that those features can be displayed within the maximal extent configured in your application.`; } if (this.layerGroupProxy) { // If group is already in the treeview, add layer to the tree directly this.layerGroupProxy.children.push(layer); } else { // Otherwise, add the layer to the group this.layerGroup.children.push(layer); // then add the group to the treeview this.stateManager.state.layers.layersList.push(this.layerGroup); // Save a reference to the proxy object in the tree for next time a file is added this.layerGroupProxy = this.stateManager.state.layers.layersList.find((l) => l.treeItemId === this.layerGroup.treeItemId); } }); return dragAndDropInteraction; } handleUnsupportedFiles(dropEvent) { if (!this.userInteractionManager.canListenerExecute('map.drop', this.name)) return; const files = dropEvent.dataTransfer?.files; if (!files?.length) { return; } const unsupportedFiles = Array.from(files).filter((file) => !this.supportedFileExtensions.includes(file.name.split('.').slice(-1)[0].toLowerCase())); if (!unsupportedFiles?.length) { return; } let msg; if (unsupportedFiles.length > 1) { msg = this.i18nManager.getTranslation('Files _fileNames_ are not supported'); msg = msg.replace('_fileNames_', unsupportedFiles.map((f) => `"${f.name}"`).join(', ')); } else { msg = this.i18nManager.getTranslation('File _fileName_ is not supported'); msg = msg.replace('_fileName_', `"${unsupportedFiles[0].name}"`); } void window.gAlert(msg, 'Unsupported file format'); } validateAndCompleteFeatures(featureType, features) { const validatedFeatures = []; const maxExtent = this.map.getView().get('extent'); let counter = 0; let globalExtent = null; for (const feature of features) { const geometry = feature.getGeometry(); if (geometry) { const featureExtent = geometry.getExtent(); if (intersects(maxExtent, featureExtent)) { // Set an id that looks like ids from WFS qureries in order to make the usage easy in the others components that understand WFS Features feature.setId(`${featureType}.${++counter}`); validatedFeatures.push(feature); globalExtent = globalExtent == null ? [...featureExtent] : extend(globalExtent, featureExtent); } } } return { features: validatedFeatures, globalExtent: globalExtent }; } addLayer(layerFile) { const vectorSource = new VectorSource({ features: layerFile._features }); const olayer = new VectorLayer({ source: vectorSource }); this.map.addLayer(olayer); this.activeLayers[layerFile.treeItemId] = { layerFile: layerFile, olayer: olayer }; } getLayer(layerFile) { if (this.layerExists(layerFile)) { return this.activeLayers[layerFile.treeItemId].olayer; } return null; } removeLayer(layerFile) { if (this.layerExists(layerFile)) { const olayer = this.activeLayers[layerFile.treeItemId].olayer; delete this.activeLayers[layerFile.treeItemId]; this.map.removeLayer(olayer); } else { throw new Error('Cannot remove this layer: it does not exist'); } } layerExists(layer) { return layer.treeItemId in this.activeLayers; } selectFeatures(extent) { for (const activeLayer of Object.values(this.activeLayers)) { const features = activeLayer.olayer.getSource()?.getFeaturesInExtent(extent); if (features && features.length > 0) { this.stateManager.state.selection.selectedFeatures.push(...features); this.stateManager.state.interface.selectionComponentVisible = true; } } } } export default LocalFileManager;