568 lines
17 KiB
JavaScript
568 lines
17 KiB
JavaScript
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, """)
|
|
.replace(/'/g, "'");
|
|
};
|
|
|
|
window.createPointIcon = function (color) {
|
|
return L.divIcon({
|
|
className: "custom-point-icon",
|
|
html: '<span class="point-dot" style="background:' + color + ';"></span>',
|
|
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();
|
|
}
|
|
});
|