let map; let points = {}; let roads = {}; let parcels = {}; let currentMapClickCoords = null; let drawMode = null; let currentDrawCoords = []; let currentDrawLayer = null; const STORAGE_KEYS = { points: 'mappoints_local_points', roads: 'mappoints_local_roads', parcels: 'mappoints_local_parcels' }; // Initialize map and load stored data document.addEventListener('DOMContentLoaded', function() { initMap(); setupModal(); loadAllData(); }); function setupModal() { const addPointBtn = document.getElementById('add-point-btn'); const drawRoadBtn = document.getElementById('draw-road-btn'); const drawParcelBtn = document.getElementById('draw-parcel-btn'); const finishDrawBtn = document.getElementById('finish-draw-btn'); const cancelDrawBtn = document.getElementById('cancel-draw-btn'); const featureCloseBtn = document.querySelector('#feature-modal .close'); const featureForm = document.getElementById('feature-form'); const roadCloseBtn = document.querySelector('.road-close'); const roadForm = document.getElementById('road-form'); const parcelCloseBtn = document.querySelector('.parcel-close'); const parcelForm = document.getElementById('parcel-form'); addPointBtn.onclick = function() { cancelDraw(); openPointModal(null); }; drawRoadBtn.onclick = function() { startDraw('road'); }; drawParcelBtn.onclick = function() { startDraw('parcel'); }; finishDrawBtn.onclick = function() { finishDraw(); }; cancelDrawBtn.onclick = function() { cancelDraw(); }; featureCloseBtn.onclick = function() { closePointModal(); }; featureForm.onsubmit = function(e) { e.preventDefault(); submitPointForm(); }; roadCloseBtn.onclick = closeRoadModal; roadForm.onsubmit = function(e) { e.preventDefault(); submitRoadForm(); }; parcelCloseBtn.onclick = closeParcelModal; parcelForm.onsubmit = function(e) { e.preventDefault(); submitParcelForm(); }; } function initMap() { map = L.map('map').setView([-0.0225, 109.3425], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }).addTo(map); map.on('click', function(e) { if (drawMode) { addDrawVertex(e.latlng); } else { openPointModal(e.latlng); } }); } function loadAllData() { loadPoints(); loadRoads(); loadParcels(); } function loadPoints() { fetch('api/get_points.php') .then(response => response.json()) .then(data => { if (data.success && Array.isArray(data.markers)) { data.markers.forEach(point => { displayPoint(point.id, point.name, point.latitude, point.longitude, point.open_24_hours); }); } }) .catch(error => console.warn('Unable to load points from backend:', error)) .finally(() => { loadLocalPoints().forEach(point => { displayPoint(point.id, point.name, point.latitude, point.longitude, point.open_24_hours, true); }); updateCounts(); }); } function loadRoads() { fetch('api/get_roads.php') .then(response => response.json()) .then(data => { if (data.success && Array.isArray(data.roads)) { data.roads.forEach(road => { displayRoad(road.id, road.name, road.status, parseFloat(road.length), JSON.parse(road.coordinates)); }); } }) .catch(error => console.warn('Unable to load roads from backend:', error)) .finally(() => { loadLocalRoads().forEach(road => { displayRoad(road.id, road.name, road.status, road.length, road.coordinates, true); }); updateCounts(); }); } function loadParcels() { fetch('api/get_parcels.php') .then(response => response.json()) .then(data => { if (data.success && Array.isArray(data.parcels)) { data.parcels.forEach(parcel => { displayParcel(parcel.id, parcel.owner_status, parseFloat(parcel.area), JSON.parse(parcel.coordinates)); }); } }) .catch(error => console.warn('Unable to load parcels from backend:', error)) .finally(() => { loadLocalParcels().forEach(parcel => { displayParcel(parcel.id, parcel.owner_status, parcel.area, parcel.coordinates, true); }); updateCounts(); }); } function loadLocalPoints() { try { const raw = localStorage.getItem(STORAGE_KEYS.points); const data = raw ? JSON.parse(raw) : []; return Array.isArray(data) ? data : []; } catch { return []; } } function loadLocalRoads() { try { const raw = localStorage.getItem(STORAGE_KEYS.roads); const data = raw ? JSON.parse(raw) : []; return Array.isArray(data) ? data : []; } catch { return []; } } function loadLocalParcels() { try { const raw = localStorage.getItem(STORAGE_KEYS.parcels); const data = raw ? JSON.parse(raw) : []; return Array.isArray(data) ? data : []; } catch { return []; } } function persistLocalPoints() { const localPoints = Object.values(points) .filter(item => item._local) .map(item => ({ id: item._localId, name: item._name, latitude: item.getLatLng().lat, longitude: item.getLatLng().lng, open_24_hours: item._open24 })); localStorage.setItem(STORAGE_KEYS.points, JSON.stringify(localPoints)); } function persistLocalRoads() { const localRoads = Object.values(roads) .filter(item => item._local) .map(item => ({ id: item._localId, name: item._name, status: item._status, length: item._length, coordinates: item._coordinates })); localStorage.setItem(STORAGE_KEYS.roads, JSON.stringify(localRoads)); } function persistLocalParcels() { const localParcels = Object.values(parcels) .filter(item => item._local) .map(item => ({ id: item._localId, owner_status: item._ownerStatus, area: item._area, coordinates: item._coordinates })); localStorage.setItem(STORAGE_KEYS.parcels, JSON.stringify(localParcels)); } function generateLocalId(prefix) { return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 10000)}`; } function startDraw(type) { cancelDraw(); drawMode = type; currentDrawCoords = []; showDrawControls(true); showNotification(type === 'road' ? 'Draw road vertices on the map.' : 'Draw parcel vertices on the map.'); } function showDrawControls(show) { const drawActions = document.getElementById('draw-actions'); if (drawActions) { drawActions.classList.toggle('hidden', !show); } } function cancelDraw() { drawMode = null; currentDrawCoords = []; if (currentDrawLayer) { map.removeLayer(currentDrawLayer); currentDrawLayer = null; } showDrawControls(false); } function finishDraw() { if (!drawMode) { return; } if (drawMode === 'road') { if (currentDrawCoords.length < 2) { showNotification('Road must have at least 2 points.', true); return; } openRoadModal(); } else if (drawMode === 'parcel') { if (currentDrawCoords.length < 3) { showNotification('Parcel must have at least 3 points.', true); return; } openParcelModal(); } } function addDrawVertex(latlng) { currentDrawCoords.push(latlng); if (currentDrawLayer) { map.removeLayer(currentDrawLayer); currentDrawLayer = null; } if (drawMode === 'road') { currentDrawLayer = L.polyline(currentDrawCoords, { color: '#ff9800', dashArray: '5,8', weight: 5 }).addTo(map); } else if (drawMode === 'parcel') { currentDrawLayer = L.polygon(currentDrawCoords, { color: '#4CAF50', dashArray: '5,8', weight: 4, fillOpacity: 0.15 }).addTo(map); } showNotification(`Vertex added (${currentDrawCoords.length})`); } function openPointModal(coords) { currentMapClickCoords = coords; const modal = document.getElementById('feature-modal'); const form = document.getElementById('feature-form'); const latInput = document.getElementById('feature-latitude'); const lngInput = document.getElementById('feature-longitude'); const nameInput = document.getElementById('feature-name'); const statusGroup = document.getElementById('field-status-group'); const checkboxGroup = document.getElementById('feature-checkbox-group'); const modalTitle = document.getElementById('modal-title'); const formInfo = document.getElementById('feature-form-info'); form.reset(); statusGroup.style.display = 'none'; checkboxGroup.style.display = 'flex'; formInfo.classList.remove('show'); if (coords) { latInput.value = coords.lat.toFixed(6); lngInput.value = coords.lng.toFixed(6); latInput.readOnly = true; lngInput.readOnly = true; modalTitle.textContent = 'Create Point'; formInfo.textContent = 'Coordinates auto-filled from map click.'; formInfo.classList.add('show'); } else { latInput.value = ''; lngInput.value = ''; latInput.readOnly = false; lngInput.readOnly = false; modalTitle.textContent = 'Add New Point'; formInfo.textContent = 'Enter coordinates manually or click on the map.'; formInfo.classList.add('show'); } nameInput.focus(); modal.classList.add('show'); } function closePointModal() { const modal = document.getElementById('feature-modal'); modal.classList.remove('show'); currentMapClickCoords = null; } function closeFeatureModal() { closePointModal(); } function submitPointForm() { const name = document.getElementById('feature-name').value.trim(); const latitude = parseFloat(document.getElementById('feature-latitude').value); const longitude = parseFloat(document.getElementById('feature-longitude').value); const open_24_hours = document.getElementById('feature-24hours').checked ? 1 : 0; if (!name) { showNotification('Please enter a point name.', true); return; } if (isNaN(latitude) || isNaN(longitude)) { showNotification('Please enter valid coordinates.', true); return; } addPoint(name, latitude, longitude, open_24_hours); closePointModal(); } function addPoint(name, lat, lng, open_24_hours) { fetch('api/save_point.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, latitude: lat, longitude: lng, open_24_hours }) }) .then(response => response.json()) .then(data => { if (data.success) { displayPoint(data.id, data.name, data.latitude, data.longitude, data.open_24_hours); showNotification('Point saved successfully.'); updateCounts(); } else { addLocalPoint(name, lat, lng, open_24_hours); } }) .catch(() => { addLocalPoint(name, lat, lng, open_24_hours); }); } function addLocalPoint(name, lat, lng, open_24_hours) { const id = generateLocalId('point'); displayPoint(id, name, lat, lng, open_24_hours, true); persistLocalPoints(); showNotification('Point saved locally.'); updateCounts(); } function displayPoint(id, name, lat, lng, open_24_hours, isLocal = false) { if (points[id]) { map.removeLayer(points[id]); delete points[id]; } const marker = L.marker([lat, lng], { draggable: true }).addTo(map); marker._local = isLocal; marker._localId = id; marker._name = name; marker._open24 = open_24_hours; marker._type = 'point'; marker.bindPopup(getPointPopupContent(id, name, lat, lng, open_24_hours)); marker.on('popupopen', function(e) { bindPopupDeleteAction(e.popup.getElement(), 'point', id); }); marker.on('dragend', function(e) { const position = e.target.getLatLng(); updatePointPosition(id, position.lat, position.lng); }); points[id] = marker; } function updatePointPosition(id, lat, lng) { const marker = points[id]; if (!marker) { return; } marker._name = marker._name || marker._localId; marker.bindPopup(getPointPopupContent(id, marker._name, lat, lng, marker._open24)); marker.setLatLng([lat, lng]); if (marker._local) { persistLocalPoints(); } } function getPointPopupContent(id, name, lat, lng, open_24_hours) { const hoursText = open_24_hours ? '🕐 Open 24 Hours' : '🕐 Limited Hours'; return `