đã áp dụng file shapefile vào train và predict

This commit is contained in:
Victor Phan
2026-01-05 11:19:34 +07:00
parent fda2852dd2
commit f401765996
23 changed files with 4148 additions and 85 deletions
+529 -11
View File
@@ -491,10 +491,84 @@
<!-- NDVI Analysis Content -->
<div id="ndviContent" class="content" style="display: none;">
<!-- Province Selection for NDVI -->
<div class="section" style="grid-column: 1 / -1;">
<h2>🗺️ Chọn Khu Vực NDVI Analysis</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="btnNDVI63Provinces" onclick="switchNDVIProvinceList('63')" style="padding: 8px 16px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">63 Tỉnh (Cũ)</button>
<button type="button" id="btnNDVI32Provinces" onclick="switchNDVIProvinceList('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="ndviProvinceListMode" style="color: #2ecc71; font-weight: bold;">Danh sách: 63 tỉnh</span>
</div>
<select id="ndviProvinceSelect" 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 for NDVI -->
<div class="form-group" style="margin-bottom: 20px;">
<label><strong>🌍 Lọc theo Vùng:</strong></label>
<div id="ndviRegionFilterContainer" style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" class="ndvi-region-filter-btn" data-region="all" style="padding: 8px 16px; background: #2ecc71; 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>
<!-- Model Selection for NDVI -->
<div class="section">
<h2>🤖 Chọn Model để Predict</h2>
<div class="form-group">
<label for="ndviModelSelect">Model đã train:</label>
<select id="ndviModelSelect" style="padding: 10px; width: 100%; border: 2px solid #ddd; border-radius: 8px; font-size: 14px; cursor: pointer;">
<option value="">Đang tải...</option>
</select>
</div>
<div id="ndviModelInfo" style="display: none; background: #e8f5e9; padding: 15px; border-radius: 8px; margin-top: 15px;">
<h4 style="color: #2e7d32; margin-bottom: 10px;">📊 Thông tin Model</h4>
<p><strong>Type:</strong> <span id="ndviModelType">-</span></p>
<p><strong>Accuracy:</strong> <span id="ndviModelAccuracy">-</span></p>
<p><strong>Training Date:</strong> <span id="ndviModelDate">-</span></p>
</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="useGpuNDVI" 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>
<!-- NDVI Configuration -->
<div class="section">
<h2>⚙️ Cấu hình NDVI để Dự đoán</h2>
<h2>⚙️ Cấu hình NDVI để so sánh thay đổi</h2>
<!-- Quick Presets -->
<div class="form-group" style="margin-bottom: 20px; padding: 15px; background: #e8f5e9; border-radius: 8px; border-left: 4px solid #4caf50;">
<label style="color: #2e7d32; font-weight: 700; margin-bottom: 10px;">🎯 Quick Presets (Khu vực có dữ liệu tốt)</label>
<select id="ndviQuickPreset" onchange="applyNDVIPreset()" style="padding: 10px; width: 100%; border: 2px solid #4caf50; border-radius: 6px; font-size: 14px; cursor: pointer;">
<option value="">-- Chọn preset để áp dụng tự động --</option>
<option value="mekong_dry">🌾 Đồng bằng Cửu Long - Mùa khô (Jan-Apr 2024)</option>
<option value="hanoi_dry">🏙️ Hà Nội - Mùa khô (Feb-Apr 2024)</option>
<option value="danang_dry">🏖️ Đà Nẵng - Mùa khô (Jan-Mar 2024)</option>
<option value="mekong_2023">🌾 Đồng bằng Cửu Long - Full năm 2023</option>
<option value="small_test">⚡ Test nhanh - Khu vực nhỏ (500 points)</option>
</select>
<div style="font-size: 12px; color: #2e7d32; margin-top: 8px;">💡 Các preset này đã được kiểm tra và có dữ liệu tốt, ít mây</div>
</div>
<!-- NDVI Map for selecting bbox -->
<div class="form-group">
<label>Chọn bbox trên bản đồ hoặc nhập tọa độ:</label>
@@ -524,8 +598,28 @@
<input type="number" id="ndviCloudCover" value="30" min="0" max="100">
</div>
<button class="btn btn-primary" onclick="calculateNDVI()" id="ndviCalculateBtn">
📊 Tính NDVI Time Series
<div class="form-group">
<label for="ndviMaxScenes">Số scenes tối đa:</label>
<input type="number" id="ndviMaxScenes" value="12" min="1" max="100">
<div style="font-size: 12px; color: #666; margin-top: 5px;">Giới hạn số lượng ảnh vệ tinh xử lý. Ít scenes = nhanh hơn nhưng ít dữ liệu.</div>
</div>
<div class="form-group">
<label for="ndviResolution">Resolution:</label>
<select id="ndviResolution">
<option value="10">10m (Chi tiết cao - Chậm)</option>
<option value="20" selected>20m (Cân bằng)</option>
</select>
</div>
<div class="form-group">
<label for="ndviSamplePoints">Số điểm dự đoán (sample):</label>
<input type="number" id="ndviSamplePoints" value="1000" min="100" max="10000" step="100">
<div style="font-size: 12px; color: #666; margin-top: 5px;">Số điểm ngẫu nhiên để predict và tính NDVI time series. Nhiều điểm = chính xác hơn nhưng chậm hơn.</div>
</div>
<button class="btn btn-primary" onclick="calculateNDVIPrediction()" id="ndviCalculateBtn">
📊 Predict & Tính NDVI Time Series
</button>
<button class="btn btn-secondary" onclick="usePredictionBbox()" style="margin-left: 10px;">
@@ -1346,6 +1440,258 @@
});
}
// === NDVI PROVINCE SELECTION FUNCTIONS ===
let allNDVIProvinces = {};
let allNDVIProvincesMerged = {};
let currentNDVIProvinceMode = '63'; // '63' or '32'
// Switch between 63 and 32 province lists for NDVI
function switchNDVIProvinceList(mode) {
currentNDVIProvinceMode = mode;
// Update button styles
const btn63 = document.getElementById('btnNDVI63Provinces');
const btn32 = document.getElementById('btnNDVI32Provinces');
const modeLabel = document.getElementById('ndviProvinceListMode');
if (mode === '63') {
btn63.style.background = '#2ecc71';
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 = '#2ecc71';
btn32.style.color = 'white';
modeLabel.textContent = 'Danh sách: 32 tỉnh (sau sáp nhập)';
}
// Update region filter buttons
populateNDVIRegionButtons();
// Reload province list
populateNDVIProvinceSelect();
}
// Populate region filter buttons for NDVI
function populateNDVIRegionButtons() {
const container = document.getElementById('ndviRegionFilterContainer');
// 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 = currentNDVIProvinceMode === '63' ? allNDVIProvinces : allNDVIProvincesMerged;
const regions = Object.keys(provinceData);
// Add button for each region
regions.forEach(region => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'ndvi-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
setupNDVIRegionFilters();
}
// Populate province select dropdown for NDVI
function populateNDVIProvinceSelect(filterRegion = 'all') {
const select = document.getElementById('ndviProvinceSelect');
select.innerHTML = '<option value="">-- Chọn tỉnh thành để tải bbox tự động --</option>';
// Choose which province list to use
const provinceData = currentNDVIProvinceMode === '63' ? allNDVIProvinces : allNDVIProvincesMerged;
// 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 (currentNDVIProvinceMode === '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 NDVI
function onNDVIProvinceSelect(event) {
const select = event.target;
const selectedOption = select.options[select.selectedIndex];
if (!selectedOption.value) {
return;
}
const provinceName = selectedOption.value;
const bbox = JSON.parse(selectedOption.dataset.bbox);
// Update NDVI bbox inputs
document.getElementById('ndviMinLon').value = bbox[0].toFixed(4);
document.getElementById('ndviMinLat').value = bbox[1].toFixed(4);
document.getElementById('ndviMaxLon').value = bbox[2].toFixed(4);
document.getElementById('ndviMaxLat').value = bbox[3].toFixed(4);
// Draw rectangle on NDVI map
const bounds = [[bbox[1], bbox[0]], [bbox[3], bbox[2]]];
// Remove previous rectangle
ndviDrawnItems.clearLayers();
// Add new rectangle
const rectangle = L.rectangle(bounds, {
color: '#2ecc71',
weight: 3,
fillOpacity: 0.2
});
ndviDrawnItems.addLayer(rectangle);
// Fit map to bounds
ndviMap.fitBounds(bounds, { padding: [50, 50] });
// Show notification
console.log(`✅ NDVI - Đã chọn tỉnh: ${provinceName}`);
alert(`✅ Đã chọn tỉnh cho NDVI Analysis: ${provinceName}\n\nBbox: [${bbox.join(', ')}]`);
}
// Handle region filter for NDVI
function setupNDVIRegionFilters() {
const filterButtons = document.querySelectorAll('.ndvi-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 = '#2ecc71';
this.style.color = 'white';
this.style.fontWeight = '600';
// Filter provinces
const region = this.dataset.region;
populateNDVIProvinceSelect(region);
});
});
}
// Load NDVI provinces data
async function loadNDVIProvinces() {
try {
// Reuse data from prediction if already loaded
if (Object.keys(allPredProvinces).length > 0) {
allNDVIProvinces = allPredProvinces;
allNDVIProvincesMerged = allPredProvincesMerged;
} else {
// Load fresh
const response63 = await fetch('/api/provinces/by-region');
allNDVIProvinces = await response63.json();
const response32 = await fetch('/api/provinces-32/by-region');
allNDVIProvincesMerged = await response32.json();
}
// Populate NDVI province UI
populateNDVIRegionButtons();
populateNDVIProvinceSelect();
} catch (error) {
console.error('Error loading NDVI provinces:', error);
document.getElementById('ndviProvinceSelect').innerHTML = '<option value="">Lỗi tải danh sách tỉnh</option>';
}
}
// Load models for NDVI
async function loadNDVIModels() {
try {
const response = await fetch('/api/models/list');
const data = await response.json();
const select = document.getElementById('ndviModelSelect');
select.innerHTML = '<option value="">Chọn model...</option>';
// Chỉ lấy các file model thực sự (.joblib), loại bỏ các file có chứa '_info.joblib'
data.models
.filter(m => m.filename.endsWith('.joblib') && !m.filename.includes('_info.joblib'))
.forEach(model => {
const option = document.createElement('option');
option.value = model.filename;
option.textContent = model.filename;
if (model.info) {
option.dataset.info = JSON.stringify(model.info);
}
select.appendChild(option);
});
// Auto-select first model
const firstJoblib = data.models.find(m => m.filename.endsWith('.joblib') && !m.filename.includes('_info.joblib'));
if (firstJoblib) {
select.value = firstJoblib.filename;
updateNDVIModelInfo();
}
} catch (error) {
console.error('Error loading NDVI models:', error);
}
}
// Update NDVI model info display
function updateNDVIModelInfo() {
const select = document.getElementById('ndviModelSelect');
const option = select.options[select.selectedIndex];
if (option && option.dataset && option.dataset.info) {
try {
const info = JSON.parse(option.dataset.info);
const infoDiv = document.getElementById('ndviModelInfo');
document.getElementById('ndviModelType').textContent = info.model_type || 'N/A';
document.getElementById('ndviModelAccuracy').textContent = info.metrics?.accuracy
? (info.metrics.accuracy * 100).toFixed(2) + '%'
: 'N/A';
document.getElementById('ndviModelDate').textContent = info.training_date || 'N/A';
infoDiv.style.display = 'block';
} catch (e) {
console.warn('Error parsing NDVI model info:', e);
document.getElementById('ndviModelInfo').style.display = 'none';
}
} else {
document.getElementById('ndviModelInfo').style.display = 'none';
}
}
// Initialize on page load
window.onload = function() {
initMap();
@@ -1353,14 +1699,19 @@
loadPredictionsList();
loadCacheList();
loadPredProvinces(); // Load provinces list
loadNDVIProvinces(); // Load NDVI provinces list
loadNDVIModels(); // Load models for NDVI
// Add event listener for model selection
document.getElementById('modelSelect').addEventListener('change', updateModelInfo);
document.getElementById('ndviModelSelect').addEventListener('change', updateNDVIModelInfo);
// Add event listener for cache selection
document.getElementById('cacheSelect').addEventListener('change', applyCachePreset);
// Add event listener for province selection
document.getElementById('predProvinceSelect').addEventListener('change', onPredProvinceSelect);
document.getElementById('ndviProvinceSelect').addEventListener('change', onNDVIProvinceSelect);
setupPredRegionFilters();
setupNDVIRegionFilters();
};
// Cleanup on page unload
@@ -1379,6 +1730,10 @@
document.getElementById('tabPrediction').style.color = 'white';
document.getElementById('tabNDVI').style.background = '#ccc';
document.getElementById('tabNDVI').style.color = '#666';
// Invalidate map size when switching back to prediction tab
setTimeout(() => {
if (map) map.invalidateSize();
}, 100);
} else {
document.getElementById('predictionContent').style.display = 'none';
document.getElementById('ndviContent').style.display = 'grid';
@@ -1386,6 +1741,10 @@
document.getElementById('tabPrediction').style.color = '#666';
document.getElementById('tabNDVI').style.background = '#2ecc71';
document.getElementById('tabNDVI').style.color = 'white';
// Fix NDVI map rendering issue when tab is shown
setTimeout(() => {
if (ndviMap) ndviMap.invalidateSize();
}, 100);
}
}
@@ -1395,14 +1754,88 @@
alert('⚠️ Chưa chọn bbox trong phần Prediction. Vui lòng vẽ bbox trên bản đồ trước!');
return;
}
document.getElementById('ndviMinLon').value = selectedBbox[0].toFixed(4);
document.getElementById('ndviMinLat').value = selectedBbox[1].toFixed(4);
document.getElementById('ndviMaxLon').value = selectedBbox[2].toFixed(4);
document.getElementById('ndviMaxLat').value = selectedBbox[3].toFixed(4);
document.getElementById('ndviMinLon').value = selectedBbox.min_lon.toFixed(4);
document.getElementById('ndviMinLat').value = selectedBbox.min_lat.toFixed(4);
document.getElementById('ndviMaxLon').value = selectedBbox.max_lon.toFixed(4);
document.getElementById('ndviMaxLat').value = selectedBbox.max_lat.toFixed(4);
alert('✅ Đã copy bbox từ Prediction!');
}
// Calculate NDVI time series
// Calculate NDVI time series with prediction
async function calculateNDVIPrediction() {
const minLon = parseFloat(document.getElementById('ndviMinLon').value);
const minLat = parseFloat(document.getElementById('ndviMinLat').value);
const maxLon = parseFloat(document.getElementById('ndviMaxLon').value);
const maxLat = parseFloat(document.getElementById('ndviMaxLat').value);
const startDate = document.getElementById('ndviStartDate').value;
const endDate = document.getElementById('ndviEndDate').value;
const maxCloudCover = parseInt(document.getElementById('ndviCloudCover').value);
const maxScenes = parseInt(document.getElementById('ndviMaxScenes').value);
const modelFilename = document.getElementById('ndviModelSelect').value;
const useGpu = document.getElementById('useGpuNDVI').checked;
const resolution = parseInt(document.getElementById('ndviResolution').value);
const samplePoints = parseInt(document.getElementById('ndviSamplePoints').value);
// Validate
if (isNaN(minLon) || isNaN(minLat) || isNaN(maxLon) || isNaN(maxLat)) {
showNDVIError('Vui lòng nhập đầy đủ tọa độ bbox!');
return;
}
if (!startDate || !endDate) {
showNDVIError('Vui lòng chọn khoảng thời gian!');
return;
}
if (!modelFilename) {
showNDVIError('Vui lòng chọn model để predict!');
return;
}
// Show loading
document.getElementById('ndviLoading').style.display = 'block';
document.getElementById('ndviResults').style.display = 'none';
document.getElementById('ndviChartSection').style.display = 'none';
document.getElementById('ndviError').style.display = 'none';
document.getElementById('ndviCalculateBtn').disabled = true;
const config = {
model_filename: modelFilename,
bbox: [minLon, minLat, maxLon, maxLat],
start_date: startDate,
end_date: endDate,
max_cloud_cover: maxCloudCover,
max_scenes: maxScenes,
resolution: resolution,
sample_points: samplePoints,
use_gpu: useGpu
};
try {
const response = await fetch('/api/ndvi/predict-timeseries', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
if (response.ok) {
ndviData = result;
displayNDVIResults(result);
} else {
throw new Error(result.detail || 'Lỗi khi predict NDVI');
}
} catch (error) {
console.error('Error calculating NDVI with prediction:', error);
showNDVIError(error.message);
} finally {
document.getElementById('ndviLoading').style.display = 'none';
document.getElementById('ndviCalculateBtn').disabled = false;
}
}
// Calculate NDVI time series (legacy - without prediction)
async function calculateNDVI() {
const minLon = parseFloat(document.getElementById('ndviMinLon').value);
const minLat = parseFloat(document.getElementById('ndviMinLat').value);
@@ -1487,7 +1920,7 @@
}
const dates = data.timeseries.map(item => new Date(item.date).toLocaleDateString('vi-VN'));
const ndviValues = data.timeseries.map(item => item.ndvi);
const ndviValues = data.timeseries.map(item => item.mean_ndvi);
ndviChart = new Chart(ctx, {
type: 'line',
@@ -1548,9 +1981,9 @@
function downloadNDVIData() {
if (!ndviData) return;
let csv = 'Date,NDVI\\n';
let csv = 'Date,Mean_NDVI,Min_NDVI,Max_NDVI,Std_NDVI,Valid_Points\\n';
ndviData.timeseries.forEach(item => {
csv += `${item.date},${item.ndvi}\\n`;
csv += `${item.date},${item.mean_ndvi},${item.min_ndvi},${item.max_ndvi},${item.std_ndvi},${item.n_valid_points}\\n`;
});
const blob = new Blob([csv], { type: 'text/csv' });
@@ -1578,6 +2011,91 @@
document.getElementById('ndviError').style.display = 'block';
document.getElementById('ndviErrorMessage').textContent = message;
}
// Apply NDVI preset
function applyNDVIPreset() {
const preset = document.getElementById('ndviQuickPreset').value;
if (!preset) return;
const presets = {
'mekong_dry': {
name: 'Đồng bằng Cửu Long - Mùa khô',
bbox: [105.6, 9.3, 106.2, 9.8],
start_date: '2024-01-01',
end_date: '2024-04-30',
cloud_cover: 30,
max_scenes: 15,
sample_points: 1000
},
'hanoi_dry': {
name: 'Hà Nội - Mùa khô',
bbox: [105.7, 20.9, 105.9, 21.1],
start_date: '2024-02-01',
end_date: '2024-04-30',
cloud_cover: 40,
max_scenes: 12,
sample_points: 800
},
'danang_dry': {
name: 'Đà Nẵng - Mùa khô',
bbox: [107.9, 15.9, 108.3, 16.2],
start_date: '2024-01-01',
end_date: '2024-03-31',
cloud_cover: 35,
max_scenes: 12,
sample_points: 800
},
'mekong_2023': {
name: 'Đồng bằng Cửu Long - Full 2023',
bbox: [105.6, 9.3, 106.2, 9.8],
start_date: '2023-01-01',
end_date: '2023-12-31',
cloud_cover: 40,
max_scenes: 20,
sample_points: 1000
},
'small_test': {
name: 'Test nhanh - Khu vực nhỏ',
bbox: [105.8, 9.5, 105.9, 9.6],
start_date: '2024-01-01',
end_date: '2024-03-31',
cloud_cover: 50,
max_scenes: 10,
sample_points: 500
}
};
const config = presets[preset];
if (!config) return;
// Apply bbox
document.getElementById('ndviMinLon').value = config.bbox[0];
document.getElementById('ndviMinLat').value = config.bbox[1];
document.getElementById('ndviMaxLon').value = config.bbox[2];
document.getElementById('ndviMaxLat').value = config.bbox[3];
// Apply dates
document.getElementById('ndviStartDate').value = config.start_date;
document.getElementById('ndviEndDate').value = config.end_date;
// Apply other params
document.getElementById('ndviCloudCover').value = config.cloud_cover;
document.getElementById('ndviMaxScenes').value = config.max_scenes;
document.getElementById('ndviSamplePoints').value = config.sample_points;
// Draw bbox on map
ndviDrawnItems.clearLayers();
const bounds = [[config.bbox[1], config.bbox[0]], [config.bbox[3], config.bbox[2]]];
const rectangle = L.rectangle(bounds, {
color: '#2ecc71',
weight: 3,
fillOpacity: 0.2
});
ndviDrawnItems.addLayer(rectangle);
ndviMap.fitBounds(bounds, { padding: [50, 50] });
alert(`✅ Đã áp dụng preset: ${config.name}\n\nBbox: [${config.bbox.join(', ')}]\nThời gian: ${config.start_date}${config.end_date}\nSample points: ${config.sample_points}`);
}
</script>
</body>
</html>