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"> <script setup lang="ts">
const router = useRoute(); const router = useRoute();
const store = useMresoStore(); const store = useMresoStore();
watchEffect(() => { watch(
if (router.params.line_id === undefined) { () => router.params.line_id,
store.selectedRouteIds = []; () => {
} if (router.params.line_id === undefined) {
if (typeof router.params.line_id !== "string") { store.selectedRouteIds = [];
store.selectedRouteIds = []; }
} if (typeof router.params.line_id !== "string") {
store.selectedRouteIds = [router.params.line_id]; store.selectedRouteIds = [];
}); }
store.selectedRouteIds = [router.params.line_id];
},
{ immediate: true },
);
</script> </script>
<style lang="css"> <style lang="css">
+64 -14
View File
@@ -17,7 +17,6 @@
'icon-overlap': 'always', 'icon-overlap': 'always',
'text-overlap': 'always', 'text-overlap': 'always',
}" }"
@click="clusterClick"
@mouseenter="clusterEnter" @mouseenter="clusterEnter"
@mouseleave="clusterLeave" @mouseleave="clusterLeave"
/> />
@@ -32,9 +31,10 @@
Direction {{ data.pattern.lastStopName }} Direction {{ data.pattern.lastStopName }}
<div v-for="time in data.times" :key="time.tripId"> <div v-for="time in data.times" :key="time.tripId">
{{ {{
time.realtimeArrival - stopTimeToRealTime(
(Math.floor(new Date().getTime() / 1000) - time.realtimeArrival,
time.serviceDay) time.serviceDay,
)
}} }}
</div> </div>
</div> </div>
@@ -48,7 +48,7 @@ import { useMap } from "@indoorequal/vue-maplibre-gl";
import { Popup } from "maplibre-gl"; import { Popup } from "maplibre-gl";
const cluster_label = { const cluster_label = {
"text-field": ["get", "id"], "text-field": ["get", "name"],
}; };
const map = useMap("main"); const map = useMap("main");
const popupDiv = useTemplateRef("popup-div"); const popupDiv = useTemplateRef("popup-div");
@@ -65,13 +65,26 @@ const cluster_paint = {
const popupData = ref<StopTimesPattern[]>([]); const popupData = ref<StopTimesPattern[]>([]);
const popup = new Popup().setMaxWidth("500px").addTo(map.map!); const popup = new Popup().setMaxWidth("500px").addTo(map.map!);
const router = useRoute();
const store = useMresoStore();
onUnmounted(() => { onUnmounted(() => {
popup.off("close", closePopupEventHandler);
popup.remove(); 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) { async function clusterClick(e: FeatureCollection) {
popupData.value = []; popup.off("close", closePopupEventHandler);
const feature = e.features[0]; const feature = e.features[0];
if (!feature || !feature.properties) { if (!feature || !feature.properties) {
return; return;
@@ -80,26 +93,63 @@ async function clusterClick(e: FeatureCollection) {
if (!code) { if (!code) {
return; return;
} }
// let data; console.log(code);
const coordinates = feature.geometry.coordinates.slice(); navigateTo(`/lines/${router.params.line_id}/cluster/${code}`);
// [data, popupData.value] = await Promise.all([ popup.remove();
// $fetch<FeatureCollection<Point>>( popup.on("close", closePopupEventHandler);
// `https://data.mobilites-m.fr/api/clusters/${code}/stops`, }
// ),
// ]); 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[]>( 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: { headers: {
origin: "PersonalProjectMreso", origin: "PersonalProjectMreso",
}, },
}, },
); );
if (!popupDiv.value) {
return;
}
popup.setDOMContent(popupDiv.value).setLngLat(coordinates).addTo(map.map!); 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() { function clusterEnter() {
map.map!.getCanvas().style.cursor = "pointer"; 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 shownClusters = ref<FeatureCollection<Point> | null>(null);
const image = useSvgImage(); const image = useSvgImage();
const map = useMap("main"); const map = useMap("main");
watchEffect(async () => { watchEffect(async () => {
const allClusters = []; const allClusters = [];
for (const routeId of store.selectedRouteIds) { for (const routeId of store.selectedRouteIds) {
@@ -47,9 +48,10 @@ watchEffect(async () => {
shownClusters.value = { shownClusters.value = {
type: "FeatureCollection", type: "FeatureCollection",
features: allClusters features: allClusters
.map((c) => store.clustersById[c.id]) .map((c) => store.clustersByCode[c.code])
.filter((c) => c !== undefined), .filter((c) => c !== undefined),
}; };
const firstPoint = shownClusters.value.features[0]; const firstPoint = shownClusters.value.features[0];
if (!firstPoint) { if (!firstPoint) {
map.map?.flyTo({ center, zoom }); map.map?.flyTo({ center, zoom });
+4
View File
@@ -3,3 +3,7 @@
<SidebarContainer /> <SidebarContainer />
</main> </main>
</template> </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]), 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)) { if (!("features" in state.clusters)) {
return {}; return {};
} }
return Object.fromEntries( return Object.fromEntries(
state.clusters.features.map((l) => [l.properties!.id, l]), state.clusters.features.map((l) => [l.properties!.code, l]),
); );
}, },
}, },
+21 -4
View File
@@ -1,7 +1,24 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: "2025-07-15", compatibilityDate: "2025-07-15",
devtools: { enabled: false }, devtools: { enabled: false },
modules: ["@nuxt/eslint", "nuxt-maplibre", "@pinia/nuxt"], modules: ["@nuxt/eslint", "nuxt-maplibre", "@pinia/nuxt"],
ssr: true, ssr: true,
hooks: {
"pages:extend"(pages) {
// add a route
pages.push({
name: "lines",
path: "/lines/:line_id()",
file: "~/pages/index.vue",
meta: { key: "lines" },
});
pages.push({
name: "lines/cluster",
path: "/lines/:line_id()/cluster/:cluster_id()",
file: "~/pages/index.vue",
meta: { key: "lines" },
});
},
},
}); });