Skip to content

Commit

Permalink
Track cluster changes across zooms
Browse files Browse the repository at this point in the history
  • Loading branch information
wpf500 committed Jan 7, 2025
1 parent 0dfdf01 commit 3f48dad
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

<script lang="ts" setup>
import { faArrowLeft, faArrowRight } from '@fortawesome/free-solid-svg-icons';
import { ref, watch } from 'vue';
import { computed } from 'vue';
import CalloutSidePanel from './CalloutSidePanel.vue';
import {
Expand All @@ -44,31 +44,24 @@ import AppButton from '@components/button/AppButton.vue';
import AppButtonGroup from '@components/button/AppButtonGroup.vue';
import { useI18n } from 'vue-i18n';
const emit = defineEmits<{
(e: 'close'): void;
(e: 'change', responseNumber: number): void;
}>();
defineEmits<{ (e: 'close'): void }>();
const props = defineProps<{
callout: GetCalloutDataWith<'form' | 'responseViewSchema'>;
responses: GetCalloutResponseMapData[];
}>();
const currentResponseNumber = defineModel<number>('currentResponseNumber', {
default: 0,
});
const { n } = useI18n();
const responseIndex = ref(0);
const responseIndex = computed(() =>
props.responses.findIndex((r) => r.number === currentResponseNumber.value)
);
function changeResponse(inc: number) {
responseIndex.value = Math.max(
0,
Math.min(props.responses.length - 1, responseIndex.value + inc)
);
emit('change', props.responses[responseIndex.value].number);
const newIndex = responseIndex.value + inc;
currentResponseNumber.value = props.responses[newIndex].number;
}
watch(
() => props.responses,
() => {
responseIndex.value = 0;
}
);
</script>
112 changes: 85 additions & 27 deletions apps/frontend/src/pages/callouts/[id]/map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ meta:
@map:load="handleLoad"
@map:click="handleClick"
@map:mousemove="handleMouseOver"
@map:zoom="handleZoom"
>
<MglNavigationControl />
<MglScaleControl />
Expand Down Expand Up @@ -158,7 +159,11 @@ meta:
<CalloutShowResponsePanel
:callout="callout"
:responses="selectedResponses"
:current-response-number="selectedResponseNumber"
@close="router.push({ ...route, hash: '' })"
@update:current-response-number="
router.push({ ...route, hash: HASH_PREFIX + $event })
"
/>

<CalloutIntroPanel
Expand All @@ -177,7 +182,15 @@ meta:
</template>

<script lang="ts" setup>
import { computed, onBeforeMount, onMounted, ref, toRef, watch } from 'vue';
import {
computed,
onBeforeMount,
onMounted,
ref,
toRef,
watch,
watchEffect,
} from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
MglMap,
Expand Down Expand Up @@ -314,21 +327,14 @@ const responsesCollecton = computed<
});
// The currently selected response or cluster GeoJSON Feature
const selectedFeature = computed(() => {
if (!map.value || !route.hash.startsWith(HASH_PREFIX)) return;
const responseNumber = Number(route.hash.slice(HASH_PREFIX.length));
return map.value.queryRenderedFeatures({
layers: ['unclustered-points', 'clusters'],
filter: ['in', `<${responseNumber}>`, ['get', 'all_responses']],
})[0] as unknown as PointFeature | undefined;
});
const selectedFeature = ref<PointFeature>();
const selectedResponseNumber = ref(-1);
const clusterCount = ref(0);
const selectedResponses = computed(() => {
if (!selectedFeature.value) return [];
const responseNumbers = selectedFeature.value?.properties.all_responses;
const responseNumbers = selectedFeature.value.properties.all_responses;
if (!responseNumbers) return [];
return (
responseNumbers
Expand All @@ -343,19 +349,71 @@ const selectedResponses = computed(() => {
});
/**
* Centre the map on the selected feature when it changes
* Centre the map on the selected response when it changes
*/
watch(selectedFeature, (newFeature) => {
if (!map.value || !newFeature) return;
watch(selectedResponseNumber, () => {
if (!map.value || !selectedFeature.value) return;
introOpen.value = false;
map.value.easeTo({
center: newFeature.geometry.coordinates as LngLatLike,
center: selectedFeature.value.geometry.coordinates as LngLatLike,
padding: { left: sidePanelRef.value?.offsetWidth || 0 },
});
});
/**
* Whenever the hash or cluster count changes check. The selected feature can
* change even if the response number doesn't, because at different zooms levels
* the response is part of different clusters
* feature
*/
watchEffect(() => {
if (!map.value || !route.hash.startsWith(HASH_PREFIX)) {
selectedFeature.value = undefined;
selectedResponseNumber.value = -1;
return;
}
// We need to trigger this watcher when the cluster count changes as this
// indicates that the map has been zoomed in or out, but we don't actually
// need the value
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
clusterCount.value;
const responseNumber = Number(route.hash.slice(HASH_PREFIX.length));
const feature = map.value.queryRenderedFeatures({
layers: ['unclustered-points', 'clusters'],
filter: ['in', `<${responseNumber}>`, ['get', 'all_responses']],
})[0] as unknown as PointFeature | undefined;
// Only update selectedFeature if it has changed, as queryRenderedFeature
// doesn't have stable object references
if (
feature?.properties.all_responses !==
selectedFeature.value?.properties.all_responses
) {
selectedFeature.value = feature;
}
// This should really be a computed property on the route hash, but we need to
// respond to it after the selected feature has been updated
selectedResponseNumber.value = responseNumber;
});
/**
* Handle zoom events. This just keeps track of the number of clusters on the
* map so we can identify when the clusters are reclustered
* https://stackoverflow.com/questions/58768283/detect-when-map-reclusters-features
*/
function handleZoom() {
if (!map.value) return;
const clusters = map.value.queryRenderedFeatures({ layers: ['clusters'] });
clusterCount.value = clusters.length;
}
/**
* Handle clicking on the map to add a new response. This will set the new response
* address and geocode it to get a formatted address
Expand Down Expand Up @@ -473,16 +531,6 @@ function handleMouseOver(e: { event: MapMouseEvent; map: Map }) {
e.map.getCanvas().style.cursor = features.length > 0 ? 'pointer' : '';
}
// Start add response mode
function handleStartAddMode() {
router.push({ ...route, hash: '#add' });
}
// Cancel add response mode, clearing any state that is left over
function handleCancelAddMode() {
router.push({ ...route, hash: '' });
}
/**
* Handle map load. Attach a listener to wait until the source data is loaded.
* Add a geocoding control to the map if a key is available
Expand Down Expand Up @@ -533,6 +581,16 @@ function handleLoad({ map: mapInstance }: { map: Map }) {
}
}
// Start add response mode
function handleStartAddMode() {
router.push({ ...route, hash: '#add' });
}
// Cancel add response mode, clearing any state that is left over
function handleCancelAddMode() {
router.push({ ...route, hash: '' });
}
// Toggle add mode
watch(isAddMode, (v) => {
if (!map.value) return;
Expand Down

0 comments on commit 3f48dad

Please sign in to comment.