2 Commits

Author SHA1 Message Date
Alexis
c47bd534f4 Added subpar car-first routing 2026-04-12 15:10:22 +02:00
Alexis
b26969d682 Merge branch 'feature/map-geoloc' into dev 2026-04-10 22:03:49 +02:00
5 changed files with 82 additions and 13 deletions

View File

@@ -10,11 +10,14 @@ defineProps<{
maxClusterRadius: number
disableClusteringAtZoom: number
}>()
const emit = defineEmits<{ nudgeGeolocation: [] }>()
</script>
<template>
<LMarkerClusterGroup v-if="parkings.length > 0" :visible :max-cluster-radius :disable-clustering-at-zoom>
<BikeMarker v-once v-for="park in parkings" :key="park.id_parc_velo" :park :group />
<BikeMarker @nudge-geolocation="emit('nudgeGeolocation')" v-once v-for="park in parkings" :key="park.id_parc_velo"
:park :group />
</LMarkerClusterGroup>
</template>

View File

@@ -10,12 +10,13 @@ import type { ApiResponse } from '@/types/Api'
import type { BikeParking } from '@/types/Bikes'
import { API_BASE_URL, API_LIMIT, MAP_TILELAYER_URL, SpotAccess, SpotType } from '@/utils/const'
import { useQuery } from '@pinia/colada'
import { LControlZoom, LIcon, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
import { LControlZoom, LGeoJson, LIcon, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
import BikeFilters from './BikeFilters.vue'
import { computed, ref, useTemplateRef } from 'vue'
import { storeToRefs } from 'pinia'
import BikeClusterLayer from './BikeClusterLayer.vue'
import { PhCircleNotch, PhMapPin } from '@phosphor-icons/vue'
import { useRouting } from '@/composables/useRouting'
// Map setup
const mapRef = useTemplateRef<{ leafletObject: Map }>('map')
@@ -91,6 +92,11 @@ function handleClickGeoloc() {
mapRef.value?.leafletObject.flyTo([userCoords.value.latitude, userCoords.value.longitude], 12)
}
}
// Routing
const { activeRoute } = useRouting()
const showGeolocationNudge = ref(false)
</script>
<template>
@@ -100,6 +106,12 @@ function handleClickGeoloc() {
:nb-covered="coveredParkingsSpots" :nb-korrigo="premiumParkingsSpots" @click-geoloc="handleClickGeoloc" />
</Transition>
<Transition>
<div v-if="showGeolocationNudge" class="...">
Activez la géolocalisation pour obtenir un itinéraire
</div>
</Transition>
<main class="relative z-0 h-screen w-screen grid place-items-center">
<div v-if="state.status === 'pending'" class="flex flex-col items-center gap-3 text-sm">
<span>Chargement des parkings {{ progress }}%</span>
@@ -116,18 +128,20 @@ function handleClickGeoloc() {
<LControlZoom position="bottomright" />
<LTileLayer v-once :url="MAP_TILELAYER_URL" layer-type="base" />
<LGeoJson v-if="activeRoute" :geojson="activeRoute" />
<LMarker v-if="userCoords" :lat-lng="[userCoords.latitude, userCoords.longitude]">
<LIcon>
<PhMapPin size="24" weight="fill" class="text-amber-400" />
</LIcon>
</LMarker>
<BikeClusterLayer :parkings="coveredParkings" group="covered" :visible="filterCovered" :max-cluster-radius
:disable-clustering-at-zoom />
<BikeClusterLayer :parkings="nonCoveredParkings" group="non-covered" :visible="filterUncovered"
:max-cluster-radius :disable-clustering-at-zoom />
<BikeClusterLayer :parkings="premiumParkings" group="premium" :visible="filterKorrigo" :max-cluster-radius
:disable-clustering-at-zoom />
<BikeClusterLayer @nudge-geolocation="showGeolocationNudge = true" :parkings="coveredParkings" group="covered"
:visible="filterCovered" :max-cluster-radius :disable-clustering-at-zoom />
<BikeClusterLayer @nudge-geolocation="showGeolocationNudge = true" :parkings="nonCoveredParkings"
group="non-covered" :visible="filterUncovered" :max-cluster-radius :disable-clustering-at-zoom />
<BikeClusterLayer @nudge-geolocation="showGeolocationNudge = true" :parkings="premiumParkings" group="premium"
:visible="filterKorrigo" :max-cluster-radius :disable-clustering-at-zoom />
</LMap>
</template>
</main>

View File

@@ -1,13 +1,14 @@
<script lang="ts" setup>
import { cn } from '@/utils/cn';
import { useRouting } from '@/composables/useRouting';
import { SPOTS_MAX, useMap } from '@/stores/map';
import type { BikeParking } from '@/types/Bikes';
import { cn } from '@/utils/cn';
import { BOXED_KEY, COVERED_KEY, KORRIGO_KEY, UNCOVERED_KEY } from '@/utils/const';
import { PhBicycle, PhLockKey, PhSquareHalf, PhWarehouse } from '@phosphor-icons/vue';
import { LIcon, LMarker, LPopup } from '@vue-leaflet/vue-leaflet';
import type { PointTuple } from 'leaflet';
import { computed, ref } from 'vue';
import { UNCOVERED_KEY, COVERED_KEY, BOXED_KEY, KORRIGO_KEY } from '@/utils/const';
import { storeToRefs } from 'pinia';
import { SPOTS_MAX, useMap } from '@/stores/map';
import { computed, ref } from 'vue';
const props = defineProps<{
park: BikeParking
@@ -40,10 +41,28 @@ const isMarkerVisibleEquipments = computed(() => {
})
const isMarkerVisible = computed(() => isMarkerVisibleLayers.value && isMarkerVisibleEquipments.value)
// Routing
const { routeTo } = useRouting()
const { userCoords } = storeToRefs(useMap())
const emit = defineEmits<{
nudgeGeolocation: []
}>()
async function handleMarkerClick() {
if (!userCoords.value) {
emit('nudgeGeolocation')
return
}
await routeTo(props.park)
}
</script>
<template>
<LMarker :lat-lng="[park.geo_point_2d.lat, park.geo_point_2d.lon]" :visible="isMarkerVisible">
<LMarker :lat-lng="[park.geo_point_2d.lat, park.geo_point_2d.lon]" :visible="isMarkerVisible"
@click="handleMarkerClick">
<LPopup :options="{ offset: popupOffset, maxWidth: 520, minWidth: 240 }">
<div>
<h2 class="font-bold">

View File

@@ -0,0 +1,27 @@
import { storeToRefs } from 'pinia'
import { useMap } from '@/stores/map'
import type { BikeParking } from '@/types/Bikes'
export function useRouting() {
const { userCoords, activeRoute, isRoutingLoading } = storeToRefs(useMap())
async function routeTo(park: BikeParking) {
if (!userCoords.value) return false // caller handles the nudge
isRoutingLoading.value = true
activeRoute.value = null
const { latitude: uLat, longitude: uLon } = userCoords.value
const { lat: pLat, lon: pLon } = park.geo_point_2d
const url = `https://router.project-osrm.org/route/v1/bike/${uLon},${uLat};${pLon},${pLat}?geometries=geojson&overview=full`
const data = await fetch(url).then((r) => r.json())
activeRoute.value = data.routes[0].geometry
isRoutingLoading.value = false
return true
}
return { routeTo, activeRoute, isRoutingLoading }
}

View File

@@ -36,6 +36,10 @@ export const useMap = defineStore('map', () => {
const spotsRange = ref<[number, number]>([SPOTS_MIN, SPOTS_MAX])
// Routing
const activeRoute = ref<GeoJSON.LineString | null>(null)
const isRoutingLoading = ref(false)
return {
zoom,
minZoom,
@@ -52,5 +56,7 @@ export const useMap = defineStore('map', () => {
filterCargoOnly,
filterStdOnly,
spotsRange,
activeRoute,
isRoutingLoading,
}
})