window.appState = { mode: "view", activeType: "spbu", drawControl: null, layers: {}, layerControl: null, layerVisibility: { spbu24: true, spbuTidak: true, kemiskinan: true, masjid: true, jalan: true, kavling: true } }; window.escapeHtml = function (value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; window.createPointIcon = function (color) { return L.divIcon({ className: "custom-point-icon", html: '', iconSize: [18, 18], iconAnchor: [9, 9], popupAnchor: [0, -12] }); }; window.greenIcon = window.createPointIcon("#28a745"); window.redIcon = window.createPointIcon("#dc3545"); window.orangeIcon = window.createPointIcon("#ff8c00"); window.blueIcon = window.createPointIcon("#007bff"); window.calculateLineLength = function (latlngs) { var total = 0; for (var i = 1; i < latlngs.length; i += 1) { total += map.distance(latlngs[i - 1], latlngs[i]); } return total; }; window.calculatePolygonArea = function (latlngs) { if (L.GeometryUtil && typeof L.GeometryUtil.geodesicArea === "function") { return L.GeometryUtil.geodesicArea(latlngs); } if (!latlngs.length) { return 0; } var earthRadius = 6378137; var avgLat = latlngs.reduce(function (sum, point) { return sum + point.lat; }, 0) / latlngs.length; var avgLatRad = avgLat * Math.PI / 180; var projected = latlngs.map(function (point) { return { x: earthRadius * point.lng * Math.PI / 180 * Math.cos(avgLatRad), y: earthRadius * point.lat * Math.PI / 180 }; }); var area = 0; for (var i = 0; i < projected.length; i += 1) { var next = projected[(i + 1) % projected.length]; area += (projected[i].x * next.y) - (next.x * projected[i].y); } return Math.abs(area / 2); }; window.formatMeters = function (value) { return Number(value).toLocaleString("id-ID", { maximumFractionDigits: 2 }) + " m"; }; window.formatSquareMeters = function (value) { return Number(value).toLocaleString("id-ID", { maximumFractionDigits: 2 }) + " m²"; }; window.postForm = async function (url, payload) { var response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(payload) }); var text = (await response.text()).trim(); if (!response.ok || text !== "success") { throw new Error(text || "Permintaan gagal."); } }; window.collectSpbuData = async function () { return window.openEditModal({ title: "Tambah SPBU", subtitle: "Lengkapi atribut SPBU untuk titik yang dipilih di peta.", fields: [ { name: "nama", label: "Nama SPBU", value: "", required: true }, { name: "no", label: "Nomor SPBU", value: "", required: true }, { name: "status", label: "Status Operasional", type: "select", value: "24jam", options: [ { value: "24jam", label: "24 Jam" }, { value: "tidak", label: "Tidak 24 Jam" } ] } ], validate: function (data) { if (!data.nama || !data.no) { return "Nama dan nomor SPBU wajib diisi."; } return ""; } }); }; window.collectKemiskinanData = async function () { return window.openEditModal({ title: "Tambah Data Kemiskinan", subtitle: "Lengkapi data keluarga untuk titik yang dipilih di peta.", fields: [ { name: "nama_kepala_keluarga", label: "Nama Kepala Keluarga", value: "", required: true }, { name: "jumlah_kk", label: "Jumlah KK", type: "number", min: 1, step: 1, value: 1, required: true }, { name: "alamat", label: "Alamat", type: "textarea", value: "", required: true } ], validate: function (data) { if (!data.nama_kepala_keluarga || !data.alamat) { return "Nama kepala keluarga dan alamat wajib diisi."; } if (!Number.isInteger(data.jumlah_kk) || data.jumlah_kk < 1) { return "Jumlah KK harus angka minimal 1."; } return ""; } }); }; window.collectMasjidData = async function () { return window.openEditModal({ title: "Tambah Masjid", subtitle: "Lengkapi identitas masjid untuk titik yang dipilih di peta.", fields: [ { name: "nama_masjid", label: "Nama Masjid", value: "", required: true }, { name: "pic_masjid", label: "PIC Masjid", value: "", required: true }, { name: "alamat", label: "Alamat Masjid", type: "textarea", value: "", required: true }, { name: "radius", label: "Radius Jangkauan (meter)", type: "number", min: 100, max: 5000, step: 1, value: 500, required: true } ], validate: function (data) { if (!data.nama_masjid || !data.pic_masjid || !data.alamat) { return "Nama, PIC, dan alamat masjid wajib diisi."; } if (!Number.isInteger(data.radius) || data.radius < 100 || data.radius > 5000) { return "Radius harus angka antara 100 sampai 5000 meter."; } return ""; } }); }; window.updateMapHint = function () { var hint = document.getElementById("mapHint"); if (!hint) { return; } if (appState.mode === "view") { hint.textContent = "Mode lihat aktif. Data hanya ditampilkan di peta dan sidebar."; return; } if (appState.activeType === "spbu") { hint.textContent = "Mode edit SPBU aktif. Klik peta untuk menambahkan titik SPBU."; return; } if (appState.activeType === "kemiskinan") { hint.textContent = "Mode edit Kemiskinan aktif. Klik peta untuk menambahkan titik data kemiskinan."; return; } if (appState.activeType === "masjid") { hint.textContent = "Mode edit Masjid aktif. Klik peta untuk menambahkan titik masjid dengan radius awal 500 meter."; return; } if (appState.activeType === "jalan") { hint.textContent = "Mode edit Jalan aktif. Gunakan tool draw polyline pada peta."; return; } hint.textContent = "Mode edit Kavling aktif. Gunakan tool draw polygon pada peta."; }; window.updateToolbarState = function () { var buttonStates = { modeViewBtn: appState.mode === "view", modeEditBtn: appState.mode === "edit", typeSpbuBtn: appState.activeType === "spbu", typeKemiskinanBtn: appState.activeType === "kemiskinan", typeMasjidBtn: appState.activeType === "masjid", typeJalanBtn: appState.activeType === "jalan", typeKavlingBtn: appState.activeType === "kavling" }; Object.keys(buttonStates).forEach(function (id) { var button = document.getElementById(id); if (button) { button.classList.toggle("active", buttonStates[id]); } }); }; window.configureDrawControl = function () { if (!window.map) { return; } if (appState.drawControl) { map.removeControl(appState.drawControl); appState.drawControl = null; } if (appState.mode !== "edit" || appState.activeType === "spbu" || appState.activeType === "kemiskinan" || appState.activeType === "masjid") { return; } var drawOptions = { polyline: false, polygon: false, rectangle: false, circle: false, marker: false, circlemarker: false }; if (appState.activeType === "jalan") { drawOptions.polyline = { shapeOptions: { color: "#dc3545", weight: 4 } }; } if (appState.activeType === "kavling") { drawOptions.polygon = { allowIntersection: false, shapeOptions: { color: "#ff8c00", weight: 3, fillOpacity: 0.35 } }; } appState.drawControl = new L.Control.Draw({ edit: false, draw: drawOptions }); map.addControl(appState.drawControl); }; window.setMode = function (mode) { appState.mode = mode; window.updateToolbarState(); window.updateMapHint(); window.configureDrawControl(); if (typeof window.loadAllData === "function") { window.loadAllData(); } }; window.setDataType = function (type) { appState.activeType = type; window.updateToolbarState(); window.updateMapHint(); window.configureDrawControl(); }; window.getLayerGroupByKey = function (key) { if (key === "spbu24") { return appState.layers.spbu24; } if (key === "spbuTidak") { return appState.layers.spbuTidak; } if (key === "kemiskinan") { return appState.layers.kemiskinan; } if (key === "masjid") { return appState.layers.masjid; } if (key === "jalan") { return appState.layers.jalan; } if (key === "kavling") { return appState.layers.kavling; } return null; }; window.setLayerVisibility = function (key, isVisible) { appState.layerVisibility[key] = !!isVisible; var layer = window.getLayerGroupByKey(key); if (!layer || !window.map) { return; } if (isVisible) { if (!map.hasLayer(layer)) { map.addLayer(layer); } return; } if (map.hasLayer(layer)) { map.removeLayer(layer); } }; document.addEventListener("DOMContentLoaded", function () { window.map = L.map("map").setView([-0.0263, 109.3425], 13); appState.layers.spbu24 = L.layerGroup().addTo(map); appState.layers.spbuTidak = L.layerGroup().addTo(map); appState.layers.kemiskinan = L.layerGroup().addTo(map); appState.layers.masjid = L.layerGroup().addTo(map); appState.layers.jalan = L.layerGroup().addTo(map); appState.layers.kavling = L.layerGroup().addTo(map); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap" }).addTo(map); appState.layerControl = L.control.layers(null, { "SPBU 24 Jam": appState.layers.spbu24, "SPBU Tidak 24 Jam": appState.layers.spbuTidak, "Kemiskinan": appState.layers.kemiskinan, "Masjid": appState.layers.masjid, "Jalan": appState.layers.jalan, "Kavling": appState.layers.kavling }).addTo(map); map.on("overlayadd", function (event) { if (event.layer === appState.layers.spbu24) { appState.layerVisibility.spbu24 = true; } else if (event.layer === appState.layers.spbuTidak) { appState.layerVisibility.spbuTidak = true; } else if (event.layer === appState.layers.kemiskinan) { appState.layerVisibility.kemiskinan = true; } else if (event.layer === appState.layers.masjid) { appState.layerVisibility.masjid = true; } else if (event.layer === appState.layers.jalan) { appState.layerVisibility.jalan = true; } else if (event.layer === appState.layers.kavling) { appState.layerVisibility.kavling = true; } if (typeof window.refreshLayerFilterUI === "function") { window.refreshLayerFilterUI(); } }); map.on("overlayremove", function (event) { if (event.layer === appState.layers.spbu24) { appState.layerVisibility.spbu24 = false; } else if (event.layer === appState.layers.spbuTidak) { appState.layerVisibility.spbuTidak = false; } else if (event.layer === appState.layers.kemiskinan) { appState.layerVisibility.kemiskinan = false; } else if (event.layer === appState.layers.masjid) { appState.layerVisibility.masjid = false; } else if (event.layer === appState.layers.jalan) { appState.layerVisibility.jalan = false; } else if (event.layer === appState.layers.kavling) { appState.layerVisibility.kavling = false; } if (typeof window.refreshLayerFilterUI === "function") { window.refreshLayerFilterUI(); } }); map.on("click", async function (event) { if (appState.mode !== "edit") { return; } if (appState.activeType === "spbu") { var spbuData = await window.collectSpbuData(); if (!spbuData) { return; } try { await window.postForm("php/create.php", { nama: spbuData.nama, no: spbuData.no, status: spbuData.status, lat: event.latlng.lat.toFixed(6), lng: event.latlng.lng.toFixed(6) }); await window.loadAllData(); alert("Data SPBU berhasil disimpan."); } catch (error) { alert("Gagal menyimpan SPBU: " + error.message); } return; } if (appState.activeType === "kemiskinan") { var kemiskinanData = await window.collectKemiskinanData(); if (!kemiskinanData) { return; } try { await window.postForm("php/create_kemiskinan.php", { nama_kepala_keluarga: kemiskinanData.nama_kepala_keluarga, jumlah_kk: kemiskinanData.jumlah_kk, alamat: kemiskinanData.alamat, lat: event.latlng.lat.toFixed(6), lng: event.latlng.lng.toFixed(6) }); await window.loadAllData(); alert("Data kemiskinan berhasil disimpan."); } catch (error) { alert("Gagal menyimpan data kemiskinan: " + error.message); } return; } if (appState.activeType === "masjid") { var masjidData = await window.collectMasjidData(); if (!masjidData) { return; } try { await window.postForm("php/create_masjid.php", { nama_masjid: masjidData.nama_masjid, pic_masjid: masjidData.pic_masjid, alamat: masjidData.alamat, radius: masjidData.radius, lat: event.latlng.lat.toFixed(6), lng: event.latlng.lng.toFixed(6) }); await window.loadAllData(); alert("Data masjid berhasil disimpan."); } catch (error) { alert("Gagal menyimpan data masjid: " + error.message); } } }); map.on(L.Draw.Event.CREATED, async function (event) { var layer = event.layer; if (appState.mode !== "edit") { alert("Pindah ke mode edit sebelum menggambar data."); return; } if (event.layerType === "polyline") { if (appState.activeType !== "jalan") { alert("Tipe aktif saat ini bukan Jalan."); return; } var lineLatLngs = layer.getLatLngs(); var panjang = window.calculateLineLength(lineLatLngs); var roadData = await window.collectRoadData(); if (!roadData) { return; } try { await window.postForm("php/create_line.php", { nama: roadData.nama, status: roadData.status, panjang: panjang.toFixed(2), geom: window.serializeGeometry(lineLatLngs) }); await window.loadAllData(); alert("Data jalan berhasil disimpan."); } catch (error) { alert("Gagal menyimpan jalan: " + error.message); } return; } if (event.layerType === "polygon") { if (appState.activeType !== "kavling") { alert("Tipe aktif saat ini bukan Kavling."); return; } var polygonLatLngs = layer.getLatLngs()[0] || []; var luas = window.calculatePolygonArea(polygonLatLngs); var landData = await window.collectPolygonData(); if (!landData) { return; } try { await window.postForm("php/create_polygon.php", { nama: landData.nama, status: landData.status, luas: luas.toFixed(2), geom: window.serializeGeometry(polygonLatLngs) }); await window.loadAllData(); alert("Data kavling berhasil disimpan."); } catch (error) { alert("Gagal menyimpan kavling: " + error.message); } } }); window.setMode("view"); window.setDataType("spbu"); setTimeout(function () { map.invalidateSize(); }, 300); if (typeof window.loadAllData === "function") { window.loadAllData(); } });