Initial commit
This commit is contained in:
+982
@@ -0,0 +1,982 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user