| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Palm Monitor - Geospatial Intelligence Platform</title> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" /> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" /> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" /> |
| |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| margin: 0; |
| padding: 0; |
| overflow-x: hidden; |
| } |
| |
| .header { |
| background: #006838; |
| backdrop-filter: blur(10px); |
| color: white; |
| padding: 10px 25px; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| box-shadow: 0 4px 20px rgba(0,0,0,0.3); |
| position: relative; |
| z-index: 1000; |
| } |
| |
| .logo { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .user-info { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| } |
| |
| .status-indicator { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| font-size: 14px; |
| } |
| |
| .status-dot { |
| width: 8px; |
| height: 8px; |
| background: #27ae60; |
| border-radius: 50%; |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { opacity: 1; } |
| 50% { opacity: 0.5; } |
| 100% { opacity: 1; } |
| } |
| |
| .main-container { |
| display: flex; |
| height: calc(100vh - 90px); |
| position: relative; |
| } |
| |
| .sidebar { |
| width: 250px; |
| background: #f8f9fa; |
| border-right: 1px solid #ddd; |
| padding: 20px; |
| overflow-y: auto; |
| box-shadow: 2px 0 5px rgba(0,0,0,0.1); |
| z-index: 999; |
| } |
| |
| .tool-group { |
| margin-bottom: 25px; |
| } |
| |
| .tool-group h3 { |
| color: #2c3e50; |
| margin-bottom: 15px; |
| font-size: 16px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .tool-btn { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| width: 100%; |
| padding: 12px 15px; |
| margin-bottom: 8px; |
| border: 1px solid #ddd; |
| background: white; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| font-size: 14px; |
| text-align: left; |
| } |
| |
| .tool-btn:hover { |
| background: #e3f2fd; |
| border-color: #2196f3; |
| transform: translateY(-1px); |
| } |
| |
| .tool-btn.active { |
| background: #2196f3; |
| color: white; |
| border-color: #2196f3; |
| } |
| |
| .tool-btn i { |
| width: 16px; |
| text-align: center; |
| } |
| |
| .file-input-wrapper { |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| margin-top: 10px; |
| } |
| |
| .file-input-wrapper input[type="file"] { |
| padding: 8px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| font-size: 12px; |
| } |
| |
| .export-buttons { |
| display: flex; |
| flex-direction: column; |
| gap: 5px; |
| margin-top: 10px; |
| } |
| |
| .export-btn { |
| padding: 8px 12px; |
| background: #3498db; |
| color: white; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| font-size: 12px; |
| transition: background 0.3s ease; |
| } |
| |
| .export-btn:hover { |
| background: #2980b9; |
| } |
| |
| #map-container { |
| flex: 1; |
| position: relative; |
| } |
| |
| #map { |
| height: 100%; |
| width: 100%; |
| } |
| |
| #infoForm { |
| display: none; |
| padding: 20px; |
| background: white; |
| border-top: 2px solid #3498db; |
| box-shadow: 0 -2px 10px rgba(0,0,0,0.1); |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| z-index: 1000; |
| max-height: 300px; |
| overflow-y: auto; |
| } |
| |
| .form-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 15px; |
| } |
| |
| .close-btn { |
| background: none; |
| border: none; |
| font-size: 18px; |
| cursor: pointer; |
| color: #666; |
| } |
| |
| .close-btn:hover { |
| color: #000; |
| } |
| |
| .form-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 12px; |
| margin-bottom: 15px; |
| } |
| |
| .form-grid input { |
| padding: 8px 12px; |
| font-size: 14px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| width: 100%; |
| box-sizing: border-box; |
| } |
| |
| .form-grid input:focus { |
| outline: none; |
| border-color: #3498db; |
| box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); |
| } |
| |
| .form-actions { |
| display: flex; |
| gap: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .form-actions button { |
| padding: 10px 20px; |
| font-size: 14px; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| } |
| |
| .form-actions button:first-child { |
| background: #27ae60; |
| color: white; |
| } |
| |
| .form-actions button:first-child:hover { |
| background: #219a52; |
| } |
| |
| .form-actions button:last-child { |
| background: #3498db; |
| color: white; |
| } |
| |
| .form-actions button:last-child:hover { |
| background: #2980b9; |
| } |
| |
| h3 { |
| margin-bottom: 15px; |
| color: #2c3e50; |
| } |
| |
| .btn { |
| padding: 5px 10px; |
| margin: 2px; |
| border: none; |
| border-radius: 3px; |
| cursor: pointer; |
| font-size: 12px; |
| } |
| |
| .btn:hover { |
| opacity: 0.8; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| width: 250px; |
| } |
| |
| .form-grid { |
| grid-template-columns: 1fr; |
| } |
| } |
| |
| @media (max-width: 600px) { |
| .main-container { |
| flex-direction: column; |
| } |
| |
| .sidebar { |
| width: 100%; |
| height: 200px; |
| border-right: none; |
| border-bottom: 1px solid #ddd; |
| } |
| |
| #map-container { |
| height: calc(100vh - 290px); |
| } |
| } |
| .nav-button { |
| width: 90%; |
| padding: 12px 15px; |
| margin: 5px 0; |
| border: none; |
| background: linear-gradient(135deg, #006838 0%, #A1B593 100%); |
| color: white; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-size: 14px; |
| } |
| |
| .nav-button:hover { |
| background-color: #fcfffc; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="header"> |
| <div class="logo"> |
| <img src="https://images.squarespace-cdn.com/content/v1/604db3a6dad32a12b2415387/1636475927545-PK57PVLIB7AEKX1AJQJ8/Logo_MSPO_2020.png" alt="MSPO Logo" style="height: 60px;"> |
| <div> |
| <span style="font-weight: bold;">MSPO-Palm Monitor</span><br> |
| <span style="font-size: 12px; opacity: 0.7;">Powered by Uzma Digital Earth</span> |
| </div> |
| </div> |
| |
| <div class="user-info"> |
| <div class="status-indicator"> |
| <div class="status-dot"></div> |
| <span>Live Monitoring</span> |
| </div> |
| <i class="fas fa-user-circle" style="font-size: 24px;"></i> |
| </div> |
| </div> |
|
|
| <div class="main-container"> |
| <div class="sidebar"> |
| <div class="tool-group"> |
| <h3><i class="fas fa-satellite"></i> Imagery</h3> |
| <button class="tool-btn active" onclick="toggleLayer('satellite')"> |
| <i class="fas fa-globe"></i> High-Res Satellite |
| </button> |
| <button class="tool-btn" onclick="toggleLayer('terrain')"> |
| <i class="fas fa-mountain"></i> Topographic Data |
| </button> |
| <button class="tool-btn" onclick="toggleLayer('ndvi')"> |
| <i class="fas fa-seedling"></i> NDVI Analysis |
| </button> |
| <button class="tool-btn" onclick="requestNewImagery()"> |
| <i class="fas fa-refresh"></i> Request Update |
| </button> |
| |
| <button class="tool-btn" onclick="window.location.href='split_panel_map.html'">Deforestation Map</button> |
|
|
|
|
|
|
| <i class="fas fa-refresh"></i> |
| </button> |
| </div> |
| |
| <div class="tool-group"> |
| <h3><i class="fas fa-chart-line"></i> Analysis</h3> |
| <button class="tool-btn" onclick="runSpatialAnalysis()"> |
| <i class="fas fa-calculator"></i> Spatial Analysis |
| </button> |
| <button class="tool-btn" onclick="generateReport()"> |
| <i class="fas fa-file-alt"></i> Generate Report |
| </button> |
| <button class="tool-btn" onclick="trendAnalysis()"> |
| <i class="fas fa-trending-up"></i> Trend Analysis |
| </button> |
| </div> |
| |
| <div class="tool-group"> |
| <h3><i class="fas fa-upload"></i> Import/Export Data</h3> |
| <div class="file-input-wrapper"> |
| <input type="file" id="fileInput" accept=".geojson,.json" onchange="handleFileImport(event)" /> |
| <div class="export-buttons"> |
| <button class="export-btn" onclick="exportData('geojson')"> |
| <i class="fas fa-download"></i> Export GeoJSON |
| </button> |
| <button class="export-btn" onclick="exportData('shapefile')"> |
| <i class="fas fa-download"></i> Export Shapefile |
| </button> |
| <button class="export-btn" onclick="exportData('csv')"> |
| <i class="fas fa-download"></i> Export CSV |
| </button> |
| <button class="export-btn" onclick="exportData('report')"> |
| <i class="fas fa-file-pdf"></i> Generate Report |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div id="map-container"> |
| <div id="map"></div> |
| |
| <div id="infoForm"> |
| <div class="form-header"> |
| <h3>Polygon Info</h3> |
| <button class="close-btn" onclick="closeInfoForm()"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div class="form-grid"> |
| <input id="license" placeholder="LICENSE NO." /> |
| <input id="name" placeholder="SMALLHOLDER NAME" /> |
| <input id="state" placeholder="STATE" /> |
| <input id="district" placeholder="DISTRICT" /> |
| <input id="subdistrict" placeholder="SUBDISTRICT" /> |
| <input id="spocName" placeholder="SPOC NAME" /> |
| <input id="spocCode" placeholder="SPOC CODE" /> |
| <input id="lotNo" placeholder="LOT NO." /> |
| <input id="certified" placeholder="CERTIFIED AREA (HA)" /> |
| <input id="planted" placeholder="PLANTED AREA (HA)" /> |
| <input id="latitude" placeholder="LATITUDE" readonly /> |
| <input id="longitude" placeholder="LONGITUDE" readonly /> |
| <input id="mspo" placeholder="MSPO CERTIFICATION" /> |
| <input id="land" placeholder="LAND TITLE" /> |
| <input id="shapeLength" placeholder="Shape_Length" /> |
| <input id="shapeArea" placeholder="Shape_Area" /> |
| </div> |
| <div class="form-actions"> |
| <button onclick="saveInfo()">Save Info</button> |
| <button onclick="exportGeoJSON()">Download GeoJSON</button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
| <script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script> |
| |
| <script> |
| const map = L.map('map').setView([4.4286, 102.0581], 12); |
| |
| let baseLayers = { |
| satellite: L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { |
| attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics' |
| }), |
| terrain: L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', { |
| attribution: 'Esri, DeLorme, NAVTEQ' |
| }), |
| osm: L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { |
| attribution: 'OpenStreetMap contributors' |
| }) |
| }; |
| |
| baseLayers.satellite.addTo(map); |
| |
| const drawnItems = new L.FeatureGroup(); |
| map.addLayer(drawnItems); |
| |
| const drawControl = new L.Control.Draw({ |
| edit: { featureGroup: drawnItems }, |
| draw: { |
| polygon: true, |
| polyline: true, |
| rectangle: true, |
| circle: true, |
| marker: true, |
| circlemarker: true |
| } |
| }); |
| map.addControl(drawControl); |
| |
| let currentLayer = null; |
| let currentCircle = null; |
| const geojsonFeatures = []; |
| |
| map.on(L.Draw.Event.CREATED, function (e) { |
| const layer = e.layer; |
| const type = e.layerType; |
| |
| drawnItems.addLayer(layer); |
| |
| if (type === 'circle') { |
| currentCircle = layer; |
| const center = layer.getLatLng(); |
| const radius = layer.getRadius(); |
| |
| layer.bindPopup(` |
| <strong>New Circle</strong><br> |
| Center: ${center.lat.toFixed(6)}, ${center.lng.toFixed(6)}<br> |
| Radius: ${(radius/1000).toFixed(2)} km<br> |
| <button onclick="analyzePolygon()" class="btn" style="background: #3498db; color: white;">Analyze</button> |
| <button onclick="deleteShape()" class="btn" style="background: #e74c3c; color: white;">Delete</button> |
| `); |
| } else if (type === 'marker') { |
| const latlng = layer.getLatLng(); |
| layer.bindPopup(` |
| <strong>Marker</strong><br> |
| Location: ${latlng.lat.toFixed(6)}, ${latlng.lng.toFixed(6)}<br> |
| <button onclick="deleteShape()" class="btn" style="background: #e74c3c; color: white;">Delete</button> |
| `); |
| } else { |
| currentLayer = layer; |
| const center = layer.getBounds().getCenter(); |
| document.getElementById('latitude').value = center.lat.toFixed(10); |
| document.getElementById('longitude').value = center.lng.toFixed(10); |
| |
| const geojson = layer.toGeoJSON(); |
| if (geojson.geometry.coordinates && geojson.geometry.coordinates[0]) { |
| const coords = geojson.geometry.coordinates[0]; |
| document.getElementById('shapeLength').value = (coords.length * 0.0001).toFixed(6); |
| document.getElementById('shapeArea').value = (Math.random() * 1e-6).toFixed(12); |
| } |
| |
| document.getElementById('infoForm').style.display = 'block'; |
| } |
| }); |
| |
| |
| function deleteShape() { |
| const popup = map._popup; |
| if (popup && popup._source) { |
| const layer = popup._source; |
| drawnItems.removeLayer(layer); |
| map.closePopup(); |
| } |
| } |
| |
| |
| function analyzePolygon() { |
| showNotification('Analysis started for selected area...'); |
| } |
| |
| function toggleLayer(layerName) { |
| |
| document.querySelectorAll('.tool-group:first-child .tool-btn').forEach(btn => { |
| btn.classList.remove('active'); |
| }); |
| |
| |
| event.target.classList.add('active'); |
| |
| switch(layerName) { |
| case 'satellite': |
| |
| if (map.hasLayer(baseLayers.terrain)) { |
| map.removeLayer(baseLayers.terrain); |
| } |
| if (map.hasLayer(baseLayers.osm)) { |
| map.removeLayer(baseLayers.osm); |
| } |
| |
| if (!map.hasLayer(baseLayers.satellite)) { |
| baseLayers.satellite.addTo(map); |
| } |
| break; |
| case 'terrain': |
| |
| if (map.hasLayer(baseLayers.satellite)) { |
| map.removeLayer(baseLayers.satellite); |
| } |
| if (map.hasLayer(baseLayers.osm)) { |
| map.removeLayer(baseLayers.osm); |
| } |
| |
| if (!map.hasLayer(baseLayers.terrain)) { |
| baseLayers.terrain.addTo(map); |
| } |
| break; |
| case 'ndvi': |
| showNotification('NDVI overlay activated'); |
| break; |
| } |
| } |
| |
| function requestNewImagery() { |
| showNotification('Requesting new imagery update...'); |
| } |
| |
| function runSpatialAnalysis() { |
| showNotification('Running spatial analysis...'); |
| } |
| |
| function generateReport() { |
| showNotification('Generating report...'); |
| } |
| |
| function trendAnalysis() { |
| showNotification('Performing trend analysis...'); |
| } |
| |
| function exportData(format) { |
| switch(format) { |
| case 'geojson': |
| exportGeoJSON(); |
| break; |
| case 'shapefile': |
| showNotification('Shapefile export functionality would be implemented here'); |
| break; |
| case 'csv': |
| exportCSV(); |
| break; |
| case 'report': |
| showNotification('Report generation functionality would be implemented here'); |
| break; |
| } |
| } |
| |
| function exportCSV() { |
| if (geojsonFeatures.length === 0) { |
| showNotification('No data to export'); |
| return; |
| } |
| |
| const headers = Object.keys(geojsonFeatures[0].properties); |
| const csvContent = [ |
| headers.join(','), |
| ...geojsonFeatures.map(feature => |
| headers.map(header => feature.properties[header] || '').join(',') |
| ) |
| ].join('\n'); |
| |
| const blob = new Blob([csvContent], { type: 'text/csv' }); |
| const url = window.URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = 'polygons.csv'; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| window.URL.revokeObjectURL(url); |
| } |
| |
| function showNotification(message) { |
| alert(message); |
| } |
| |
| function closeInfoForm() { |
| document.getElementById('infoForm').style.display = 'none'; |
| |
| document.querySelectorAll('#infoForm input').forEach(input => { |
| if (!input.readOnly) { |
| input.value = ''; |
| } |
| }); |
| currentLayer = null; |
| } |
| |
| function saveInfo() { |
| if (!currentLayer) { |
| showNotification("Draw a polygon first."); |
| return; |
| } |
| |
| const get = id => document.getElementById(id).value; |
| |
| const properties = { |
| license: get('license'), |
| smallholder: get('name'), |
| state: get('state'), |
| district: get('district'), |
| subdistrict: get('subdistrict'), |
| spoc_name: get('spocName'), |
| spoc_code: get('spocCode'), |
| lot_no: get('lotNo'), |
| certified_area: get('certified'), |
| planted_area: get('planted'), |
| latitude: get('latitude'), |
| longitude: get('longitude'), |
| mspo: get('mspo'), |
| land_title: get('land'), |
| shape_length: get('shapeLength'), |
| shape_area: get('shapeArea') |
| }; |
| |
| const feature = currentLayer.toGeoJSON(); |
| feature.properties = properties; |
| geojsonFeatures.push(feature); |
| |
| const popupContent = Object.entries(properties) |
| .filter(([key, val]) => val) |
| .map(([key, val]) => `<b>${key.replace(/_/g, ' ').toUpperCase()}:</b> ${val}`) |
| .join("<br>"); |
| |
| currentLayer.bindPopup(popupContent).openPopup(); |
| |
| closeInfoForm(); |
| showNotification('Polygon information saved successfully!'); |
| } |
| |
| function exportGeoJSON() { |
| if (geojsonFeatures.length === 0) { |
| showNotification('No data to export'); |
| return; |
| } |
| |
| const geojson = { |
| type: "FeatureCollection", |
| features: geojsonFeatures |
| }; |
| const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(geojson, null, 2)); |
| const dlAnchor = document.createElement('a'); |
| dlAnchor.setAttribute("href", dataStr); |
| dlAnchor.setAttribute("download", "polygons.geojson"); |
| document.body.appendChild(dlAnchor); |
| dlAnchor.click(); |
| dlAnchor.remove(); |
| showNotification('GeoJSON file exported successfully!'); |
| } |
| |
| function handleFileImport(event) { |
| const file = event.target.files[0]; |
| if (file) { |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| try { |
| const geojson = JSON.parse(e.target.result); |
| L.geoJSON(geojson, { |
| onEachFeature: function(feature, layer) { |
| drawnItems.addLayer(layer); |
| if (feature.properties) { |
| const popupContent = Object.entries(feature.properties) |
| .filter(([key, val]) => val) |
| .map(([key, val]) => `<b>${key.replace(/_/g, ' ').toUpperCase()}:</b> ${val}`) |
| .join("<br>"); |
| layer.bindPopup(popupContent); |
| } |
| |
| geojsonFeatures.push(feature); |
| } |
| }); |
| showNotification('GeoJSON file imported successfully!'); |
| } catch (error) { |
| showNotification('Error parsing GeoJSON file: ' + error.message); |
| } |
| }; |
| reader.readAsText(file); |
| } |
| } |
| </script> |
| </body> |
| </html> |