/**
* @desc A mixin object containing methods related to editing objects on the map.
* @module MapEditingMixin
*/
import { Draw, Modify, Select, Snap } from "ol/interaction";
import { GeoJSON } from "ol/format";
import { noModifierKeys, primaryAction } from 'ol/events/condition';
import { getArea, getLength } from 'ol/sphere';
import Feature from "ol/Feature";
import { LineString, Point } from "ol/geom";
import { fromLonLat } from "ol/proj";
import { tiHelp } from "@quasar/extras/themify";
export const MapEditingMixin = {
methods: {
/**
* Reads custom features from the server and adds them to the editable source.
* @returns {Promise<void>} A promise that resolves when the features are added.
*/
async readCustomFeatures(id) {
if (this.$store.userData == null) return;
let ret = await this.get("User/GetCustomGeometry", {
id: id
});
this.editableSource.clear();
if (ret && ret.features) {
let features = this.format.readFeatures(ret);
this.reprojectFeatures(features, this.projectionForSave, this.projection);
this.editableSource.addFeatures(features);
}
},
/**
* Clears the interactions on the map.
*/
clearInteractions() {
this.map.removeInteraction(this.draw);
},
/**
* Saves the geometry of a feature by sending it to the server.
* @param {feature} feature - The feature to be saved.
* @param {format} format - The format used to serialize the feature.
* @param {projection} projection - The projection of the feature's geometry.
* @param {projection} projectionForSave - The projection to transform the feature's geometry to before saving.
* @returns {Promise<void>} A promise that resolves when the feature is successfully saved.
*/
async saveFeature(feature, format, projection, projectionForSave) {
let clonedFeature = feature.clone();
if (format) {
clonedFeature.getGeometry().transform(projection, projectionForSave);
}
format = format ?? new GeoJSON();
let gj = format.writeFeatureObject(clonedFeature);
let id = this.toStringIfNotNull(feature.get("custom_geometry_id"));
let file_id = this.toStringIfNotNull(feature.get("file_id"));
await this.post("User/UpdateCustomGeometry", {
id: id,
file_id: file_id,
feature: JSON.stringify(gj)
});
},
/**
* Formats the length of a line.
* @param {LineString} line - The line to calculate the length for.
* @returns {string} The formatted length with unit (meters or kilometers).
*/
formatLength (line) {
const length = getLength(line);
let output;
if (length > 100) {
output = Math.round((length / 1000) * 100) / 100 + ' km';
} else {
output = Math.round(length * 100) / 100 + ' m';
}
return output;
},
/**
* Calculates the area of a polygon and formats it as a string.
* If the area is greater than 10000, it is formatted in square kilometers (km²).
* Otherwise, it is formatted in square meters (m²).
* @param {Polygon} polygon - The polygon for which to calculate the area.
* @returns {string} The formatted area string.
*/
formatArea (polygon) {
const area = getArea(polygon);
let output;
if (area > 10000) {
output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2';
} else {
output = Math.round(area * 100) / 100 + ' m\xB2';
}
return output;
},
/**
* Gets the area of a given feature.
* Only works for features with a Polygon geometry type.
*
* @param {feature} feature.
*/
getFeatureArea(feature) {
if (feature.getGeometry().getType() == "Polygon") {
return getArea(feature.getGeometry()).toFixed(0);
} else {
return null;
}
},
/**
* Creates a new object for editing on the map.
*
* @param {Object} options - The options for creating the new object.
* @param {string} options.type - The type of object to create.
* @param {number} options.geometry_type_id - The ID of the geometry type.
*/
newObject(options) {
let actionTypes = {
'Point': 1,
'LineString': 3,
'Polygon': 2
};
this.showEditableSource = true;
this.clearInteractions();
this.draw = new Draw({
type: options.type,
source: this.editableSource,
stopClick: true,
condition: (e) => noModifierKeys(e) && primaryAction(e),
style: this.selectedStyleFunction,
});
this.map.addInteraction(this.draw);
//this.map.addInteraction(this.select);
this.activeAction = options;
this.draw.on('drawend', async (e) => {
e.feature.set("geometry_type_id", actionTypes[this.activeAction.type]);
e.feature.set("props", []);
this.clearInteractions();
this.selectedFeature = e.feature;
this.draw = null;
this.editProps();
});
// this.$mitt.on('escKeyPressed', () => {
// this.endObject();
// });
},
/**
* Ends the current object editing mode.
*/
endObject() {
// this.$mitt.off('escKeyPressed');
this.clearInteractions();
this.activeAction = null;
if (this.select) {
this.select.getFeatures().clear();
this.selectCount = 0;
}
// this.$refs.contextmenu.hide();
},
/**
* Deletes the selected object.
* @async
* @function deleteObject
* @returns {Promise<void>}
*/
async deleteObject() {
if (await this.confirmDialog(this.$t("Delete selected object?"))) {
await this.delete("User/DeleteCustomGeometry/" + this.selectedFeature.get("custom_geometry_id").toString());
this.editableSource.removeFeature(this.selectedFeature);
this.selectedFeature = null;
};
},
filesUrl() {
return [{
url: this.axios.API.defaults.baseURL + "User/GetFile/"
+ this.selectedFeature.get("custom_geometry_id") + "/"
+ this.selectedFeature.get("file_id") + "/"
+ this.selectedFeature.get("extension"),
compass: this.selectedFeature.get("compass"),
file_id : this.selectedFeature.get("file_id"),
}];
},
compassChanged(compass, file_id) {
this.selectedFeature.set("compass", compass);
let features = this.editableLayer.getSource().getFeatures(); // Save the current features
this.editableLayer.changed();
this.editableLayer.getSource().refresh();
this.editableLayer.getSource().addFeatures(features);
},
/**
* Opens the property editor or the image viewer for the selected feature.
* @param {Object} options - The options for editing the properties.
*/
editProps(options) {
if (this.selectedFeature) {
this.infoTip.style.visibility = 'hidden';
if (this.selectedFeature.get("extension") == ".jpg") {
this.$refs.imageViewerMap.show(this.filesUrl(), 0, true);
} else {
this.$store.popup.component = "ol-map-props";
this.$store.popup.props = {
custom_geometry_id : this.selectedFeature.get("custom_geometry_id"),
geometry_type : this.selectedFeature.getGeometry().getType(),
title : this.$t("Location"),
parent : this,
feature : this.selectedFeature,
help: "Edit properties",
persistent: true
};
this.$store.popup.show = true;
}
}
},
addFileFeature(file) {
console.log("addFileFeature", file);
let point = new Point(fromLonLat([file.lon, file.lat], this.projection));
console.log("addFileFeature", point);
let feature = new Feature({
id: 'f' + file.id,
geometry: point,
custom_geometry_id: file.custom_geometry_id,
file_id: file.id,
extension: file.extension,
compass: file.compass,
name: file.name
});
feature.setId('f' + file.id);
console.log("addFileFeature", feature);
this.editableSource.addFeature(feature);
}
},
}