@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
171 lines (170 loc) • 7.64 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import GirafeSingleton from '../../base/GirafeSingleton.js';
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.js';
import { extend, intersects } from 'ol/extent.js';
import { applyFeaturesToSelection } from '../utils/utils.js';
class LocalFileManager extends GirafeSingleton {
map;
name;
supportedFileFormats = [GPX, GeoJSON, IGC, new KML({ extractStyles: true }), TopoJSON];
supportedFileExtensions = ['gpx', 'geojson', 'igc', 'kml', 'topojson', 'json'];
activeLayers = {};
constructor(context) {
super(context);
this.map = this.context.mapManager.getMap();
this.name = `localFileManager`;
}
initializeSingleton() {
this.registerEvents();
// Add drag n drop interaction to add local files
const dragAndDropInteraction = this.createInteraction();
this.map.addInteraction(dragAndDropInteraction);
}
registerEvents() {
this.context.userInteractionManager.registerListener('map.drop', false, this.name);
}
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.context.userInteractionManager.canListenerExecute('map.drop', this.name))
return;
this.loadLocalFileFeatures(e.file, e.features);
});
return dragAndDropInteraction;
}
async loadLocalFile(localFile) {
const text = await localFile.text();
let reader;
if (text.includes('<kml') && text.includes('</kml>')) {
reader = new KML({ extractStyles: true });
}
else if (text.includes('<gpx') && text.includes('</gpx>')) {
reader = new GPX();
}
else if (text.startsWith('{') && text.endsWith('}')) {
reader = new GeoJSON();
}
else {
// dot nothing - shall we report an error ??
return;
}
const features = reader.readFeatures(text, { featureProjection: this.context.stateManager.state.projection });
this.loadLocalFileFeatures(localFile, features);
}
loadLocalFileFeatures(localFile, features) {
// 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 = localFile.name.replace('.', '_');
const acceptableFeatures = this.validateAndCompleteFeatures(featureType, features);
// Create Layer
if (acceptableFeatures.globalExtent === null) {
const title = this.context.i18nManager.getTranslation('No features within map extent');
const msg = this.context.i18nManager.getTranslation('No features where found in your file that could be displayed within the maximal extent configured in your application.');
window.gAlert(msg, title);
return;
}
const layer = new LayerLocalFile(localFile, acceptableFeatures.features, acceptableFeatures.globalExtent, this.context.configManager.Config.general.locale);
if (features && features.length > acceptableFeatures.features.length) {
// Some features are outer extent
layer.hasError = true;
layer.errorMessage = `Only ${acceptableFeatures.features.length} features among ${features.length} could be loaded.
Verify that those features can be displayed within the maximal extent configured in your application.`;
}
this.context.userLayerManager.addUserLayerToTree(layer);
}
handleUnsupportedFiles(dropEvent) {
if (!this.context.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('.').at(-1).toLowerCase()));
if (!unsupportedFiles?.length) {
return;
}
let msg;
if (unsupportedFiles.length > 1) {
msg = this.context.i18nManager.getTranslation('Files _fileNames_ are not supported');
msg = msg.replace('_fileNames_', unsupportedFiles.map((f) => `"${f.name}"`).join(', '));
}
else {
msg = this.context.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) {
applyFeaturesToSelection(features, this.context.stateManager.state);
}
}
}
changeOpacity(layer) {
const oLayer = this.activeLayers[layer.treeItemId].olayer;
oLayer.setOpacity(layer.opacity);
}
}
export default LocalFileManager;