hoàn thành chức năng phân lô trên ảnh predict
This commit is contained in:
+190
-1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user