Files
UAS_WebGIS_MuthiaNurainiAst…/parsiljalan/assets/jalan-input.js
T
2026-06-15 21:53:17 +07:00

443 lines
18 KiB
JavaScript

/* Input mode: draw jalan / kavling / rusak on map */
var drawMode = null, drawing = false, points = [], previewLayer = null, tempMarkers = [];
var pendingKoord = null, pendingUkuran = 0, currentMode = null;
var pendingRusakLatLng = null, fotoBase64 = '';
function setMode(m) {
if (drawing) cancelDraw();
drawMode = m;
currentMode = m;
var bj = document.getElementById('btn-jalan');
var bp = document.getElementById('btn-parsil');
var br = document.getElementById('btn-rusak');
if (bj) bj.className = m === 'jalan' ? 'active-jalan' : '';
if (bp) bp.className = m === 'parsil' ? 'active-parsil' : '';
if (br) br.className = m === 'rusak' ? 'active-rusak' : '';
var actions = document.getElementById('draw-actions');
var hint = document.getElementById('hint');
if (m === 'rusak') {
drawing = false;
points = [];
if (hint) hint.textContent = 'Klik titik di peta untuk menandai jalan rusak';
map.getContainer().style.cursor = 'crosshair';
if (actions) actions.style.display = 'flex';
['undo-btn', 'finish-btn'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.style.display = 'none';
});
var cancel = document.getElementById('cancel-btn');
if (cancel) cancel.style.display = 'block';
return;
}
drawing = true;
points = [];
if (actions) actions.style.display = 'flex';
['undo-btn', 'finish-btn', 'cancel-btn'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.style.display = 'block';
});
if (hint) {
hint.textContent = m === 'jalan'
? 'Klik peta untuk menambah titik jalan — minimal 2 titik'
: 'Klik peta untuk menggambar area kavling — minimal 3 titik';
}
map.getContainer().style.cursor = 'crosshair';
}
map.on('click', function (e) {
if (currentMode === 'rusak') {
pendingRusakLatLng = e.latlng;
openRusakModal(e.latlng);
return;
}
if (!drawing) return;
points.push(e.latlng);
var dot = L.circleMarker(e.latlng, {
radius: 5,
color: drawMode === 'jalan' ? '#2563eb' : '#7c3aed',
fillColor: '#fff',
fillOpacity: 1,
weight: 2.5
}).addTo(map);
tempMarkers.push(dot);
redrawPreview();
});
function redrawPreview() {
if (previewLayer) map.removeLayer(previewLayer);
if (points.length < 2) return;
var opts = { color: drawMode === 'jalan' ? '#2563eb' : '#7c3aed', weight: 2.5, dashArray: '7,5', opacity: 0.85 };
previewLayer = drawMode === 'jalan'
? L.polyline(points, opts).addTo(map)
: L.polygon(points, Object.assign({}, opts, { fillOpacity: 0.1 })).addTo(map);
}
function undoLast() {
if (!points.length) return;
points.pop();
var last = tempMarkers.pop();
if (last) map.removeLayer(last);
redrawPreview();
}
function cancelDraw() {
drawing = false;
points = [];
if (previewLayer) { map.removeLayer(previewLayer); previewLayer = null; }
tempMarkers.forEach(function (m) { map.removeLayer(m); });
tempMarkers = [];
map.getContainer().style.cursor = '';
var actions = document.getElementById('draw-actions');
if (actions) actions.style.display = 'none';
['btn-jalan', 'btn-parsil', 'btn-rusak'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.className = '';
});
var hint = document.getElementById('hint');
if (hint) hint.textContent = 'Pilih mode input di atas';
currentMode = null;
drawMode = null;
pendingRusakLatLng = null;
}
function finishShape() {
if (drawMode === 'jalan' && points.length < 2) { showToast('Minimal 2 titik untuk jalan!', 'error'); return; }
if (drawMode === 'parsil' && points.length < 3) { showToast('Minimal 3 titik untuk kavling!', 'error'); return; }
drawing = false;
map.getContainer().style.cursor = '';
var ukuran = 0;
if (drawMode === 'jalan') {
for (var i = 0; i < points.length - 1; i++) ukuran += points[i].distanceTo(points[i + 1]);
ukuran = Math.round(ukuran * 10) / 10;
document.getElementById('inp-ukuran').value = ukuran.toFixed(1) + ' meter';
document.getElementById('form-title').textContent = 'Simpan Data Jalan';
document.getElementById('form-sub').textContent = 'Lengkapi informasi ruas jalan';
document.getElementById('form-icon').textContent = '🛣';
document.getElementById('form-icon').className = 'modal-icon jalan';
document.getElementById('label-nama').textContent = 'Nama Jalan';
document.getElementById('label-status').textContent = 'Status Jalan';
document.getElementById('label-ukuran').innerHTML = 'Panjang Jalan <span class="readonly-tag">Otomatis</span>';
document.getElementById('inp-nama').placeholder = 'Contoh: Jl. Ahmad Yani';
document.getElementById('inp-status').innerHTML =
'<option value="Jalan Nasional">Jalan Nasional</option>' +
'<option value="Jalan Provinsi">Jalan Provinsi</option>' +
'<option value="Jalan Kabupaten">Jalan Kabupaten</option>';
document.getElementById('parsil-extra-fields').style.display = 'none';
} else {
ukuran = calcArea(points);
ukuran = Math.round(ukuran * 10) / 10;
document.getElementById('inp-ukuran').value = ukuran.toFixed(1) + ' m²';
document.getElementById('form-title').textContent = 'Simpan Data Kavling';
document.getElementById('form-sub').textContent = 'Lengkapi informasi kavling tanah';
document.getElementById('form-icon').textContent = '🏘';
document.getElementById('form-icon').className = 'modal-icon parsil';
document.getElementById('label-nama').textContent = 'Nama / Nomor Parsil';
document.getElementById('label-status').textContent = 'Status Kepemilikan';
document.getElementById('label-ukuran').innerHTML = 'Luas Area <span class="readonly-tag">Otomatis</span>';
document.getElementById('inp-nama').placeholder = 'Contoh: Parsil A-01';
document.getElementById('inp-status').innerHTML =
'<option value="SHM">SHM — Sertifikat Hak Milik</option>' +
'<option value="HGB">HGB — Hak Guna Bangunan</option>' +
'<option value="HGU">HGU — Hak Guna Usaha</option>' +
'<option value="HP">HP — Hak Pakai</option>';
document.getElementById('parsil-extra-fields').style.display = 'block';
document.getElementById('inp-pemilik').value = '';
document.getElementById('inp-nosert').value = '';
document.getElementById('inp-keterangan').value = '';
}
pendingKoord = points.map(function (p) { return [p.lat, p.lng]; });
pendingUkuran = ukuran;
document.getElementById('inp-nama').value = '';
document.getElementById('form-modal').classList.add('active');
setTimeout(function () { document.getElementById('inp-nama').focus(); }, 100);
}
function calcArea(latlngs) {
var R = 6371000, n = latlngs.length, area = 0;
if (n < 3) return 0;
for (var i = 0; i < n; i++) {
var j = (i + 1) % n;
var xi = latlngs[i].lng * Math.PI / 180 * R * Math.cos(latlngs[i].lat * Math.PI / 180);
var yi = latlngs[i].lat * Math.PI / 180 * R;
var xj = latlngs[j].lng * Math.PI / 180 * R * Math.cos(latlngs[j].lat * Math.PI / 180);
var yj = latlngs[j].lat * Math.PI / 180 * R;
area += xi * yj - xj * yi;
}
return Math.abs(area / 2);
}
function saveData() {
var nama = document.getElementById('inp-nama').value.trim();
var status = document.getElementById('inp-status').value;
if (!nama) { showToast('Nama tidak boleh kosong!', 'error'); return; }
var btn = document.querySelector('#form-modal .btn-primary');
btn.textContent = 'Menyimpan…';
btn.disabled = true;
var params = {
action: currentMode === 'jalan' ? 'save_jalan' : 'save_parsil',
nama: nama,
status: status,
ukuran: pendingUkuran,
koordinat: JSON.stringify(pendingKoord)
};
if (currentMode === 'parsil') {
params.pemilik = document.getElementById('inp-pemilik').value.trim();
params.nosert = document.getElementById('inp-nosert').value.trim();
params.keterangan = document.getElementById('inp-keterangan').value.trim();
}
fetch('api.php?' + new URLSearchParams(params))
.then(function (r) { return r.json(); })
.then(function (res) {
btn.textContent = 'Simpan Data';
btn.disabled = false;
if (res.ok) {
showToast('Data berhasil disimpan!', 'success');
closeForm();
var id = res.id;
var ll = pendingKoord.map(function (p) { return [p[0], p[1]]; });
if (currentMode === 'jalan') {
var layer = L.polyline(ll, { color: WARNA_JALAN[status] || '#999', weight: 5, opacity: 0.9 }).addTo(map);
layer.bindPopup(makeJalanPopup(id, nama, status, pendingUkuran.toFixed(1)), { maxWidth: 280 });
layer.on('click', function () { lnavHighlightFromMap('jalan', id); });
jalanLayers[id] = layer;
jalanData[id] = { id: id, nama: nama, status: status, pjg: pendingUkuran.toFixed(1), ll: ll };
} else {
var opt = WARNA_PARSIL[status] || { color: '#999', fillColor: '#999', fillOpacity: 0.2, weight: 2 };
var layer2 = L.polygon(ll, opt).addTo(map);
layer2.bindPopup(makeParsilPopup(id, nama, params.pemilik || '', params.nosert || '', params.keterangan || '', status, pendingUkuran.toFixed(1)), { maxWidth: 280 });
layer2.on('click', function () { lnavHighlightFromMap('parsil', id); });
parsilLayers[id] = layer2;
parsilData[id] = { id: id, nama: nama, pemilik: params.pemilik || '', nosert: params.nosert || '', ket: params.keterangan || '', status: status, luas: pendingUkuran.toFixed(1), ll: ll };
}
if (res.tj !== undefined) updateStats(res);
cancelDraw();
} else {
showToast('Gagal: ' + (res.msg || 'unknown'), 'error');
}
})
.catch(function () {
btn.textContent = 'Simpan Data';
btn.disabled = false;
showToast('Koneksi gagal!', 'error');
});
}
function closeForm() {
document.getElementById('form-modal').classList.remove('active');
}
document.getElementById('form-modal').addEventListener('click', function (e) {
if (e.target === this) closeForm();
});
function openRusakModal(latlng) {
var d = new Date();
var today = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
document.getElementById('rusak-tanggal').value = today;
document.getElementById('rusak-koordinat').value = latlng.lat.toFixed(6) + ', ' + latlng.lng.toFixed(6);
document.getElementById('rusak-sub').textContent = latlng.lat.toFixed(5) + ', ' + latlng.lng.toFixed(5);
document.getElementById('rusak-pelapor').value = '';
document.getElementById('rusak-nama-jalan').value = 'Mencari nama jalan…';
document.getElementById('rusak-ket').value = '';
clearFoto();
fetch('api.php?action=reverse&lat=' + latlng.lat + '&lon=' + latlng.lng)
.then(function (r) { return r.json(); })
.then(function (geo) {
var a = geo.address || {};
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
})
.catch(function () {
document.getElementById('rusak-nama-jalan').value = '(Gagal deteksi nama jalan)';
});
var badge = document.getElementById('gps-badge');
badge.style.display = 'inline-flex';
badge.className = 'gps-badge loading';
badge.textContent = 'Mengambil GPS…';
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (pos) {
badge.className = 'gps-badge';
badge.textContent = 'GPS: ±' + Math.round(pos.coords.accuracy) + 'm akurasi';
}, function () {
badge.className = 'gps-badge';
badge.textContent = 'Koordinat dari klik peta';
}, { timeout: 5000 });
} else {
badge.className = 'gps-badge';
badge.textContent = 'Koordinat dari klik peta';
}
document.getElementById('rusak-modal').classList.add('active');
setTimeout(function () { document.getElementById('rusak-pelapor').focus(); }, 100);
}
function closeRusakModal() {
document.getElementById('rusak-modal').classList.remove('active');
fotoBase64 = '';
pendingRusakLatLng = null;
}
document.getElementById('rusak-modal').addEventListener('click', function (e) {
if (e.target === this) closeRusakModal();
});
function handleFoto(input) {
var file = input.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) { showToast('Foto maks 5 MB!', 'error'); return; }
EXIF.getData(file, function() {
var lat = EXIF.getTag(this, "GPSLatitude");
var lon = EXIF.getTag(this, "GPSLongitude");
var latRef = EXIF.getTag(this, "GPSLatitudeRef") || "N";
var lonRef = EXIF.getTag(this, "GPSLongitudeRef") || "E";
if (lat && lon) {
function parseDms(dms) {
if (!dms || dms.length < 3) return null;
var d = (typeof dms[0] === 'object' && dms[0].denominator) ? dms[0].numerator / dms[0].denominator : Number(dms[0]);
var m = (typeof dms[1] === 'object' && dms[1].denominator) ? dms[1].numerator / dms[1].denominator : Number(dms[1]);
var s = (typeof dms[2] === 'object' && dms[2].denominator) ? dms[2].numerator / dms[2].denominator : Number(dms[2]);
return d + (m / 60) + (s / 3600);
}
var latDec = parseDms(lat);
var lonDec = parseDms(lon);
if (latDec !== null && lonDec !== null && !isNaN(latDec) && !isNaN(lonDec)) {
if (latRef === "S") latDec = -latDec;
if (lonRef === "W") lonDec = -lonDec;
pendingRusakLatLng = {lat: latDec, lng: lonDec};
document.getElementById('rusak-koordinat').value = latDec.toFixed(6) + ', ' + lonDec.toFixed(6);
showToast('Geotagging terdeteksi!', 'success');
var badge = document.getElementById('gps-badge');
if(badge) {
badge.style.display = 'inline-flex';
badge.className = 'gps-badge';
badge.textContent = 'GPS dari Foto';
}
fetch('api.php?action=reverse&lat=' + latDec + '&lon=' + lonDec)
.then(function (r) { return r.json(); })
.then(function (geo) {
var a = geo.address || {};
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
});
}
}
});
var reader = new FileReader();
reader.onload = function (e) {
// Kompres & resize foto agar payload kecil (hindari post_max_size & batas kolom DB)
var img = new Image();
img.onload = function () {
var maxDim = 1280;
var w = img.width, h = img.height;
if (w > h && w > maxDim) { h = Math.round(h * maxDim / w); w = maxDim; }
else if (h >= w && h > maxDim) { w = Math.round(w * maxDim / h); h = maxDim; }
var canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
fotoBase64 = canvas.toDataURL('image/jpeg', 0.7);
var preview = document.getElementById('photo-preview');
preview.src = fotoBase64;
preview.style.display = 'block';
document.getElementById('foto-clear-btn').style.display = 'flex';
};
img.onerror = function () {
// Fallback bila gambar tak bisa di-decode (mis. format tak didukung canvas)
fotoBase64 = e.target.result;
var preview = document.getElementById('photo-preview');
preview.src = fotoBase64;
preview.style.display = 'block';
document.getElementById('foto-clear-btn').style.display = 'flex';
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function clearFoto() {
fotoBase64 = '';
document.getElementById('photo-preview').style.display = 'none';
document.getElementById('photo-preview').src = '';
document.getElementById('foto-clear-btn').style.display = 'none';
document.getElementById('foto-file').value = '';
document.getElementById('foto-cam').value = '';
}
function saveRusak() {
if (!pendingRusakLatLng) { showToast('Pilih titik di peta dulu!', 'error'); return; }
var pelapor = document.getElementById('rusak-pelapor').value.trim() || 'Anonym';
var namaJalan = document.getElementById('rusak-nama-jalan').value.trim();
var tanggal = document.getElementById('rusak-tanggal').value;
var ket = document.getElementById('rusak-ket').value.trim();
var lat = pendingRusakLatLng.lat;
var lng = pendingRusakLatLng.lng;
var btn = document.getElementById('rusak-save-btn');
btn.textContent = 'Menyimpan…';
btn.disabled = true;
var formData = new FormData();
formData.append('action', 'save_rusak');
formData.append('pelapor', pelapor);
formData.append('nama_jalan', namaJalan);
formData.append('tanggal', tanggal);
formData.append('lat', lat);
formData.append('lng', lng);
formData.append('keterangan', ket);
formData.append('foto', fotoBase64);
fetch('api.php', { method: 'POST', body: formData })
.then(function (r) {
return r.text().then(function (txt) {
var res;
try { res = JSON.parse(txt); }
catch (e) {
// Respons bukan JSON: tampilkan status & potongan isi supaya jelas penyebabnya
var info = txt.trim() ? txt.trim().slice(0, 200) : '(respons kosong)';
throw new Error('HTTP ' + r.status + ' — ' + info);
}
return res;
});
})
.then(function (res) {
btn.textContent = 'Simpan Titik';
btn.disabled = false;
if (res.ok) {
showToast('Titik jalan rusak disimpan!', 'success');
var savedFoto = fotoBase64; // simpan dulu: closeRusakModal() me-reset fotoBase64
closeRusakModal();
var id = res.id;
var nama = res.nama || pelapor;
var layer = makeRusakLayer(id, nama, tanggal, lat, lng, ket, savedFoto, 'recent', false);
rusakLayers[id] = layer;
rusakData[id] = { id: id, nama: nama, tgl: tanggal, lat: lat, lng: lng, ket: ket, foto: savedFoto, age: 'recent', recurring: false };
if (res.tr !== undefined) updateStats(res);
cancelDraw();
} else {
showToast('Gagal: ' + (res.msg || 'unknown'), 'error');
}
})
.catch(function (err) {
btn.textContent = 'Simpan Titik';
btn.disabled = false;
showToast('Gagal simpan: ' + (err && err.message ? err.message : 'koneksi gagal'), 'error');
console.error('saveRusak error:', err);
});
}