fix: sửa lại các tuyến đường đề xuất trên bản đồ

This commit is contained in:
2026-06-17 20:49:30 +07:00
parent f181009fa5
commit 69c67a1636
+142 -13
View File
@@ -49,6 +49,26 @@ import L from 'leaflet';
// Định nghĩa kiểu dữ liệu cho Địa điểm để khớp với Schema Prisma
type LocationType = 'MOVE' | 'VISIT' | 'REST' | 'EAT';
// Định nghĩa kiểu dữ liệu cho OSRM Route
interface OSRMRoute {
geometry: {
coordinates: [number, number][]; // [lng, lat]
};
distance: number; // meters
duration: number; // seconds
legs: { distance: number; duration: number; }[]; // OSRM's internal legs for a route
}
// Định nghĩa kiểu dữ liệu cho điểm (bao gồm cả userLocation khi được chuyển đổi)
interface LocationPoint {
latitude: number;
longitude: number;
id: string;
name: string;
type: string;
legId: string;
}
// Fix lỗi icon mặc định của Leaflet cho môi trường Vite
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
@@ -69,6 +89,53 @@ function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: numbe
return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}
// Helper function to combine segment routes into a single overall route
const combineSegmentRoutes = (segmentRoutes: OSRMRoute[][], selectedIndices: number[]): OSRMRoute | null => {
if (segmentRoutes.length === 0) return null;
let combinedGeometry: [number, number][] = [];
let combinedDistance = 0;
let combinedDuration = 0;
const combinedLegs: { distance: number; duration: number; }[] = [];
for (let i = 0; i < segmentRoutes.length; i++) {
const segmentIndex = selectedIndices[i] !== undefined ? selectedIndices[i] : 0; // Default to first alternative
const chosenRoute = segmentRoutes[i][segmentIndex];
if (!chosenRoute) {
// If a segment has no chosen route (e.g., no alternatives or API failed),
// we cannot form a complete route.
return null;
}
// Concatenate geometry, avoiding duplicate points at segment junctions
if (i > 0 && combinedGeometry.length > 0 && chosenRoute.geometry.coordinates.length > 0) {
const lastPointOfPrev = combinedGeometry[combinedGeometry.length - 1];
const firstPointOfCurrent = chosenRoute.geometry.coordinates[0];
// OSRM coordinates are [lng, lat]
if (Math.abs(lastPointOfPrev[0] - firstPointOfCurrent[0]) < 1e-6 &&
Math.abs(lastPointOfPrev[1] - firstPointOfCurrent[1]) < 1e-6) {
combinedGeometry = combinedGeometry.concat(chosenRoute.geometry.coordinates.slice(1));
} else {
combinedGeometry = combinedGeometry.concat(chosenRoute.geometry.coordinates);
}
} else {
combinedGeometry = combinedGeometry.concat(chosenRoute.geometry.coordinates);
}
combinedDistance += chosenRoute.distance;
combinedDuration += chosenRoute.duration;
combinedLegs.push(...chosenRoute.legs); // OSRM legs are sub-segments within a route
}
return {
geometry: { coordinates: combinedGeometry },
distance: combinedDistance,
duration: combinedDuration,
legs: combinedLegs,
};
};
// Component Helper để tự động điều chỉnh khung nhìn bản đồ bao phủ toàn bộ lộ trình
const MapTourBounds = ({ locations }: { locations: any[] }) => {
const map = useMap();
@@ -524,16 +591,30 @@ export const TourDetailPage = ({
const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]);
// Tạo key định danh cho lộ trình để buộc bản đồ vẽ lại khi dữ liệu thay đổi
const routeKey = useMemo(() => {
const locsKey = allLocations.map(l => `${l.id}-${l.latitude}-${l.longitude}`).join('|');
const userKey = userLocation ? `${userLocation[0].toFixed(5)}-${userLocation[1].toFixed(5)}` : 'no-user';
return `${locsKey}-${userKey}`;
}, [allLocations, userLocation]);
const routeKey = useMemo(() => JSON.stringify({
locations: allLocations.map(l => ({ id: l.id, lat: l.latitude, lon: l.longitude })),
user: userLocation ? { lat: userLocation[0].toFixed(5), lon: userLocation[1].toFixed(5) } : null,
travelMode: travelMode,
selectedRouteIndex: selectedRouteIndex // Include selectedRouteIndex to force re-render when alternative is chosen
}), [allLocations, userLocation, travelMode, selectedRouteIndex]);
// Tự động tìm các quãng đường di chuyển thực tế theo phương tiện và vẽ lên bản đồ
useEffect(() => {
const fetchRoutes = async () => {
if (allLocations.length === 0 || (allLocations.length < 2 && !userLocation)) {
// Prepare points: userLocation + allLocations
const currentPoints: LocationPoint[] = [...allLocations];
if (userLocation) {
currentPoints.unshift({
latitude: userLocation[0],
longitude: userLocation[1],
id: 'user-location', // Dummy ID
name: 'Vị trí hiện tại', // Dummy name
type: 'MOVE', // Dummy type
legId: '', // Dummy legId
});
}
if (currentPoints.length < 2) {
setRoutes([]);
setSelectedRouteIndex(0);
setDrivingRoute([]);
@@ -552,16 +633,64 @@ export const TourDetailPage = ({
setIsRoutingLoading(true);
try {
// Yêu cầu tối đa 3 phương án lộ trình. Lưu ý: OSRM chỉ hỗ trợ alternatives cho hành trình 2 điểm.
const response = await fetch(`https://router.project-osrm.org/route/v1/${travelMode}/${coordsString}?overview=full&geometries=geojson&alternatives=3`);
const data = await response.json();
if (data.code === 'Ok' && data.routes.length > 0) {
setRoutes(data.routes);
// Reset về lộ trình đầu tiên khi danh sách điểm đến thay đổi hoàn toàn
setSelectedRouteIndex(0);
const segmentPromises: Promise<OSRMRoute[] | null>[] = [];
for (let i = 0; i < currentPoints.length - 1; i++) {
const p1 = currentPoints[i];
const p2 = currentPoints[i + 1];
const coordsString = `${p1.longitude},${p1.latitude};${p2.longitude},${p2.latitude}`;
segmentPromises.push(
fetch(`https://router.project-osrm.org/route/v1/${travelMode}/${coordsString}?overview=full&geometries=geojson&alternatives=true`)
.then(res => {
if (!res.ok) throw new Error(`OSRM API error: ${res.status}`);
return res.json();
})
.then(data => {
if (data.code === 'Ok' && data.routes.length > 0) {
return data.routes; // Array of OSRMRoute for this segment
}
return null; // No routes for this segment
})
.catch(error => {
console.error(`Lỗi lấy lộ trình cho phân đoạn ${i}-${i+1}:`, error);
return null;
})
);
}
const allSegmentAlternativesRaw = await Promise.all(segmentPromises);
const allSegmentAlternatives: OSRMRoute[][] = allSegmentAlternativesRaw.filter((seg): seg is OSRMRoute[] => seg !== null);
if (allSegmentAlternatives.length === 0) {
setRoutes([]);
setSelectedRouteIndex(0);
return;
}
// Now, construct overall alternative routes from segment alternatives
// Heuristic: Take the first alternative of each segment to form the primary route.
// Then, for subsequent overall alternatives, try different alternatives for the first segment,
// keeping other segments at their primary alternative.
const overallAlternativeRoutes: OSRMRoute[] = [];
// Limit overall alternatives based on the number of alternatives for the first segment, up to 3
const maxOverallAlternatives = Math.min(3, allSegmentAlternatives[0]?.length || 1);
for (let altIdx = 0; altIdx < maxOverallAlternatives; altIdx++) {
const selectedSegmentIndices: number[] = allSegmentAlternatives.map((_, segIdx) =>
segIdx === 0 ? altIdx : 0 // Use altIdx for first segment, 0 for others
);
const combinedRoute = combineSegmentRoutes(allSegmentAlternatives, selectedSegmentIndices);
if (combinedRoute) {
overallAlternativeRoutes.push(combinedRoute);
}
}
setRoutes(overallAlternativeRoutes);
setSelectedRouteIndex(0); // Always select the first overall alternative by default
} catch (error) {
console.error(`Lỗi lấy lộ trình ${travelMode}:`, error);
setRoutes([]);
} finally {
setIsRoutingLoading(false);
}