Skip to content

Commit

Permalink
PB-856 : add a style selector for KML in layer settings
Browse files Browse the repository at this point in the history
this enables users to opt-out of the default style (Google Earth like) we apply by default to external KML, and let them go back to the GeoAdmin (all red) style that was previously (on mf-geoadmin3) applied across the board.

adding this choice to the layer URL extra attributes
  • Loading branch information
pakb committed Nov 5, 2024
1 parent 1b9a9d1 commit 6352abe
Show file tree
Hide file tree
Showing 19 changed files with 254 additions and 156 deletions.
13 changes: 13 additions & 0 deletions src/api/layers/KMLLayer.class.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AbstractLayer, { LayerAttribution } from '@/api/layers/AbstractLayer.class'
import { InvalidLayerDataError } from '@/api/layers/InvalidLayerData.error'
import KmlStyles from '@/api/layers/KmlStyles.enum'
import LayerTypes from '@/api/layers/LayerTypes.enum'
import { getServiceKmlBaseUrl } from '@/config/baseUrl.config'
import { EMPTY_KML_DATA, parseKmlName } from '@/utils/kmlUtils'
Expand Down Expand Up @@ -35,6 +36,7 @@ export default class KMLLayer extends AbstractLayer {
* files are usually sent with the kml inside a KMZ archive and can be referenced inside the
* KML (e.g. icon, image, ...). Default is `Map()`
* @param {[Number, Number, Number, Number] | null} kmlLayerData.extent
* @param {KmlStyles} kmlLayerData.style
* @throws InvalidLayerDataError if no `gpxLayerData` is given or if it is invalid
*/
constructor(kmlLayerData) {
Expand All @@ -51,6 +53,7 @@ export default class KMLLayer extends AbstractLayer {
kmlMetadata = null,
linkFiles = new Map(),
extent = null,
style = null,
} = kmlLayerData
if (kmlFileUrl === null) {
throw new InvalidLayerDataError('Missing KML file URL', kmlLayerData)
Expand Down Expand Up @@ -96,6 +99,16 @@ export default class KMLLayer extends AbstractLayer {
this.kmlData = kmlData
this.linkFiles = linkFiles
this.extent = extent
if (style === null) {
// if no style was given, we select the default style depending on the origin of the KML
if (isExternal) {
this.style = KmlStyles.DEFAULT
} else {
this.style = KmlStyles.GEOADMIN
}
} else {
this.style = style
}
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/api/layers/KmlStyles.enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* List of available styles to be applied to KMLs features.
*
* For the time being, this can be either the default style (meaning what Google Earth does) or our
* custom-made Geoadmin style (everything red)
*
* @enum
*/
const KmlStyles = {
DEFAULT: 'DEFAULT',
GEOADMIN: 'GEOADMIN',
}

export const allKmlStyles = [KmlStyles.DEFAULT, KmlStyles.GEOADMIN]

export default KmlStyles
4 changes: 2 additions & 2 deletions src/modules/drawing/components/DrawingSelectInteraction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ watch(selectedFeatures, (newSelectedFeatures) => {
}
})
watch(currentlySelectedFeature, (newFeature, oldFeature) => {
if (newFeature) {
if (newFeature && newFeature.get('editableFeature')) {
const editableFeature = newFeature.get('editableFeature')
editableFeature.setCoordinatesFromFeature(newFeature)
// binding store feature change events to our handlers
Expand All @@ -66,7 +66,7 @@ watch(currentlySelectedFeature, (newFeature, oldFeature) => {
} else {
store.dispatch('clearAllSelectedFeatures', dispatcher)
}
if (oldFeature) {
if (oldFeature && oldFeature.get('editableFeature')) {
// editableFeature was removed from the state just before, so we can edit it directly again.
oldFeature.get('editableFeature').removeListener('change:style', onFeatureChange)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DEFAULT_MARKER_TITLE_OFFSET } from '@/api/icon.api'
import { editingFeatureStyleFunction } from '@/modules/drawing/lib/style'
import useSaveKmlOnChange from '@/modules/drawing/useKmlDataManagement.composable'
import { wrapXCoordinates } from '@/utils/coordinates/coordinateUtils'
import { featureStyleFunction } from '@/utils/featureStyleUtils'
import { geoadminStyleFunction } from '@/utils/featureStyleUtils'
import { GeodesicGeometries } from '@/utils/geodesicManager'
import log from '@/utils/logging'

Expand Down Expand Up @@ -178,7 +178,7 @@ export default function useDrawingModeInteraction({
// setting the definitive style function for this feature (thus replacing the editing style from the interaction)
// This function will be automatically recalled every time the feature object is modified or rerendered.
// (so there is no need to recall setstyle after modifying an extended property)
feature.setStyle(featureStyleFunction)
feature.setStyle(geoadminStyleFunction)
// see https://openlayers.org/en/latest/apidoc/module-ol_interaction_Draw-Draw.html#finishDrawing
interaction.finishDrawing()
store.dispatch('addDrawingFeature', { featureId: feature.getId(), ...dispatcher })
Expand Down
4 changes: 2 additions & 2 deletions src/modules/drawing/lib/export-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Style from 'ol/style/Style'

import i18n from '@/modules/i18n/index'
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
import { featureStyleFunction } from '@/utils/featureStyleUtils'
import { geoadminStyleFunction } from '@/utils/featureStyleUtils'
import { EMPTY_KML_DATA } from '@/utils/kmlUtils'
import log from '@/utils/logging'
// FIXME: as soon as https://github.com/openlayers/openlayers/pull/15964 is merged and released, go back to using OL files
Expand Down Expand Up @@ -87,7 +87,7 @@ export function generateKmlString(projection, features = [], fileName) {
const clone = f.clone()
clone.setId(f.getId())
clone.getGeometry().setProperties(f.getGeometry().getProperties())
const styles = featureStyleFunction(clone)
const styles = geoadminStyleFunction(clone)
const newStyle = {
fill: styles[0].getFill(),
stroke: styles[0].getStroke(),
Expand Down
6 changes: 3 additions & 3 deletions src/modules/drawing/lib/style.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LineString, MultiPoint, Point, Polygon } from 'ol/geom'
import { Circle, Fill, Style } from 'ol/style'

import { featureStyleFunction } from '@/utils/featureStyleUtils'
import { geoadminStyleFunction } from '@/utils/featureStyleUtils'
import {
dashedRedStroke,
redStroke,
Expand Down Expand Up @@ -39,7 +39,7 @@ export const editingVertexStyleFunction = (vertex) => {
* edited by our drawing module.
*
* Note that the style differs when the feature is selected (or drawn for the first time) or when
* displayed without interaction (see {@link featureStyleFunction} for this case)
* displayed without interaction (see {@link geoadminStyleFunction} for this case)
*/
export const editingFeatureStyleFunction = (feature, resolution) => {
/* This style (image tag) will only be shown for point geometries. So this style will display
Expand Down Expand Up @@ -71,7 +71,7 @@ export const editingFeatureStyleFunction = (feature, resolution) => {
})
)
}
const defStyle = featureStyleFunction(feature, resolution)
const defStyle = geoadminStyleFunction(feature, resolution)
if (defStyle) {
styles.push(...defStyle)
}
Expand Down
2 changes: 2 additions & 0 deletions src/modules/map/components/openlayers/OpenLayersKMLLayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ const layerName = computed(() => kmlLayerConfig.value.name)
const opacity = computed(() => parentLayerOpacity.value ?? kmlLayerConfig.value.opacity)
const url = computed(() => kmlLayerConfig.value.baseUrl)
const kmlData = computed(() => kmlLayerConfig.value.kmlData)
const kmlStyle = computed(() => kmlLayerConfig.value.style)
watch(opacity, (newOpacity) => layer.setOpacity(newOpacity))
watch(projection, createSourceForProjection)
watch(iconsArePresent, createSourceForProjection)
watch(availableIconSets, createSourceForProjection)
watch(kmlData, createSourceForProjection)
watch(kmlStyle, createSourceForProjection)
/* We cannot directly let the vectorSource load the URL. We need to run the deserialize
function on each feature before it is added to the vectorsource, as it may overwrite
Expand Down
163 changes: 98 additions & 65 deletions src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { computed, onMounted, ref, toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import AbstractLayer from '@/api/layers/AbstractLayer.class'
import GPXLayer from '@/api/layers/GPXLayer.class.js'
import KMLLayer from '@/api/layers/KMLLayer.class.js'
import { allKmlStyles } from '@/api/layers/KmlStyles.enum'
import MenuActiveLayersListItemTimeSelector from '@/modules/menu/components/activeLayers/MenuActiveLayersListItemTimeSelector.vue'
import DropdownButton, { DropdownItem } from '@/utils/components/DropdownButton.vue'
import ErrorButton from '@/utils/components/ErrorButton.vue'
import TextTruncate from '@/utils/components/TextTruncate.vue'
import ThirdPartyDisclaimer from '@/utils/components/ThirdPartyDisclaimer.vue'
Expand Down Expand Up @@ -58,13 +61,20 @@ const { index, layer, showLayerDetail, focusMoveButton, isTopLayer, isBottomLaye
const emit = defineEmits(['showLayerDescriptionPopup', 'toggleLayerDetail', 'moveLayer'])
const store = useStore()
const { t } = useI18n()
useTippyTooltip('.menu-layer-item [data-tippy-content]')
const layerUpButton = ref(null)
const layerDownButton = ref(null)
const transparencySlider = ref(null)
const currentKmlStyle = ref(layer.value?.style ?? null)
const id = computed(() => layer.value.id)
const kmlStylesAsDropdownItems = computed(() =>
allKmlStyles.map((style) => new DropdownItem(style, style.toLowerCase(), style))
)
const isLocalFile = computed(() => store.getters.isLocalFile(layer.value))
const hasDataDisclaimer = computed(() =>
store.getters.hasDataDisclaimer(id.value, layer.value.isExternal, layer.value.baseUrl)
Expand All @@ -77,9 +87,8 @@ const hasMultipleTimestamps = computed(() => layer.value.hasMultipleTimestamps)
const isPhoneMode = computed(() => store.getters.isPhoneMode)
const is3dActive = computed(() => store.state.cesium.active)
const isLayerKmlOrGpx = computed(
() => layer.value instanceof KMLLayer || layer.value instanceof GPXLayer
)
const isLayerKml = computed(() => layer.value instanceof KMLLayer)
const isLayerKmlOrGpx = computed(() => isLayerKml.value || layer.value instanceof GPXLayer)
// only show the spinner for external layer, for our layers the
// backend should be quick enough and don't require any spinner
Expand Down Expand Up @@ -134,6 +143,17 @@ function showLayerDescriptionPopup() {
function duplicateLayer() {
store.dispatch('addLayer', { layer: layer.value.clone(), ...dispatcher })
}
function changeStyle(newStyle) {
store.dispatch('updateLayer', {
layerId: id.value,
values: {
style: newStyle.value,
},
...dispatcher,
})
currentKmlStyle.value = newStyle.value
}
</script>
<template>
Expand Down Expand Up @@ -224,67 +244,77 @@ function duplicateLayer() {
<FontAwesomeIcon icon="cog" />
</button>
</div>
<div
v-show="showLayerDetail"
class="menu-layer-item-details"
:data-cy="`div-layer-settings-${id}-${index}`"
>
<label :for="`transparency-${id}`" class="menu-layer-transparency-title">
{{ $t('transparency') }}
</label>
<input
:id="`transparency-${id}`"
ref="transparencySlider"
:disabled="isLayerKmlOrGpx && is3dActive"
class="menu-layer-transparency-slider ms-2 me-4"
type="range"
min="0.0"
max="1.0"
step="0.01"
:value="1.0 - layer.opacity"
:data-cy="`slider-transparency-layer-${id}-${index}`"
@mouseup="onTransparencyCommit"
@input="debounceTransparencyChange"
/>
<button
v-if="hasMultipleTimestamps"
class="btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:data-cy="`button-duplicate-layer-${id}-${index}`"
data-tippy-content="duplicate_layer"
@click.prevent="duplicateLayer()"
>
<FontAwesomeIcon :icon="['far', 'copy']" />
</button>
<button
ref="layerUpButton"
class="btn-layer-up-down btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:disabled="isTopLayer"
:data-cy="`button-raise-order-layer-${id}-${index}`"
@click.prevent="emit('moveLayer', index, index + 1)"
>
<FontAwesomeIcon icon="arrow-up" />
</button>
<button
ref="layerDownButton"
class="btn-layer-up-down btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:disabled="isBottomLayer"
:data-cy="`button-lower-order-layer-${id}-${index}`"
@click.prevent="emit('moveLayer', index, index - 1)"
>
<FontAwesomeIcon icon="arrow-down" />
</button>
<button
v-if="showLayerDescriptionIcon"
class="btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:data-cy="`button-show-description-layer-${id}-${index}`"
@click="showLayerDescriptionPopup"
>
<FontAwesomeIcon icon="info-circle" />
</button>
<div v-show="showLayerDetail" :data-cy="`div-layer-settings-${id}-${index}`">
<div class="menu-layer-item-details">
<label :for="`transparency-${id}`" class="menu-layer-transparency-title">
{{ $t('transparency') }}
</label>
<input
:id="`transparency-${id}`"
ref="transparencySlider"
:disabled="isLayerKmlOrGpx && is3dActive"
class="menu-layer-transparency-slider ms-2 me-4"
type="range"
min="0.0"
max="1.0"
step="0.01"
:value="1.0 - layer.opacity"
:data-cy="`slider-transparency-layer-${id}-${index}`"
@mouseup="onTransparencyCommit"
@input="debounceTransparencyChange"
/>
<button
v-if="hasMultipleTimestamps"
class="btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:data-cy="`button-duplicate-layer-${id}-${index}`"
data-tippy-content="duplicate_layer"
@click.prevent="duplicateLayer()"
>
<FontAwesomeIcon :icon="['far', 'copy']" />
</button>
<button
ref="layerUpButton"
class="btn-layer-up-down btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:disabled="isTopLayer"
:data-cy="`button-raise-order-layer-${id}-${index}`"
@click.prevent="emit('moveLayer', index, index + 1)"
>
<FontAwesomeIcon icon="arrow-up" />
</button>
<button
ref="layerDownButton"
class="btn-layer-up-down btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:disabled="isBottomLayer"
:data-cy="`button-lower-order-layer-${id}-${index}`"
@click.prevent="emit('moveLayer', index, index - 1)"
>
<FontAwesomeIcon icon="arrow-down" />
</button>
<button
v-if="showLayerDescriptionIcon"
class="btn d-flex align-items-center"
:class="{ 'btn-lg': !compact }"
:data-cy="`button-show-description-layer-${id}-${index}`"
@click="showLayerDescriptionPopup"
>
<FontAwesomeIcon icon="info-circle" />
</button>
</div>
<div v-if="isLayerKml" v-show="showLayerDetail" class="menu-layer-item-details mt-n1">
<label class="menu-layer-transparency-title me-2">
{{ t('vector_feedback_select_style') }}
</label>
<DropdownButton
:title="currentKmlStyle.toLowerCase()"
:items="kmlStylesAsDropdownItems"
:current-value="currentKmlStyle"
small
@select:item="changeStyle"
/>
</div>
</div>
</div>
</template>
Expand Down Expand Up @@ -313,7 +343,10 @@ function duplicateLayer() {
padding-bottom: 0.4rem;
}
.menu-layer-transparency-title {
font-size: 0.9em;
$smallerFont: 0.9em;
font-size: $smallerFont;
// also setting the line height, so that vertical alignment isn't broken
line-height: $smallerFont;
}
.menu-layer-transparency-slider {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ async function loadFile() {
ref="fileUrlInput"
v-model="fileUrl"
required
class="mb-2"
class="mt-2"
placeholder="import_file_url_placeholder"
:activate-validation="activateValidation"
:invalid-marker="!!errorFileLoadingMessage"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import KMLLayer from '@/api/layers/KMLLayer.class'
import KmlStyles from '@/api/layers/KmlStyles.enum.js'
import EmptyFileContentError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/EmptyFileContentError.error'
import InvalidFileContentError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/InvalidFileContentError.error'
import OutOfBoundsError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/OutOfBoundsError.error'
Expand Down Expand Up @@ -67,6 +68,7 @@ export class KMLParser extends FileParser {
extent: extentInCurrentProjection,
extentProjection: currentProjection,
linkFiles,
style: KmlStyles.DEFAULT,
})
}
}
Loading

0 comments on commit 6352abe

Please sign in to comment.