diff --git a/src/api/layers/KMLLayer.class.js b/src/api/layers/KMLLayer.class.js index 00b56e0f0..5694bf671 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' @@ -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) { @@ -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) @@ -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 + } } /** 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/menu/components/activeLayers/MenuActiveLayersListItem.vue b/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue index c5c8de9f7..8277ce9d6 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' @@ -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) @@ -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 @@ -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 +} @@ -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; diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue index 4a2e94701..240d1ca34 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue @@ -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" diff --git a/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.js b/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.js index 73ec94b66..a39a243d1 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.js +++ b/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.js @@ -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' @@ -67,6 +68,7 @@ export class KMLParser extends FileParser { extent: extentInCurrentProjection, extentProjection: currentProjection, linkFiles, + style: KmlStyles.DEFAULT, }) } } diff --git a/src/router/storeSync/LayerParamConfig.class.js b/src/router/storeSync/LayerParamConfig.class.js index 2285e98be..80e05bfd8 100644 --- a/src/router/storeSync/LayerParamConfig.class.js +++ b/src/router/storeSync/LayerParamConfig.class.js @@ -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 @@ -312,6 +313,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/store/plugins/load-kml-kmz-data.plugin.js b/src/store/plugins/load-kml-kmz-data.plugin.js index bdd9a37a1..c5b2e48dd 100644 --- a/src/store/plugins/load-kml-kmz-data.plugin.js +++ b/src/store/plugins/load-kml-kmz-data.plugin.js @@ -45,8 +45,8 @@ async function loadMetadata(store, kmlLayer) { */ async function loadData(store, kmlLayer) { log.debug(`Loading data for added KML layer`, kmlLayer) - // to avoid having 2 HEAD and 2 GET request in case the file is a KML, we load this data here (instead of letting each file parser load it for itself) + // to avoid having 2 HEAD and 2 GET request in case the file is a KML, we load this data here (instead of letting each file parser load it for itself) const { mimeType, loadedContent } = await getOnlineFileContent(kmlLayer.kmlFileUrl, { responseType: 'arraybuffer', }) @@ -62,51 +62,43 @@ async function loadData(store, kmlLayer) { // stopping there, there won't be anything to do with this file return } + let layer = null try { - const kmz = await kmzParser.parse( - { - fileSource: kmlLayer.kmlFileUrl, - currentProjection: store.state.position.projection, - }, - { - allowServiceProxy: false, - mimeType, + if ( + await kmzParser.isOnlineFileParsingPossible(kmlLayer.kmlFileUrl, { loadedContent, - } - ) - store.dispatch('updateLayer', { - layerId: kmlLayer.id, - values: kmz, - ...dispatcher, - }) - // avoiding going below in the KML parsing - return - } catch (error) { - // not a KMZ layer, we proceed below to check if it is a KML - } - - try { - const kml = await kmlParser.parse( - { - fileSource: kmlLayer.kmlFileUrl, - currentProjection: store.state.position.projection, - }, - { - allowServiceProxy: false, mimeType, + }) + ) { + layer = await kmzParser.parse( + { + fileSource: kmlLayer.kmlFileUrl, + currentProjection: store.state.position.projection, + }, + { + allowServiceProxy: false, + mimeType, + loadedContent, + } + ) + } else if ( + await kmlParser.isOnlineFileParsingPossible(kmlLayer.kmlFileUrl, { loadedContent, - } - ) - store.dispatch('updateLayer', { - layerId: kmlLayer.id, - values: { - ...kml, - // we have to pass the adminId (when defined) as it won't be read by the KML parser - // meaning it will be updated to null if it was previously defined - adminId: kmlLayer.adminId, - }, - ...dispatcher, - }) + mimeType, + }) + ) { + layer = await kmlParser.parse( + { + fileSource: kmlLayer.kmlFileUrl, + currentProjection: store.state.position.projection, + }, + { + allowServiceProxy: false, + mimeType, + loadedContent, + } + ) + } } catch (error) { log.error(`Error while fetching KML data for layer ${kmlLayer?.id}: ${error}`) store.dispatch('addLayerError', { @@ -117,6 +109,17 @@ async function loadData(store, kmlLayer) { ...dispatcher, }) } + if (layer) { + store.dispatch('updateLayer', { + layerId: kmlLayer.id, + values: { + name: layer.name, + kmlData: layer.kmlData, + isLoading: false, + }, + ...dispatcher, + }) + } } /** 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 272c75dae..505c850df 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, @@ -363,12 +364,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 @@ -531,16 +531,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 @@ -550,6 +547,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 0e629c60f..d20d437e9 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/extentUtils' @@ -42,6 +42,8 @@ import { normalizeExtent } from '@/utils/extentUtils' * 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 */