Files
d1041231044_webgis_geopoverty/js/map.js
T
2026-06-11 21:18:48 +07:00

378 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
map.js WebGIS Pemetaan Kemiskinan & Masjid (SRS v1.0)
============================================================ */
window.appState = {
mode: "view",
activeType: "kemiskinan",
layers: {},
layerVisibility: {
kemiskinan: true,
masjid: true
},
isHeatmap: false,
isRouting: false,
routingWaypoints: []
};
/* ── Utility: escape HTML ── */
window.escapeHtml = function (value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
/* ── Utility: SVG pin marker ── */
window.createPinIcon = function (bgColor, svgSymbol) {
var svg = [
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="42" viewBox="0 0 32 42">',
'<defs>',
'<filter id="shadow" x="-30%" y="-20%" width="160%" height="160%">',
'<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.35)"/>',
'</filter>',
'</defs>',
/* Pin body */
'<path d="M16 2C9.37 2 4 7.37 4 14c0 9 12 26 12 26S28 23 28 14C28 7.37 22.63 2 16 2z"',
'fill="' + bgColor + '" filter="url(#shadow)"/>',
/* White inner circle */
'<circle cx="16" cy="14" r="8" fill="rgba(255,255,255,0.25)"/>',
/* Symbol */
svgSymbol,
'</svg>'
].join('');
return L.divIcon({
className: 'custom-pin-icon',
html: svg,
iconSize: [32, 42],
iconAnchor: [16, 42],
popupAnchor: [0, -44]
});
};
/* Kemiskinan merah (tidak terjangkau) */
window.redIcon = window.createPinIcon('#dc2626',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* Kemiskinan hijau (terjangkau) */
window.greenIcon = window.createPinIcon('#16a34a',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* Masjid biru */
window.blueIcon = window.createPinIcon('#1d4ed8',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128332;</text>'
);
/* Fallback orange (tidak digunakan, tapi tetap didefinisikan) */
window.orangeIcon = window.createPinIcon('#f97316',
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">&#128106;</text>'
);
/* ── Utility: POST form-encoded ── */
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.");
}
};
/* ── Navbar hint ── */
window.updateMapHint = function () {
var hint = document.getElementById("mapHint");
if (!hint) { return; }
if (appState.mode === "view") {
hint.textContent = "Mode Lihat aktif — data keluarga prasejahtera & rumah ibadah ditampilkan. Klik kartu untuk navigasi peta.";
return;
}
if (appState.activeType === "kemiskinan") {
hint.textContent = "Mode Edit aktif — klik titik di peta untuk menambahkan data keluarga prasejahtera.";
return;
}
hint.textContent = "Mode Edit aktif — klik titik di peta untuk menambahkan data rumah ibadah baru.";
};
/* ── Toolbar state ── */
window.updateToolbarState = function () {
var states = {
modeViewBtn: appState.mode === "view",
modeEditBtn: appState.mode === "edit",
typeKemiskinanBtn: appState.activeType === "kemiskinan",
typeMasjidBtn: appState.activeType === "masjid"
};
Object.keys(states).forEach(function (id) {
var btn = document.getElementById(id);
if (!btn) { return; }
btn.classList.toggle("active", states[id]);
/* Mode buttons get a different active colour */
if (id === "modeEditBtn" && states[id]) {
btn.classList.add("mode-active");
} else {
btn.classList.remove("mode-active");
}
});
var viewBtn = document.getElementById("luminaModeView");
var editBtn = document.getElementById("luminaModeEdit");
if (viewBtn && editBtn) {
viewBtn.style.opacity = appState.mode === "view" ? "1" : "0.5";
viewBtn.style.boxShadow = appState.mode === "view" ? "0 4px 12px rgba(99,102,241,0.25)" : "none";
editBtn.style.opacity = appState.mode === "edit" ? "1" : "0.5";
editBtn.style.boxShadow = appState.mode === "edit" ? "0 4px 12px rgba(20,184,166,0.25)" : "none";
}
};
window.setActiveMenu = function(element) {
var items = document.querySelectorAll('.lumina-menu-item');
items.forEach(function(item) {
item.classList.remove('active');
});
element.classList.add('active');
};
/* ── Layer visibility ── */
window.setLayerVisibility = function (key, isVisible) {
appState.layerVisibility[key] = !!isVisible;
var layer = appState.layers[key];
if (!layer || !window.map) { return; }
if (isVisible) {
if (!map.hasLayer(layer)) { map.addLayer(layer); }
} else {
if (map.hasLayer(layer)) { map.removeLayer(layer); }
}
};
/* ── Mode & type switches ── */
window.setMode = function (mode) {
appState.mode = mode;
window.updateToolbarState();
window.updateMapHint();
var mapEl = document.getElementById("map");
if (mapEl) {
if (mode === "edit") {
mapEl.classList.add("edit-mode");
} else {
mapEl.classList.remove("edit-mode");
}
}
if (typeof window.loadAllData === "function") {
window.loadAllData();
}
};
window.setDataType = function (type) {
appState.activeType = type;
window.updateToolbarState();
window.updateMapHint();
if (typeof window.filterSidebar === 'function') {
window.filterSidebar(type);
}
};
window.filterSidebar = function(type) {
var secKem = document.getElementById('sec-kemiskinan');
var secMsj = document.getElementById('sec-masjid');
if (secKem) secKem.style.display = (type === 'semua' || type === 'kemiskinan') ? 'block' : 'none';
if (secMsj) secMsj.style.display = (type === 'semua' || type === 'masjid') ? 'block' : 'none';
var btnSemua = document.getElementById('filter-btn-semua');
var btnKem = document.getElementById('filter-btn-kemiskinan');
var btnMsj = document.getElementById('filter-btn-masjid');
if (btnSemua) btnSemua.classList.toggle('active', type === 'semua');
if (btnKem) btnKem.classList.toggle('active', type === 'kemiskinan');
if (btnMsj) btnMsj.classList.toggle('active', type === 'masjid');
var sidebarSub = document.getElementById('sidebar-sub');
if (sidebarSub) {
if (type === 'semua') sidebarSub.textContent = 'Menampilkan semua data';
else if (type === 'kemiskinan') sidebarSub.textContent = 'Menampilkan list Data Keluarga';
else sidebarSub.textContent = 'Menampilkan list Rumah Ibadah';
}
var sidebar = document.getElementById('sidebar');
if (sidebar && sidebar.style.display === 'none') {
sidebar.style.display = 'flex';
var pes = document.getElementById('panel-pesan');
if (pes) pes.style.display = 'none';
var lap = document.getElementById('panel-laporan');
if (lap) lap.style.display = 'none';
}
};
/* ── Format helpers ── */
window.formatMeters = function (value) {
return Number(value).toLocaleString("id-ID", { maximumFractionDigits: 0 }) + " m";
};
window.isValidCoordinate = function (lat, lng) {
return Number.isFinite(Number(lat)) && Number.isFinite(Number(lng));
};
window.isValidRadius = function (radius) {
var r = Number(radius);
return Number.isFinite(r) && r >= 100 && r <= 5000;
};
/* ─────────────────────────────────────────────
DOMContentLoaded inisialisasi peta (KF-01)
───────────────────────────────────────────── */
document.addEventListener("DOMContentLoaded", function () {
/* Inisialisasi peta terpusat di Pontianak */
window.map = L.map("map").setView([-0.0263, 109.3425], 13);
// --- Base maps ---
window.osmBase = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
});
window.darkBase = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OpenStreetMap contributors &copy; CARTO'
});
// Add default basemap
window.osmBase.addTo(map);
/* ── Layers ── */
appState.layers.kemiskinan = L.layerGroup().addTo(map);
appState.layers.masjid = L.layerGroup().addTo(map);
appState.layers.heatmap = L.layerGroup(); // Do not add yet
/* Klik peta hanya aktif di Mode Edit (KF-03, KF-07) */
map.on("click", function (event) {
if (appState.mode !== "edit") { return; }
var lat = event.latlng.lat;
var lng = event.latlng.lng;
if (appState.activeType === "kemiskinan") {
if (typeof window.openKemiskinanModal === "function") {
window.openKemiskinanModal(lat, lng);
}
return;
}
if (appState.activeType === "masjid") {
if (typeof window.openMasjidModal === "function") {
window.openMasjidModal(lat, lng);
}
}
});
/* Init state awal */
window.setMode("view");
window.setDataType("kemiskinan");
setTimeout(function () { map.invalidateSize(); }, 300);
/* Cek session login, lalu load data */
if (typeof window.checkAuthState === "function") {
window.checkAuthState().then(function () {
if (typeof window.loadAllData === "function") { window.loadAllData(); }
});
} else if (typeof window.loadAllData === "function") {
window.loadAllData();
}
});
/* ── Heatmap & Routing Features ── */
window.toggleHeatmap = function(btnEl) {
if(!window.appState) return;
window.appState.isHeatmap = !window.appState.isHeatmap;
btnEl.classList.toggle('active', window.appState.isHeatmap);
if(window.appState.isHeatmap) {
document.getElementById('desc-heatmap').style.display = 'block';
// Hide kemiskinan markers
window.appState.layers.kemiskinan.eachLayer(function(l) { l.setOpacity(0); });
// Show heatmap
if(!window.map.hasLayer(window.appState.layers.heatmap)) {
window.appState.layers.heatmap.addTo(window.map);
}
// Switch to Dark mode for better visual
window.map.removeLayer(window.osmBase);
window.darkBase.addTo(window.map);
} else {
document.getElementById('desc-heatmap').style.display = 'none';
// Show kemiskinan markers
window.appState.layers.kemiskinan.eachLayer(function(l) { l.setOpacity(1); });
// Hide heatmap
if(window.map.hasLayer(window.appState.layers.heatmap)) {
window.map.removeLayer(window.appState.layers.heatmap);
}
// Restore map base
window.map.removeLayer(window.darkBase);
window.osmBase.addTo(window.map);
}
};
window.toggleRoutingMode = function(btnEl) {
if(!window.appState) return;
window.appState.isRouting = true;
window.appState.routingWaypoints = [];
btnEl.classList.add('active');
document.getElementById('desc-routing').style.display = 'block';
// Show instruction panel
document.getElementById('routing-instruction-panel').style.display = 'block';
document.getElementById('routing-instruction-text').innerHTML = 'Silakan klik <b>Masjid</b> atau <b>Rumah Warga</b> di peta sebagai titik awal rute.';
};
window.cancelRouting = function() {
if(!window.appState) return;
window.appState.isRouting = false;
window.appState.routingWaypoints = [];
document.getElementById('btn-toggle-routing').classList.remove('active');
document.getElementById('desc-routing').style.display = 'none';
document.getElementById('routing-instruction-panel').style.display = 'none';
// Remove routing control if exists
if(window.routingControl) {
window.map.removeControl(window.routingControl);
window.routingControl = null;
}
};
window.handleRoutingClick = function(lat, lng, name) {
window.appState.routingWaypoints.push(L.latLng(lat, lng));
var pts = window.appState.routingWaypoints;
var inst = document.getElementById('routing-instruction-text');
if(pts.length === 1) {
inst.innerHTML = '<div style="margin-bottom:6px; color:#16a34a;"><i class="fa-solid fa-check"></i> Titik Awal: <b>' + window.escapeHtml(name) + '</b></div>' +
'<div style="margin-bottom:6px;"><b>Langkah 2:</b> Pilih Tujuan</div>' +
'<span style="color:var(--lumina-text-muted);">Klik rumah/masjid lain di peta sebagai tujuan akhir.</span>';
} else if(pts.length === 2) {
inst.innerHTML = '<div style="margin-bottom:6px; color:#16a34a;"><i class="fa-solid fa-check"></i> Rute Berhasil Dibuat</div>' +
'<span style="color:var(--lumina-text-muted);">Detail arah jalan (navigasi) muncul di layar sebelah kanan peta.</span>';
// Remove existing control
if(window.routingControl) {
window.map.removeControl(window.routingControl);
}
window.routingControl = L.Routing.control({
waypoints: pts,
routeWhileDragging: false,
addWaypoints: false,
show: true,
lineOptions: {
styles: [{color: '#2563eb', opacity: 0.8, weight: 6}]
}
}).addTo(window.map);
window.appState.isRouting = false; // Turn off click interception after 2 points
document.getElementById('btn-toggle-routing').classList.remove('active');
}
};