feat: plan trip

This commit is contained in:
2026-07-13 18:22:58 +02:00
parent 9a97bf6088
commit 69ec5436f8
11 changed files with 228 additions and 28 deletions
+49
View File
@@ -10,6 +10,23 @@
:clusters="shownClusters"
:route="r.route"
/></template>
<mgl-geo-json-source
:source-id="`trip_leg_${index}`"
:data="line"
v-for="(line, index) in store.selectedTrips"
>
<mgl-line-layer
:layer-id="`trip_line_leg_${index}`"
:paint="{
'line-width': 4,
'line-color':
'#' +
(store.routesById[line.properties?.routeId]
?.color || '000000'),
}"
/>
</mgl-geo-json-source>
</MglMap>
</ClientOnly>
</template>
@@ -22,18 +39,38 @@ import type {
Point,
} from "geojson";
const paint = {
"line-color": "#FF0000",
"line-width": 4,
};
import { useMap } from "@indoorequal/vue-maplibre-gl";
import { LngLat, LngLatBounds } from "maplibre-gl";
const style = "https://tiles.versatiles.org/assets/styles/colorful/style.json";
// const style =
// "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json";
const center = new LngLat(5.735, 45.185);
const zoom = 12;
const emit = defineEmits<{
loaded: [];
}>();
const store = useMresoStore();
await store.fetchData();
const shownClusters = ref<FeatureCollection<Point> | null>(null);
const image = useSvgImage();
const map = useMap("main");
watchEffect(() => {
console.log(store.selectedTrip);
});
watch(map, () => {
if (map.isLoaded) {
emit("loaded");
}
});
watchEffect(async () => {
const allClusters = [];
for (const routeId of store.selectedRouteIds) {
@@ -102,3 +139,15 @@ const shownLines = computed(() => {
return l;
});
</script>
<style scoped lang="css">
.maplibregl-map {
grid-area: "map";
}
</style>
<style lang="css">
.maplibregl-map {
grid-area: map;
}
</style>
+6 -10
View File
@@ -1,5 +1,5 @@
<template>
<div id="lineSelector">
<div id="sidebar">
<div
v-for="route of store.routes"
:key="route.id"
@@ -27,19 +27,15 @@ function selectRoute(routeId: string) {
</script>
<style scoped lang="css">
#lineSelector {
position: fixed;
left: 0;
top: 0;
height: 100vh;
overflow: auto;
background: white;
}
.lineLabel {
cursor: pointer;
}
.lineLabel.selected {
color: red !important;
}
#sidebar {
hewight: 100%;
overflow: auto;
grid-area: sidebar;
}
</style>
+4 -7
View File
@@ -1,5 +1,5 @@
<template>
<div id="tripSelector">
<div id="sidebar">
<input type="text" placeholder="from" value="SEM:GENBIBLIUNI" />
<input type="text" placeholder="to" value="SEM:GENPERE" />
<button @click="$emit('plan')">request</button>
@@ -9,12 +9,9 @@
<script setup lang="ts"></script>
<style scoped>
#tripSelector {
position: fixed;
left: 0;
top: 0;
height: 100vh;
#sidebar {
height: 100%;
overflow: auto;
background: white;
grid-area: sidebar;
}
</style>
+46 -1
View File
@@ -1,6 +1,51 @@
<template>
<main>
<Map map-key="main" />
<div id="map-container">
<Map map-key="main" @loaded="loaded" />
<Transition>
<div id="map-loading" v-if="!mapReady">Loading</div>
</Transition>
</div>
<slot />
</main>
</template>
<style scoped lang="css">
main {
display: grid;
grid-template-columns: 400px 1fr;
grid-template-areas: "sidebar map";
height: 100%;
}
#map-container {
position: relative;
height: 100%;
}
#map-loading {
height: 100%;
width: 100%;
position: absolute;
top: 0;
background: white;
display: flex;
align-items: center;
justify-content: center;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
<script setup lang="ts">
const mapReady = ref(false);
function loaded() {
mapReady.value = true;
}
</script>
+1 -3
View File
@@ -1,7 +1,5 @@
<template>
<main>
<SidebarLines />
</main>
<SidebarLines />
</template>
<script lang="ts">
+31 -6
View File
@@ -1,15 +1,15 @@
<template>
<main>
<SidebarTrip @plan="plan" />
</main>
<SidebarTrip @plan="plan" />
</template>
<script setup lang="ts">
definePageMeta({ key: "trip" });
import { LngLat } from "maplibre-gl";
import { decode } from "@googlemaps/polyline-codec";
import { RoutePlanner } from "~/utils/trip";
import type { Feature, LineString } from "geojson";
const from = "SEM:GENBIBLIUNI";
const to = "SEM:GENPERE";
const to = "SEM:GENALSACELO";
const store = useMresoStore();
function plan() {
@@ -18,12 +18,37 @@ function plan() {
if (!fromCoord || !toCoord) {
return;
}
const planner = new RoutePlanner(
const planner = new TransitRoutePlanner(
new LngLat(fromCoord[1]!, fromCoord[0]!),
new LngLat(toCoord[1]!, toCoord[0]!),
);
planner.request().then((data) => {
console.log(data);
const GeoLines: Feature<LineString>[] = [];
const itinerary = data.plan.itineraries[0];
if (!itinerary) {
console.log("No Itineraries found");
return;
}
for (const leg of itinerary.legs) {
console.log(leg);
const points = leg.legGeometry.points;
if (!points) {
return;
}
const lines = decode(points).map(([lat, lng]) => [lng, lat]);
const lineString: Feature<LineString> = {
type: "Feature",
properties: {
routeId: leg.routeId,
},
geometry: {
type: "LineString",
coordinates: lines,
},
};
GeoLines.push(lineString);
}
store.selectedTrips = GeoLines;
});
}
</script>
+6
View File
@@ -1,6 +1,7 @@
import type {
Feature,
FeatureCollection,
LineString,
MultiLineString,
Point,
} from "geojson";
@@ -12,12 +13,17 @@ export const useMresoStore = defineStore("mresoStore", {
clusters: {} as FeatureCollection<Point>,
stops: {} as FeatureCollection<Point>,
selectedRouteIds: [] as string[],
selectedTrips: [] as Feature<LineString>[],
}),
actions: {
async fetchData(options: { signal?: AbortSignal } = {}) {
[this.routes, this.lines, this.clusters, this.stops] = await Promise.all([
$fetch<Route[]>("/routes.json", options),
$fetch<FeatureCollection<MultiLineString>>("/lines.json", options),
// $fetch<FeatureCollection<MultiLineString>>(
// "/trajets presque clean.geojson",
// options,
// ),
$fetch<FeatureCollection<Point>>("/clusters.json", options),
$fetch<FeatureCollection<Point>>("/stops.json", options),
]);
+76
View File
@@ -78,3 +78,79 @@ type OptimizeType =
"FLAT" | "GREENWAYS" | "QUICK" | "SAFE" | "TRANSFERS" | "TRIANGLE";
type PlannerModes = "WALK" | "TRANSIT";
interface OTPPlace {
name: string;
lon: number;
lat: number;
vertedType: "NORMAL";
}
interface OTPItineraries {
duration: number;
startTime: number;
endTime: number;
walkTime: number;
transitTime: number;
waitingTime: number;
walkDistance: number;
walkLimitExceeded: boolean;
generalizedCost: number;
elevationLost: number;
elevationGained: number;
transfers: number;
legs: OTPLegs[];
}
interface OTPLegs {
startTime: number;
endTime: number;
departureDelay: number;
arrivalDelay: number;
realTime: boolean;
distance: number;
generalizedCost: number;
pathway: boolean;
mode: PlannerModes;
transitLeg: boolean;
route: string;
routeColor?: string;
routeId?: string;
routeLongName?: string;
routeShortName?: string;
routeTextColor?: string;
routeType?: number;
agencyTimeZoneOffset: number;
interlineWithPreviousLeg: boolean;
from: OTPPlace;
to: OTPPlace;
legGeometry: {
points: "string";
};
legElevation: string;
steps: OTPStep[];
duration: number;
}
interface OTPStep {
distance: number;
relativeDirection: string;
streetName: string;
absoluteDirection: string;
stayOn: boolean;
area: boolean;
bogusName: boolean;
lon: numbeer;
lat: numbeer;
elevation: string;
walkingBike: boolean;
}
interface OTPPlanOutput {
plan: {
date: number;
from: OTPPlace;
to: OTPPlace;
itineraries: OTPItineraries[];
};
}
+1 -1
View File
@@ -64,7 +64,7 @@ class RoutePlanner {
async request() {
this.otpUrl.search = this.toURLSearchParams().toString();
return await $fetch(this.otpUrl.toString());
return await $fetch<OTPPlanOutput>(this.otpUrl.toString());
}
}
+7
View File
@@ -7,6 +7,7 @@
"name": "better-mrso",
"hasInstallScript": true,
"dependencies": {
"@googlemaps/polyline-codec": "^1.0.28",
"@nuxt/eslint": "^1.16.0",
"@pinia/nuxt": "^0.11.3",
"@types/geojson": "^7946.0.16",
@@ -1188,6 +1189,12 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@googlemaps/polyline-codec": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/@googlemaps/polyline-codec/-/polyline-codec-1.0.28.tgz",
"integrity": "sha512-m7rh8sbxlrHvebXEweBHX8r1uPtToPRYxWDD6p6k2YG8hyhBe0Wi6xRUVFpxpEseMNgF+OBotFQC5senj8K7TQ==",
"license": "Apache-2.0"
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+1
View File
@@ -10,6 +10,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@googlemaps/polyline-codec": "^1.0.28",
"@nuxt/eslint": "^1.16.0",
"@pinia/nuxt": "^0.11.3",
"@types/geojson": "^7946.0.16",