diff --git a/config/locales/de.yml b/config/locales/de.yml index 9795c66..92ffb4c 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -72,6 +72,7 @@ de: linestring: Linieneditor polygon: Flächeneditor clear_map: "Clear map" + remove_point: "Remove point" geolocation_activated: Geolokalisierung aktiviert geolocation_deactivated: Geolokalisierung deaktiviert copied_location_to_clipboard: Standort in die Zwischenablage kopiert @@ -81,6 +82,9 @@ de: messages: baselayer_missing: "Es ist kein Baselayer verfügbar!" zoom_in_more: "Zoomen Sie hinein, um die Objekte zu sehen." + modify_start: "Hold down ALT and click on a vertex to delete it." + modify_start_mac: "Hold down Option and click on a vertex to delete it." + modify_start_touch: "Tap on a vertex to delete it." gtt_map_rotate_label: Kartenrotation gtt_map_rotate_info_html: Halten Sie Shift+Alt gedrückt und ziehen Sie die Karte, um sie zu drehen. diff --git a/config/locales/en.yml b/config/locales/en.yml index ae97bef..fe22c99 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -123,6 +123,7 @@ en: linestring: "Line editor" polygon: "Area editor" clear_map: "Clear map" + remove_point: "Remove point" copied_location_to_clipboard: "Copied location to clipboard" modal: load: "Load" @@ -130,3 +131,6 @@ en: messages: baselayer_missing: "There is no baselayer available!" zoom_in_more: "Zoom in to view objects." + modify_start: "Hold down ALT and click on a vertex to delete it." + modify_start_mac: "Hold down Option and click on a vertex to delete it." + modify_start_touch: "Tap on a vertex to delete it." diff --git a/config/locales/ja.yml b/config/locales/ja.yml index c1687aa..b6789da 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -118,6 +118,7 @@ ja: linestring: ライン編集 polygon: エリア編集 clear_map: "地図をクリア" + remove_point: "Remove point" copied_location_to_clipboard: 位置情報をクリップボードにコピーしました modal: load: 読み込み @@ -125,3 +126,6 @@ ja: messages: baselayer_missing: "背景レイヤーが存在しません!" zoom_in_more: "地物表示のためズームします。" + modify_start: "Hold down ALT and click on a vertex to delete it." + modify_start_mac: "Hold down Option and click on a vertex to delete it." + modify_start_touch: "Tap on a vertex to delete it." diff --git a/src/components/gtt-client/helpers/platforms.ts b/src/components/gtt-client/helpers/platforms.ts new file mode 100644 index 0000000..f0cdd12 --- /dev/null +++ b/src/components/gtt-client/helpers/platforms.ts @@ -0,0 +1,17 @@ +/** + * Utility function to detect touch devices + * @param isTouchDevice - boolean + * @returns boolean + */ +export const isTouchDevice = (): boolean => { + return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); +} + +/** + * Utility function to detect macOS + * @param isMacOS - boolean + * @returns boolean + */ +export const isMacOS = (): boolean => { + return /Macintosh|MacIntel|MacPPC|Mac68K/.test(navigator.userAgent); +} diff --git a/src/components/gtt-client/openlayers/index.ts b/src/components/gtt-client/openlayers/index.ts index e57f04a..c6fe69f 100644 --- a/src/components/gtt-client/openlayers/index.ts +++ b/src/components/gtt-client/openlayers/index.ts @@ -6,7 +6,8 @@ import { Style, Fill, Stroke, Circle } from 'ol/style'; import { createEmpty, extend, containsCoordinate } from 'ol/extent'; import { transform, fromLonLat } from 'ol/proj'; -import { Modify, Draw, Select } from 'ol/interaction' +import { Draw, Select, Snap } from 'ol/interaction' +import ModifyTouch from 'ol-ext/interaction/ModifyTouch'; import Bar from 'ol-ext/control/Bar'; import Button from 'ol-ext/control/Button'; import Toggle from 'ol-ext/control/Toggle'; @@ -16,12 +17,30 @@ import { position } from 'ol-ext/control/control'; import { GeoJSON } from 'ol/format'; import { getCookie, getMapSize, degreesToRadians, updateForm, formatLength, formatArea } from "../helpers"; +import { isTouchDevice, isMacOS } from "../helpers/platforms"; // Define the types for the Tooltip and the custom methods you added interface ExtendedTooltip extends Tooltip { prevHTML?: string; } +/** + * Helper class to manage the visibility of controls. + */ +class ControlManager { + static hide(controls: any[]) { + controls.forEach(control => { + control.element.style.display = 'none'; + }); + } + + static show(controls: any[]) { + controls.forEach(control => { + control.element.style.display = ''; + }); + } +} + /** * Get the z-value for a given geometry. * If the geometry is a Point, return the z-coordinate of the Point. @@ -156,9 +175,17 @@ function createTooltip(): ExtendedTooltip { */ export function setControls(types: Array) { // Make vector features editable - const modify = new Modify({ + const modify = new ModifyTouch({ + title: this.i18n.control.remove_point, features: this.vector.getSource().getFeaturesCollection() - }) + } as any) + + modify.on('showpopup', evt => { + const geometryType = evt.feature.getGeometry().getType(); + if (geometryType === 'Point') { + modify.removePoint(); // don't show the popup + } + }); modify.on('modifyend', evt => { updateForm(this, evt.features.getArray(), true) @@ -197,20 +224,43 @@ export function setControls(types: Array) { }) draw.on('drawstart', evt => { + // Change the style of existing features to light gray and transparent and dashed line + this.vector.getSource().getFeatures().forEach((feature: any) => { + feature.setStyle(new Style({ + fill: new Fill({ + color: 'rgba(0, 0, 0, 0.1)' + }), + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.5)', + width: 2, + lineDash: [5, 5] + }) + })); + }); + if (this.contents.measure) { tooltip.setFeature(evt.feature) } }) draw.on('change:active', evt => { - tooltip.removeFeature() - }) + // If the Draw interaction is deactivated + if (!evt.target.getActive()) { + // Reset the style of existing features + this.vector.getSource().getFeatures().forEach((feature: any) => { + feature.setStyle(null); // Reset the style to the default style + }); + } + + tooltip.removeFeature(); + }); draw.on('drawend', evt => { tooltip.removeFeature() this.vector.getSource().clear() const feature = setZValueForGeometry(evt.feature, zValue); updateForm(this, [feature], true) + ControlManager.show([editModeControl, clearMapCtrl]); }) // Material design icon @@ -230,11 +280,53 @@ export function setControls(types: Array) { html: ``, title: this.i18n.control[type.toLowerCase()], interaction: draw, - active: (type === geometryType) + active: false, + onToggle: (active: boolean) => { + modify.setActive(false); + if (active) { + draw.setActive(true); + } else { + draw.setActive(false); + } + } }) editbar.addControl(control) }) + // Add the edit control + const editModeControl = new Toggle({ + html: '', + title: this.i18n.control.edit_mode, + active: false, + onToggle: (active: boolean) => { + if (active) { + modify.setActive(true); + this.map.getInteractions().forEach((interaction: any) => { + if (interaction instanceof Draw) { + interaction.setActive(false); + } + }); + + if (this.vector.getSource().getFeatures().length > 0) { + const firstFeature = this.vector.getSource().getFeatures()[0]; + if (firstFeature && firstFeature.getGeometry().getType() !== 'Point') { + // Code to execute if the first feature is not a Point + let message = this.i18n.messages.modify_start; + if (isTouchDevice()) { + message = this.i18n.messages.modify_start_touch; + } else if (isMacOS()) { + message = this.i18n.messages.modify_start_mac; + } + this.map.notification.show(message); + } + } + } else { + modify.setActive(false); + } + } + }); + editbar.addControl(editModeControl); + // Add the clear map control const clearMapCtrl = new Button({ html: '', @@ -242,10 +334,29 @@ export function setControls(types: Array) { handleClick: () => { this.vector.getSource().clear(); updateForm(this, null); + (editbar.getControls()[0] as Toggle).setActive(true); + ControlManager.hide([editModeControl, clearMapCtrl]); } }); editbar.addControl(clearMapCtrl); + // if the vector layer is not empty, set the editModeControl to active + if (this.vector.getSource().getFeatures().length > 0) { + editModeControl.setActive(true); + ControlManager.show([editModeControl, clearMapCtrl]); + } + // otherwise set the first draw control to active + else { + (editbar.getControls()[0] as Toggle).setActive(true); + ControlManager.hide([editModeControl, clearMapCtrl]); + } + + // Add the snap interaction + const snap = new Snap({ + source: this.vector.getSource(), + }); + this.map.addInteraction(snap); + // Uses jQuery UI for GeoJSON Upload modal window const mapObj = this const dialog = $("#dialog-geojson-upload").dialog({