Việc tích hợp chức năng tìm kiếm tọa độ địa lý trong ứng dụng bản đồ 3D là yêu cầu phổ biến khi làm việc với CesiumJS. Quy trình thực hiện bao gồm việc tiếp nhận dữ liệu đầu vào từ người dùng, xác thực định dạng (bao gồm cả số thập phân và độ-phút-giây), chuyển đổi sang hệ tọa độ phù hợp, tạo đối tượng đánh dấu trên bề mặt trái đất và điều khiển góc nhìn máy bay đến vị trí đích.
1. Cơ chế hiển thị điểm và di chuyển Camera
Khi nhận được cặp giá trị kinh độ và vĩ độ đã chuẩn hóa, chúng ta cần xóa các thực thể cũ trên bản đồ để tránh trùng lặp, sau đó thêm một biểu tượng mới kèm theo nhãn văn bản. Đồng thời, kích hoạt hàm flyTo để đưa mắt xem đến mục tiêu một cách mượt mà.
renderMarker(longitude, latitude) {
// Xóa toàn bộ thực thể hiện có để dọn dẹp
this.viewer.entities.removeAll();
// Tạo cấu hình cho điểm đánh dấu
const position = Cesium.Cartesian3.fromDegrees(longitude, latitude);
const entityConfig = {
position: position,
billboard: {
image: markerIconUrl, // Đường dẫn hoặc tham chiếu đến ảnh marker
scale: 1.0
},
label: {
text: 'Vị trí mục tiêu',
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
backgroundColor: Cesium.Color.BLUE,
showBackground: true,
style: Cesium.LabelStyle.FILL,
outlineWidth: 1,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
pixelOffset: new Cesium.Cartesian2(15, 0)
}
};
// Thêm thực thể vào scene
this.viewer.entities.add(entityConfig);
// Điều khiển camera bay tới vị trí với độ cao nhất định
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, 300000),
duration: 1.5
});
}
2. Xử lý nhập liệu và chuẩn hóa tọa độ
Hệ thống cần hỗ trợ hai kiểu nhập liệu phổ biến: số thập phân (ví dụ: 120.5, 30.5) và định dạng độ/phút/giây (ví dụ: 120°11'11",30°11'11"). Hàm xử lý chính sẽ phân tách chuỗi, kiểm tra tính hợp lệ của ký tự ngăn cách và đảm bảo giá trị nằm trong phạm vi địa lý quy chuẩn trước khi chuyển đổi.
handleCoordinateSearch(rawInput) {
if (!rawInput || typeof rawInput !== 'string') return;
const parts = rawInput.split(',').map(item => item.trim());
// Kiểm tra số lượng thành phần phải đúng 2
if (parts.length !== 2 || parts.includes('')) {
this.notifyUser('Dữ liệu không đủ 2 thành phần', 'warning');
return;
}
let finalLon, finalLat;
const [lonStr, latStr] = parts;
try {
// Thử phân tích dưới dạng số thập phân trước
const lonDec = Number(lonStr);
const latDec = Number(latStr);
if (!isNaN(lonDec) && !isNaN(latDec)) {
// Xác thực khoảng số thập phân
if (lonDec < -180 || lonDec > 180 || latDec < -90 || latDec > 90) {
throw new Error('Giá trị ngoài phạm vi');
}
finalLon = lonDec;
finalLat = latDec;
} else {
// Nếu không phải số thập phân, chuyển sang xử lý DMS
const parsedLon = this.parseDMSCoordinate(lonStr, -180, 180, true);
const parsedLat = this.parseDMSCoordinate(latStr, -90, 90, false);
if (parsedLon === null || parsedLat === null) {
throw new Error('Định dạng DMS không hợp lệ');
}
finalLon = parsedLon;
finalLat = parsedLat;
}
// Gọi hàm hiển thị nếu tất cả kiểm duyệt qua
this.renderMarker(finalLon, finalLat);
} catch (err) {
this.notifyUser(err.message || 'Lỗi phân tích tọa độ', 'error');
}
}
parseDMSCoordinate(str, minLimit, maxLimit, isLongitude) {
// Regex để khớp mẫu: Số độ ° Số phút ' Số giây "
const dmsPattern = /(-?\d+)\s*°\s*(\d+)\s*'\s*(\d+)\s*"/;
const match = str.match(dmsPattern);
if (!match) return null;
const degrees = parseFloat(match[1]);
const minutes = parseFloat(match[2]);
const seconds = parseFloat(match[3]);
// Kiểm tra điều kiện đặc biệt cho đường kinh tuyến 180
if ((degrees === 180 || degrees === -180) && (minutes !== 0 || seconds !== 0)) {
return null;
}
// Kiểm tra giới hạn phút và giây
if (minutes < 0 || minutes > 60 || seconds < 0 || seconds > 60) {
return null;
}
// Chuyển đổi về hệ thập phân
let decimalValue = Math.abs(degrees) + (minutes / 60) + (seconds / 3600);
if (degrees < 0) decimalValue *= -1;
// Kiểm tra phạm vi tổng quát
if (isLongitude && (decimalValue > 180 || decimalValue < -180)) return null;
if (!isLongitude && (decimalValue > 90 || decimalValue < -90)) return null;
return decimalValue;
}