fix: sửa lại các tuyến đường đề xuất trên bản đồ
This commit is contained in:
@@ -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();
|
||||
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) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user