Skip to content

Commit 69f4904

Browse files
authored
Merge pull request #25 from AIoT-Lab-AI4LIFE/feat/mout-center-map-layout
feat/mout-center-map-layout
2 parents c02acc5 + be6b7ad commit 69f4904

3 files changed

Lines changed: 84 additions & 50 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useEffect } from "react";
2+
import { useMap } from "react-leaflet";
3+
import { useWeatherMapStore } from "../store";
4+
5+
export function MapUpdater() {
6+
const { mapCenter, mapZoom } = useWeatherMapStore();
7+
const map = useMap();
8+
9+
useEffect(() => {
10+
map.flyTo(mapCenter, mapZoom, {
11+
animate: true,
12+
duration: 1.5,
13+
});
14+
}, [mapCenter, mapZoom, map]);
15+
16+
return null;
17+
}

src/features/weather-map/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { WeatherSidebar } from "./components/weather-sidebar";
88
import { useWeatherMapStore } from "./store";
99
import Icon from "@mdi/react";
1010
import { mdiMenu } from "@mdi/js";
11+
import { MapUpdater } from "./components/map-updater";
1112

1213
function WeatherMapLayoutContent() {
1314
const { selectedStation, mapCenter, mapZoom, setIsMobileSidebarOpen } =
@@ -23,6 +24,7 @@ function WeatherMapLayoutContent() {
2324
zoomControl={false}
2425
>
2526
<MapCanvas />
27+
<MapUpdater />
2628
<Outlet />
2729
</MapContainer>
2830
</div>

src/features/weather-map/pages/tropical-cyclone.page.tsx

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface CyclonePoint extends StormLifecycleRead {
1717

1818
function createCycloneIcon(status: "past" | "forecast") {
1919
const iconHtml = ReactDOMServer.renderToString(
20-
<Icon path={mdiWeatherHurricaneOutline} size={1} color="white" />,
20+
<Icon path={mdiWeatherHurricaneOutline} size={1} color="white" />
2121
);
2222
const backgroundColor = status === "forecast" ? "#FF2D55D0" : "#757474D0";
2323

@@ -44,7 +44,18 @@ function createCycloneIcon(status: "past" | "forecast") {
4444
export function TropicalCyclonePage() {
4545
const [cycloneData, setCycloneData] = useState<CyclonePoint[]>([]);
4646
const [loading, setLoading] = useState(true);
47-
const { selectedDate, setSelectedDate, selectedHour, setSliderMarks, selectedStormId, setSelectedStormId, setIsStormSelectorOpen } = useWeatherMapStore();
47+
48+
const {
49+
selectedDate,
50+
setSelectedDate,
51+
selectedHour,
52+
setSliderMarks,
53+
selectedStormId,
54+
setSelectedStormId,
55+
setIsStormSelectorOpen,
56+
setMapCenter,
57+
} = useWeatherMapStore();
58+
4859
const [data, setData] = useState<StormLifecycleRead[]>([]);
4960

5061
useEffect(() => {
@@ -66,12 +77,16 @@ export function TropicalCyclonePage() {
6677
storm_ids: [selectedStormId || 0],
6778
});
6879

69-
const data = stormLifecycles.data.sort((a, b) => {
70-
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
71-
}).map((u) => {
72-
u.timestamp = `${u.timestamp}Z`;
73-
return u;
74-
});
80+
const data = stormLifecycles.data
81+
.sort((a, b) => {
82+
return (
83+
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
84+
);
85+
})
86+
.map((u) => {
87+
u.timestamp = `${u.timestamp}Z`;
88+
return u;
89+
});
7590

7691
if (data.length === 0) {
7792
setSelectedDate(new Date());
@@ -80,18 +95,18 @@ export function TropicalCyclonePage() {
8095
}
8196

8297
const startDate = new Date(data[0].timestamp);
83-
startDate.setDate(startDate.getDate() - 0);
8498

8599
setSelectedDate(startDate || new Date());
86100

101+
setMapCenter([data[0].latitude, data[0].longitude]);
102+
87103
setData(data);
88104
};
89105
fetchCycloneData();
90-
}, [selectedStormId, setSelectedDate]);
106+
}, [selectedStormId, setSelectedDate, setMapCenter]);
91107

92108
useEffect(() => {
93-
if (data.length === 0)
94-
return;
109+
if (data.length === 0) return;
95110

96111
// const maxDate = dayjs(data[data.length - 1].timestamp);
97112
// const minDate = dayjs(data[0].timestamp);
@@ -100,47 +115,51 @@ export function TropicalCyclonePage() {
100115
// return;
101116
// }
102117

103-
const sliderMarks = data.filter((item: StormLifecycleRead) => {
104-
return dayjs(item.timestamp).isSame(selectedDate, "day");
105-
}).reduce((marks: Record<number, string>, item: StormLifecycleRead) => {
106-
const hours = dayjs(item.timestamp).hour();
107-
marks[hours] = `${hours}:00`;
108-
return marks;
109-
}, {});
118+
const sliderMarks = data
119+
.filter((item: StormLifecycleRead) => {
120+
return dayjs(item.timestamp).isSame(selectedDate, "day");
121+
})
122+
.reduce((marks: Record<number, string>, item: StormLifecycleRead) => {
123+
const hours = dayjs(item.timestamp).hour();
124+
marks[hours] = `${hours}:00`;
125+
return marks;
126+
}, {});
110127

111128
setSliderMarks(sliderMarks);
112129

113-
// Determine the reference time based on selectedDate and selectedHour
114130
let referenceTime: Date;
115131
if (selectedDate) {
116132
referenceTime = new Date(selectedDate);
117133
referenceTime.setHours(selectedHour, 0, 0, 0);
118-
}
119-
else {
134+
} else {
120135
referenceTime = new Date();
121136
}
122137

123138
// Transform data and determine status
124-
const transformedData: CyclonePoint[] = data.map((item: StormLifecycleRead, index: number) => {
125-
const timestamp = new Date(item.timestamp);
126-
const isForecast = timestamp > referenceTime;
127-
128-
// Calculate radius for forecast points with increment
129-
let radius = 0;
130-
if (isForecast) {
131-
// Base radius of 50km, increasing by 20km for each subsequent forecast point
132-
const forecastIndex = data.slice(0, index + 1).filter((d: StormLifecycleRead) =>
133-
new Date(d.timestamp) > referenceTime,
134-
).length;
135-
radius = 8000 + 20000 * Math.log(forecastIndex + 1);
136-
}
139+
const transformedData: CyclonePoint[] = data.map(
140+
(item: StormLifecycleRead, index: number) => {
141+
const timestamp = new Date(item.timestamp);
142+
const isForecast = timestamp > referenceTime;
137143

138-
return {
139-
...item,
140-
status: isForecast ? "forecast" : "past",
141-
radius,
142-
};
143-
});
144+
// Calculate radius for forecast points with increment
145+
let radius = 0;
146+
if (isForecast) {
147+
// Base radius of 50km, increasing by 20km for each subsequent forecast point
148+
const forecastIndex = data
149+
.slice(0, index + 1)
150+
.filter(
151+
(d: StormLifecycleRead) => new Date(d.timestamp) > referenceTime
152+
).length;
153+
radius = 8000 + 20000 * Math.log(forecastIndex + 1);
154+
}
155+
156+
return {
157+
...item,
158+
status: isForecast ? "forecast" : "past",
159+
radius,
160+
};
161+
}
162+
);
144163

145164
// Filter data based on selected time frame
146165
// Show past points up to the reference time and forecast points after
@@ -156,8 +175,7 @@ export function TropicalCyclonePage() {
156175
// Show past points that are close to the reference time (within a reasonable range)
157176
// This helps show the cyclone's path leading up to the selected time
158177
return itemTime <= referenceTime;
159-
}
160-
else {
178+
} else {
161179
// Show forecast points after the reference time
162180
return itemTime > referenceTime;
163181
}
@@ -171,10 +189,10 @@ export function TropicalCyclonePage() {
171189
}
172190

173191
const allPositions = cycloneData.map(
174-
p => [p.latitude, p.longitude] as [number, number],
192+
(p) => [p.latitude, p.longitude] as [number, number]
175193
);
176194
const forecastStartIndex = cycloneData.findIndex(
177-
p => p.status === "forecast",
195+
(p) => p.status === "forecast"
178196
);
179197

180198
// Xử lý trường hợp selectedDate nằm ngoài khoảng dữ liệu
@@ -184,12 +202,10 @@ export function TropicalCyclonePage() {
184202
if (forecastStartIndex === -1) {
185203
// Tất cả là past (selectedDate sau tất cả các điểm)
186204
pastPath = allPositions;
187-
}
188-
else if (forecastStartIndex === 0) {
205+
} else if (forecastStartIndex === 0) {
189206
// Tất cả là forecast (selectedDate trước tất cả các điểm)
190207
forecastPath = allPositions;
191-
}
192-
else {
208+
} else {
193209
// Trường hợp bình thường: có cả past và forecast
194210
pastPath = allPositions.slice(0, forecastStartIndex + 1);
195211
forecastPath = allPositions.slice(forecastStartIndex);
@@ -222,7 +238,6 @@ export function TropicalCyclonePage() {
222238
fillOpacity: 0.4,
223239
fillRule: "evenodd",
224240
}}
225-
226241
/>
227242
)}
228243

0 commit comments

Comments
 (0)