first commit
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
/* ============================================================
|
||||
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, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
};
|
||||
|
||||
/* ── 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">👪</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">👪</text>'
|
||||
);
|
||||
/* Masjid – biru */
|
||||
window.blueIcon = window.createPinIcon('#1d4ed8',
|
||||
'<text x="16" y="19" text-anchor="middle" font-size="11" fill="white" font-family="Arial">🕌</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">👪</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: '© OpenStreetMap contributors'
|
||||
});
|
||||
window.darkBase = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© OpenStreetMap contributors © 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');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user