982 lines
35 KiB
HTML
982 lines
35 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>WebGIS FINAL FIX</title>
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
|
|
|
<style>
|
|
html, body { height:100%; margin:0; }
|
|
#map { height:100%; }
|
|
input, select { width:120px; margin-bottom:5px; }
|
|
button { background:green; color:white; border:none; padding:4px; cursor:pointer; margin-right:5px;}
|
|
#control-panel {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 10px;
|
|
top: auto;
|
|
z-index: 1001;
|
|
background: white;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 0 14px rgba(0,0,0,0.2);
|
|
font-family: Arial, sans-serif;
|
|
width: 280px;
|
|
}
|
|
#control-panel label {
|
|
display: block;
|
|
margin-top: 8px;
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
}
|
|
#control-panel input,
|
|
#control-panel select,
|
|
#control-panel button {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
margin-top: 4px;
|
|
margin-bottom: 10px;
|
|
padding: 8px;
|
|
font-size: 13px;
|
|
}
|
|
#control-panel button {
|
|
background: #2e8b57;
|
|
color: white;
|
|
border: none;
|
|
}
|
|
.radius-handle {
|
|
background: white;
|
|
border: 2px solid #007bff;
|
|
border-radius: 50%;
|
|
width: 14px;
|
|
height: 14px;
|
|
box-shadow: 0 0 4px rgba(0,0,0,0.3);
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="control-panel">
|
|
<b>Kontrol Pemetaan</b>
|
|
<label for="marker-type">Jenis titik baru</label>
|
|
<select id="marker-type">
|
|
<option value="worship">Rumah Ibadah</option>
|
|
<option value="poor_house">Rumah Penduduk Miskin</option>
|
|
<option value="point">Data Umum</option>
|
|
</select>
|
|
<label for="default-radius">Radius Rumah Ibadah (m)</label>
|
|
<input id="default-radius" type="number" min="1" max="500" value="200">
|
|
<button id="refresh-data">Muat Ulang Data</button>
|
|
<small>Gunakan tombol gambar marker lalu klik titik untuk menyimpan data.</small>
|
|
</div>
|
|
<div id="map"></div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function(){
|
|
|
|
// =====================
|
|
// INIT MAP
|
|
// =====================
|
|
var map = L.map('map').setView([-0.0554, 109.3494], 13);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
|
|
|
// =====================
|
|
// ICONS
|
|
// =====================
|
|
var iconWorship = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
var iconPoorGreen = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
var iconPoorRed = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/red-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
var iconGeneric = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/purple-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
var iconPointOpen = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/orange-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
var iconPointClosed = L.icon({
|
|
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png',
|
|
iconSize: [32, 32]
|
|
});
|
|
|
|
function getPointIcon(type, buka) {
|
|
if (type === 'worship') return iconWorship;
|
|
if (type === 'poor_house') return buka === 'green' ? iconPoorGreen : iconPoorRed;
|
|
if (type === 'point') return buka === 'ya' ? iconPointOpen : iconPointClosed;
|
|
return iconGeneric;
|
|
}
|
|
|
|
// =====================
|
|
// LAYERS
|
|
// =====================
|
|
var drawnItems = new L.FeatureGroup().addTo(map);
|
|
var worshipLayer = new L.LayerGroup().addTo(map);
|
|
var poorLayer = new L.LayerGroup().addTo(map);
|
|
var pointLayer = new L.LayerGroup().addTo(map);
|
|
var circleLayer = new L.LayerGroup().addTo(map);
|
|
var kecamatanLayer = new L.LayerGroup().addTo(map);
|
|
var pointMarkers = {};
|
|
var worshipCircles = {};
|
|
var worshipHandles = {};
|
|
|
|
var resizeHandleIcon = L.divIcon({
|
|
className: 'radius-handle',
|
|
html: '',
|
|
iconSize: [14, 14],
|
|
iconAnchor: [7, 7]
|
|
});
|
|
|
|
function getHandleLatLng(circle) {
|
|
var center = circle.getLatLng();
|
|
var dest = turf.destination(turf.point([center.lng, center.lat]), circle.getRadius() / 1000, 90, { units: 'kilometers' });
|
|
return L.latLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]);
|
|
}
|
|
|
|
function updateRadiusPopupDisplay(id, radius) {
|
|
var input = document.getElementById('radius_input_' + id);
|
|
var value = document.getElementById('radius_value_' + id);
|
|
if (input) input.value = Math.round(radius);
|
|
if (value) value.innerText = Math.round(radius);
|
|
}
|
|
|
|
function createWorshipResizeHandle(circle, id) {
|
|
var handle = L.marker(getHandleLatLng(circle), {
|
|
icon: resizeHandleIcon,
|
|
draggable: true,
|
|
zIndexOffset: 1000
|
|
});
|
|
|
|
handle.on('drag', function(e) {
|
|
var newRadius = Math.max(1, map.distance(circle.getLatLng(), e.target.getLatLng()));
|
|
circle.setRadius(newRadius);
|
|
updateRadiusPopupDisplay(id, newRadius);
|
|
});
|
|
|
|
handle.on('dragend', function(e) {
|
|
var handleLatLng = e.target.getLatLng();
|
|
var newRadius = Math.max(1, map.distance(circle.getLatLng(), handleLatLng));
|
|
var bearing = turf.bearing(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), turf.point([handleLatLng.lng, handleLatLng.lat]));
|
|
var dest = turf.destination(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), newRadius / 1000, bearing, { units: 'kilometers' });
|
|
handle.setLatLng(L.latLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]));
|
|
saveWorshipRadius(id, newRadius);
|
|
});
|
|
|
|
circleLayer.addLayer(handle);
|
|
return handle;
|
|
}
|
|
|
|
function setWorshipCircleRadius(id, radius, save) {
|
|
var circle = worshipCircles[id];
|
|
if (!circle) return;
|
|
radius = Math.max(1, Math.min(500, radius));
|
|
circle.setRadius(radius);
|
|
updateRadiusPopupDisplay(id, radius);
|
|
if (worshipHandles[id]) {
|
|
worshipHandles[id].setLatLng(getHandleLatLng(circle));
|
|
}
|
|
if (save) {
|
|
var fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('tipe', 'worship');
|
|
fd.append('radius_meter', radius);
|
|
fetch('update_point.php', { method: 'POST', body: fd })
|
|
.then(function() { loadData(); })
|
|
.catch(function(err) { console.error('Update radius error:', err); });
|
|
}
|
|
}
|
|
|
|
|
|
// =====================
|
|
// LOAD GEOJSON KECAMATAN
|
|
// =====================
|
|
fetch('Admin_Kecamatan.json')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
|
|
L.geoJSON(data, {
|
|
|
|
style: {
|
|
color: "purple",
|
|
weight: 2,
|
|
fillOpacity: 0.3
|
|
},
|
|
|
|
onEachFeature: function(feature, layer){
|
|
|
|
var luas = turf.area(feature);
|
|
|
|
var nama = feature.properties.Ket ||
|
|
feature.properties.nama ||
|
|
feature.properties.NAMOBJ ||
|
|
"Tidak ada";
|
|
|
|
layer.bindPopup(`
|
|
<b>Kecamatan</b><br>
|
|
Nama: ${nama}<br>
|
|
Luas: ${(luas/1000000).toFixed(2)} km²<br>
|
|
Populasi: ${feature.properties.Populasi}
|
|
`);
|
|
|
|
layer.bindTooltip(nama);
|
|
}
|
|
|
|
}).addTo(kecamatanLayer);
|
|
|
|
});
|
|
|
|
// =====================
|
|
// DRAW CONTROL
|
|
// =====================
|
|
var drawControl = new L.Control.Draw({
|
|
edit: { featureGroup: drawnItems },
|
|
draw: {
|
|
marker:true,
|
|
polygon:true,
|
|
polyline:true,
|
|
circle:true
|
|
}
|
|
});
|
|
map.addControl(drawControl);
|
|
|
|
// =====================
|
|
// MODE DRAW
|
|
// =====================
|
|
var isDrawing=false;
|
|
|
|
map.on('draw:drawstart', function(){
|
|
isDrawing = true;
|
|
});
|
|
|
|
map.on('draw:drawstop', function(){
|
|
isDrawing = false;
|
|
});
|
|
|
|
// =====================
|
|
// SIMPAN GAMBAR
|
|
// =====================
|
|
map.on(L.Draw.Event.CREATED, function (e) {
|
|
|
|
var layer = e.layer;
|
|
var type = e.layerType;
|
|
|
|
drawnItems.addLayer(layer);
|
|
|
|
if (type === 'marker') {
|
|
showMarkerForm(layer);
|
|
return;
|
|
}
|
|
|
|
if (type === 'polyline') {
|
|
|
|
var latlngs = layer.getLatLngs();
|
|
var panjang = 0;
|
|
|
|
for (var i = 0; i < latlngs.length - 1; i++) {
|
|
panjang += map.distance(latlngs[i], latlngs[i+1]);
|
|
}
|
|
|
|
var status = prompt("Status Jalan (Nasional/Provinsi/Kabupaten):");
|
|
|
|
var color = 'gray';
|
|
var weight = 4;
|
|
if (status === 'Nasional') { color = '#c0392b'; weight = 6; }
|
|
else if (status === 'Provinsi') { color = '#2980b9'; weight = 5; }
|
|
else if (status === 'Kabupaten') { color = '#27ae60'; weight = 4; }
|
|
|
|
layer.setStyle({ color: color, weight: weight });
|
|
|
|
fetch('save.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tipe: "jalan",
|
|
status: status,
|
|
nilai: panjang,
|
|
geom: layer.toGeoJSON()
|
|
})
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
loadGambar();
|
|
})
|
|
.catch(error => {
|
|
console.error('Save error:', error);
|
|
});
|
|
}
|
|
|
|
if (type === 'circle') {
|
|
// Save circle as a worship radius (ask minimal info)
|
|
var center = layer.getLatLng();
|
|
var radius = Math.round(layer.getRadius());
|
|
var nama = prompt('Nama Rumah Ibadah:');
|
|
if (!nama) { drawnItems.removeLayer(layer); return; }
|
|
var jenis = prompt('Jenis (Masjid/Gereja/etc):', 'Masjid') || 'Masjid';
|
|
var alamat = prompt('Alamat (opsional):', '');
|
|
var kontak = prompt('Kontak / WA (opsional):', '');
|
|
var kegiatan = prompt('Kegiatan (opsional):', 'Pengajian');
|
|
var kapasitas = prompt('Kapasitas (angka):', '100');
|
|
|
|
var fd = new FormData();
|
|
fd.append('tipe', 'worship');
|
|
fd.append('lat', center.lat);
|
|
fd.append('lng', center.lng);
|
|
fd.append('nama', nama);
|
|
fd.append('jenis', jenis);
|
|
fd.append('alamat', alamat);
|
|
fd.append('kontak', kontak);
|
|
fd.append('no_wa', kontak);
|
|
fd.append('kegiatan', kegiatan);
|
|
fd.append('kapasitas', kapasitas || 0);
|
|
fd.append('radius_meter', radius || getDefaultRadius());
|
|
|
|
fetch('save_point.php', { method: 'POST', body: fd })
|
|
.then(function(res){ return res.json(); })
|
|
.then(function(data){
|
|
if (!data.success) { alert('Gagal menyimpan: ' + (data.error || 'Unknown')); drawnItems.removeLayer(layer); return; }
|
|
loadData();
|
|
})
|
|
.catch(function(err){ console.error('Save circle error:', err); drawnItems.removeLayer(layer); });
|
|
}
|
|
|
|
if (type === 'polygon') {
|
|
|
|
var geojson = layer.toGeoJSON();
|
|
var luas = turf.area(geojson);
|
|
|
|
var status = prompt("Status Tanah (SHM/HGB/HGU/HP):");
|
|
|
|
var color = 'gray';
|
|
var fillOpacity = 0.3;
|
|
if (status === 'SHM') { color = 'green'; fillOpacity = 0.5; }
|
|
else if (status === 'HGB') { color = 'yellow'; fillOpacity = 0.5; }
|
|
else if (status === 'HGU') { color = 'orange'; fillOpacity = 0.5; }
|
|
else if (status === 'HP') { color = 'red'; fillOpacity = 0.5; }
|
|
|
|
layer.setStyle({ color: color, weight: 2, fillOpacity: fillOpacity });
|
|
|
|
fetch('save.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tipe: "parsil",
|
|
status: status,
|
|
nilai: luas,
|
|
geom: geojson
|
|
})
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
loadGambar();
|
|
})
|
|
.catch(error => {
|
|
console.error('Save error:', error);
|
|
});
|
|
}
|
|
});
|
|
|
|
// =====================
|
|
// EDIT
|
|
// =====================
|
|
map.on(L.Draw.Event.EDITED, function (e) {
|
|
|
|
e.layers.eachLayer(function(layer){
|
|
|
|
var geojson = layer.toGeoJSON();
|
|
|
|
fetch('update.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
id: layer._id,
|
|
geojson: geojson
|
|
})
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// =====================
|
|
// DELETE
|
|
// =====================
|
|
map.on(L.Draw.Event.DELETED, function (e) {
|
|
|
|
e.layers.eachLayer(function(layer){
|
|
|
|
fetch('delete.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
id: layer._id,
|
|
tipe: layer._tipe
|
|
})
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// =====================
|
|
// GLOBAL FUNCTIONS FOR MARKER FORM
|
|
// =====================
|
|
function getDefaultRadius() {
|
|
var el = document.getElementById('default-radius');
|
|
return el ? Math.min(500, Math.max(1, parseInt(el.value) || 200)) : 200;
|
|
}
|
|
|
|
function calculatePoorColor(data) {
|
|
if (!data || !data.latitude || !data.longitude) return 'red';
|
|
var latlng = L.latLng(data.latitude, data.longitude);
|
|
for (var id in worshipCircles) {
|
|
var circle = worshipCircles[id];
|
|
if (circle && map.distance(latlng, circle.getLatLng()) <= circle.getRadius()) {
|
|
return 'green';
|
|
}
|
|
}
|
|
return 'red';
|
|
}
|
|
|
|
function updatePointPosition(id, latlng, tipe) {
|
|
var fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('tipe', tipe);
|
|
fd.append('lat', latlng.lat);
|
|
fd.append('lng', latlng.lng);
|
|
|
|
fetch('update_point.php', { method: 'POST', body: fd })
|
|
.then(function() { loadData(); })
|
|
.catch(function(err) { console.error('Update position error:', err); });
|
|
}
|
|
|
|
window.deletePoint = function(id) {
|
|
if (!confirm('Hapus data ini?')) return;
|
|
var marker = pointMarkers[id];
|
|
var tipe = marker && marker._tipe ? marker._tipe : '';
|
|
var fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('tipe', tipe);
|
|
|
|
fetch('delete_point.php', { method: 'POST', body: fd })
|
|
.then(function(){ loadData(); })
|
|
.catch(function(err){ console.error('Delete error:', err); });
|
|
};
|
|
|
|
function showMarkerForm(layer) {
|
|
var layerId = layer._leaflet_id;
|
|
var selectedType = document.getElementById('marker-type').value;
|
|
var defaultRadius = getDefaultRadius();
|
|
var formHtml = '';
|
|
|
|
if (selectedType === 'worship') {
|
|
formHtml = '<div style="width:300px;">' +
|
|
'<b>Tambah Rumah Ibadah</b><br><br>' +
|
|
'Nama:<br><input id="nama_' + layerId + '" type="text"><br>' +
|
|
'Jenis:<br><input id="jenis_' + layerId + '" type="text" value="Masjid"><br>' +
|
|
'Alamat:<br><input id="alamat_' + layerId + '" type="text"><br>' +
|
|
'Kontak / WA:<br><input id="kontak_' + layerId + '" type="text"><br>' +
|
|
'Kegiatan:<br><input id="kegiatan_' + layerId + '" type="text" value="Pengajian"><br>' +
|
|
'Kapasitas:<br><input id="kapasitas_' + layerId + '" type="number" min="1" value="100"><br>' +
|
|
'Radius (m):<br><input id="radius_' + layerId + '" type="number" min="1" max="500" value="' + defaultRadius + '"><br>' +
|
|
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
|
|
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
|
|
'</div>';
|
|
} else if (selectedType === 'poor_house') {
|
|
formHtml = '<div style="width:300px;">' +
|
|
'<b>Tambah Rumah Penduduk Miskin</b><br><br>' +
|
|
'Nama Kepala Keluarga:<br><input id="nama_' + layerId + '" type="text"><br>' +
|
|
'Alamat:<br><input id="alamat_' + layerId + '" type="text"><br>' +
|
|
'Jumlah Anggota:<br><input id="jumlah_' + layerId + '" type="number" min="1" value="1"><br>' +
|
|
'Kondisi Rumah:<br><input id="kondisi_' + layerId + '" type="text" value="Tidak layak"><br>' +
|
|
'Sumber Penghasilan:<br><input id="sumber_' + layerId + '" type="text"><br>' +
|
|
'Kebutuhan Prioritas:<br><input id="kebutuhan_' + layerId + '" type="text"><br>' +
|
|
'Status Bantuan:<br><input id="status_' + layerId + '" type="text" value="Belum Terlayani"><br>' +
|
|
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
|
|
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
|
|
'</div>';
|
|
} else {
|
|
formHtml = '<div style="width:300px;">' +
|
|
'<b>Tambah Data Umum</b><br><br>' +
|
|
'Nama:<br><input id="nama_' + layerId + '" type="text"><br>' +
|
|
'WA:<br><input id="wa_' + layerId + '" type="text"><br>' +
|
|
'Waktu Operasi:<br><select id="buka_' + layerId + '"><option value="ya">Buka 24 Jam</option><option value="tidak">Tutup Malam</option></select><br>' +
|
|
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
|
|
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
|
|
'</div>';
|
|
}
|
|
|
|
layer.bindPopup(formHtml, { maxWidth: 340, autoPan: true, closeButton: true });
|
|
layer.openPopup();
|
|
}
|
|
|
|
window.savePoint = function(layerId) {
|
|
var layer = drawnItems._layers[layerId];
|
|
if (!layer) { alert('Layer tidak ditemukan'); return; }
|
|
|
|
var selectedType = document.getElementById('marker-type').value;
|
|
var fd = new FormData();
|
|
fd.append('tipe', selectedType);
|
|
fd.append('lat', layer.getLatLng().lat);
|
|
fd.append('lng', layer.getLatLng().lng);
|
|
|
|
if (selectedType === 'worship') {
|
|
var nama = document.getElementById('nama_' + layerId).value.trim();
|
|
var jenis = document.getElementById('jenis_' + layerId).value.trim();
|
|
var alamat = document.getElementById('alamat_' + layerId).value.trim();
|
|
var kontak = document.getElementById('kontak_' + layerId).value.trim();
|
|
var kegiatan = document.getElementById('kegiatan_' + layerId).value.trim();
|
|
var kapasitas = document.getElementById('kapasitas_' + layerId).value.trim();
|
|
var radius = document.getElementById('radius_' + layerId).value.trim();
|
|
|
|
if (!nama) { alert('Nama rumah ibadah harus diisi!'); return; }
|
|
fd.append('nama', nama);
|
|
fd.append('jenis', jenis);
|
|
fd.append('alamat', alamat);
|
|
fd.append('kontak', kontak);
|
|
fd.append('no_wa', kontak);
|
|
fd.append('kegiatan', kegiatan);
|
|
fd.append('kapasitas', kapasitas || 0);
|
|
fd.append('radius_meter', radius || getDefaultRadius());
|
|
} else if (selectedType === 'poor_house') {
|
|
var nama = document.getElementById('nama_' + layerId).value.trim();
|
|
var alamat = document.getElementById('alamat_' + layerId).value.trim();
|
|
var jumlah = document.getElementById('jumlah_' + layerId).value.trim();
|
|
var kondisi = document.getElementById('kondisi_' + layerId).value.trim();
|
|
var sumber = document.getElementById('sumber_' + layerId).value.trim();
|
|
var kebutuhan = document.getElementById('kebutuhan_' + layerId).value.trim();
|
|
var status = document.getElementById('status_' + layerId).value.trim();
|
|
|
|
if (!nama) { alert('Nama kepala keluarga harus diisi!'); return; }
|
|
fd.append('nama_keluarga', nama);
|
|
fd.append('alamat', alamat);
|
|
fd.append('jumlah_anggota', jumlah || 0);
|
|
fd.append('kondisi_rumah', kondisi);
|
|
fd.append('sumber_penghasilan', sumber);
|
|
fd.append('kebutuhan_prioritas', kebutuhan);
|
|
fd.append('status_bantuan', status);
|
|
} else {
|
|
var nama = document.getElementById('nama_' + layerId).value.trim();
|
|
var wa = document.getElementById('wa_' + layerId).value.trim();
|
|
var buka = document.getElementById('buka_' + layerId).value;
|
|
if (!nama || !wa) { alert('Nama dan WA harus diisi!'); return; }
|
|
fd.append('nama', nama);
|
|
fd.append('no_wa', wa);
|
|
fd.append('buka', buka);
|
|
}
|
|
|
|
fetch('save_point.php', { method: 'POST', body: fd })
|
|
.then(function(res) {
|
|
return res.text().then(function(text) {
|
|
if (!res.ok) {
|
|
throw new Error('HTTP ' + res.status + ': ' + text);
|
|
}
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (e) {
|
|
throw new Error('Invalid JSON response: ' + text);
|
|
}
|
|
});
|
|
})
|
|
.then(function(data){
|
|
if (!data.success) {
|
|
var message = data.error || 'Unknown error';
|
|
alert('Gagal menyimpan: ' + message);
|
|
console.error('SavePoint failed:', message);
|
|
return;
|
|
}
|
|
drawnItems.removeLayer(layer);
|
|
map.closePopup();
|
|
loadData();
|
|
})
|
|
.catch(function(err){
|
|
console.error('Error:', err);
|
|
alert('Gagal menyimpan data: ' + err.message);
|
|
});
|
|
};
|
|
|
|
window.cancelPoint = function(layerId) {
|
|
var layer = drawnItems._layers[layerId];
|
|
if (layer) {
|
|
drawnItems.removeLayer(layer);
|
|
map.closePopup();
|
|
}
|
|
};
|
|
|
|
function saveWorshipRadius(id, radius) {
|
|
if (typeof radius === 'undefined') {
|
|
var input = document.getElementById('radius_input_' + id);
|
|
if (!input) return;
|
|
radius = Math.min(500, Math.max(1, parseInt(input.value) || 100));
|
|
} else {
|
|
radius = Math.min(500, Math.max(1, Math.round(radius)));
|
|
}
|
|
var fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('tipe', 'worship');
|
|
fd.append('radius_meter', radius);
|
|
fetch('update_point.php', { method: 'POST', body: fd })
|
|
.then(function() { loadData(); })
|
|
.catch(function(err) { console.error('Update radius error:', err); });
|
|
}
|
|
|
|
// =====================
|
|
// HELPERS: edit/delete single feature
|
|
// =====================
|
|
window.deleteFeature = function(id, tipe) {
|
|
if (!confirm('Hapus data ini?')) return;
|
|
fetch('delete.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: id, tipe: tipe })
|
|
})
|
|
.then(function(res){ return res.text(); })
|
|
.then(function(){
|
|
loadGambar();
|
|
})
|
|
.catch(function(err){ console.error('Delete feature error:', err); });
|
|
};
|
|
|
|
window.enableLayerEdit = function(id) {
|
|
// Enable editing for a single layer and add a Save button in the popup
|
|
var layer = null;
|
|
drawnItems.eachLayer(function(l){ if (l._id == id) layer = l; });
|
|
if (!layer) { alert('Layer tidak ditemukan'); return; }
|
|
|
|
if (layer.editing && layer.editing.enable) {
|
|
layer.editing.enable();
|
|
} else if (layer instanceof L.Marker) {
|
|
layer.dragging.enable();
|
|
}
|
|
|
|
var saveBtn = document.createElement('button');
|
|
saveBtn.innerText = 'Simpan Perubahan';
|
|
saveBtn.style.marginTop = '8px';
|
|
saveBtn.onclick = function(){
|
|
// disable editing then send update
|
|
if (layer.editing && layer.editing.disable) layer.editing.disable();
|
|
if (layer.dragging && layer.dragging.disable) layer.dragging.disable();
|
|
|
|
var geojson = layer.toGeoJSON ? layer.toGeoJSON() : null;
|
|
if (!geojson) { alert('Tidak dapat mengambil GeoJSON'); return; }
|
|
|
|
fetch('update.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id: id, geojson: geojson })
|
|
})
|
|
.then(function(res){ return res.text(); })
|
|
.then(function(){ loadGambar(); })
|
|
.catch(function(err){ console.error('Save edited feature error:', err); });
|
|
};
|
|
|
|
layer.bindPopup((layer.getPopup() ? layer.getPopup().getContent() : '') + '<br>').openPopup();
|
|
// append button to popup DOM when opened
|
|
layer.once('popupopen', function(){
|
|
var px = layer.getPopup().getElement();
|
|
if (px && px.appendChild) px.appendChild(saveBtn);
|
|
});
|
|
};
|
|
|
|
// Refresh button handler
|
|
document.addEventListener('DOMContentLoaded', function(){
|
|
var btn = document.getElementById('refresh-data');
|
|
if (btn) btn.addEventListener('click', function(){ refreshAllData(); });
|
|
});
|
|
|
|
function renderWorshipMarker(d) {
|
|
var radius = d.radius_meter ? parseInt(d.radius_meter) : getDefaultRadius();
|
|
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: iconWorship });
|
|
marker._id = d.id;
|
|
marker._tipe = 'worship';
|
|
marker._pointData = d;
|
|
|
|
var circle = L.circle([d.latitude, d.longitude], {
|
|
radius: radius,
|
|
color: 'blue',
|
|
fillOpacity: 0.1,
|
|
weight: 2
|
|
});
|
|
circle._worshipId = d.id;
|
|
worshipCircles[d.id] = circle;
|
|
circleLayer.addLayer(circle);
|
|
|
|
var handle = createWorshipResizeHandle(circle, d.id);
|
|
worshipHandles[d.id] = handle;
|
|
|
|
marker.bindPopup(
|
|
'<b>Rumah Ibadah</b><br>' +
|
|
'Nama: ' + (d.nama || '-') + '<br>' +
|
|
'Jenis: ' + (d.jenis || '-') + '<br>' +
|
|
'Alamat: ' + (d.alamat || '-') + '<br>' +
|
|
'Kontak: ' + (d.kontak || d.no_wa || '-') + '<br>' +
|
|
'Kegiatan: ' + (d.kegiatan || '-') + '<br>' +
|
|
'Kapasitas: ' + (d.kapasitas || '-') + ' orang<br><br>' +
|
|
'Radius: <input id="radius_input_' + d.id + '" type="range" min="1" max="500" value="' + radius + '" oninput="setWorshipCircleRadius(' + d.id + ', this.value, false);"> ' +
|
|
'<span id="radius_value_' + d.id + '">' + radius + '</span> m<br>' +
|
|
'<button onclick="saveWorshipRadius(' + d.id + ')" style="margin-top:8px;">Simpan Radius</button> ' +
|
|
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
|
|
);
|
|
|
|
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), 'worship'); });
|
|
marker.addTo(worshipLayer);
|
|
pointMarkers[d.id] = marker;
|
|
}
|
|
|
|
function renderPoorMarker(d) {
|
|
var color = calculatePoorColor(d);
|
|
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: getPointIcon('poor_house', color) });
|
|
marker._id = d.id;
|
|
marker._tipe = 'poor_house';
|
|
marker._pointData = d;
|
|
|
|
marker.bindPopup(
|
|
'<b>Rumah Penduduk Miskin</b><br>' +
|
|
'Nama Kepala Keluarga: ' + (d.nama_kepala_keluarga || '-') + '<br>' +
|
|
'Alamat: ' + (d.alamat || '-') + '<br>' +
|
|
'Jumlah Anggota: ' + (d.jumlah_anggota || '-') + '<br>' +
|
|
'Kondisi Rumah: ' + (d.kondisi_rumah || '-') + '<br>' +
|
|
'Sumber Penghasilan: ' + (d.sumber_penghasilan || '-') + '<br>' +
|
|
'Kebutuhan Prioritas: ' + (d.kebutuhan_prioritas || '-') + '<br>' +
|
|
'Status Bantuan: ' + (d.status_bantuan || '-') + '<br><br>' +
|
|
'<b>Status:</b> ' + (color === 'green' ? 'Di dalam radius' : 'Di luar radius') + '<br>' +
|
|
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
|
|
);
|
|
|
|
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), 'poor_house'); });
|
|
marker.addTo(poorLayer);
|
|
pointMarkers[d.id] = marker;
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
return String(str || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
}
|
|
|
|
window.cancelEdit = function(id) {
|
|
var marker = pointMarkers[id];
|
|
if (marker) {
|
|
marker.closePopup();
|
|
}
|
|
};
|
|
|
|
window.savePointEdit = function(id) {
|
|
var marker = pointMarkers[id];
|
|
if (!marker) return;
|
|
|
|
var nama = document.getElementById('edit_nama_' + id).value.trim();
|
|
var wa = document.getElementById('edit_wa_' + id).value.trim();
|
|
var buka = document.getElementById('edit_buka_' + id).value;
|
|
|
|
if (!nama || !wa) {
|
|
alert('Nama dan WA harus diisi!');
|
|
return;
|
|
}
|
|
|
|
var fd = new FormData();
|
|
fd.append('id', id);
|
|
fd.append('tipe', 'point');
|
|
fd.append('nama', nama);
|
|
fd.append('no_wa', wa);
|
|
fd.append('buka', buka);
|
|
|
|
fetch('update_point.php', { method: 'POST', body: fd })
|
|
.then(function(res){ return res.json(); })
|
|
.then(function(data){
|
|
if (!data.success) { alert('Gagal update: ' + (data.error || 'Unknown')); return; }
|
|
loadData();
|
|
})
|
|
.catch(function(err){ console.error('Update edit error:', err); });
|
|
};
|
|
|
|
window.editPoint = function(id) {
|
|
var marker = pointMarkers[id];
|
|
if (!marker) return;
|
|
var d = marker._pointData || {};
|
|
var title = d.tipe === 'point' ? 'Edit SPBU/Data Umum' : 'Edit Data Umum';
|
|
var formHtml = '<div style="width:300px;">' +
|
|
'<b>' + title + '</b><br><br>' +
|
|
'Nama:<br><input id="edit_nama_' + id + '" type="text" value="' + escapeHtml(d.nama) + '"><br>' +
|
|
'WA:<br><input id="edit_wa_' + id + '" type="text" value="' + escapeHtml(d.no_wa) + '"><br>' +
|
|
'Waktu Operasi:<br><select id="edit_buka_' + id + '">' +
|
|
'<option value="ya"' + (d.buka_24jam === 'ya' ? ' selected' : '') + '>Buka 24 Jam</option>' +
|
|
'<option value="tidak"' + (d.buka_24jam === 'tidak' ? ' selected' : '') + '>Tutup Malam</option>' +
|
|
'</select><br>' +
|
|
'<button onclick="savePointEdit(' + id + ')">Simpan</button> ' +
|
|
'<button onclick="cancelEdit(' + id + ')" style="background:#c0392b;">Batal</button>' +
|
|
'</div>';
|
|
|
|
marker.bindPopup(formHtml, { maxWidth: 340, autoPan: true, closeButton: true }).openPopup();
|
|
};
|
|
|
|
function renderGenericPoint(d) {
|
|
var controlType = d.tipe || 'point';
|
|
var icon = getPointIcon(controlType, d.buka_24jam);
|
|
var label = controlType === 'point' ? 'SPBU / Data Umum' : 'Data Umum';
|
|
var bukaText = d.buka_24jam ? (d.buka_24jam === 'ya' ? 'Buka 24 Jam' : 'Tutup Malam') : '-';
|
|
var kontakText = d.no_wa || d.kontak || '-';
|
|
|
|
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: icon });
|
|
marker._id = d.id;
|
|
marker._tipe = controlType;
|
|
marker._pointData = d;
|
|
|
|
marker.bindPopup(
|
|
'<b>' + label + '</b><br>' +
|
|
'Nama: ' + (d.nama || '-') + '<br>' +
|
|
'Kontak: ' + kontakText + '<br>' +
|
|
'Waktu: ' + bukaText + '<br>' +
|
|
'<button onclick="editPoint(' + d.id + ')" style="margin-top:8px;background:#f39c12;">Edit</button> ' +
|
|
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
|
|
);
|
|
|
|
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), marker._tipe); });
|
|
marker.addTo(pointLayer);
|
|
pointMarkers[d.id] = marker;
|
|
}
|
|
|
|
window.cancelPoint = function(layerId) {
|
|
var layer = drawnItems._layers[layerId];
|
|
if (layer) {
|
|
drawnItems.removeLayer(layer);
|
|
map.closePopup();
|
|
}
|
|
};
|
|
|
|
// =====================
|
|
// LOAD GAMBAR
|
|
|
|
function loadGambar(){
|
|
|
|
drawnItems.clearLayers(); // 🔥 FIX biar tidak dobel
|
|
|
|
return fetch('get.php')
|
|
.then(res=>res.json())
|
|
.then(data=>{
|
|
|
|
L.geoJSON(data, {
|
|
onEachFeature: function(feature, layer){
|
|
layer._id = feature.properties.id;
|
|
layer._tipe = feature.properties.tipe;
|
|
|
|
// Make layer interactive so it can be clicked and edited
|
|
if (layer.setStyle) {
|
|
layer.setStyle({ interactive: true });
|
|
}
|
|
|
|
var popupContent = '';
|
|
if (feature.properties.tipe === 'jalan') {
|
|
popupContent = `<b>Jalan</b><br>Status: ${feature.properties.status}<br>Panjang: ${feature.properties.nilai} m`;
|
|
} else if (feature.properties.tipe === 'parsil') {
|
|
popupContent = `<b>Parsil</b><br>Status: ${feature.properties.status}<br>Luas: ${(feature.properties.nilai / 1000000).toFixed(2)} km²`;
|
|
}
|
|
|
|
// Add Edit / Delete buttons to popup for convenience
|
|
popupContent += '<br><br>' +
|
|
`<button onclick="enableLayerEdit(${feature.properties.id})">Edit</button> ` +
|
|
`<button onclick="deleteFeature(${feature.properties.id}, '${feature.properties.tipe}')" style="background:#c0392b;color:white;border:none;padding:4px;">Hapus</button>`;
|
|
|
|
layer.bindPopup(popupContent);
|
|
drawnItems.addLayer(layer);
|
|
},
|
|
style: function(feature) {
|
|
var status = feature.properties.status;
|
|
if (feature.properties.tipe === 'jalan') {
|
|
// Thicker lines so they are easy to click/edit/delete
|
|
if (status === 'Nasional') return { color: '#c0392b', weight: 6, opacity: 0.9 };
|
|
if (status === 'Provinsi') return { color: '#2980b9', weight: 5, opacity: 0.9 };
|
|
if (status === 'Kabupaten') return { color: '#27ae60', weight: 4, opacity: 0.9 };
|
|
return { color: 'gray', weight: 4, opacity: 0.8 };
|
|
} else if (feature.properties.tipe === 'parsil') {
|
|
// Polygons with visible fill and border
|
|
if (status === 'SHM') return { color: '#16a085', weight: 2, fillColor: '#16a085', fillOpacity: 0.4 };
|
|
if (status === 'HGB') return { color: '#f1c40f', weight: 2, fillColor: '#f1c40f', fillOpacity: 0.35 };
|
|
if (status === 'HGU') return { color: '#e67e22', weight: 2, fillColor: '#e67e22', fillOpacity: 0.35 };
|
|
if (status === 'HP') return { color: '#e74c3c', weight: 2, fillColor: '#e74c3c', fillOpacity: 0.35 };
|
|
return { color: 'gray', weight: 2, fillOpacity: 0.25 };
|
|
}
|
|
}
|
|
}).eachLayer(function(layer){
|
|
// Ensure polylines/polygons are clickable (increase tolerance)
|
|
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)) {
|
|
layer.options.smoothFactor = 1;
|
|
}
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// =====================
|
|
// LAYER CONTROLS
|
|
// =====================
|
|
var overlayMaps = {
|
|
'Rumah Ibadah': worshipLayer,
|
|
'Rumah Penduduk Miskin': poorLayer,
|
|
'SPBU / Data Umum': pointLayer,
|
|
'Lingkaran Radius': circleLayer,
|
|
'Kecamatan': kecamatanLayer
|
|
};
|
|
|
|
L.control.layers(null, overlayMaps, { collapsed: false }).addTo(map);
|
|
|
|
// =====================
|
|
// LOAD POINT DATA
|
|
// =====================
|
|
function loadData(){
|
|
worshipLayer.clearLayers();
|
|
poorLayer.clearLayers();
|
|
pointLayer.clearLayers();
|
|
circleLayer.clearLayers();
|
|
pointMarkers = {};
|
|
worshipCircles = {};
|
|
worshipHandles = {};
|
|
|
|
return fetch('get_point.php')
|
|
.then(function(res){ return res.json(); })
|
|
.then(function(data){
|
|
data.forEach(function(d){
|
|
if (d.tipe === 'worship') {
|
|
renderWorshipMarker(d);
|
|
} else if (d.tipe === 'poor_house') {
|
|
renderPoorMarker(d);
|
|
} else {
|
|
renderGenericPoint(d);
|
|
}
|
|
});
|
|
})
|
|
.catch(function(err){ console.error('Load point error:', err); });
|
|
}
|
|
|
|
function refreshAllData() {
|
|
return Promise.all([loadData(), loadGambar()]);
|
|
}
|
|
|
|
// =====================
|
|
refreshAllData();
|
|
// =====================
|
|
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html> |