bổ sung chức năng load 64 tỉnh thành và 32 tỉnh thành/ bổ sung mô hình Swin-Unet

This commit is contained in:
Victor Phan
2026-01-03 22:35:33 +07:00
parent 5342df77b4
commit fda2852dd2
10 changed files with 2722 additions and 62 deletions
+266 -15
View File
@@ -313,12 +313,43 @@
<!-- Prediction Content -->
<div id="predictionContent" class="content">
<!-- Province Selection Section -->
<div class="section" style="grid-column: 1 / -1;">
<h2>🗺️ Chọn Khu Vực Prediction</h2>
<div class="form-group" style="margin-bottom: 20px;">
<label>
<strong>🗺️ Chọn theo Tỉnh Thành:</strong>
<span style="color: #999; font-size: 13px; font-weight: normal;">(Hoặc vẽ bbox thủ công bên dưới)</span>
</label>
<!-- Toggle between 63 and 32 provinces -->
<div style="margin-bottom: 10px; display: flex; gap: 10px; align-items: center;">
<button type="button" id="btnPred63Provinces" onclick="switchPredProvinceList('63')" style="padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">63 Tỉnh (Cũ)</button>
<button type="button" id="btnPred32Provinces" onclick="switchPredProvinceList('32')" style="padding: 8px 16px; background: #f0f0f0; color: #333; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">32 Tỉnh (Sau sáp nhập)</button>
<span id="predProvinceListMode" style="color: #667eea; font-weight: bold;">Danh sách: 63 tỉnh</span>
</div>
<select id="predProvinceSelect" style="padding: 12px; width: 100%; border: 2px solid #ddd; border-radius: 8px; font-size: 14px; cursor: pointer;">
<option value="">-- Chọn tỉnh thành để tải bbox tự động --</option>
</select>
</div>
<!-- Region Filter -->
<div class="form-group" style="margin-bottom: 20px;">
<label><strong>🌍 Lọc theo Vùng:</strong></label>
<div id="predRegionFilterContainer" style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" class="pred-region-filter-btn" data-region="all" style="padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">Tất cả</button>
<!-- Dynamic region buttons will be added here -->
</div>
</div>
</div>
<!-- Map Section -->
<div class="map-container">
<div class="map-instructions">
<h3>📍 Chọn khu vực để predict</h3>
<p>✏️ Click vào nút hình vuông bên phải để vẽ bbox</p>
<p>🖱️ Kéo và thả để tạo vùng muốn phân loại</p>
<h3>💡 Hướng dẫn:</h3>
<p>📍 Chọn tỉnh thành ở trên để tự động điền bbox, hoặc sử dụng công cụ vẽ hình chữ nhật trên bản đồ</p>
<p>🔄 Có thể chỉnh sửa sau khi vẽ</p>
</div>
<div id="predictMap"></div>
@@ -382,6 +413,16 @@
</select>
</div>
<div class="form-group" style="margin-top: 15px; padding: 15px; background: #fff3e0; border-radius: 8px; border-left: 4px solid #ff9800;">
<label style="display: flex; align-items: center; cursor: pointer; margin: 0;">
<input type="checkbox" id="useGpuPred" checked style="width: 18px; height: 18px; margin-right: 10px;">
<span style="font-weight: 600; color: #e65100;">🚀 Sử dụng GPU (Deep Learning Models)</span>
</label>
<div style="font-size: 12px; color: #e65100; margin-top: 8px; margin-left: 28px;">
⚡ Tăng tốc prediction cho CNN/Swin-UNet models (yêu cầu GPU khả dụng)
</div>
</div>
<div class="form-group" style="margin-top: 20px; padding: 15px; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #2196F3;">
<label style="display: flex; align-items: center; cursor: pointer; margin: 0;">
<input type="checkbox" id="exportNDVI" checked style="width: 18px; height: 18px; margin-right: 10px;">
@@ -453,7 +494,7 @@
<!-- NDVI Configuration -->
<div class="section">
<h2>⚙️ Cấu hình NDVI</h2>
<h2>⚙️ Cấu hình NDVI để Dự đoán</h2>
<!-- NDVI Map for selecting bbox -->
<div class="form-group">
<label>Chọn bbox trên bản đồ hoặc nhập tọa độ:</label>
@@ -559,6 +600,12 @@
let ndviData = null;
let ndviChart = null;
// Province data
let allPredProvinces = {};
let allPredProvincesMerged = {};
let currentPredProvinceMode = '63'; // '63' or '32'
let currentPredProvinceName = '';
// Initialize map
function initMap() {
map = L.map('predictMap').setView([9.5, 105.9], 9);
@@ -724,17 +771,22 @@
const select = document.getElementById('modelSelect');
const option = select.options[select.selectedIndex];
if (option.dataset.info) {
const info = JSON.parse(option.dataset.info);
const infoDiv = document.getElementById('modelInfo');
document.getElementById('modelType').textContent = info.model_type || 'N/A';
document.getElementById('modelAccuracy').textContent = info.metrics?.accuracy
? (info.metrics.accuracy * 100).toFixed(2) + '%'
: 'N/A';
document.getElementById('modelDate').textContent = info.training_date || 'N/A';
infoDiv.style.display = 'block';
if (option && option.dataset && option.dataset.info) {
try {
const info = JSON.parse(option.dataset.info);
const infoDiv = document.getElementById('modelInfo');
document.getElementById('modelType').textContent = info.model_type || 'N/A';
document.getElementById('modelAccuracy').textContent = info.metrics?.accuracy
? (info.metrics.accuracy * 100).toFixed(2) + '%'
: 'N/A';
document.getElementById('modelDate').textContent = info.training_date || 'N/A';
infoDiv.style.display = 'block';
} catch (e) {
console.warn('Error parsing model info:', e);
document.getElementById('modelInfo').style.display = 'none';
}
} else {
document.getElementById('modelInfo').style.display = 'none';
}
@@ -765,6 +817,7 @@
// Check if export NDVI is enabled
const exportNDVI = document.getElementById('exportNDVI').checked;
const useGpu = document.getElementById('useGpuPred').checked;
const config = {
model_filename: modelFilename,
@@ -777,6 +830,7 @@
max_scenes: parseInt(document.getElementById('predMaxScenes').value),
cloud_cover: parseInt(document.getElementById('predCloudCover').value),
resolution: parseInt(document.getElementById('predResolution').value),
use_gpu: useGpu,
export_ndvi: exportNDVI,
export_classification: true
};
@@ -1100,16 +1154,213 @@
alert(`✅ Đã áp dụng cache preset!\n\nBBox: [${meta.bbox?.join(', ') || 'N/A'}]\nTime: ${meta.start_date || '?'}${meta.end_date || '?'}\n\nBạn có thể predict ngay mà không cần vẽ bbox!`);
}
// === PROVINCE SELECTION FUNCTIONS FOR PREDICTION ===
// Load provinces list (both 63 and 32)
async function loadPredProvinces() {
try {
// Load 63 provinces
const response63 = await fetch('/api/provinces/by-region');
allPredProvinces = await response63.json();
// Load 32 merged provinces
const response32 = await fetch('/api/provinces-32/by-region');
allPredProvincesMerged = await response32.json();
// Default to 63 provinces
populatePredRegionButtons();
populatePredProvinceSelect();
} catch (error) {
console.error('Error loading provinces:', error);
document.getElementById('predProvinceSelect').innerHTML = '<option value="">Lỗi tải danh sách tỉnh</option>';
}
}
// Switch between 63 and 32 province lists
function switchPredProvinceList(mode) {
currentPredProvinceMode = mode;
// Update button styles
const btn63 = document.getElementById('btnPred63Provinces');
const btn32 = document.getElementById('btnPred32Provinces');
const modeLabel = document.getElementById('predProvinceListMode');
if (mode === '63') {
btn63.style.background = '#667eea';
btn63.style.color = 'white';
btn32.style.background = '#f0f0f0';
btn32.style.color = '#333';
modeLabel.textContent = 'Danh sách: 63 tỉnh';
} else {
btn63.style.background = '#f0f0f0';
btn63.style.color = '#333';
btn32.style.background = '#667eea';
btn32.style.color = 'white';
modeLabel.textContent = 'Danh sách: 32 tỉnh (sau sáp nhập)';
}
// Update region filter buttons
populatePredRegionButtons();
// Reload province list
populatePredProvinceSelect();
}
// Populate region filter buttons
function populatePredRegionButtons() {
const container = document.getElementById('predRegionFilterContainer');
// Keep the "Tất cả" button
const allButton = container.querySelector('[data-region="all"]');
container.innerHTML = '';
container.appendChild(allButton);
// Get regions from current data
const provinceData = currentPredProvinceMode === '63' ? allPredProvinces : allPredProvincesMerged;
const regions = Object.keys(provinceData);
// Add button for each region
regions.forEach(region => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'pred-region-filter-btn';
button.dataset.region = region;
button.textContent = region;
button.style.cssText = 'padding: 8px 16px; background: #f0f0f0; color: #333; border: none; border-radius: 6px; cursor: pointer;';
container.appendChild(button);
});
// Re-setup event listeners
setupPredRegionFilters();
}
// Populate province select dropdown
function populatePredProvinceSelect(filterRegion = 'all') {
const select = document.getElementById('predProvinceSelect');
select.innerHTML = '<option value="">-- Chọn tỉnh thành để tải bbox tự động --</option>';
// Choose which province list to use
const provinceData = currentPredProvinceMode === '63' ? allPredProvinces : allPredProvincesMerged;
// Get all regions dynamically from data
const regions = Object.keys(provinceData);
regions.forEach(region => {
if (filterRegion !== 'all' && filterRegion !== region) {
return;
}
const provinces = provinceData[region];
if (!provinces || provinces.length === 0) return;
const optgroup = document.createElement('optgroup');
optgroup.label = `${region} (${provinces.length} tỉnh)`;
provinces.forEach(province => {
const option = document.createElement('option');
option.value = province.name;
// For merged provinces, show additional info
if (currentPredProvinceMode === '32' && province.merged_from) {
option.textContent = `${province.name} (${province.merged_from.join(', ')})`;
} else {
option.textContent = `${province.name} - ${province.name_en || ''}`;
}
option.dataset.bbox = JSON.stringify(province.bbox);
optgroup.appendChild(option);
});
select.appendChild(optgroup);
});
}
// Handle province selection for prediction
function onPredProvinceSelect(event) {
const select = event.target;
const selectedOption = select.options[select.selectedIndex];
if (!selectedOption.value) {
currentPredProvinceName = '';
return;
}
const provinceName = selectedOption.value;
const bbox = JSON.parse(selectedOption.dataset.bbox);
currentPredProvinceName = provinceName;
// Update selectedBbox
selectedBbox = {
min_lon: bbox[0],
min_lat: bbox[1],
max_lon: bbox[2],
max_lat: bbox[3]
};
// Save to localStorage
localStorage.setItem('prediction_bbox', JSON.stringify(selectedBbox));
// Draw rectangle on map
const bounds = [[bbox[1], bbox[0]], [bbox[3], bbox[2]]];
// Remove previous rectangle
drawnItems.clearLayers();
// Add new rectangle
const rectangle = L.rectangle(bounds, {
color: '#667eea',
weight: 3,
fillOpacity: 0.2
});
drawnItems.addLayer(rectangle);
// Fit map to bounds
map.fitBounds(bounds, { padding: [50, 50] });
// Show notification
console.log(`✅ Đã chọn tỉnh: ${provinceName}`);
alert(`✅ Đã chọn tỉnh: ${provinceName}\n\nBbox: [${bbox.join(', ')}]`);
}
// Handle region filter for prediction
function setupPredRegionFilters() {
const filterButtons = document.querySelectorAll('.pred-region-filter-btn');
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
// Update active button style
filterButtons.forEach(b => {
b.style.background = '#f0f0f0';
b.style.color = '#333';
b.style.fontWeight = 'normal';
});
this.style.background = '#667eea';
this.style.color = 'white';
this.style.fontWeight = '600';
// Filter provinces
const region = this.dataset.region;
populatePredProvinceSelect(region);
});
});
}
// Initialize on page load
window.onload = function() {
initMap();
loadModels();
loadPredictionsList();
loadCacheList();
loadPredProvinces(); // Load provinces list
// Add event listener for model selection
document.getElementById('modelSelect').addEventListener('change', updateModelInfo);
// Add event listener for cache selection
document.getElementById('cacheSelect').addEventListener('change', applyCachePreset);
// Add event listener for province selection
document.getElementById('predProvinceSelect').addEventListener('change', onPredProvinceSelect);
setupPredRegionFilters();
};
// Cleanup on page unload