Skip to content

Commit

Permalink
Merge pull request #970 from geoadmin/fix-PB-661-android-feature-sele…
Browse files Browse the repository at this point in the history
  • Loading branch information
pakb authored Jul 2, 2024
2 parents 1eef52c + a642913 commit 77d1dd9
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 250 deletions.
10 changes: 5 additions & 5 deletions src/modules/map/components/LocationPopup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ watch(showEmbedSharing, () => {
})
watch(
() => route.query,
(newQuery, oldQuery) => {
//Cannot watch language and zoom directly due to the delayed url update
() => {
if (showEmbedSharing.value) {
if (oldQuery['lang'] != newQuery['lang']) {
updateShareLink()
}
updateShareLink()
}
},
{
deep: true,
}
)
Expand Down
91 changes: 0 additions & 91 deletions src/modules/map/components/common/mouse-click.composable.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { useStore } from 'vuex'
import LayerFeature from '@/api/features/LayerFeature.class'
import LayerTypes from '@/api/layers/LayerTypes.enum'
import { DRAWING_HIT_TOLERANCE, IS_TESTING_WITH_CYPRESS } from '@/config'
import { useMouseOnMap } from '@/modules/map/components/common/mouse-click.composable'
import { useDragBoxSelect } from '@/modules/map/components/openlayers/utils/useDragBoxSelect.composable'
import { normalizeExtent } from '@/utils/coordinates/coordinateUtils.js'
import { ClickInfo, ClickType } from '@/store/modules/map.store'
import { normalizeExtent } from '@/utils/coordinates/coordinateUtils'
import log from '@/utils/logging'

export default function useMapInteractions(map) {
const { onLeftClickDown, onLeftClickUp, onRightClick, onMouseMove } = useMouseOnMap()
const store = useStore()

const isCurrentlyDrawing = computed(() => store.state.drawing.drawingOverlay.show)
Expand Down Expand Up @@ -64,18 +63,28 @@ export default function useMapInteractions(map) {
unregisterPointerEvents()
})

/*
* Many ways of dealing with click explained as below :
*
* - Mouse down -> less than 500ms -> Mouse up => OL fires a singleclick event, we handle it in onMapLeftClick
* - Mouse down -> Mouse move -> Mouse up (time doesn't matter) => We do nothing, the map has moved
* - Mouse down -> 500ms (no map move) -> we detect a long press and trigger a right click (singleclick needs then to be muted in this case)
*
*/
let mapHasMoved = false
let longClickTriggered = false
let longClickTimeout

function registerPointerEvents() {
log.debug(`Register map pointer events`)
const mapElement = map.getTargetElement()
if (mapElement) {
mapElement.addEventListener('pointerdown', onPointerDown)
mapElement.addEventListener('pointerup', onPointerUp)
mapElement.addEventListener('pointermove', onMouseMove)
if (IS_TESTING_WITH_CYPRESS) {
window.mapPointerEventReady = true
}
} else {
log.error(`Failed to set map pointer events, map element not found`)
map.on('singleclick', onMapLeftClick)
map.on('contextmenu', onMapRightClick)
map.on('pointermove', onMapMove)

map.getTargetElement().addEventListener('pointerdown', onMapPointerDown)

if (IS_TESTING_WITH_CYPRESS) {
window.mapPointerEventReady = true
}
}

Expand All @@ -84,88 +93,113 @@ export default function useMapInteractions(map) {
if (IS_TESTING_WITH_CYPRESS) {
window.mapPointerEventReady = false
}
const mapElement = map.getTargetElement()
if (mapElement) {
mapElement.removeEventListener('pointerdown', onPointerDown)
mapElement.removeEventListener('pointerup', onPointerUp)
mapElement.removeEventListener('pointermove', onMouseMove)
}

map.getTargetElement().removeEventListener('pointerdown', onMapPointerDown)

map.un('pointermove', onMapMove)
map.un('singleclick', onMapLeftClick)
map.un('contextmenu', onMapRightClick)
}

function onPointerDown(event) {
log.debug(`map pointer down event ${event.target?.nodeName}`)
// Checking that we are dealing with OL canvas here, and not another part of OL elements,
// such as the floating tooltip. Without this check, clicking on the floating tooltip button
// will trigger an identification of feature at the position of the button.
if (event.target?.nodeName?.toLowerCase() === 'canvas') {
const pixel = [event.x, event.y]
onLeftClickDown(event.pixel, map.getCoordinateFromPixel(pixel))
function onMapLeftClick(event) {
clearTimeout(longClickTimeout)
if (longClickTriggered) {
// click was already processed, ignoring (will otherwise select features at the same time as the right click has been triggered)
longClickTriggered = false
return
}
}
function onPointerUp(event) {
log.debug(`map pointer up event ${event.target?.nodeName}`)
// see comment in onPointDown why we check that we deal with the canvas only
if (event.target?.nodeName?.toLowerCase() === 'canvas') {
const pixel = [event.x, event.y]
const coordinate = map.getCoordinateFromPixel(pixel)
const features = []
activeVectorLayers.value.forEach((vectorLayer) => {
map.getLayers().forEach((olLayer) => {
if (olLayer.get('id') === vectorLayer.id) {
const layerFeatures = map
.getFeaturesAtPixel(pixel, {
layerFilter: (layer) => layer.get('id') === olLayer.get('id'),
hitTolerance: DRAWING_HIT_TOLERANCE,
})
.map(
(olFeature) =>
new LayerFeature({
layer: vectorLayer,
id: olFeature.getId(),
name:
olFeature.get('label') ??
// exception for MeteoSchweiz GeoJSONs, we use the station name instead of the ID
// some of their layers are
// - ch.meteoschweiz.messwerte-niederschlag-10min
// - ch.meteoschweiz.messwerte-lufttemperatur-10min
olFeature.get('station_name') ??
olFeature.getId(),
data: {
title: olFeature.get('name'),
description: olFeature.get('description'),
},
coordinates: olFeature.getGeometry().getCoordinates(),
geometry: new GeoJSON().writeGeometryObject(
olFeature.getGeometry()
),
extent: normalizeExtent(
olFeature.getGeometry().getExtent()
),
})
)
// unique filter on features (OL sometimes return twice the same features)
.filter(
(feature, index, self) =>
self.indexOf(
self.find(
(anotherFeature) => anotherFeature.id === feature.id
)
) === index
)
if (layerFeatures.length > 0) {
features.push(...layerFeatures)
}
const { coordinate, pixel } = event
const features = []
activeVectorLayers.value.forEach((vectorLayer) => {
map.getLayers().forEach((olLayer) => {
if (olLayer.get('id') === vectorLayer.id) {
const layerFeatures = map
.getFeaturesAtPixel(pixel, {
layerFilter: (layer) => layer.get('id') === olLayer.get('id'),
hitTolerance: DRAWING_HIT_TOLERANCE,
})
.map(
(olFeature) =>
new LayerFeature({
layer: vectorLayer,
id: olFeature.getId(),
name:
olFeature.get('label') ??
// exception for MeteoSchweiz GeoJSONs, we use the station name instead of the ID
// some of their layers are
// - ch.meteoschweiz.messwerte-niederschlag-10min
// - ch.meteoschweiz.messwerte-lufttemperatur-10min
olFeature.get('station_name') ??
olFeature.getId(),
data: {
title: olFeature.get('name'),
description: olFeature.get('description'),
},
coordinates: olFeature.getGeometry().getCoordinates(),
geometry: new GeoJSON().writeGeometryObject(
olFeature.getGeometry()
),
extent: normalizeExtent(olFeature.getGeometry().getExtent()),
})
)
// unique filter on features (OL sometimes return twice the same features)
.filter(
(feature, index, self) =>
self.indexOf(
self.find((anotherFeature) => anotherFeature.id === feature.id)
) === index
)
if (layerFeatures.length > 0) {
features.push(...layerFeatures)
}
})
}
})
switch (event.button) {
case 0:
onLeftClickUp(pixel, coordinate, features)
break
case 2:
onRightClick(pixel, coordinate)
break
})
store.dispatch('click', {
clickInfo: new ClickInfo({
coordinate,
pixelCoordinate: pixel,
features,
clickType: ClickType.LEFT_SINGLECLICK,
}),
})
}

function onMapRightClick(event) {
clearTimeout(longClickTimeout)
store.dispatch('click', {
clickInfo: new ClickInfo({
coordinate: event.coordinate,
pixelCoordinate: event.pixel,
features: [],
clickType: ClickType.CONTEXTMENU,
}),
})
}

function onMapMove() {
mapHasMoved = true
clearTimeout(longClickTimeout)
}

function onMapPointerDown(event) {
clearTimeout(longClickTimeout)
mapHasMoved = false
// triggering a long click on the same spot after 500ms, so that mobile cas have access to the
// LocationPopup by touching the same-ish spot for 500ms
longClickTimeout = setTimeout(() => {
if (!mapHasMoved) {
longClickTriggered = true
// we are outside of OL event handling, on the HTML element, so we do not receive map pixel and coordinate automatically
const pixel = map.getEventPixel(event)
const coordinate = map.getCoordinateFromPixel(pixel)
onMapRightClick({
...event,
pixel,
coordinate,
})
}
}
mapHasMoved = false
}, 500)
}
}
15 changes: 12 additions & 3 deletions tests/cypress/tests-e2e/drawing.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ describe('Drawing module tests', () => {
}
function readCoordinateClipboard(name, coordinate) {
cy.log(name)
cy.get(`[data-cy="${name}-button"]`).focus()
cy.get(`[data-cy="${name}-button"]`).realClick()
cy.get(`[data-cy="${name}-icon"]`).should('have.class', 'fa-check')
cy.get(`[data-cy="${name}-button"]`).click()
cy.readClipboardValue().then((clipboardText) => {
expect(clipboardText).to.be.equal(coordinate)
})
Expand Down Expand Up @@ -267,6 +265,11 @@ describe('Drawing module tests', () => {
)
cy.log('Coordinates for marker are updated when selecting new marker')
cy.get('[data-cy="ol-map"]').click(200, 234)
// OL waits 250ms before deciding a click is a single click (and then start the event chain)
// and as we do not have a layer that will fire identify features to wait on, we have to resort
// to wait arbitrarily 250ms
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(250)
readCoordinateClipboard(
'feature-detail-coordinate-copy',
"2'680'013.50, 1'210'172.00"
Expand Down Expand Up @@ -448,6 +451,7 @@ describe('Drawing module tests', () => {
cy.get('[data-cy="ol-map"]').click(180, 160)
cy.get('[data-cy="feature-detail-media-disclaimer-opened"]').should('not.exist')
cy.get('[data-cy="feature-detail-media-disclaimer-closed"]').should('be.visible')
cy.get('[data-cy="infobox-close"]').click()

cy.log('Disclaimer should not appear when host is whitelisted')
cy.mockupBackendResponse('**map.geo.admin.ch*', {}, 'map-geo-admin')
Expand Down Expand Up @@ -530,6 +534,11 @@ describe('Drawing module tests', () => {
readCoordinateClipboard('feature-detail-coordinate-copy', "2'660'013.50, 1'227'172.00")
cy.log('Coordinates for annotation are updated when selecting new marker')
cy.get('[data-cy="ol-map"]').click('center')
// OL waits 250ms before deciding a click is a single click (and then start the event chain)
// and as we do not have a layer that will fire identify features to wait on, we have to resort
// to wait arbitrarily 250ms
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(250)
readCoordinateClipboard('feature-detail-coordinate-copy', "2'660'013.50, 1'185'172.00")
})
it('can create line/polygons and edit them', () => {
Expand Down
Loading

0 comments on commit 77d1dd9

Please sign in to comment.