From 4cb37d26e0876e9d907a61ddd83373e019922476 Mon Sep 17 00:00:00 2001 From: Pascal Barth Date: Fri, 13 Sep 2024 11:21:32 +0200 Subject: [PATCH] PB-856 : add a style selector for KML in layer settings 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 --- src/api/layers/KMLLayer.class.js | 12 ++ src/api/layers/KmlStyles.enum.js | 16 ++ .../components/DrawingSelectInteraction.vue | 4 +- .../useDrawingModeInteraction.composable.js | 4 +- src/modules/drawing/lib/export-utils.js | 4 +- src/modules/drawing/lib/style.js | 6 +- .../openlayers/OpenLayersKMLLayer.vue | 2 + .../utils/useMapInteractions.composable.js | 3 +- .../activeLayers/MenuActiveLayersListItem.vue | 164 +++++++++++------- .../ImportFile/ImportFileLocalTab.vue | 4 +- .../ImportFile/ImportFileOnlineTab.vue | 5 +- .../advancedTools/ImportFile/utils.js | 6 +- .../storeSync/LayerParamConfig.class.js | 4 +- src/router/storeSync/layersParamParser.js | 14 ++ src/utils/components/DropdownButton.vue | 6 +- src/utils/featureStyleUtils.js | 49 +++--- src/utils/kmlUtils.js | 14 +- src/utils/layerUtils.js | 4 +- src/utils/styleUtils.js | 16 +- tests/cypress/tests-e2e/print.cy.js | 2 +- 20 files changed, 219 insertions(+), 120 deletions(-) create mode 100644 src/api/layers/KmlStyles.enum.js diff --git a/src/api/layers/KMLLayer.class.js b/src/api/layers/KMLLayer.class.js index 884f514eb..ab1e2cca2 100644 --- a/src/api/layers/KMLLayer.class.js +++ b/src/api/layers/KMLLayer.class.js @@ -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' @@ -49,6 +50,7 @@ export default class KMLLayer extends AbstractLayer { kmlData = null, kmlMetadata = null, linkFiles = new Map(), + style = null, } = kmlLayerData if (kmlFileUrl === null) { throw new InvalidLayerDataError('Missing KML file URL', kmlLayerData) @@ -93,6 +95,16 @@ export default class KMLLayer extends AbstractLayer { } this.kmlData = kmlData this.linkFiles = linkFiles + 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 + } } /** diff --git a/src/api/layers/KmlStyles.enum.js b/src/api/layers/KmlStyles.enum.js new file mode 100644 index 000000000..32b77e295 --- /dev/null +++ b/src/api/layers/KmlStyles.enum.js @@ -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 diff --git a/src/modules/drawing/components/DrawingSelectInteraction.vue b/src/modules/drawing/components/DrawingSelectInteraction.vue index 460f594db..01f1fa5ed 100644 --- a/src/modules/drawing/components/DrawingSelectInteraction.vue +++ b/src/modules/drawing/components/DrawingSelectInteraction.vue @@ -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 @@ -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) } diff --git a/src/modules/drawing/components/useDrawingModeInteraction.composable.js b/src/modules/drawing/components/useDrawingModeInteraction.composable.js index 94d8d697b..ceb85c5f7 100644 --- a/src/modules/drawing/components/useDrawingModeInteraction.composable.js +++ b/src/modules/drawing/components/useDrawingModeInteraction.composable.js @@ -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' @@ -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 }) diff --git a/src/modules/drawing/lib/export-utils.js b/src/modules/drawing/lib/export-utils.js index def6bdbe2..1188fda38 100644 --- a/src/modules/drawing/lib/export-utils.js +++ b/src/modules/drawing/lib/export-utils.js @@ -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 @@ -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(), diff --git a/src/modules/drawing/lib/style.js b/src/modules/drawing/lib/style.js index a77585993..08551ee5c 100644 --- a/src/modules/drawing/lib/style.js +++ b/src/modules/drawing/lib/style.js @@ -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, @@ -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 @@ -71,7 +71,7 @@ export const editingFeatureStyleFunction = (feature, resolution) => { }) ) } - const defStyle = featureStyleFunction(feature, resolution) + const defStyle = geoadminStyleFunction(feature, resolution) if (defStyle) { styles.push(...defStyle) } diff --git a/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue b/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue index 103071e1f..71ec23d3a 100644 --- a/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue @@ -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 diff --git a/src/modules/map/components/openlayers/utils/useMapInteractions.composable.js b/src/modules/map/components/openlayers/utils/useMapInteractions.composable.js index 17782a85c..e33dae2a8 100644 --- a/src/modules/map/components/openlayers/utils/useMapInteractions.composable.js +++ b/src/modules/map/components/openlayers/utils/useMapInteractions.composable.js @@ -3,6 +3,7 @@ import DoubleClickZoomInteraction from 'ol/interaction/DoubleClickZoom' import { computed, onBeforeUnmount, watch } from 'vue' import { useStore } from 'vuex' +import KmlStyles from '@/api/layers/KmlStyles.enum' import LayerTypes from '@/api/layers/LayerTypes.enum' import { DRAWING_HIT_TOLERANCE } from '@/config/map.config' import { IS_TESTING_WITH_CYPRESS } from '@/config/staging.config' @@ -250,7 +251,7 @@ export default function useMapInteractions(map) { async function handleFile(file) { try { const fileContent = await readFileContent(file) - await handleFileContent(store, fileContent, file.name, file) + await handleFileContent(store, fileContent, file.name, file, KmlStyles.DEFAULT) } catch (error) { let errorKey log.error(`Error loading file`, file.name, error) diff --git a/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue b/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue index 869b558f7..1ac759123 100644 --- a/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue +++ b/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue @@ -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' @@ -57,13 +60,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) @@ -76,9 +86,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 @@ -133,6 +142,18 @@ function showLayerDescriptionPopup() { function duplicateLayer() { store.dispatch('addLayer', { layer: layer.value.clone(), ...dispatcher }) } + +function changeStyle(newStyle) { + console.log('woot', newStyle) + store.dispatch('updateLayer', { + index: index.value, + layer: { + style: newStyle.value, + }, + ...dispatcher, + }) + currentKmlStyle.value = newStyle.value +} @@ -311,7 +342,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; diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue index 4d9e4586a..0c79cc9cd 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue @@ -2,6 +2,7 @@ import { computed, ref, toRefs } from 'vue' import { useStore } from 'vuex' +import KmlStyles from '@/api/layers/KmlStyles.enum' import ImportFileButtons from '@/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue' import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils' import FileInput from '@/utils/components/FileInput.vue' @@ -44,7 +45,7 @@ async function loadFile() { // The file might be a KMZ which is a zip archive. Handling zip archive as text is // asking for trouble, therefore we need first to get it as binary const content = await selectedFile.value.arrayBuffer() - await handleFileContent(store, content, selectedFile.value.name) + await handleFileContent(store, content, selectedFile.value.name, KmlStyles.DEFAULT) importSuccessMessage.value = 'file_imported_success' } catch (error) { if (error instanceof OutOfBoundsError) { @@ -81,6 +82,7 @@ function validateForm(valid) { (buttonState.value = 'default'), 3000) } catch (error) { @@ -127,7 +128,7 @@ async function loadFile() { ref="fileUrlInput" v-model="fileUrl" required - class="mb-2" + class="mt-1" placeholder="import_file_url_placeholder" :activate-validation="activateValidation" :invalid-marker="!!errorFileLoadingMessage" diff --git a/src/modules/menu/components/advancedTools/ImportFile/utils.js b/src/modules/menu/components/advancedTools/ImportFile/utils.js index 5af61e6f0..303c950de 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/utils.js +++ b/src/modules/menu/components/advancedTools/ImportFile/utils.js @@ -38,13 +38,14 @@ export function isGpx(fileContent) { /** * Handle file content * - * @param {OBject} store Vuex store + * @param {Object} store Vuex store * @param {ArrayBuffer} content Content of the file * @param {string} source Source of the file (either URL or file path) * @param {File | null} [originalFile] + * @param {KmlStyles} style Style to apply to this file when displayed on the map * @returns {ExternalLayer} External layer object */ -export async function handleFileContent(store, content, source, originalFile = null) { +export async function handleFileContent(store, content, source, originalFile = null, style = KmlStyles.DEFAULT) { let layer = null let parsedContent let linkFiles @@ -99,6 +100,7 @@ export async function handleFileContent(store, content, source, originalFile = n adminId: null, kmlData: parsedContent, linkFiles, + style, }) const extent = getKmlExtent(parsedContent) if (!extent) { diff --git a/src/router/storeSync/LayerParamConfig.class.js b/src/router/storeSync/LayerParamConfig.class.js index e7c120956..9ecfb559e 100644 --- a/src/router/storeSync/LayerParamConfig.class.js +++ b/src/router/storeSync/LayerParamConfig.class.js @@ -15,7 +15,7 @@ import { parseLayersParam, transformLayerIntoUrlString, } from '@/router/storeSync/layersParamParser' -import { flattenExtent } from '@/utils/coordinates/coordinateUtils.js' +import { flattenExtent } from '@/utils/coordinates/coordinateUtils' import ErrorMessage from '@/utils/ErrorMessage.class' import { getExtentOfGeometries } from '@/utils/geoJsonUtils' import log from '@/utils/logging' @@ -59,6 +59,7 @@ export function createLayerObject(parsedLayer, currentLayer, store, featuresRequ visible: parsedLayer.visible, opacity: parsedLayer.opacity ?? defaultOpacity, adminId: adminId, + style: parsedLayer.customAttributes?.style, }) } else { // If the url does not start with http, then it is a local file and we don't add it @@ -311,6 +312,7 @@ export default class LayerParamConfig extends AbstractParamConfig { 'setLayers', 'setSelectedFeatures', 'addSelectedFeatures', + 'updateLayer', ], setValuesInStore: dispatchLayersFromUrlIntoStore, extractValueFromStore: generateLayerUrlParamFromStoreValues, diff --git a/src/router/storeSync/layersParamParser.js b/src/router/storeSync/layersParamParser.js index ed9d164f0..fb4fbcf35 100644 --- a/src/router/storeSync/layersParamParser.js +++ b/src/router/storeSync/layersParamParser.js @@ -1,4 +1,5 @@ import LayerFeature from '@/api/features/LayerFeature.class' +import KmlStyles from '@/api/layers/KmlStyles.enum' import { decodeExternalLayerParam, encodeExternalLayerParam, @@ -118,6 +119,11 @@ export function parseLayersParam(queryValue) { parsedValue = Number(value) } else if (key === 'year' && value.toLowerCase() === 'none') { parsedValue = null + } else if ( + key === 'style' && + Object.values(KmlStyles).includes(value?.toUpperCase()) + ) { + parsedValue = value.toUpperCase() } else { parsedValue = value } @@ -177,6 +183,14 @@ export function transformLayerIntoUrlString(layer, defaultLayerConfig, featuresI layerUrlString += `@updateDelay=${layer.updateDelay}` } + if (layer.type === LayerTypes.KML) { + // for our own files, the default style is GeoAdmin (and we don't want to write that in the URL) + const defaultKmlStyle = layer.isExternal ? KmlStyles.DEFAULT : KmlStyles.GEOADMIN + if (layer.style !== defaultKmlStyle) { + layerUrlString += `@style=${layer.style.toLowerCase()}` + } + } + // Add custom attributes if any if (layer.customAttributes !== null) { for (const [key, value] of Object.entries(layer.customAttributes)) { diff --git a/src/utils/components/DropdownButton.vue b/src/utils/components/DropdownButton.vue index 5f812037c..12d69625e 100644 --- a/src/utils/components/DropdownButton.vue +++ b/src/utils/components/DropdownButton.vue @@ -5,7 +5,7 @@ ref="dropdownMainButton" :disabled="disabled" class="btn btn-light" - :class="{ 'dropdown-toggle': !withToggleButton }" + :class="{ 'dropdown-toggle': !withToggleButton, 'btn-sm': small }" type="button" data-cy="dropdown-main-button" :data-bs-toggle="withToggleButton ? null : 'dropdown'" @@ -118,6 +118,10 @@ export default { type: Boolean, default: false, }, + small: { + type: Boolean, + default: false, + }, }, emits: ['click', 'select:item'], setup() { diff --git a/src/utils/featureStyleUtils.js b/src/utils/featureStyleUtils.js index 3ad3aca1f..4722dacec 100644 --- a/src/utils/featureStyleUtils.js +++ b/src/utils/featureStyleUtils.js @@ -232,10 +232,13 @@ export function calculateTextOffset(textScale, iconScale, anchor, iconSize) { } /** - * OpenLayers style function that will style a feature that is not currently edited but loaded in - * the drawing layer. + * Style function that renders a feature with the distinct Geoadmin style. Meaning, by default, all + * red. * - * It can then be selected by the user, but this time the styling will be done by + * If an editableFeature is found attached to the feature, its properties will be used to set + * color/text and such things. + * + * To style a selected feature, within the drawing module context, please use * {@link editingFeatureStyleFunction} * * @param {Feature} feature OpenLayers feature to style @@ -243,45 +246,49 @@ export function calculateTextOffset(textScale, iconScale, anchor, iconSize) { * meters / pixel for the webmercator projection used in this project) * @returns {Style[]} */ -export function featureStyleFunction(feature, resolution) { +export function geoadminStyleFunction(feature, resolution) { const editableFeature = feature.get('editableFeature') - if (!editableFeature) { - return + + const styleConfig = { + fillColor: editableFeature?.fillColor ?? RED, + strokeColor: editableFeature?.strokeColor ?? RED, + textColor: editableFeature?.textColor ?? RED, } + // Tells if we are drawing a polygon for the first time, in this case we want // to fill this polygon with a transparent white (instead of red) - const isDrawing = feature.get('isDrawing') + const isDrawing = !!feature.get('isDrawing') const styles = [ new Style({ - geometry: feature.get('geodesic')?.getGeodesicGeom(), - image: editableFeature.generateOpenlayersIcon(), + geometry: feature.get('geodesic')?.getGeodesicGeom() ?? feature.getGeometry(), + image: editableFeature?.generateOpenlayersIcon(), text: new Text({ - text: editableFeature.title, + text: editableFeature?.title ?? feature.get('name'), //font: editableFeature.font, font: `normal 16px Helvetica`, fill: new Fill({ - color: editableFeature.textColor.fill, + color: styleConfig.textColor.fill, }), stroke: new Stroke({ - color: editableFeature.textColor.border, + color: styleConfig.textColor.border, width: 3, }), - scale: editableFeature.textSizeScale || 1, - offsetX: editableFeature.textOffset[0], - offsetY: editableFeature.textOffset[1], + scale: editableFeature?.textSizeScale ?? 1, + offsetX: editableFeature?.textOffset[0] ?? 0, + offsetY: editableFeature?.textOffset[1] ?? 0, }), stroke: - editableFeature.featureType === EditableFeatureTypes.MEASURE + editableFeature?.featureType === EditableFeatureTypes.MEASURE ? dashedRedStroke : new Stroke({ - color: editableFeature.fillColor.fill, + color: styleConfig.fillColor.fill, width: 3, }), // filling a polygon with white if first time being drawn (otherwise fallback to user set color) fill: isDrawing ? whiteSketchFill : new Fill({ - color: [...editableFeature.fillColor.rgb.slice(0, 3), 0.4], + color: [...styleConfig.fillColor.rgb.slice(0, 3), 0.4], }), zIndex: 10, }), @@ -294,11 +301,11 @@ export function featureStyleFunction(feature, resolution) { fill: isDrawing ? whiteSketchFill : new Fill({ - color: [...editableFeature.fillColor.rgb.slice(0, 3), 0.4], + color: [...styleConfig.fillColor.rgb.slice(0, 3), 0.4], }), zIndex: 0, stroke: new Stroke({ - color: editableFeature.fillColor.fill, + color: styleConfig.strokeColor.fill, width: 3, }), }) @@ -306,7 +313,7 @@ export function featureStyleFunction(feature, resolution) { } /* This function is also called when saving the feature to KML, where "feature.get('geodesic')" is not there anymore, thats why we have to check for it here */ - if (editableFeature.featureType === EditableFeatureTypes.MEASURE && feature.get('geodesic')) { + if (editableFeature?.featureType === EditableFeatureTypes.MEASURE && feature.get('geodesic')) { styles.push(...feature.get('geodesic').getMeasureStyles(resolution)) } return styles diff --git a/src/utils/kmlUtils.js b/src/utils/kmlUtils.js index 7e609c9e4..94f079b51 100644 --- a/src/utils/kmlUtils.js +++ b/src/utils/kmlUtils.js @@ -14,10 +14,11 @@ import EditableFeature, { EditableFeatureTypes } from '@/api/features/EditableFe import { extractOlFeatureCoordinates } from '@/api/features/features.api' import { proxifyUrl } from '@/api/file-proxy.api' import { DEFAULT_TITLE_OFFSET, DrawingIcon } from '@/api/icon.api' +import KmlStyles from '@/api/layers/KmlStyles.enum' import { WGS84 } from '@/utils/coordinates/coordinateSystems' import { allStylingSizes, - featureStyleFunction, + geoadminStyleFunction, getFeatureStyleColor, getStyle, getTextColor, @@ -364,12 +365,11 @@ export function getFillColor(style, geometryType, iconArgs) { * Get the geoadmin editable feature for the given open layer KML feature * * @param {Feature} kmlFeature Open layer KML feature - * @param {kmlLayer} kmlLayer Open layer KML layer * @param {DrawingIconSet[]} availableIconSets * @returns {EditableFeature | null} Returns EditableFeature or null if this is not a geoadmin * feature */ -export function getEditableFeatureFromKmlFeature(kmlFeature, kmlLayer, availableIconSets) { +export function getEditableFeatureFromKmlFeature(kmlFeature, availableIconSets) { if (!(kmlFeature instanceof Feature)) { log.error(`Cannot generate EditableFeature from KML feature`, kmlFeature) return null @@ -532,16 +532,13 @@ export function parseKml(kmlLayer, projection, iconSets, iconUrlProxy = iconUrlP dataProjection: WGS84.epsg, // KML files should always be in WGS84 featureProjection: projection.epsg, }) - // we do not force our DrawingModule styling (especially colors) to external/non-drawing KMLs - if (!kmlLayer.isExternal) { + if (kmlLayer.style === KmlStyles.GEOADMIN) { features.forEach((olFeature) => { - const editableFeature = getEditableFeatureFromKmlFeature(olFeature, kmlLayer, iconSets) - + const editableFeature = getEditableFeatureFromKmlFeature(olFeature, iconSets) if (editableFeature) { // Set the EditableFeature coordinates from the olFeature geometry editableFeature.setCoordinatesFromFeature(olFeature) olFeature.set('editableFeature', editableFeature) - olFeature.setStyle(featureStyleFunction) if (editableFeature.isLineOrMeasure()) { /* The featureStyleFunction uses the geometries calculated in the geodesic object @@ -551,6 +548,7 @@ export function parseKml(kmlLayer, projection, iconSets, iconUrlProxy = iconUrlP olFeature.set('geodesic', new GeodesicGeometries(olFeature, projection)) } } + olFeature.setStyle(geoadminStyleFunction) }) } diff --git a/src/utils/layerUtils.js b/src/utils/layerUtils.js index 9772a36f2..c98e89bd1 100644 --- a/src/utils/layerUtils.js +++ b/src/utils/layerUtils.js @@ -3,7 +3,7 @@ import GeoJSON from 'ol/format/GeoJSON' import LayerFeature from '@/api/features/LayerFeature.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' -import LayerTypes from '@/api/layers/LayerTypes.enum.js' +import LayerTypes from '@/api/layers/LayerTypes.enum' import { getBaseUrlOverride } from '@/config/baseUrl.config' import { normalizeExtent } from '@/utils/coordinates/coordinateUtils' @@ -42,6 +42,8 @@ import { normalizeExtent } from '@/utils/coordinates/coordinateUtils' * feature IDs to select. Default is `undefined` * @property {String | undefined} [customAttributes.adminId=undefined] KML admin ID required to edit * a KML drawing. Default is `undefined` + * @property {KmlStyles | undefined} [customAttributes.style=undefined] KML style to be applied to + * its features, can be one of the value from KmlStyles.enum.js. Default is `undefined` */ /** diff --git a/src/utils/styleUtils.js b/src/utils/styleUtils.js index 75b6985df..0e9ead508 100644 --- a/src/utils/styleUtils.js +++ b/src/utils/styleUtils.js @@ -17,6 +17,8 @@ function hexToRgba(hexValue, alpha = 1.0) { ] } +const STROKE_WIDTH = 3 + export const whiteSketchFill = new Fill({ color: hexToRgba(white, 0.4), }) @@ -26,23 +28,23 @@ export const redFill = new Fill({ }) /** Standard line styling */ export const redStroke = new Stroke({ - width: 3, + width: STROKE_WIDTH, color: hexToRgba(red), }) export const malibuStroke = new Stroke({ - width: 3, + width: STROKE_WIDTH, color: hexToRgba(malibu), }) /** Styling specific for measurement, with a dashed red line */ export const dashedRedStroke = new Stroke({ color: hexToRgba(red), - width: 3, + width: STROKE_WIDTH, lineDash: [8], }) -export const gpxStrokeStyle = new Stroke({ width: 1.5, color: hexToRgba(red, 1) }) +export const gpxStrokeStyle = new Stroke({ width: STROKE_WIDTH, color: hexToRgba(red, 1) }) export const pointStyle = { radius: 7, @@ -78,7 +80,7 @@ export const gpxStyles = { export const geolocationPointWidth = 10 export const geolocationPointFillColor = hexToRgba(red, 0.9) -export const geolocationPointBorderWidth = 3 +export const geolocationPointBorderWidth = STROKE_WIDTH export const geolocationPointBorderColor = hexToRgba(white, 1.0) export const geolocationPointStyle = new Style({ @@ -118,7 +120,7 @@ export const highlightedFill = new Fill({ }) export const highlightedStroke = new Stroke({ color: hexToRgba(mocassinToRed2, 1.0), - width: 3, + width: STROKE_WIDTH, }) export const hoveredFill = new Fill({ @@ -126,7 +128,7 @@ export const hoveredFill = new Fill({ }) export const hoveredStroke = new Stroke({ color: hexToRgba(red, 1.0), - width: 3, + width: STROKE_WIDTH, }) export const hoveredLinePolygonStyle = new Style({ diff --git a/tests/cypress/tests-e2e/print.cy.js b/tests/cypress/tests-e2e/print.cy.js index 21c024e90..d0f15d3ef 100644 --- a/tests/cypress/tests-e2e/print.cy.js +++ b/tests/cypress/tests-e2e/print.cy.js @@ -423,7 +423,7 @@ describe('Testing print', () => { ) expect( gpxLayer['style']["[_mfp_style = '2']"]['symbolizers'][0]['strokeWidth'] - ).to.lessThan(1.5) // thinner than the drawn in the OL map. + ).to.lessThan(2) // thinner than the drawn in the OL map. }) }) /** We need to ensure the structure of the query sent is correct */