hoàn thành chức năng phân lô trên ảnh predict

This commit is contained in:
2026-02-18 16:22:18 +07:00
committed by Victor Phan
parent eacc6f9b96
commit ae4d8cbbc9
5 changed files with 785 additions and 2 deletions
+190 -1
View File
@@ -774,6 +774,23 @@
</div>
</label>
</div>
<!-- Shapefile Overlay Option -->
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-weight: 600; color: #c2410c; margin-bottom: 8px; display: block;">
🗺️ Overlay Shapefile (Hiển thị ranh giới lô đất)
</label>
<select id="shapefileOverlay" onchange="onShapefileSelected(event)" style="width: 100%; padding: 10px; border: 2px solid #fdba74; border-radius: 8px; font-size: 14px; background: white;">
<option value="">-- Không overlay --</option>
<!-- Shapefiles will be loaded here -->
</select>
<div style="font-size: 0.85em; color: #9a3412; margin-top: 4px; line-height: 1.4;">
<b>🎯 Tự động cập nhật vùng prediction:</b><br>
✅ Khi chọn shapefile → <b>Bbox trên bản đồ tự động thay đổi</b> theo vùng shapefile<br>
<b>CRS sẽ tự động chuyển đổi</b> - không cần lo về EPSG:4326/9209/32648<br>
💡 Không chọn shapefile → Dùng bbox tùy chỉnh do bạn vẽ trên bản đồ
</div>
</div>
</div>
<button class="btn btn-primary" onclick="startPrediction()" id="predictBtn" style="margin-top: 5px; width: 100%; font-size: 1.1em; padding: 16px;">
@@ -1651,6 +1668,29 @@
}
}
// Get shapefile overlay option
const shapefileOverlay = document.getElementById('shapefileOverlay').value;
// Warn if no shapefile selected (optional but recommended)
if (!shapefileOverlay) {
const confirmWithoutShapefile = confirm(
'⚠️ CẢNH BÁO: Bạn chưa chọn shapefile!\n\n' +
'❌ Kết quả sẽ KHÔNG có đường ranh giới lô đất.\n\n' +
'💡 Để có đường phân lô trên ảnh kết quả:\n' +
' - Hủy bỏ\n' +
' - Chọn shapefile trong dropdown "Overlay Shapefile"\n' +
' - Chạy lại prediction\n\n' +
'Bạn có muốn tiếp tục KHÔNG CÓ ranh giới lô đất không?'
);
if (!confirmWithoutShapefile) {
console.log('[PREDICTION] User cancelled to select shapefile');
return;
}
} else {
console.log(`[PREDICTION] Shapefile selected: ${shapefileOverlay}`);
}
const config = {
model_filename: modelFilename,
min_lon: selectedBbox.min_lon,
@@ -1666,7 +1706,8 @@
export_ndvi: exportNDVI,
export_classification: true,
cloud_removal_method: cloudRemovalConfig.method,
cloud_removal_model: cloudRemovalConfig.model_filename || null
cloud_removal_model: cloudRemovalConfig.model_filename || null,
shapefile_overlay: shapefileOverlay || null
};
try {
@@ -2598,6 +2639,7 @@
loadPredProvinces(); // Load provinces list
loadNDVIProvinces(); // Load NDVI provinces list
loadNDVIModels(); // Load models for NDVI
loadOverlayShapefiles(); // Load shapefiles for overlay
// Add event listener for model selection
document.getElementById('modelSelect').addEventListener('change', updateModelInfo);
@@ -2993,6 +3035,153 @@
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}`);
}
// Load overlay shapefiles
async function loadOverlayShapefiles() {
try {
const response = await fetch('/api/overlay/shapefiles');
const data = await response.json();
const select = document.getElementById('shapefileOverlay');
select.innerHTML = '<option value="">-- Không overlay --</option>';
if (data.shapefiles && data.shapefiles.length > 0) {
data.shapefiles.forEach(shp => {
const option = document.createElement('option');
option.value = shp.path;
// Build detailed label with CRS and bbox info
let label = `${shp.filename} - ${shp.feature_count} features`;
// Add CRS info (important for matching!)
if (shp.crs) {
const crsCode = shp.crs.split(':').pop(); // Extract code from "EPSG:4326"
label += ` | CRS: ${crsCode}`;
}
// Add bbox info for easy matching with prediction area
if (shp.bbox && shp.bbox.length === 4) {
const [minLon, minLat, maxLon, maxLat] = shp.bbox;
label += ` | Vùng: [${minLon.toFixed(2)}, ${minLat.toFixed(2)}, ${maxLon.toFixed(2)}, ${maxLat.toFixed(2)}]`;
}
option.textContent = label;
// Store full shapefile info as data attributes for later use
option.dataset.crs = shp.crs || '';
option.dataset.bbox = JSON.stringify(shp.bbox || []);
option.dataset.featureCount = shp.feature_count;
select.appendChild(option);
});
console.log(`[Overlay Shapefiles] Loaded ${data.shapefiles.length} shapefiles`);
} else {
console.log('[Overlay Shapefiles] No shapefiles found');
}
// Add event listener for shapefile selection change (OUTSIDE the if block)
// Remove old listener first to prevent duplicates
select.removeEventListener('change', onShapefileSelected);
select.addEventListener('change', onShapefileSelected);
console.log('[Overlay Shapefiles] Event listener attached');
} catch (error) {
console.error('[Overlay Shapefiles] Error loading shapefiles:', error);
const select = document.getElementById('shapefileOverlay');
select.innerHTML = '<option value="">Error loading shapefiles</option>';
}
}
// Handle shapefile selection - auto update bbox on map
function onShapefileSelected(event) {
console.log('[Shapefile Select] Event triggered');
console.log('[Shapefile Select] map exists:', typeof map !== 'undefined');
console.log('[Shapefile Select] drawnItems exists:', typeof drawnItems !== 'undefined');
const selectedOption = event.target.selectedOptions[0];
// If no shapefile selected (empty value), keep current bbox
if (!selectedOption || !selectedOption.value) {
console.log('[Shapefile Select] No shapefile selected, keeping current bbox');
return;
}
console.log('[Shapefile Select] Selected shapefile:', selectedOption.value);
// Get bbox from data attribute
const bboxData = selectedOption.dataset.bbox;
console.log('[Shapefile Select] Bbox data:', bboxData);
if (!bboxData || bboxData === '[]') {
console.warn('[Shapefile Select] Selected shapefile has no bbox data');
return;
}
try {
const bbox = JSON.parse(bboxData);
console.log('[Shapefile Select] Parsed bbox:', bbox);
if (bbox.length !== 4) {
console.warn('[Shapefile Select] Invalid bbox format:', bbox);
return;
}
const [minLon, minLat, maxLon, maxLat] = bbox;
// Validate bbox
if (minLon < -180 || maxLon > 180 || minLat < -90 || maxLat > 90) {
alert('❌ Bbox của shapefile không hợp lệ!');
return;
}
console.log('[Shapefile Select] Creating rectangle with bounds:', [[minLat, minLon], [maxLat, maxLon]]);
// Update prediction map bbox
const bounds = [
[minLat, minLon],
[maxLat, maxLon]
];
const rectangle = L.rectangle(bounds, {
color: '#667eea',
weight: 3,
fillOpacity: 0.2
});
// Clear old bbox and add new one
console.log('[Shapefile Select] Clearing old layers...');
drawnItems.clearLayers();
console.log('[Shapefile Select] Adding new rectangle...');
drawnItems.addLayer(rectangle);
console.log('[Shapefile Select] Fitting map to bounds...');
map.fitBounds(bounds, { padding: [50, 50] });
// Update selected bbox variable
selectedBbox = {
min_lon: minLon,
min_lat: minLat,
max_lon: maxLon,
max_lat: maxLat
};
// Save to localStorage
localStorage.setItem('prediction_bbox', JSON.stringify(selectedBbox));
console.log(`[Shapefile Select] Auto-updated bbox from shapefile:`, selectedBbox);
// Show notification
const crs = selectedOption.dataset.crs || 'Unknown';
alert(`✅ Đã tự động cập nhật vùng prediction theo shapefile!\n\n` +
`📍 Bbox: [${minLon.toFixed(4)}, ${minLat.toFixed(4)}, ${maxLon.toFixed(4)}, ${maxLat.toFixed(4)}]\n` +
`🗺️ CRS: ${crs}\n\n` +
`💡 Bạn có thể điều chỉnh lại bằng cách vẽ lại trên bản đồ nếu muốn.`);
} catch (e) {
console.error('[Shapefile Select] Error parsing bbox:', e);
alert(`❌ Lỗi khi xử lý bbox: ${e.message}`);
}
}
</script>
</body>
</html>