@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
219 lines (218 loc) • 9.45 kB
JavaScript
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;