diff --git a/src/api/features/features.api.js b/src/api/features/features.api.js index bd2f24014..b104e07b5 100644 --- a/src/api/features/features.api.js +++ b/src/api/features/features.api.js @@ -155,7 +155,7 @@ export async function identifyOnGeomAdminLayer({ geometryType: `esriGeometry${coordinate.length === 2 ? 'Point' : 'Envelope'}`, limit: featureCount, tolerance: DEFAULT_FEATURE_IDENTIFICATION_TOLERANCE, - returnGeometry: true, + returnGeometry: layer.isHighlightable, timeInstant: getApi3TimeInstantParam(layer), lang: lang, offset, @@ -171,10 +171,12 @@ export async function identifyOnGeomAdminLayer({ screenWidth, screenHeight, mapExtent, + coordinate, }) features.push( parseGeomAdminFeature(layer, feature, featureData, projection, { lang, + coordinate, }) ) } @@ -575,9 +577,16 @@ function generateFeatureUrl(layer, featureId) { * @param {Number} [options.screenWidth] Current screen width in pixels * @param {Number} [options.screenHeight] Current screen height in pixels * @param {[Number, Number, Number, Number]} [options.mapExtent] + * @param {[Number, Number]} [options.coordinate] */ function generateFeatureParams(options = {}) { - const { lang = 'en', screenWidth = null, screenHeight = null, mapExtent = null } = options + const { + lang = 'en', + screenWidth = null, + screenHeight = null, + mapExtent = null, + coordinate = null, + } = options let imageDisplay = null if (screenWidth && screenHeight) { imageDisplay = `${screenWidth},${screenHeight},96` @@ -587,6 +596,7 @@ function generateFeatureParams(options = {}) { lang, imageDisplay, mapExtent: mapExtent?.join(',') ?? null, + coord: coordinate?.join(',') ?? null, } } @@ -600,6 +610,8 @@ function generateFeatureParams(options = {}) { * @param {String} [options.lang] The lang the title of the feature should be look up. Some features * do provide a title per lang, instead of an all-purpose title. In this case we need the lang ISO * code to be able to decide which title the feature will have. Default is `en` + * @param {[Number, Number]} [options.coordinate] Where the identify took place, will be used if no + * geometry was requested (if the layer isn't highlightable) to place the anchor of the tooltip * @returns {LayerFeature} */ function parseGeomAdminFeature( @@ -609,11 +621,10 @@ function parseGeomAdminFeature( outputProjection, options = {} ) { - const { lang = 'en' } = options - const featureGeoJSONGeometry = featureMetadata.geometry - let featureExtent = [] + const { lang = 'en', coordinate = null } = options + let featureExtent = null if (featureMetadata.bbox) { - featureExtent.push(...featureMetadata.bbox) + featureExtent = [...featureMetadata.bbox] } let featureName = featureMetadata.id if (featureMetadata.properties) { @@ -630,18 +641,30 @@ function parseGeomAdminFeature( } } - if (outputProjection.epsg !== LV95.epsg) { - if (featureExtent.length === 4) { - featureExtent = projExtent(LV95, outputProjection, featureExtent) + if (outputProjection.epsg !== LV95.epsg && featureExtent?.length === 4) { + featureExtent = projExtent(LV95, outputProjection, featureExtent) + } + + let featureGeoJSONGeometry = null + if (layer.isHighlightable) { + featureGeoJSONGeometry = featureMetadata.geometry + } else if (coordinate) { + featureGeoJSONGeometry = { + type: 'MultiPoint', + coordinates: [coordinate], } } + let coordinates = null + if (featureGeoJSONGeometry) { + coordinates = getGeoJsonFeatureCoordinates(featureGeoJSONGeometry, LV95, outputProjection) + } return new LayerFeature({ layer, id: featureMetadata.id, name: featureName, data: featureHtmlPopup, - coordinates: getGeoJsonFeatureCoordinates(featureGeoJSONGeometry, LV95, outputProjection), + coordinates, extent: featureExtent, geometry: featureGeoJSONGeometry, }) @@ -724,6 +747,8 @@ const getFeature = (layer, featureId, outputProjection, options = {}) => { * @param {Number} [options.screenHeight] Height of the screen in pixels * @param {[Number, Number, Number, Number]} [options.mapExtent] Current extent of the map, * described in LV95. + * @param {[Number, Number]} [options.coordinate] Coordinate of the click/identify, will be used by + * some layer to gather more information in the HTML popup * @returns {Promise} */ export function getFeatureHtmlPopup(layer, featureId, options) { diff --git a/src/modules/infobox/components/FeatureDetail.vue b/src/modules/infobox/components/FeatureDetail.vue index b924273f1..2f560433e 100644 --- a/src/modules/infobox/components/FeatureDetail.vue +++ b/src/modules/infobox/components/FeatureDetail.vue @@ -134,6 +134,10 @@ function getIframeHosts(value) { :global(.htmlpopup-content) { padding: 7px; } +// fix for layer HTML containing table, such as ch.bafu.gefahren-aktuelle_erdbeben +:global(.t_list) { + width: 100%; +} td { vertical-align: top; } diff --git a/src/modules/infobox/components/FeatureListCategoryItem.vue b/src/modules/infobox/components/FeatureListCategoryItem.vue index 6d14cd7f4..0d678edb8 100644 --- a/src/modules/infobox/components/FeatureListCategoryItem.vue +++ b/src/modules/infobox/components/FeatureListCategoryItem.vue @@ -15,7 +15,7 @@ const dispatcher = { dispatcher: 'FeatureListCategoryItem.vue' } const props = defineProps({ name: { - type: String, + type: [String, Number], required: true, }, item: { @@ -91,7 +91,7 @@ function showContentAndScrollIntoView(event) { @mouseleave.passive="clearHighlightedFeature" > - + {{ name }} diff --git a/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue b/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue index 0bd6b39da..99e0399b0 100644 --- a/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue +++ b/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue @@ -67,6 +67,7 @@ const popoverCoordinate = computed(() => { // If no editable feature is selected while drawing, we place the popover depending on the geometry of all // selected features. We will find the most southern coordinate present in all features and use it as anchor. const mostSouthernFeature = selectedFeatures.value + .filter((feature) => feature.geometry !== null) .map((feature) => feature.geometry) .map((geometry) => transformIntoTurfEquivalent(geometry, projection.value)) .map((turfGeom) => explode(turfGeom)) diff --git a/src/modules/map/components/openlayers/OpenLayersPopover.vue b/src/modules/map/components/openlayers/OpenLayersPopover.vue index 83cbc7971..b339acad1 100644 --- a/src/modules/map/components/openlayers/OpenLayersPopover.vue +++ b/src/modules/map/components/openlayers/OpenLayersPopover.vue @@ -11,7 +11,7 @@ import MapPopover, { MapPopoverMode } from '@/modules/map/components/MapPopover. const props = defineProps({ coordinates: { type: Array, - required: true, + default: null, }, authorizePrint: { type: Boolean, @@ -38,17 +38,20 @@ const popoverAnchor = ref(null) const olMap = inject('olMap') -watch(coordinates, getPixelForCoordinateFromMap) +watch(coordinates, calculateAnchorPosition) onMounted(() => { - getPixelForCoordinateFromMap() - olMap.on('postrender', getPixelForCoordinateFromMap) + calculateAnchorPosition() + olMap.on('postrender', calculateAnchorPosition) }) onUnmounted(() => { - olMap.un('postrender', getPixelForCoordinateFromMap) + olMap.un('postrender', calculateAnchorPosition) }) -function getPixelForCoordinateFromMap() { +function calculateAnchorPosition() { + if (!coordinates.value) { + return + } const computedPixel = olMap.getPixelFromCoordinate(coordinates.value) // when switching back from Cesium (or any other map framework), there can be a very small // period where the map isn't yet able to process a pixel, this if is there to defend against that diff --git a/src/router/storeSync/LayerParamConfig.class.js b/src/router/storeSync/LayerParamConfig.class.js index 9609bdfb8..1bced074f 100644 --- a/src/router/storeSync/LayerParamConfig.class.js +++ b/src/router/storeSync/LayerParamConfig.class.js @@ -122,23 +122,20 @@ export function createLayerObject(parsedLayer, currentLayer, store, featuresRequ layer.updateDelay = updateDelay } - if (features !== undefined) { + // only highlightable feature will output something, for the others a click coordinate is required + // (and we don't have it if we are here, as we are dealing with pre-selected feature in the URL at app startup) + if (layer.isHighlightable && features !== undefined) { features .toString() .split(':') .forEach((featureId) => { featuresRequests.push( - getFeature( - store.getters.getLayerConfigById(parsedLayer.id), - featureId, - store.state.position.projection, - { - lang: store.state.i18n.lang, - screenWidth: store.state.ui.width, - screenHeight: store.state.ui.height, - mapExtent: flattenExtent(store.getters.extent), - } - ) + getFeature(layer, featureId, store.state.position.projection, { + lang: store.state.i18n.lang, + screenWidth: store.state.ui.width, + screenHeight: store.state.ui.height, + mapExtent: flattenExtent(store.getters.extent), + }) ) }) } diff --git a/src/store/modules/features.store.js b/src/store/modules/features.store.js index 39d523c3c..9b9c0309e 100644 --- a/src/store/modules/features.store.js +++ b/src/store/modules/features.store.js @@ -17,10 +17,11 @@ import log from '@/utils/logging' /** @param {SelectableFeature} feature */ export function canFeatureShowProfile(feature) { return ( - (feature?.geometry?.type && ['LineString', 'Polygon'].includes(feature.geometry.type)) || - // if MultiLineString or MultiPolygon but only contains one "feature", that's fine too (mislabeled as "multi") - (['MultiLineString', 'MultiPolygon'].includes(feature.geometry.type) && - feature.geometry.coordinates.length === 1) + feature?.geometry?.type && + (['LineString', 'Polygon'].includes(feature.geometry.type) || + // if MultiLineString or MultiPolygon but only contains one "feature", that's fine too (mislabeled as "multi") + (['MultiLineString', 'MultiPolygon'].includes(feature.geometry.type) && + feature.geometry.coordinates.length === 1)) ) } @@ -653,6 +654,7 @@ export default { screenWidth: rootState.ui.width, screenHeight: rootState.ui.height, mapExtent: flattenExtent(rootState.getters.mapExtent), + coordinate: rootState.map.clickInfo?.coordinate, }) ) }