thêm zustand để cấu hình global state, hook để lấy platform, thêm polyline và polygon b vào map

This commit is contained in:
Tran Anh Tuan
2025-10-31 19:54:16 +07:00
parent 2fac0b8093
commit 5801992eae
19 changed files with 1202 additions and 89 deletions

88
utils/geom.ts Normal file
View File

@@ -0,0 +1,88 @@
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ó";
}
};

157
utils/polyline.ts Normal file
View 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,
};
};

11
utils/tranform.ts Normal file
View 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,
};
}