cluster selection

This commit is contained in:
2026-07-12 11:19:16 +02:00
parent 9f1104b34f
commit 235cdacf9e
8 changed files with 107 additions and 40 deletions
+13 -9
View File
@@ -8,15 +8,19 @@
<script setup lang="ts">
const router = useRoute();
const store = useMresoStore();
watchEffect(() => {
if (router.params.line_id === undefined) {
store.selectedRouteIds = [];
}
if (typeof router.params.line_id !== "string") {
store.selectedRouteIds = [];
}
store.selectedRouteIds = [router.params.line_id];
});
watch(
() => router.params.line_id,
() => {
if (router.params.line_id === undefined) {
store.selectedRouteIds = [];
}
if (typeof router.params.line_id !== "string") {
store.selectedRouteIds = [];
}
store.selectedRouteIds = [router.params.line_id];
},
{ immediate: true },
);
</script>
<style lang="css">
+64 -14
View File
@@ -17,7 +17,6 @@
'icon-overlap': 'always',
'text-overlap': 'always',
}"
@click="clusterClick"
@mouseenter="clusterEnter"
@mouseleave="clusterLeave"
/>
@@ -32,9 +31,10 @@
Direction {{ data.pattern.lastStopName }}
<div v-for="time in data.times" :key="time.tripId">
{{
time.realtimeArrival -
(Math.floor(new Date().getTime() / 1000) -
time.serviceDay)
stopTimeToRealTime(
time.realtimeArrival,
time.serviceDay,
)
}}
</div>
</div>
@@ -48,7 +48,7 @@ import { useMap } from "@indoorequal/vue-maplibre-gl";
import { Popup } from "maplibre-gl";
const cluster_label = {
"text-field": ["get", "id"],
"text-field": ["get", "name"],
};
const map = useMap("main");
const popupDiv = useTemplateRef("popup-div");
@@ -65,13 +65,26 @@ const cluster_paint = {
const popupData = ref<StopTimesPattern[]>([]);
const popup = new Popup().setMaxWidth("500px").addTo(map.map!);
const router = useRoute();
const store = useMresoStore();
onUnmounted(() => {
popup.off("close", closePopupEventHandler);
popup.remove();
});
watch(
() => router.params.cluster_id,
async () => {
popup.off("close", closePopupEventHandler);
await routerParamChange();
popup.on("close", closePopupEventHandler);
},
{ immediate: true },
);
async function clusterClick(e: FeatureCollection) {
popupData.value = [];
popup.off("close", closePopupEventHandler);
const feature = e.features[0];
if (!feature || !feature.properties) {
return;
@@ -80,26 +93,63 @@ async function clusterClick(e: FeatureCollection) {
if (!code) {
return;
}
// let data;
const coordinates = feature.geometry.coordinates.slice();
// [data, popupData.value] = await Promise.all([
// $fetch<FeatureCollection<Point>>(
// `https://data.mobilites-m.fr/api/clusters/${code}/stops`,
// ),
console.log(code);
navigateTo(`/lines/${router.params.line_id}/cluster/${code}`);
popup.remove();
popup.on("close", closePopupEventHandler);
}
// ]);
async function routerParamChange() {
if (router.params.cluster_id === undefined) {
popup.remove();
return;
}
if (typeof router.params.cluster_id !== "string") {
return;
}
const cluster = store.clustersByCode[router.params.cluster_id];
if (!cluster) {
return;
}
const code = cluster.properties?.code;
if (!code) {
return;
}
popupData.value = [];
const coordinates = cluster.geometry.coordinates.slice();
popupData.value = await $fetch<StopTimesPattern[]>(
`https://data.mobilites-m.fr/api/routers/default/index/clusters/${feature.properties.code}/stoptimes?showCancelledTrips=false&route=${route.id}`,
`https://data.mobilites-m.fr/api/routers/default/index/clusters/${code}/stoptimes?showCancelledTrips=false&route=${route.id}`,
{
headers: {
origin: "PersonalProjectMreso",
},
},
);
if (!popupDiv.value) {
return;
}
popup.setDOMContent(popupDiv.value).setLngLat(coordinates).addTo(map.map!);
}
function closePopupEventHandler() {
navigateTo(`/lines/${router.params.line_id}`);
}
function stopTimeToRealTime(realtimeArrival: number, serviceDay: number) {
const seconds =
realtimeArrival -
(Math.floor(new Date().getTime() / 1000) - serviceDay);
if (seconds < 0) {
return "00:00:00";
}
const date = new Date(0);
date.setSeconds(seconds); // specify value for SECONDS here
const timeString = date.toISOString().substring(11, 19);
return timeString;
}
function clusterEnter() {
map.map!.getCanvas().style.cursor = "pointer";
}
+3 -1
View File
@@ -33,6 +33,7 @@ await store.fetchData();
const shownClusters = ref<FeatureCollection<Point> | null>(null);
const image = useSvgImage();
const map = useMap("main");
watchEffect(async () => {
const allClusters = [];
for (const routeId of store.selectedRouteIds) {
@@ -47,9 +48,10 @@ watchEffect(async () => {
shownClusters.value = {
type: "FeatureCollection",
features: allClusters
.map((c) => store.clustersById[c.id])
.map((c) => store.clustersByCode[c.code])
.filter((c) => c !== undefined),
};
const firstPoint = shownClusters.value.features[0];
if (!firstPoint) {
map.map?.flyTo({ center, zoom });
+4
View File
@@ -3,3 +3,7 @@
<SidebarContainer />
</main>
</template>
<script lang="ts">
definePageMeta({ key: "lines" });
</script>
@@ -1,5 +0,0 @@
<template>
<main>
<SidebarContainer />
</main>
</template>
-5
View File
@@ -1,5 +0,0 @@
<template>
<div>
<SidebarContainer />
</div>
</template>
+2 -2
View File
@@ -35,12 +35,12 @@ export const useMresoStore = defineStore("mresoStore", {
state.lines.features.map((l) => [l.properties!.id, l]),
);
},
clustersById(state): { [id: string]: Feature<Point> } {
clustersByCode(state): { [id: string]: Feature<Point> } {
if (!("features" in state.clusters)) {
return {};
}
return Object.fromEntries(
state.clusters.features.map((l) => [l.properties!.id, l]),
state.clusters.features.map((l) => [l.properties!.code, l]),
);
},
},