diff --git a/app/helpers/gtt_map_helper.rb b/app/helpers/gtt_map_helper.rb index fa58a91b..22e22262 100644 --- a/app/helpers/gtt_map_helper.rb +++ b/app/helpers/gtt_map_helper.rb @@ -33,6 +33,7 @@ def map_tag(map: nil, layers: map&.layers, data[:upload] = upload data[:collapsed] = collapsed if collapsed data[:geocoding] = true if Setting.plugin_redmine_gtt['enable_geocoding_on_map'] == 'true' + data[:measure] = true if Setting.plugin_redmine_gtt['default_measure_enabled'] == 'true' data[:target] = true if Setting.plugin_redmine_gtt['default_target_enabled'] == 'true' uid = "ol-" + rand(36**8).to_s(36) diff --git a/app/views/settings/gtt/_general.html.erb b/app/views/settings/gtt/_general.html.erb index 540f4fd8..0a07239f 100644 --- a/app/views/settings/gtt/_general.html.erb +++ b/app/views/settings/gtt/_general.html.erb @@ -72,6 +72,11 @@ <%= check_box_tag 'settings[default_target_enabled]', true, @settings[:default_target_enabled] %>

+

+ <%= content_tag(:label, l(:label_default_measure_enabled)) %> + <%= check_box_tag 'settings[default_measure_enabled]', true, @settings[:default_measure_enabled] %> +

+

<%= content_tag(:label, l(:label_hide_map_for_invalid_geom)) %> <%= check_box_tag 'settings[hide_map_for_invalid_geom]', 1, Setting.plugin_redmine_gtt['hide_map_for_invalid_geom'] %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 3c017577..ae97bef4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -56,6 +56,7 @@ en: label_enable_geojson_upload_on_issue_map: "Enable GeoJSON upload on issue map" label_enable_geocoding_on_map: "Enable geocoding on map" label_default_target_enabled: "Show target on map center" + label_default_measure_enabled: "Show measure control" select_other_gtt_settings: "Other GTT settings" label_hide_map_for_invalid_geom: "Hide issue map for invalid geometry" diff --git a/init.rb b/init.rb index 75beb000..d9052f7e 100644 --- a/init.rb +++ b/init.rb @@ -31,6 +31,7 @@ 'default_map_fit_maxzoom_level' => 17, 'vector_minzoom_level' => 0, 'default_target_enabled' => false, + 'default_measure_enabled' => false, 'default_geocoder_options' => '{}', 'editable_geometry_types_on_issue_map' => ["Point"], 'enable_geojson_upload_on_issue_map' => false, diff --git a/src/components/gtt-client/helpers/index.ts b/src/components/gtt-client/helpers/index.ts index 045a7670..bfee4488 100644 --- a/src/components/gtt-client/helpers/index.ts +++ b/src/components/gtt-client/helpers/index.ts @@ -274,3 +274,30 @@ export function parseHistory() { }); } +/** + * Format length in meters or kilometers. + * @param length + * @returns + */ +export function formatLength(length: number): string { + if (length < 1000) { + return length.toFixed(0) + ' m'; + } else { + return (length / 1000).toFixed(2) + ' km'; + } +} + +/** + * Format area in square meters or square kilometers. + * @param area + * @returns + */ +export function formatArea(area: number): string { + if (area < 10000) { + return area.toFixed(1) + ' m2'; + } else { + return (area / 1000000).toFixed(2) + ' km2'; + } +} + + diff --git a/src/components/gtt-client/init/controls.ts b/src/components/gtt-client/init/controls.ts index 6aa7e247..ce2ecac1 100644 --- a/src/components/gtt-client/init/controls.ts +++ b/src/components/gtt-client/init/controls.ts @@ -5,10 +5,11 @@ import Button from 'ol-ext/control/Button'; import LayerPopup from 'ol-ext/control/LayerPopup'; import LayerSwitcher from 'ol-ext/control/LayerSwitcher'; import Target from 'ol-ext/control/Target'; +import Hover from 'ol-ext/interaction/Hover'; import Notification from 'ol-ext/control/Notification'; import { position } from 'ol-ext/control/control'; -import { radiansToDegrees, degreesToRadians, parseHistory } from "../helpers"; +import { radiansToDegrees, degreesToRadians, parseHistory, formatLength, formatArea } from "../helpers"; import { zoomToExtent, setGeolocation, setView, setControls, setPopover } from "../openlayers"; import { createSearchControl } from '../geocoding/SearchFactory'; @@ -110,6 +111,35 @@ function addMaximizeControl(instance: any): void { instance.toolbar.addControl(maximizeCtrl); } +/** + * Adds hover control to provide additional information + * @param instance + */ +function addHoverControl(instance: any): void { + const hover = new Hover({ + cursor: 'pointer', + handleEvent: (evt: any) => { + return true; + }, + layerFilter: (layer: any) => { + // Respond only to the specific vector layer + return layer === instance.vector; + } + }); + + instance.map.addInteraction(hover); + + // Show the length or area of the feature on hover + hover.on('enter', (evt: any) => { + const geometry = evt.feature.getGeometry(); + if (geometry.getType() === 'LineString') { + instance.map.notification.show(formatLength(geometry.getLength())); + } else if (geometry.getType() === 'Polygon') { + instance.map.notification.show(formatArea(geometry.getArea())); + } + }); +} + /** * Handles the map rotation functionality. * @param {any} instance - The GttClient instance. @@ -199,6 +229,10 @@ export function initControls(this: any): void { handleMapRotation(this); addTargetControl(this); + if (this.contents.measure) { + addHoverControl(this); + } + if (this.contents.edit) { setControls.call(this, this.contents.edit.split(' ')); } else if (this.contents.popup) { diff --git a/src/components/gtt-client/openlayers/index.ts b/src/components/gtt-client/openlayers/index.ts index 1695167e..e57f04a1 100644 --- a/src/components/gtt-client/openlayers/index.ts +++ b/src/components/gtt-client/openlayers/index.ts @@ -11,10 +11,16 @@ import Bar from 'ol-ext/control/Bar'; import Button from 'ol-ext/control/Button'; import Toggle from 'ol-ext/control/Toggle'; import Popup from 'ol-ext/overlay/Popup'; +import Tooltip from 'ol-ext/overlay/Tooltip' import { position } from 'ol-ext/control/control'; import { GeoJSON } from 'ol/format'; -import { getCookie, getMapSize, degreesToRadians, updateForm } from "../helpers"; +import { getCookie, getMapSize, degreesToRadians, updateForm, formatLength, formatArea } from "../helpers"; + +// Define the types for the Tooltip and the custom methods you added +interface ExtendedTooltip extends Tooltip { + prevHTML?: string; +} /** * Get the z-value for a given geometry. @@ -133,6 +139,18 @@ function setZValueForGeometry(feature: any, zValue: number): any { return feature; } +/** + * Create extended tooltip control + * @returns + */ +function createTooltip(): ExtendedTooltip { + return new Tooltip({ + maximumFractionDigits: 2, + formatLength, + formatArea + }) as ExtendedTooltip; +} + /** * Add editing tools */ @@ -165,6 +183,11 @@ export function setControls(types: Array) { zValue = getZValueForGeometry(ftr.getGeometry()); }); + // Create tooltip + const tooltip = createTooltip(); + this.map.addOverlay(tooltip); + + // Add the draw controls types.forEach((type: any, idx) => { const draw = new Draw({ @@ -173,7 +196,18 @@ export function setControls(types: Array) { geometryLayout: 'XYZ' }) + draw.on('drawstart', evt => { + if (this.contents.measure) { + tooltip.setFeature(evt.feature) + } + }) + + draw.on('change:active', evt => { + tooltip.removeFeature() + }) + draw.on('drawend', evt => { + tooltip.removeFeature() this.vector.getSource().clear() const feature = setZValueForGeometry(evt.feature, zValue); updateForm(this, [feature], true)