Skip to content

Commit

Permalink
PB-1297: make position of text labels configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
sommerfe committed Jan 8, 2025
1 parent e13b1bb commit a17f8b4
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 9 deletions.
31 changes: 30 additions & 1 deletion src/api/features/EditableFeature.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { extractOlFeatureGeodesicCoordinates } from '@/api/features/features.api
import SelectableFeature from '@/api/features/SelectableFeature.class'
import { DEFAULT_ICON_URL_PARAMS } from '@/api/icon.api'
import { DEFAULT_TITLE_OFFSET } from '@/api/icon.api'
import { allStylingColors, allStylingSizes, MEDIUM, RED } from '@/utils/featureStyleUtils'
import {
allStylingColors,
allStylingSizes,
allStylingTextPlacements,
MEDIUM,
RED,
TOP,
} from '@/utils/featureStyleUtils'

/** @enum */
export const EditableFeatureTypes = {
Expand Down Expand Up @@ -33,6 +40,10 @@ export default class EditableFeature extends SelectableFeature {
* @param {DrawingIcon} featureData.icon Icon that will be covering this feature, can be null
* @param {FeatureStyleSize} featureData.iconSize Size of the icon (if defined) that will be
* covering this feature
*
* @typedef {'top' | 'bottom' | 'left' | 'right', 'center', 'unknown'} TextPlacement
* @param {TextPlacement} featureData.textPlacement Size of the icon (if defined) that will be
* covering this feature
*/
constructor(featureData) {
const {
Expand All @@ -48,6 +59,7 @@ export default class EditableFeature extends SelectableFeature {
fillColor = RED,
icon = null,
iconSize = MEDIUM,
textPlacement = TOP,
} = featureData
super({ id, coordinates, title, description, geometry, isEditable: true })
this._featureType = featureType
Expand All @@ -59,6 +71,7 @@ export default class EditableFeature extends SelectableFeature {
this._iconSize = iconSize
this._geodesicCoordinates = null
this._isDragged = false
this._textPlacement = textPlacement
}

/**
Expand Down Expand Up @@ -108,6 +121,22 @@ export default class EditableFeature extends SelectableFeature {
}
}

/** @returns {TextPlacement} */
get textPlacement() {
return this._textPlacement
}

/** @param newPlacement {TextPlacement} */
set textPlacement(newPlacement) {
if (
newPlacement &&
allStylingTextPlacements.find((placement) => placement === newPlacement)
) {
this._textPlacement = newPlacement
this.emitStylingChangeEvent('textPlacement')
}
}

/** @returns {FeatureStyleSize} */
get textSize() {
return this._textSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
/>
</div>
</div>

<DrawingStyleColorSelector
v-if="currentIconSet && currentIconSet.isColorable"
class="mb-3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<template>
<div>
<label class="form-label" for="drawing-style-text-placement-selector">
{{ i18n.t('modify_text_placement_label') }}
</label>
<DropdownButton
id="drawing-style-text-placement-selector"
data-cy="drawing-style-placement-selector"
:current-value="currentPlacement"
:title="placementLabel"
:items="dropdownItems"
@select:item="onPlacementSelect"
/>
</div>
</template>

<script setup>
import { computed, toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import DropdownButton, { DropdownItem } from '@/utils/components/DropdownButton.vue'
import { allStylingTextPlacements } from '@/utils/featureStyleUtils'
const props = defineProps({
currentPlacement: {
type: String,
required: true,
},
})
const { currentPlacement } = toRefs(props)
const emit = defineEmits(['change'])
const i18n = useI18n()
const placements = allStylingTextPlacements
const placementLabel = computed(() => i18n.t(currentPlacement.value) ?? null)
const dropdownItems = computed(() =>
placements.map((placement) => new DropdownItem(placement, i18n.t(placement), placement))
)
const onPlacementSelect = (dropdownItem) => {
emit('change', dropdownItem.value)
}
</script>
19 changes: 18 additions & 1 deletion src/modules/infobox/components/styling/FeatureStyleEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import allFormats from '@/utils/coordinates/coordinateFormat'
import debounce from '@/utils/debounce'
import { calculateTextOffset } from '@/utils/featureStyleUtils'
import DrawingStylePositionSelector from './DrawingStylePositionSelector.vue'
const dispatcher = { dispatcher: 'FeatureStyleEdit.vue' }
const props = defineProps({
Expand Down Expand Up @@ -95,6 +97,7 @@ function updateFeatureTitle() {
title: title.value.trim(),
...dispatcher,
})
updateTextOffset()
}
function updateFeatureDescription() {
Expand Down Expand Up @@ -129,6 +132,14 @@ function onTextSizeChange(textSize) {
store.dispatch('changeFeatureTextSize', { feature: feature.value, textSize, ...dispatcher })
updateTextOffset()
}
function onPlacementChange(textPlacement) {
store.dispatch('changeFeatureTextPlacement', {
feature: feature.value,
textPlacement,
...dispatcher,
})
updateTextOffset()
}
function onTextColorChange(textColor) {
store.dispatch('changeFeatureTextColor', { feature: feature.value, textColor, ...dispatcher })
}
Expand Down Expand Up @@ -157,7 +168,9 @@ function updateTextOffset() {
feature.value.textSize.textScale,
feature.value.iconSize.iconScale,
feature.value.icon.anchor,
feature.value.icon.size
feature.value.icon.size,
feature.value.textPlacement,
title.value
)
store.dispatch('changeFeatureTextOffset', {
Expand Down Expand Up @@ -275,6 +288,10 @@ function mediaTypes() {
:current-size="feature.textSize"
@change="onTextSizeChange"
/>
<DrawingStylePositionSelector
:current-placement="feature.textPlacement"
@change="onPlacementChange"
/>
<DrawingStyleTextColorSelector
:current-color="feature.textColor"
@change="onTextColorChange"
Expand Down
33 changes: 32 additions & 1 deletion src/store/modules/features.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
GPX_GEOMETRY_SIMPLIFICATION_TOLERANCE,
} from '@/config/map.config'
import { flattenExtent } from '@/utils/extentUtils'
import { allStylingColors, allStylingSizes } from '@/utils/featureStyleUtils'
import {
allStylingColors,
allStylingSizes,
allStylingTextPlacements,
} from '@/utils/featureStyleUtils'
import { transformIntoTurfEquivalent } from '@/utils/geoJsonUtils'
import log from '@/utils/logging'

Expand Down Expand Up @@ -474,6 +478,30 @@ export default {
})
}
},

/**
* Changes the text placement of the title of the feature. Only changes the text placement
* if the feature is editable and part of the currently selected features
*
* @param commit
* @param state
* @param {EditableFeature} feature
* @param {TextPlacement} textPlacement
* @param dispatcher
*/
changeFeatureTextPlacement({ commit, state }, { feature, textPlacement, dispatcher }) {
const selectedFeature = getEditableFeatureWithId(state, feature.id)
const wantedPlacement = allStylingTextPlacements.find(
(position) => position === textPlacement
)
if (wantedPlacement && selectedFeature && selectedFeature.isEditable) {
commit('changeFeatureTextPlacement', {
feature: selectedFeature,
textPlacement: wantedPlacement,
dispatcher,
})
}
},
/**
* Changes the text offset of the feature. Only change the text offset if the feature is
* editable and part of the currently selected features
Expand Down Expand Up @@ -735,6 +763,9 @@ export default {
changeFeatureTextSize(state, { feature, textSize }) {
feature.textSize = textSize
},
changeFeatureTextPlacement(state, { feature, textPlacement }) {
feature.textPlacement = textPlacement
},
changeFeatureTextOffset(state, { feature, textOffset }) {
feature.textOffset = textOffset
},
Expand Down
58 changes: 53 additions & 5 deletions src/utils/featureStyleUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export const WHITE = new FeatureStyleColor('white', '#ffffff', '#000000')
export const YELLOW = new FeatureStyleColor('yellow', '#ffff00', '#000000')

export const allStylingColors = [BLACK, BLUE, GRAY, GREEN, ORANGE, RED, WHITE, YELLOW]

export const FEATURE_FONT_SIZE = 16
export const FEATURE_FONT = 'Helvetica'
/**
* Representation of a size for feature style
*
Expand Down Expand Up @@ -118,7 +119,7 @@ export class FeatureStyleSize {
}

get font() {
return `normal ${16 * this.textScale}px Helvetica`
return `normal ${FEATURE_FONT_SIZE * this.textScale}px ${FEATURE_FONT}`
}
}

Expand All @@ -132,12 +133,20 @@ export const MEDIUM = new FeatureStyleSize('medium_size', 1.5, 0.75)
export const LARGE = new FeatureStyleSize('large_size', 2.0, 1)
export const EXTRA_LARGE = new FeatureStyleSize('extra_large_size', 2.5, 1.25)

export const LEFT = 'left'
export const RIGHT = 'right'
export const TOP = 'top'
export const BOTTOM = 'bottom'
export const CENTER = 'center'
export const UNKNOWN = 'unknown'

/**
* List of all available sizes for drawing style
*
* @type {FeatureStyleSize[]}
*/
export const allStylingSizes = [SMALL, MEDIUM, LARGE, EXTRA_LARGE]
export const allStylingTextPlacements = [LEFT, RIGHT, TOP, BOTTOM, CENTER, UNKNOWN]

/**
* Get Feature style from feature
Expand Down Expand Up @@ -212,25 +221,64 @@ export function getTextColor(style) {
* @param {Number} iconScale Icon scaling
* @param {Array} anchor Relative position of Anchor
* @param {Array} iconSize Absolute size of icon in pixel
*
* @typedef {'top' | 'bottom' | 'left' | 'right', 'center', 'unknown'} TextPlacement
* @param {TextPlacement} textPlacement Absolute position of text in pixel
* @returns {Array | null} Returns the feature label offset
*/
export function calculateTextOffset(textScale, iconScale, anchor, iconSize) {
export function calculateTextOffset(textScale, iconScale, anchor, iconSize, textPlacement, text) {
if (!iconScale) {
return DEFAULT_TITLE_OFFSET
}

let xOffset = 0
let yOffset = 0
const fontSize = 11
let anchorScale = anchor ? anchor[1] * 2 : 1

const iconOffset = 0.5 * iconScale * anchorScale * iconSize[1]
const textOffset = 0.5 * fontSize * textScale
const defaultOffset = 5
const offset = [0, -(defaultOffset + iconOffset + textOffset)]
yOffset = -(defaultOffset + iconOffset + textOffset)
const textWidth = getTextWidth(text, textScale)

switch (textPlacement) {
case TOP:
xOffset = 0
break
case BOTTOM:
yOffset = -yOffset
xOffset = 0
break
case LEFT:
yOffset = 0
xOffset = -(defaultOffset + iconOffset + textOffset + textWidth / 2) // / 2 because the text is centered
break
case RIGHT:
yOffset = 0
xOffset = defaultOffset + iconOffset + textOffset + textWidth / 2
break
case CENTER:
yOffset = 0
xOffset = 0
break
case UNKNOWN:
break
default:
break
}
const offset = [xOffset, yOffset]
log.debug('title offset of feature is calculated to be : ', offset)

return offset
}

function getTextWidth(text, textScale) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.font = `normal ${FEATURE_FONT_SIZE * textScale}px ${FEATURE_FONT}`
return context.measureText(text).width
}

/**
* Style function that renders a feature with the distinct Geoadmin style. Meaning, by default, all
* red.
Expand Down
7 changes: 7 additions & 0 deletions src/utils/kmlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
getTextSize,
RED,
SMALL,
TOP,
UNKNOWN,
} from '@/utils/featureStyleUtils'
import { GeodesicGeometries } from '@/utils/geodesicManager'
import log from '@/utils/logging'
Expand Down Expand Up @@ -409,6 +411,10 @@ export function getEditableFeatureFromKmlFeature(kmlFeature, availableIconSets)
}
const icon = iconArgs ? getIcon(iconArgs, iconStyle, availableIconSets) : null
const iconSize = iconStyle ? getIconSize(iconStyle) : null
const textPlacement =
textOffset[0] === DEFAULT_TITLE_OFFSET[0] && textOffset[1] === DEFAULT_TITLE_OFFSET[1]
? TOP
: UNKNOWN // FIXME
const fillColor = getFillColor(style, kmlFeature.getGeometry().getType(), iconArgs)

const geometry = new GeoJSON().writeGeometryObject(kmlFeature.getGeometry())
Expand Down Expand Up @@ -452,6 +458,7 @@ export function getEditableFeatureFromKmlFeature(kmlFeature, availableIconSets)
fillColor,
icon,
iconSize,
textPlacement,
})
}

Expand Down

0 comments on commit a17f8b4

Please # to comment.