feat: thêm tính năng vẽ đường di chuyển trên bản đồ
This commit is contained in:
@@ -270,6 +270,8 @@ export const TourDetailPage = ({
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
const [drivingRoute, setDrivingRoute] = useState<[number, number][]>([]);
|
||||||
|
const [segmentDistances, setSegmentDistances] = useState<number[]>([]);
|
||||||
|
|
||||||
const handleSearchLocation = async (query: string) => {
|
const handleSearchLocation = async (query: string) => {
|
||||||
setSearchQuery(query);
|
setSearchQuery(query);
|
||||||
@@ -421,6 +423,37 @@ export const TourDetailPage = ({
|
|||||||
// Memoize danh sách tọa độ để MapTourBounds không bị trigger thừa
|
// Memoize danh sách tọa độ để MapTourBounds không bị trigger thừa
|
||||||
const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]);
|
const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]);
|
||||||
|
|
||||||
|
// Tự động tìm quãng đường di chuyển bằng ô tô thực tế và vẽ lên bản đồ
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDrivingRoute = async () => {
|
||||||
|
if (allLocations.length < 2) {
|
||||||
|
setDrivingRoute([]);
|
||||||
|
setSegmentDistances([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const coordsString = allLocations
|
||||||
|
.map(loc => `${loc.longitude},${loc.latitude}`)
|
||||||
|
.join(';');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://router.project-osrm.org/route/v1/driving/${coordsString}?overview=full&geometries=geojson`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 'Ok' && data.routes.length > 0) {
|
||||||
|
// OSRM trả về [lng, lat], cần đổi sang [lat, lng] cho Leaflet
|
||||||
|
const mappedCoords: [number, number][] = data.routes[0].geometry.coordinates.map((c: any) => [c[1], c[0]]);
|
||||||
|
setDrivingRoute(mappedCoords);
|
||||||
|
// Lưu quãng đường từng chặng (đơn vị km)
|
||||||
|
setSegmentDistances(data.routes[0].legs.map((leg: any) => leg.distance / 1000));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Lỗi lấy lộ trình di chuyển ô tô:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDrivingRoute();
|
||||||
|
}, [allLocations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialViewState) {
|
if (initialViewState) {
|
||||||
setMapCenter(initialViewState.center);
|
setMapCenter(initialViewState.center);
|
||||||
@@ -1100,8 +1133,16 @@ export const TourDetailPage = ({
|
|||||||
{/* Tự động đóng khung Start và End khi dữ liệu thay đổi */}
|
{/* Tự động đóng khung Start và End khi dữ liệu thay đổi */}
|
||||||
<MapTourBounds locations={allLocations} />
|
<MapTourBounds locations={allLocations} />
|
||||||
|
|
||||||
{/* Vẽ đường Polyline nối các điểm - Liên tục toàn bộ lộ trình xuyên suốt các chặng */}
|
{/* Vẽ đường đi ô tô thực tế (Driving route) nếu lấy được dữ liệu, nếu không dùng đường thẳng nét đứt */}
|
||||||
{allLocations.length > 1 && (
|
{drivingRoute.length > 0 ? (
|
||||||
|
<Polyline
|
||||||
|
positions={drivingRoute}
|
||||||
|
color="#2563eb"
|
||||||
|
weight={5}
|
||||||
|
opacity={0.8}
|
||||||
|
smoothFactor={1}
|
||||||
|
/>
|
||||||
|
) : allLocations.length > 1 && (
|
||||||
<Polyline
|
<Polyline
|
||||||
positions={allLocations.map(l => [l.latitude, l.longitude]) as any}
|
positions={allLocations.map(l => [l.latitude, l.longitude]) as any}
|
||||||
color="#3b82f6"
|
color="#3b82f6"
|
||||||
@@ -1124,6 +1165,7 @@ export const TourDetailPage = ({
|
|||||||
const distanceToPrev = prevLoc
|
const distanceToPrev = prevLoc
|
||||||
? calculateDistance(prevLoc.latitude, prevLoc.longitude, loc.latitude, loc.longitude).toFixed(1)
|
? calculateDistance(prevLoc.latitude, prevLoc.longitude, loc.latitude, loc.longitude).toFixed(1)
|
||||||
: null;
|
: null;
|
||||||
|
const drivingDist = index > 0 ? segmentDistances[index - 1] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Marker key={`marker-${loc.id}-${isStart ? 'start' : isEnd ? 'end' : 'visit'}`} position={[loc.latitude, loc.longitude]} icon={icon}>
|
<Marker key={`marker-${loc.id}-${isStart ? 'start' : isEnd ? 'end' : 'visit'}`} position={[loc.latitude, loc.longitude]} icon={icon}>
|
||||||
@@ -1153,14 +1195,14 @@ export const TourDetailPage = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{distanceToPrev && distanceToPrev !== "0.0" && (
|
{(drivingDist || (distanceToPrev && distanceToPrev !== "0.0")) && (
|
||||||
<div className="mt-3 pt-3 border-t border-gray-100 flex items-center gap-2 animate-in fade-in slide-in-from-bottom-1">
|
<div className="mt-3 pt-3 border-t border-gray-100 flex items-center gap-2 animate-in fade-in slide-in-from-bottom-1">
|
||||||
<div className="p-1.5 bg-blue-50 rounded-lg">
|
<div className="p-1.5 bg-blue-50 rounded-lg">
|
||||||
<Navigation className="w-3 h-3 text-blue-600 rotate-45" />
|
<Navigation className="w-3 h-3 text-blue-600 rotate-45" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-[9px] font-black text-blue-400 uppercase leading-none tracking-tighter mb-0.5">Khoảng cách từ chặng trước</span>
|
<span className="text-[9px] font-black text-blue-400 uppercase leading-none tracking-tighter mb-0.5">{drivingDist ? 'Đường bộ từ điểm trước' : 'Khoảng cách chim bay'}</span>
|
||||||
<span className="text-xs font-black text-blue-700">{distanceToPrev} km</span>
|
<span className="text-xs font-black text-blue-700">{drivingDist ? drivingDist.toFixed(1) : distanceToPrev} km</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user