Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SD-266] add max zoom support #1312

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions examples/nuxt-app/test/features/maps/maps.feature
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ Feature: Custom collection map component
When I wait 2 seconds
Then the map matches the image snapshot "map-sidepanel-item-click"

@mockserver
Scenario: Reaching the maximum zoom level will show clustered features in grouped popup
Given I load the page fixture with "/maps/basic-page"
And the popup type is "popover"
And the maximum zoom level is set to "7"
And the clustering distance is set to "300"
Then the page endpoint for path "/map" returns the loaded fixture
And the "/api/tide/elasticsearch/elasticsearch_index_develop_node/_search" network request is stubbed with fixture "/maps/simple-map-results" and status 200 as alias "searchReq"
Given I visit the page "/map"
When I wait 2 seconds
Then I click the map component at coordinates 545 385
And I wait 1 seconds
Then I click the map component at coordinates 660 320
And I wait 2 seconds
Then the map matches the image snapshot "map-popover-max-zoom-cluster"

@mockserver
Scenario: Map zooms to intended initial location with results hook
Given I load the page fixture with "/maps/basic-page"
Expand Down
20 changes: 20 additions & 0 deletions packages/ripple-test-utils/step_definitions/components/maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,26 @@ Given('the popup type is {string}', (popupType) => {
})
})

Given('the maximum zoom level is set to {string}', (maxZoom) => {
cy.get('@pageFixture').then((response) => {
set(
response,
'bodyComponents[0].props.mapConfig.props.maxZoom',
Number(maxZoom)
)
})
})

Given('the clustering distance is set to {string}', (clusteringDistance) => {
cy.get('@pageFixture').then((response) => {
set(
response,
'bodyComponents[0].props.mapConfig.props.clusteringDistance',
Number(clusteringDistance)
)
})
})

