Khởi tạo ban đầu
This commit is contained in:
5
utils/eventBus.ts
Normal file
5
utils/eventBus.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
const eventBus = new EventEmitter();
|
||||
|
||||
export default eventBus;
|
||||
110
utils/geom.ts
Normal file
110
utils/geom.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
export const convertWKTPointToLatLng = (wktString: string) => {
|
||||
if (
|
||||
!wktString ||
|
||||
typeof wktString !== "string" ||
|
||||
!wktString.startsWith("POINT")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matched = wktString.match(/POINT\s*\(([-\d.]+)\s+([-\d.]+)\)/);
|
||||
if (!matched) return null;
|
||||
|
||||
const lng = parseFloat(matched[1]);
|
||||
const lat = parseFloat(matched[2]);
|
||||
|
||||
return [lng, lat]; // [longitude, latitude]
|
||||
};
|
||||
export const convertWKTLineStringToLatLngArray = (wktString: string) => {
|
||||
if (
|
||||
!wktString ||
|
||||
typeof wktString !== "string" ||
|
||||
!wktString.startsWith("LINESTRING")
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matched = wktString.match(/LINESTRING\s*\((.*)\)/);
|
||||
if (!matched) return [];
|
||||
|
||||
const coordinates = matched[1].split(",").map((coordStr) => {
|
||||
const [x, y] = coordStr.trim().split(" ").map(Number);
|
||||
return [y, x]; // [lat, lng]
|
||||
});
|
||||
|
||||
return coordinates;
|
||||
};
|
||||
|
||||
export const convertWKTtoLatLngString = (wktString: string) => {
|
||||
if (!wktString || typeof wktString !== "string") return [];
|
||||
|
||||
const clean = wktString.trim();
|
||||
|
||||
// MULTIPOLYGON
|
||||
if (clean.startsWith("MULTIPOLYGON")) {
|
||||
const matched = clean.match(/MULTIPOLYGON\s*\(\(\((.*)\)\)\)/);
|
||||
if (!matched) return [];
|
||||
|
||||
const polygons = matched[1].split(")),((").map((polygonStr) =>
|
||||
polygonStr
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((coordStr) => {
|
||||
const [lng, lat] = coordStr.trim().split(/\s+/).map(Number);
|
||||
return [lat, lng]; // Đảo ngược: [latitude, longitude]
|
||||
})
|
||||
);
|
||||
|
||||
return polygons; // Mỗi phần tử là 1 polygon (mảng các [lat, lng])
|
||||
}
|
||||
|
||||
// POLYGON
|
||||
if (clean.startsWith("POLYGON")) {
|
||||
const matched = clean.match(/POLYGON\s*\(\((.*)\)\)/);
|
||||
if (!matched) return [];
|
||||
|
||||
const polygon = matched[1].split(",").map((coordStr) => {
|
||||
const [lng, lat] = coordStr.trim().split(/\s+/).map(Number);
|
||||
return [lat, lng];
|
||||
});
|
||||
|
||||
return [polygon];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getBanzoneNameByType = (type: number) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return "Cấm đánh bắt";
|
||||
case 2:
|
||||
return "Cấm di chuyển";
|
||||
case 3:
|
||||
return "Vùng an toàn";
|
||||
default:
|
||||
return "Chưa có";
|
||||
}
|
||||
};
|
||||
|
||||
export const convertToDMS = (value: number, isLat: boolean): string => {
|
||||
const deg = Math.floor(Math.abs(value));
|
||||
const minFloat = (Math.abs(value) - deg) * 60;
|
||||
const min = Math.floor(minFloat);
|
||||
const sec = (minFloat - min) * 60;
|
||||
|
||||
const direction = value >= 0 ? (isLat ? "N" : "E") : isLat ? "S" : "W";
|
||||
|
||||
return `${deg}°${min}'${sec.toFixed(2)}"${direction}`;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Chuyển đổi tốc độ từ km/h sang knot (hải lý/giờ)
|
||||
* @param kmh - tốc độ tính bằng km/h
|
||||
* @returns tốc độ tính bằng knot
|
||||
*/
|
||||
export function kmhToKnot(kmh: number): number {
|
||||
const KNOT_PER_KMH = 1 / 1.852; // 1 knot = 1.852 km/h
|
||||
return parseFloat((kmh * KNOT_PER_KMH).toFixed(2)); // làm tròn 2 chữ số thập phân
|
||||
}
|
||||
157
utils/polyline.ts
Normal file
157
utils/polyline.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Utility functions for Polyline
|
||||
*/
|
||||
|
||||
export interface LatLng {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tìm điểm ở giữa của polyline
|
||||
*/
|
||||
export const getMiddlePointOfPolyline = (coordinates: LatLng[]): LatLng => {
|
||||
if (coordinates.length === 0) {
|
||||
return { latitude: 0, longitude: 0 };
|
||||
}
|
||||
|
||||
if (coordinates.length === 1) {
|
||||
return coordinates[0];
|
||||
}
|
||||
|
||||
const middleIndex = Math.floor(coordinates.length / 2);
|
||||
return coordinates[middleIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
* Tính toán điểm ở giữa của 2 điểm
|
||||
*/
|
||||
export const getMidpoint = (point1: LatLng, point2: LatLng): LatLng => {
|
||||
return {
|
||||
latitude: (point1.latitude + point2.latitude) / 2,
|
||||
longitude: (point1.longitude + point2.longitude) / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Tính khoảng cách giữa 2 điểm (Haversine formula)
|
||||
* Trả về khoảng cách theo km
|
||||
*/
|
||||
export const calculateDistance = (point1: LatLng, point2: LatLng): number => {
|
||||
const R = 6371; // Bán kính trái đất (km)
|
||||
const dLat = (point2.latitude - point1.latitude) * (Math.PI / 180);
|
||||
const dLon = (point2.longitude - point1.longitude) * (Math.PI / 180);
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(point1.latitude * (Math.PI / 180)) *
|
||||
Math.cos(point2.latitude * (Math.PI / 180)) *
|
||||
Math.sin(dLon / 2) *
|
||||
Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tính tổng khoảng cách của polyline
|
||||
*/
|
||||
export const calculateTotalDistance = (coordinates: LatLng[]): number => {
|
||||
if (coordinates.length < 2) return 0;
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||
totalDistance += calculateDistance(coordinates[i], coordinates[i + 1]);
|
||||
}
|
||||
|
||||
return totalDistance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tính heading (hướng) giữa 2 điểm
|
||||
* Trả về góc độ (0-360)
|
||||
*/
|
||||
export const calculateHeading = (point1: LatLng, point2: LatLng): number => {
|
||||
const dLon = point2.longitude - point1.longitude;
|
||||
const lat1 = point1.latitude * (Math.PI / 180);
|
||||
const lat2 = point2.latitude * (Math.PI / 180);
|
||||
const dLonRad = dLon * (Math.PI / 180);
|
||||
|
||||
const y = Math.sin(dLonRad) * Math.cos(lat2);
|
||||
const x =
|
||||
Math.cos(lat1) * Math.sin(lat2) -
|
||||
Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLonRad);
|
||||
|
||||
const bearing = Math.atan2(y, x) * (180 / Math.PI);
|
||||
return (bearing + 360) % 360;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tính điểm trung tâm (centroid) của polygon
|
||||
* Sử dụng thuật toán Shoelace formula để tính centroid chính xác
|
||||
* Thuật toán này tính centroid dựa trên diện tích, phù hợp với polygon bất kỳ
|
||||
*/
|
||||
export const getPolygonCenter = (coordinates: LatLng[]): LatLng => {
|
||||
if (coordinates.length === 0) {
|
||||
return { latitude: 0, longitude: 0 };
|
||||
}
|
||||
|
||||
if (coordinates.length === 1) {
|
||||
return coordinates[0];
|
||||
}
|
||||
|
||||
if (coordinates.length === 2) {
|
||||
return {
|
||||
latitude: (coordinates[0].latitude + coordinates[1].latitude) / 2,
|
||||
longitude: (coordinates[0].longitude + coordinates[1].longitude) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
let area = 0;
|
||||
let centroidLat = 0;
|
||||
let centroidLon = 0;
|
||||
|
||||
// Đảm bảo polygon đóng (điểm đầu = điểm cuối)
|
||||
const coords = [...coordinates];
|
||||
if (
|
||||
coords[0].latitude !== coords[coords.length - 1].latitude ||
|
||||
coords[0].longitude !== coords[coords.length - 1].longitude
|
||||
) {
|
||||
coords.push(coords[0]);
|
||||
}
|
||||
|
||||
// Tính diện tích và centroid sử dụng Shoelace formula
|
||||
for (let i = 0; i < coords.length - 1; i++) {
|
||||
const lat1 = coords[i].latitude;
|
||||
const lon1 = coords[i].longitude;
|
||||
const lat2 = coords[i + 1].latitude;
|
||||
const lon2 = coords[i + 1].longitude;
|
||||
|
||||
const cross = lat1 * lon2 - lon1 * lat2;
|
||||
area += cross;
|
||||
centroidLat += (lat1 + lat2) * cross;
|
||||
centroidLon += (lon1 + lon2) * cross;
|
||||
}
|
||||
|
||||
area = area / 2;
|
||||
|
||||
// Nếu diện tích quá nhỏ (polygon suy biến), dùng trung bình đơn giản
|
||||
if (Math.abs(area) < 0.0000001) {
|
||||
let latSum = 0;
|
||||
let lonSum = 0;
|
||||
for (const coord of coordinates) {
|
||||
latSum += coord.latitude;
|
||||
lonSum += coord.longitude;
|
||||
}
|
||||
return {
|
||||
latitude: latSum / coordinates.length,
|
||||
longitude: lonSum / coordinates.length,
|
||||
};
|
||||
}
|
||||
|
||||
centroidLat = centroidLat / (6 * area);
|
||||
centroidLon = centroidLon / (6 * area);
|
||||
|
||||
return {
|
||||
latitude: centroidLat,
|
||||
longitude: centroidLon,
|
||||
};
|
||||
};
|
||||
91
utils/sosUtils.ts
Normal file
91
utils/sosUtils.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Định nghĩa cấu trúc cho mỗi lý do cần hỗ trợ/SOS
|
||||
*/
|
||||
interface SosMessage {
|
||||
ma: number; // Mã số thứ tự của lý do
|
||||
moTa: string; // Mô tả ngắn gọn về sự cố
|
||||
mucDoNghiemTrong: string;
|
||||
chiTiet: string; // Chi tiết sự cố
|
||||
}
|
||||
|
||||
/**
|
||||
* Mảng 10 lý do phát tín hiệu SOS/Yêu cầu trợ giúp trên biển
|
||||
* Sắp xếp từ nhẹ (yêu cầu hỗ trợ) đến nặng (SOS khẩn cấp)
|
||||
*/
|
||||
export const sosMessage: SosMessage[] = [
|
||||
{
|
||||
ma: 11,
|
||||
moTa: "Tình huống khẩn cấp, không kịp chọn !!!",
|
||||
mucDoNghiemTrong: "Nguy Hiem Can Ke (SOS)",
|
||||
chiTiet:
|
||||
"Tình huống nghiêm trọng nhất, đe dọa trực tiếp đến tính mạng tất cả người trên tàu.",
|
||||
},
|
||||
{
|
||||
ma: 1,
|
||||
moTa: "Hỏng hóc động cơ không tự khắc phục được",
|
||||
mucDoNghiemTrong: "Nhe",
|
||||
chiTiet: "Tàu bị trôi hoặc mắc cạn nhẹ; cần tàu lai hoặc thợ máy.",
|
||||
},
|
||||
{
|
||||
ma: 2,
|
||||
moTa: "Thiếu nhiên liệu/thực phẩm/nước uống nghiêm trọng",
|
||||
mucDoNghiemTrong: "Nhe",
|
||||
chiTiet:
|
||||
"Dự trữ thiết yếu cạn kiệt do hành trình kéo dài không lường trước được.",
|
||||
},
|
||||
{
|
||||
ma: 3,
|
||||
moTa: "Sự cố y tế không nguy hiểm đến tính mạng",
|
||||
mucDoNghiemTrong: "Trung Binh",
|
||||
chiTiet:
|
||||
"Cần chăm sóc y tế chuyên nghiệp khẩn cấp (ví dụ: gãy xương, viêm ruột thừa).",
|
||||
},
|
||||
{
|
||||
ma: 4,
|
||||
moTa: "Hỏng hóc thiết bị định vị/thông tin liên lạc chính",
|
||||
mucDoNghiemTrong: "Trung Binh",
|
||||
chiTiet: "Mất khả năng xác định vị trí hoặc liên lạc, tăng rủi ro bị lạc.",
|
||||
},
|
||||
{
|
||||
ma: 5,
|
||||
moTa: "Thời tiết cực đoan sắp tới không kịp trú ẩn",
|
||||
mucDoNghiemTrong: "Trung Binh",
|
||||
chiTiet:
|
||||
"Tàu không kịp chạy vào nơi trú ẩn an toàn trước cơn bão lớn hoặc gió giật mạnh.",
|
||||
},
|
||||
{
|
||||
ma: 6,
|
||||
moTa: "Va chạm gây hư hỏng cấu trúc",
|
||||
mucDoNghiemTrong: "Nang",
|
||||
chiTiet:
|
||||
"Tàu bị hư hại một phần do va chạm, cần kiểm tra và hỗ trợ lai dắt khẩn cấp.",
|
||||
},
|
||||
{
|
||||
ma: 7,
|
||||
moTa: "Có cháy/hỏa hoạn trên tàu không kiểm soát được",
|
||||
mucDoNghiemTrong: "Nang",
|
||||
chiTiet:
|
||||
"Lửa bùng phát vượt quá khả năng chữa cháy của tàu, nguy cơ cháy lan.",
|
||||
},
|
||||
{
|
||||
ma: 8,
|
||||
moTa: "Tàu bị thủng/nước vào không kiểm soát được",
|
||||
mucDoNghiemTrong: "Rat Nang",
|
||||
chiTiet:
|
||||
"Nước tràn vào khoang quá nhanh, vượt quá khả năng bơm tát, đe dọa tàu chìm.",
|
||||
},
|
||||
{
|
||||
ma: 9,
|
||||
moTa: "Sự cố y tế nguy hiểm đến tính mạng (MEDEVAC)",
|
||||
mucDoNghiemTrong: "Rat Nang",
|
||||
chiTiet:
|
||||
"Thương tích/bệnh tật nghiêm trọng, cần sơ tán y tế (MEDEVAC) ngay lập tức bằng trực thăng/tàu cứu hộ.",
|
||||
},
|
||||
{
|
||||
ma: 10,
|
||||
moTa: "Tàu bị chìm/lật úp hoàn toàn hoặc sắp xảy ra",
|
||||
mucDoNghiemTrong: "Nguy Hiem Can Ke (SOS)",
|
||||
chiTiet:
|
||||
"Tình huống nghiêm trọng nhất, đe dọa trực tiếp đến tính mạng tất cả người trên tàu.",
|
||||
},
|
||||
];
|
||||
30
utils/storage.ts
Normal file
30
utils/storage.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
export async function setStorageItem(
|
||||
key: string,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.error("Error setting storage item:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStorageItem(key: string): Promise<string | null> {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem(key);
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error("Error getting storage item:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeStorageItem(key: string): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.error("Error removing storage item:", error);
|
||||
}
|
||||
}
|
||||
13
utils/token.ts
Normal file
13
utils/token.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export function parseJwtToken(token: string) {
|
||||
if (!token) return null;
|
||||
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split('')
|
||||
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
||||
.join(''),
|
||||
);
|
||||
return JSON.parse(jsonPayload);
|
||||
}
|
||||
11
utils/tranform.ts
Normal file
11
utils/tranform.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function transformEntityResponse(
|
||||
raw: Model.EntityResponse
|
||||
): Model.TransformedEntity {
|
||||
return {
|
||||
id: raw.id,
|
||||
value: raw.v,
|
||||
valueString: raw.vs,
|
||||
time: raw.t,
|
||||
type: raw.type,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user