import { PolygonWithLabel } from "@/components/map/PolygonWithLabel"; import { PolylineWithLabel } from "@/components/map/PolylineWithLabel"; import { showToastError } from "@/config"; import { AUTO_REFRESH_INTERVAL, ENTITY, IOS_PLATFORM, LIGHT_THEME, } from "@/constants"; import { queryAlarm, queryEntities, queryGpsData, queryTrackPoints, } from "@/controller/DeviceController"; import { useColorScheme } from "@/hooks/use-color-scheme.web"; import { usePlatform } from "@/hooks/use-platform"; import { getShipIcon } from "@/services/map_service"; import { useBanzones } from "@/state/use-banzones"; import { convertWKTLineStringToLatLngArray, convertWKTtoLatLngString, } from "@/utils/geom"; import { Image as ExpoImage } from "expo-image"; import { useEffect, useRef, useState } from "react"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import MapView, { Circle, Marker } from "react-native-maps"; import { SafeAreaProvider } from "react-native-safe-area-context"; const testPolyline = "MULTIPOLYGON(((108.7976074 17.5392966,110.390625 14.2217886,109.4677734 10.8548863,112.9227161 10.6933337,116.4383411 12.565622,116.8997669 17.0466095,109.8685169 17.8013229,108.7973446 17.5393669,108.7976074 17.5392966)))"; export default function HomeScreen() { const [gpsData, setGpsData] = useState(null); const [alarmData, setAlarmData] = useState(null); const [trackPoints, setTrackPoints] = useState( null ); const [circleRadius, setCircleRadius] = useState(100); const [zoomLevel, setZoomLevel] = useState(10); const [isFirstLoad, setIsFirstLoad] = useState(true); const [polylineCoordinates, setPolylineCoordinates] = useState< number[][] | null >(null); const [polygonCoordinates, setPolygonCoordinates] = useState< number[][][] | null >(null); const [, setZoneGeometries] = useState>(new Map()); const intervalRef = useRef | null>(null); const platform = usePlatform(); const theme = useColorScheme(); const { banzones, getBanzone } = useBanzones(); const banzonesRef = useRef(banzones); // console.log("Platform: ", platform); // console.log("Theme: ", theme); const getGpsData = async () => { try { const response = await queryGpsData(); // console.log("GpsData: ", response.data); // console.log( // "Heading value:", // response.data?.h, // "Type:", // typeof response.data?.h // ); setGpsData(response.data); } catch (error) { console.error("Error fetching GPS data:", error); showToastError("Lỗi", "Không thể lấy dữ liệu GPS"); } }; const drawPolyline = () => { const data = convertWKTtoLatLngString(testPolyline); console.log("Data: ", data); // setPolygonCoordinates(data[0]); // ; // console.log("Banzones: ", banzones.length); }; useEffect(() => { banzonesRef.current = banzones; }, [banzones]); const areGeometriesEqual = ( left?: { geom_type: number; geom_lines?: string | null; geom_poly?: string | null; }, right?: { geom_type: number; geom_lines?: string | null; geom_poly?: string | null; } ) => { if (!left && !right) { return true; } if (!left || !right) { return false; } return ( left.geom_type === right.geom_type && (left.geom_lines || "") === (right.geom_lines || "") && (left.geom_poly || "") === (right.geom_poly || "") ); }; const areCoordinatesEqual = ( current: number[][] | null, next: number[][] | null ) => { if (!current || !next || current.length !== next.length) { return false; } return current.every( (coord, index) => coord[0] === next[index][0] && coord[1] === next[index][1] ); }; const areMultiPolygonCoordinatesEqual = ( current: number[][][] | null, next: number[][][] | null ) => { if (!current || !next || current.length !== next.length) { return false; } return current.every((polygon, polyIndex) => { const nextPolygon = next[polyIndex]; if (!nextPolygon || polygon.length !== nextPolygon.length) { return false; } return polygon.every( (coord, coordIndex) => coord[0] === nextPolygon[coordIndex][0] && coord[1] === nextPolygon[coordIndex][1] ); }); }; const getAlarmData = async () => { try { const response = await queryAlarm(); // console.log("AlarmData: ", response.data); setAlarmData(response.data); } catch (error) { console.error("Error fetching Alarm Data: ", error); showToastError("Lỗi", "Không thể lấy dữ liệu báo động"); } }; const getEntities = async () => { try { const entities = await queryEntities(); if (!entities) { // Clear tất cả khu vực khi không có dữ liệu setPolylineCoordinates(null); setPolygonCoordinates(null); setZoneGeometries(new Map()); return; } const currentBanzones = banzonesRef.current || []; let nextPolyline: number[][] | null = null; let nextMultiPolygon: number[][][] | null = null; let foundPolyline = false; let foundPolygon = false; // Process zones để tìm geometries for (const entity of entities) { if (entity.id !== ENTITY.ZONE_ALARM_LIST) { continue; } let zones: any[] = []; try { zones = entity.valueString ? JSON.parse(entity.valueString) : []; } catch (parseError) { console.error("Error parsing zone list:", parseError); continue; } // Nếu danh sách zone rỗng, clear tất cả if (zones.length === 0) { setPolylineCoordinates(null); setPolygonCoordinates(null); setZoneGeometries(new Map()); return; } for (const zone of zones) { const geom = currentBanzones.find((b) => b.id === zone.zone_id); if (!geom) { continue; } const { geom_type, geom_lines, geom_poly } = geom.geom || {}; if (typeof geom_type !== "number") { continue; } if (geom_type === 2) { foundPolyline = true; const coordinates = convertWKTLineStringToLatLngArray( geom_lines || "" ); if (coordinates.length > 0) { nextPolyline = coordinates; } } else if (geom_type === 1) { foundPolygon = true; const coordinates = convertWKTtoLatLngString(geom_poly || ""); if (coordinates.length > 0) { console.log("Polygon Coordinate: ", coordinates); nextMultiPolygon = coordinates; } } } } // Update state sau khi đã process xong setZoneGeometries((prevGeometries) => { const updated = new Map(prevGeometries); let hasChanges = false; for (const entity of entities) { if (entity.id !== ENTITY.ZONE_ALARM_LIST) { continue; } let zones: any[] = []; try { zones = entity.valueString ? JSON.parse(entity.valueString) : []; } catch { continue; } if (zones.length === 0) { if (updated.size > 0) { hasChanges = true; updated.clear(); } break; } for (const zone of zones) { const geom = currentBanzones.find((b) => b.id === zone.zone_id); if (!geom) { continue; } const { geom_type, geom_lines, geom_poly } = geom.geom || {}; if (typeof geom_type !== "number") { continue; } const key = `${zone.zone_id}_${geom_type}`; const newGeomData = { geom_type, geom_lines, geom_poly }; const oldGeom = updated.get(key); if (!areGeometriesEqual(oldGeom, newGeomData)) { hasChanges = true; updated.set(key, newGeomData); console.log("Geometry changed", { key, oldGeom, newGeomData }); } } } return hasChanges ? updated : prevGeometries; }); // Cập nhật hoặc clear polyline if (foundPolyline && nextPolyline) { setPolylineCoordinates((prev) => areCoordinatesEqual(prev, nextPolyline) ? prev : nextPolyline ); } else if (!foundPolyline) { console.log("Hết cảnh báo qua polyline"); setPolylineCoordinates(null); } // Cập nhật hoặc clear polygon if (foundPolygon && nextMultiPolygon) { setPolygonCoordinates((prev) => areMultiPolygonCoordinatesEqual(prev, nextMultiPolygon) ? prev : nextMultiPolygon ); } else if (!foundPolygon) { console.log("Hết cảnh báo qua polygon"); setPolygonCoordinates(null); } } catch (error) { console.error("Error fetching Entities: ", error); // Clear tất cả khi có lỗi setPolylineCoordinates(null); setPolygonCoordinates(null); setZoneGeometries(new Map()); } }; const getShipTrackPoints = async () => { try { const response = await queryTrackPoints(); // console.log("TrackPoints Data Length: ", response.data.length); setTrackPoints(response.data); } catch (error) { console.error("Error fetching TrackPoints Data: ", error); showToastError("Lỗi", "Không thể lấy lịch sử di chuyển"); } }; const fetchAllData = async () => { await Promise.all([ getGpsData(), getAlarmData(), getShipTrackPoints(), getEntities(), ]); }; const setupAutoRefresh = () => { // Clear existing interval if any if (intervalRef.current) { clearInterval(intervalRef.current); } // Set new interval to refresh data every 5 seconds // Không fetch banzones vì dữ liệu không thay đổi intervalRef.current = setInterval(async () => { // console.log("Auto-refreshing data..."); await fetchAllData(); }, AUTO_REFRESH_INTERVAL); }; const handleMapReady = () => { // console.log("Map loaded successfully!"); // Gọi fetchAllData ngay lập tức (không cần đợi banzones) fetchAllData(); setupAutoRefresh(); // Set isFirstLoad to false sau khi map ready để chỉ zoom lần đầu tiên setTimeout(() => { setIsFirstLoad(false); }, 2000); }; // Cleanup interval on component unmount useEffect(() => { return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, []); useEffect(() => { getBanzone(); }, [getBanzone]); // Hàm tính radius cố định khi zoom change const calculateRadiusFromZoom = (zoom: number) => { const baseZoom = 10; const baseRadius = 100; const zoomDifference = baseZoom - zoom; const calculatedRadius = baseRadius * Math.pow(2, zoomDifference); // console.log("Caculate Radius: ", calculatedRadius); return Math.max(calculatedRadius, 50); }; // Xử lý khi region (zoom) thay đổi const handleRegionChangeComplete = (newRegion: any) => { // Tính zoom level từ latitudeDelta // zoom = log2(360 / (latitudeDelta * 2)) + 8 const zoom = Math.round(Math.log2(360 / (newRegion.latitudeDelta * 2)) + 8); const newRadius = calculateRadiusFromZoom(zoom); setCircleRadius(newRadius); setZoomLevel(zoom); // console.log("Zoom level:", zoom, "Circle radius:", newRadius); }; // Tính toán region để focus vào marker của tàu (chỉ lần đầu tiên) const getMapRegion = () => { if (!isFirstLoad) { // Sau lần đầu, return undefined để không force region return undefined; } if (!gpsData) { return { latitude: 15.70581, longitude: 116.152685, latitudeDelta: 0.05, longitudeDelta: 0.05, }; } return { latitude: gpsData.lat, longitude: gpsData.lon, latitudeDelta: 0.05, longitudeDelta: 0.05, }; }; return ( {banzones.length > 0 && ( Banzones loaded: {banzones.length} )} { console.log("Poi clicked: ", point.nativeEvent); }} onRegionChangeComplete={handleRegionChangeComplete} style={styles.map} // initialRegion={getMapRegion()} region={getMapRegion()} userInterfaceStyle={theme === LIGHT_THEME ? "light" : "dark"} showsBuildings={false} showsIndoors={false} loadingEnabled={true} mapType="standard" > {trackPoints && trackPoints.length > 0 && trackPoints.map((point, index) => { // console.log(`Rendering circle ${index}:`, point); return ( ); })} {polylineCoordinates && ( ({ latitude: coord[0], longitude: coord[1], }))} label="Tuyến bờ" strokeColor="#FF5733" strokeWidth={4} showDistance={false} zIndex={50} /> )} {polygonCoordinates && polygonCoordinates.length > 0 && ( <> {polygonCoordinates.map((polygon, index) => { // Tạo key ổn định từ tọa độ đầu tiên của polygon const polygonKey = polygon.length > 0 ? `polygon-${polygon[0][0]}-${polygon[0][1]}-${index}` : `polygon-${index}`; return ( ({ latitude: coords[0], longitude: coords[1], }))} label="Test khu đánh bắt" content="Thời gian cấm (từ tháng 1 đến tháng 12)" fillColor="rgba(16, 85, 201, 0.6)" strokeColor="rgba(16, 85, 201, 0.8)" strokeWidth={2} zIndex={50} zoomLevel={zoomLevel} /> ); })} )} {gpsData && ( )} Get GPS Data ); } const styles = StyleSheet.create({ container: { flex: 1, }, map: { flex: 1, }, button: { display: "none", position: "absolute", top: 50, right: 20, backgroundColor: "#007AFF", paddingHorizontal: 16, paddingVertical: 12, borderRadius: 8, elevation: 5, shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, }, buttonText: { color: "#fff", fontSize: 16, fontWeight: "600", }, titleContainer: { flexDirection: "row", alignItems: "center", gap: 8, }, stepContainer: { gap: 8, marginBottom: 8, }, reactLogo: { height: 178, width: 290, bottom: 0, left: 0, position: "absolute", }, });