Given('the side panel is enabled', () => {
cy.get('@pageFixture').then((response) => {
set(response, 'bodyComponents[0].props.mapConfig.sidePanel.enabled', true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
:noresults="noresults"
:hasSidePanel="hasSidePanel"
:getFeatureTitle="getTitle"
:clusteringDistance="clusteringDistance"
:maxZoom="maxZoom"
>
<template #noresults>
<slot name="noresults"></slot>
Expand Down Expand Up @@ -99,6 +101,8 @@ interface Props {
noresults?: boolean
hasSidePanel?: boolean
initialising?: boolean
clusteringDistance?: number
maxZoom?: number
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -111,7 +115,9 @@ const props = withDefaults(defineProps<Props>(), {
legendItems: () => [],
noresults: false,
hasSidePanel: false,
initialising: false
initialising: false,
clusteringDistance: undefined,
maxZoom: undefined
})

const appConfig = useAppConfig()
Expand Down
22 changes: 11 additions & 11 deletions packages/ripple-ui-maps/src/components/map/RplMap.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import {
import { ref, provide } from 'vue'
import { RplAccordion } from '@dpc-sdp/ripple-ui-core/vue'
import RplMap from './RplMap.vue'
import RplMapPopUpAccordion from '../popup/RplMapPopUpAccordion.vue'
import RplMapProviderEsri from './providers/RplMapProviderEsri.vue'
import RplMapProviderVicMap from './providers/RplMapProviderVicMap.vue'
import RplMapProviderMapbox from './providers/RplMapProviderMapbox.vue'
import Icon from 'ol/style/Icon'
import featureData from './__fixture__/largeset.json'
import ExampleVectorLayer from './__fixture__/VectorLayer.example.vue'
import '@dpc-sdp/ripple-ui-core/style/components'
import { getIconForPopulation } from './__fixture__/utils.ts'

export const Template = (args) => ({
components: { RplMap, RplMapProviderEsri, RplMapProviderVicMap, RplMapProviderMapbox, RplAccordion, ExampleVectorLayer },
components: { RplMap, RplMapPopUpAccordion, RplMapProviderEsri, RplMapProviderVicMap, RplMapProviderMapbox, RplAccordion, ExampleVectorLayer },
setup() {
const rplMapRef = ref(null)
const popup = ref({
Expand Down Expand Up @@ -66,20 +65,21 @@ export const Template = (args) => ({
</template>
<template #popupTitle="{ selectedFeatures }">
<span v-if="selectedFeatures.length === 1">
{{ selectedFeatures[0].title}}
{{ selectedFeatures[0].title }}
</span>
<span v-else>
{{ selectedFeatures.length }} items found in this area
</span>
</template>
<template #popupContent="{ selectedFeatures }">
<div class="rpl-u-margin-4">
<p class="rpl-type-p-small" v-if="selectedFeatures.length === 1">
{{ selectedFeatures[0].description}}
</p>
<RplAccordion v-else :items="getClusteredFeatures(selectedFeatures)" :displayToggleAll="false">
</RplAccordion>
</div>
<p class="rpl-type-p-small rpl-u-margin-t-4" v-if="selectedFeatures.length === 1">
{{ selectedFeatures[0].description }}
</p>
<RplMapPopUpAccordion v-else :features="getClusteredFeatures(selectedFeatures)" :getTitle="(f) => f.title">
<template #feature="{ feature }">
<p class="rpl-type-p-small">{{ feature.content }}</p>
</template>
</RplMapPopUpAccordion>
</template>
</RplMap>
`
Expand Down
13 changes: 8 additions & 5 deletions packages/ripple-ui-maps/src/components/map/RplMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ import {
zoomToClusterExtent,
centerMap,
fitDefaultExtent,
areFeaturesCloseTogether
areFeaturesCloseTogether,
getFeaturesCenterPoint
} from './utils'

interface Props {
features?: IRplMapFeature[]
projection?: 'EPSG:4326' | 'EPSG:3857'
initialZoom?: number
maxZoom?: number
initialCenter?: [number, number]
pinStyle?: Function
mapHeight?: number
Expand All @@ -57,6 +59,7 @@ const props = withDefaults(defineProps<Props>(), {
projection: 'EPSG:4326',
features: () => [],
initialZoom: 7.3,
maxZoom: undefined,
mapHeight: 600,
popupType: 'sidebar',
hasSidePanel: false,
Expand Down Expand Up @@ -188,16 +191,15 @@ async function onMapSingleClick(evt) {
) {
// if there are lots of features and we are zoomed out, we just zoom in a bit
view.animate({
zoom: view.getZoom() + largeClusterZoomAmount,
zoom: currentZoom + largeClusterZoomAmount,
center: evt.coordinate
})
} else {
const isCloseTogether = areFeaturesCloseTogether(point.features, 20)

if (isCloseTogether) {
if (isCloseTogether || currentZoom >= props.maxZoom) {
// if the features are very close together/in the same location we show them all in an accordion in a popup
const coords = point.features[0].getGeometry().flatCoordinates

const coords = getFeaturesCenterPoint(point.features)
popup.value.feature = point.features.map((f) => f.getProperties())
popup.value.position = coords
popup.value.isOpen = true
Expand Down Expand Up @@ -344,6 +346,7 @@ const fullScreenLabel = computed(() =>
:rotation="rotation"
:projection="projection"
:zoom="zoom"
:maxZoom="maxZoom"
:minZoom="5"
/>
<slot name="map-provider"> </slot>
Expand Down
17 changes: 17 additions & 0 deletions packages/ripple-ui-maps/src/components/map/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,23 @@ export const zoomToClusterExtent = (
})
}

export function getFeaturesCenterPoint(features) {
const coordinates = features.map(
(feature) => feature.getGeometry().flatCoordinates
)

const sum = coordinates.reduce(
(acc: number, coord: number) => {
acc[0] += coord[0]
acc[1] += coord[1]
return acc
},
[0, 0]
)

return [sum[0] / coordinates.length, sum[1] / coordinates.length]
}

/**
* Find the distance we need to move from the center of the map to account for the
* dead space taken up by the sidepanel/sidebars/popups etc. The purpose of this function
Expand Down
2 changes: 2 additions & 0 deletions packages/ripple-ui-maps/src/components/popup/RplMapPopUp.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@
}

.rpl-map-popup--popover.rpl-map-popup--area {
margin-top: var(--rpl-sp-3);

&::before {
position: absolute;
content: '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const Template = (args) => ({
</span>
</template>
<template #popupContent="{ selectedFeatures }">
<p class="rpl-type-p-small" v-if="selectedFeatures.length === 1">
<p class="rpl-type-p-small rpl-u-margin-t-4" v-if="selectedFeatures.length === 1">
{{ selectedFeatures[0].description}}
</p>
</template>
Expand Down