Initial commit of unified WebGIS projects

This commit is contained in:
ilham_gmail
2026-06-11 17:42:40 +07:00
commit a0c61f2bc2
64 changed files with 7924 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# Example environment file for WebGIS
# Copy this file to `.env` and fill in real values. Do NOT commit `.env`.
# Generate a random key: `openssl rand -hex 16`
API_KEY=replace_with_a_secure_random_hex_key
+7
View File
@@ -0,0 +1,7 @@
/.env
/.env.local
/*.env
# ignore common editor temp files
*.swp
*.swo
.vscode/
+12
View File
@@ -0,0 +1,12 @@
FROM php:8.2-apache
# Install mysqli extension for PHP
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
# Copy project files into container
COPY . /var/www/html/
# Set ownership and permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
+204
View File
@@ -0,0 +1,204 @@
WebGIS (Leaflet + PHP + MySQL)
Deskripsi singkat
------------------
Proyek WebGIS sederhana yang menggunakan `Leaflet` di frontend dan skrip PHP untuk menyediakan API CRUD terhadap data spasial (lokasi/marker, jalan, tanah) yang disimpan di database MySQL.
Fitur utama
-----------
- Peta interaktif dengan marker, polyline, polygon (Leaflet + Leaflet.Draw)
- Endpoint PHP untuk mengambil, menambah, memperbarui, dan menghapus data: lokasi, jalan, tanah
- Kontrol layer untuk memfilter jenis data (SPBU, Rumah, Masjid, Jalan, Tanah)
Persyaratan
-----------
- Web server dengan PHP (mis. Apache/Nginx + PHP)
- MySQL atau MariaDB
- Akses internet untuk memuat tile OpenStreetMap dan library Leaflet (atau host sendiri)
Instalasi cepat
---------------
1. Salin folder proyek ke root web server Anda (mis. `/var/www/html/webgis`).
2. Impor database dari file `webgis.sql`:
```bash
mysql -u root -p webgis < webgis.sql
```
3. Perbarui konfigurasi koneksi database di file [koneksi.php](koneksi.php) sesuai kredensial lokal Anda.
4. Akses frontend melalui browser: `http://localhost/webgis/index.html` (atau path sesuai server Anda).
Catatan konfigurasi
-------------------
File [koneksi.php](koneksi.php) saat ini mengandung koneksi contoh:
```php
<?php
$conn = new mysqli("localhost", "root", "ilham", "webgis");
// ...
?>
```
Jangan gunakan kredensial default pada lingkungan produksi — ubah username/password dan batasi akses.
API / Endpoints
---------------
Berikut file PHP utama yang tersedia di proyek ini:
- `get_lokasi.php` : Mengambil daftar lokasi/marker (JSON)
- `tambah_lokasi.php`: Menambah lokasi baru
- `update_lokasi.php`: Memperbarui lokasi
- `hapus_lokasi.php` : Menghapus lokasi
- `get_jalan.php` : Mengambil data jalan (GeoJSON/JSON)
- `tambah_jalan.php` : Menambah data jalan
- `update_jalan.php` : Memperbarui data jalan
- `hapus_jalan.php` : Menghapus data jalan
- `get_tanah.php` : Mengambil data tanah (GeoJSON/JSON)
- `tambah_tanah.php` : Menambah data tanah
- `update_tanah.php` : Memperbarui data tanah
- `hapus_tanah.php` : Menghapus data tanah
File penting
-----------
- [index.html](index.html) : Frontend peta (Leaflet + draw tools)
- [koneksi.php](koneksi.php) : File koneksi database
- [webgis.sql](webgis.sql) : Dump database untuk impor
Pengembangan & catatan
-----------------------
- Frontend mengambil endpoint dari `http://localhost/webgis/...` — sesuaikan URL jika Anda menaruh proyek di subfolder atau domain berbeda.
- Beberapa record geometrik pada data jalan/tanah mungkin berformat JSON ganda; frontend mencoba parsing defensif.
- Jika menambah kolom baru ke tabel `lokasi`, contoh SQL yang pernah dipakai:
```sql
ALTER TABLE lokasi ADD COLUMN alamat TEXT;
ALTER TABLE lokasi ADD COLUMN jenis VARCHAR(50) NOT NULL DEFAULT 'spbu';
CREATE INDEX idx_lokasi_alamat ON lokasi( (left(alamat,255)) );
```
Keamanan
--------
- Hindari menyimpan password database langsung di repositori untuk lingkungan produksi.
- Sanitasi input pada skrip PHP sebelum memasukkannya ke database (prepared statements lebih aman).
Lisensi
-------
Silakan tambahkan licensi sesuai kebutuhan (mis. MIT) atau hubungi pemilik proyek.
Jika Anda ingin, saya bisa juga menambahkan langkah menjalankan server lokal atau contoh curl untuk setiap endpoint.
Contoh penggunaan API & API key
-------------------------------
Untuk operasi baca (`GET`) tidak diperlukan API key. Contoh mengambil daftar lokasi:
```bash
curl -s http://localhost/webgis/src/api/get_lokasi.php | jq .
```
Untuk operasi tulis/hapus/perbarui, server memerlukan header `X-API-KEY` atau field `api_key` di body.
API key sebaiknya disimpan di file `/.env` (di luar kontrol versi) atau di environment variable pada server.
Menambahkan `.env.example`
-------------------------
Proyek ini menyediakan `.env.example` yang berisi contoh variabel environment yang diperlukan.
Untuk menyiapkan environment lokal:
```bash
# salin contoh ke file .env (jangan commit .env)
cp .env.example .env
# generate key (openssl) dan masukkan ke .env
openssl rand -hex 16
# edit .env dan set API_KEY=hasil_generate
```
Atau buat key dengan PHP:
```bash
php -r "echo bin2hex(random_bytes(16)).PHP_EOL;"
```
Setelah `.env` berisi `API_KEY`, server PHP akan memuat nilai tersebut melalui `src/config/config.php`.
Contoh panggilan (curl)
-----------------------
Menambah lokasi (POST JSON):
```bash
curl -X POST "http://localhost/webgis/src/api/tambah_lokasi.php" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key_here" \
-d '{"nama":"Contoh","jenis":"rumah","no_telp":"0812","buka_24_jam":0,"alamat":"Jalan Contoh","latitude":-0.04,"longitude":109.33}'
```
Contoh hapus (POST body dengan id):
```bash
curl -X POST "http://localhost/webgis/src/api/hapus_lokasi.php" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key_here" \
-d '{"id":"5092"}'
```
Catatan keamanan: jangan commit `.env` atau API key produksi ke repositori. Untuk produksi pertimbangkan memuat kunci dari sistem secrets, variabel lingkungan server (mis. systemd/Apache/Nginx), atau layanan vault.
Menjalankan (Windows / Linux / macOS)
-----------------------------------
Berikut langkah singkat untuk menjalankan proyek ini pada masing-masing sistem operasi. Inti yang sama: tempatkan folder proyek di root web server Anda atau jalankan server PHP built-in, lalu impor `webgis.sql` dan sesuaikan [koneksi.php](koneksi.php).
- **Linux (cepat, tanpa XAMPP):**
1. Pastikan `php` dan `mysql`/`mariadb` tersedia.
2. Impor database:
```bash
mysql -u root -p webgis < webgis.sql
```
3. Jalankan server PHP built-in (untuk pengujian cepat):
```bash
php -S localhost:8000 -t .
```
4. Buka `http://localhost:8000/index.html`.
- **Windows (XAMPP/WAMP):**
1. Salin folder proyek ke `C:\xampp\htdocs\webgis` (XAMPP) atau folder www pada WAMP.
2. Import `webgis.sql` menggunakan phpMyAdmin atau CLI:
```powershell
mysql -u root -p webgis < webgis.sql
```
3. Pastikan Apache dan MySQL sudah dijalankan via XAMPP/WAMP control panel.
4. Akses `http://localhost/webgis/index.html`.
Catatan: Bisa juga menggunakan `php -S` dari Command Prompt jika PHP terpasang secara terpisah.
- **macOS (MAMP atau PHP built-in):**
1. Gunakan MAMP: tempatkan proyek di folder `htdocs` MAMP lalu jalankan server MAMP.
2. Atau gunakan PHP built-in (macOS yang memiliki PHP):
```bash
mysql -u root -p webgis < webgis.sql
php -S localhost:8000 -t .
```
3. Buka `http://localhost:8000/index.html` atau `http://localhost/webgis/index.html` jika menggunakan MAMP/Apache.
Penyesuaian koneksi
-------------------
- Edit [koneksi.php](koneksi.php) untuk menyesuaikan `host`, `username`, `password`, dan `database` sesuai lingkungan Anda.
- Jika menggunakan server built-in (`php -S`), pastikan `koneksi.php` menggunakan host dan port database yang benar (biasanya `localhost`).
Permasalahan umum
-----------------
- Jika tile OSM atau library tidak muncul, cek koneksi internet atau muat ulang (CORS biasanya tidak menjadi masalah untuk tile default).
- Jika endpoint PHP mengembalikan error, periksa log PHP/Apache dan periksa kredensial di [koneksi.php](koneksi.php).
+778
View File
@@ -0,0 +1,778 @@
<!DOCTYPE html>
<html>
<head>
<title>WebGIS</title>
<meta charset="utf-8">
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<!-- Leaflet Draw CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
<!-- Leaflet Draw JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<!-- GeometryUtil (FIXED & WORKING) -->
<script src="https://unpkg.com/leaflet-geometryutil"></script>
<style>
/* Simple modal styles */
.modal {position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:9999}
.modal.hidden{display:none}
.modal-content{background:#fff;padding:18px;border-radius:8px;min-width:320px;max-width:480px;box-shadow:0 6px 18px rgba(0,0,0,0.2)}
.modal-content h3{margin:0 0 10px 0;font-size:18px}
.modal-content label{display:block;margin-top:8px;font-size:13px}
.modal-content input[type=text], .modal-content select{width:100%;padding:8px;margin-top:4px;border:1px solid #ddd;border-radius:4px}
.modal-actions{margin-top:12px;text-align:right}
.modal-actions button{margin-left:8px;padding:8px 12px;border-radius:4px;border:0;cursor:pointer}
.btn-primary{background:#1976d2;color:white}
.btn-secondary{background:#e0e0e0}
</style>
</head>
<body>
<!-- Modal for adding items -->
<div id="modal" class="modal hidden" role="dialog" aria-hidden="true">
<div class="modal-content">
<h3 id="modal-title">Form</h3>
<form id="modal-form">
<div id="modal-fields"></div>
<div class="modal-actions">
<button type="button" id="modal-cancel" class="btn-secondary">Batal</button>
<button type="submit" id="modal-submit" class="btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
<div id="auth-controls" style="position: absolute; top: 10px; right: 10px; z-index: 1000; background: white; padding: 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; gap: 8px; align-items: center; font-family: sans-serif;">
<span id="api-key-status" style="font-size: 13px; font-weight: bold; color: #666;">Guest Mode</span>
<button id="btn-api-key" onclick="promptApiKey()" style="padding: 6px 12px; border-radius: 4px; border: none; background: #1976d2; color: white; cursor: pointer; font-weight: bold; font-size: 12px; transition: all 0.2s;">🔑 Set API Key</button>
</div>
<div id="map" style="height:100vh;"></div>
<script>
let isDrawing = false;
let pendingLayer = null;
let pendingType = null;
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
function updateApiKeyStatus() {
const key = localStorage.getItem('spbu_api_key');
const statusEl = document.getElementById('api-key-status');
const btnEl = document.getElementById('btn-api-key');
if (statusEl && btnEl) {
if (key) {
statusEl.textContent = 'Admin Mode';
statusEl.style.color = '#2e7d32';
btnEl.textContent = '🔑 Ubah API Key';
btnEl.style.background = '#2e7d32';
} else {
statusEl.textContent = 'Guest Mode';
statusEl.style.color = '#666';
btnEl.textContent = '🔑 Set API Key';
btnEl.style.background = '#1976d2';
}
}
}
function promptApiKey() {
const currentKey = localStorage.getItem('spbu_api_key') || '';
const newKey = prompt('Masukkan API Key untuk otentikasi Admin SPBU:', currentKey);
if (newKey !== null) {
const trimmed = newKey.trim();
if (trimmed) {
localStorage.setItem('spbu_api_key', trimmed);
} else {
localStorage.removeItem('spbu_api_key');
}
updateApiKeyStatus();
}
}
function handleApiResponse(res, callback) {
if (res.status === 401) {
alert('Akses Ditolak: API Key tidak valid atau kosong. Silakan atur API Key terlebih dahulu.');
promptApiKey();
} else {
res.json().then(data => {
if (data && data.success === false) {
alert('Error: ' + (data.error || 'Terjadi kesalahan'));
} else if (callback) {
callback(data);
}
}).catch(() => {
if (res.ok && callback) {
callback();
} else {
alert('Terjadi kesalahan koneksi server');
}
});
}
}
function buildHeaders(){
const headers = {'Content-Type':'application/json'};
const key = localStorage.getItem('spbu_api_key');
if(key){ headers['X-API-KEY'] = key; }
return headers;
}
const map = L.map('map').setView([-0.0552, 109.3500], 13);
const drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
const drawControl = new L.Control.Draw({
draw: {
polyline: true,
polygon: true,
marker: true,
rectangle: false,
circle: false,
circlemarker: false
},
edit: {
featureGroup: drawnItems,
edit: true,
remove: true
}
});
map.addControl(drawControl);
map.on(L.Draw.Event.CREATED, function (e) {
const layer = e.layer;
if (e.layerType === 'marker') {
// opening lokasi modal for marker creation
pendingType = 'lokasi';
const ll = layer.getLatLng();
// store the created layer so we can remove it from drawnItems after save
pendingLayer = layer;
openModal('Tambah Lokasi', `
<label>Nama <input name="nama" type="text" required></label>
<label>Jenis <select name="jenis"><option value="rumah">Rumah</option><option value="masjid">Masjid</option><option value="spbu">SPBU</option></select></label>
<label>No Telp <input name="no_telp" type="text"></label>
<label><input name="buka_24_jam" type="checkbox"> Buka 24 Jam</label>
<label>Koordinat <input name="coords" type="text" readonly value="${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}"></label>
<label>Alamat <input name="alamat" type="text"></label>
`, handleModalSubmit);
// try to reverse-geocode and prefill the alamat field
reverseGeocode(ll.lat, ll.lng).then(addr=>{
try{ const f = document.querySelector('#modal-fields input[name="alamat"]'); if(f && addr) f.value = addr; }catch(e){}
}).catch(()=>{});
}
if (e.layerType === 'polyline') {
handlePolyline(layer);
}
if (e.layerType === 'polygon') {
handlePolygon(layer);
}
drawnItems.addLayer(layer);
});
map.on('draw:drawstart', function () {
isDrawing = true;
});
map.on('draw:drawstop', function () {
isDrawing = false;
});
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// let markers = [];
// ====== LAYER GROUPS & CONTROL ======
const spbu24Group = L.layerGroup().addTo(map);
const spbuNon24Group = L.layerGroup().addTo(map);
const rumahGroup = L.layerGroup().addTo(map);
const masjidGroup = L.layerGroup().addTo(map);
const jalanGroup = L.layerGroup().addTo(map);
const tanahGroup = L.layerGroup().addTo(map);
const lokasiGroup = L.layerGroup(); // fallback group (not added by default)
const rumahMarkers = []; // store rumah circleMarkers for radius checks
L.control.layers(null, {
"SPBU Buka 24 Jam": spbu24Group,
"SPBU Tidak 24 Jam": spbuNon24Group,
"Rumah": rumahGroup,
"Masjid": masjidGroup,
"Jalan": jalanGroup,
"Tanah": tanahGroup
}, { collapsed: false }).addTo(map);
// Using the built-in Leaflet.Draw toolbar (marker, polyline, polygon).
function tanahStatusFull(code){
return code === 'SHM' ? 'Sertifikat Hak Milik (SHM)'
: code === 'HGB' ? 'Sertifikat Hak Guna Bangunan (HGB)'
: code === 'HGU' ? 'Sertifikat Hak Guna Usaha (HGU)'
: code === 'HP' ? 'Sertifikat Hak Pakai (HP)'
: code || '';
}
function loadData() {
// clear previous markers from groups
spbu24Group.clearLayers();
spbuNon24Group.clearLayers();
// hide any active masjid radius when reloading data
try{ hideMasjidRadius(); }catch(e){}
fetch('src/api/get_lokasi.php')
.then(res => res.json())
.then(data => {
// clear previous lokasi markers/groups
lokasiGroup.clearLayers(); rumahGroup.clearLayers(); masjidGroup.clearLayers(); rumahMarkers.length = 0;
data.forEach(item => {
const statusHtml = parseInt(item.buka_24_jam) === 1
? "<span style='color:green'>Buka 24 Jam</span>"
: "<span style='color:red'>Tidak 24 Jam</span>";
const safeNama = escapeHtml(item.nama);
const safeAlamat = escapeHtml(item.alamat);
const jsEscapedNama = safeNama.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const jsEscapedAlamat = safeAlamat.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const jenis = (item.jenis||'').toLowerCase();
let marker;
if(jenis === 'rumah' || jenis === 'masjid'){
// use circleMarker so we can change color dynamically
const color = jenis === 'masjid' ? '#1976d2' : '#d32f2f';
marker = L.circleMarker([item.latitude, item.longitude], {radius:8, color:color, fillColor:color, fillOpacity:1});
marker.bindPopup(`
<b>${safeNama}</b><br>
${statusHtml}<br>
<div style="font-size:12px;color:#444;margin-top:6px">${safeAlamat}</div>
<br>
<button onclick="hapus('${item.id}')">Hapus</button>
<button onclick="edit('${item.id}','${jsEscapedNama}','${item.no_telp}',${item.buka_24_jam}, '${jsEscapedAlamat}', '${jenis}')">Edit</button>
`);
// store rumah markers for radius checks
if(jenis === 'rumah') { rumahMarkers.push(marker); rumahGroup.addLayer(marker); }
if(jenis === 'masjid') { masjidGroup.addLayer(marker); }
} else {
// default: spbu or other — normal marker
marker = L.marker([item.latitude, item.longitude]);
marker.bindPopup(`
<b>${safeNama}</b><br>
${statusHtml}<br>
<div style="font-size:12px;color:#444;margin-top:6px">${safeAlamat}</div>
<br>
<button onclick="hapus('${item.id}')">Hapus</button>
<button onclick="edit('${item.id}','${jsEscapedNama}','${item.no_telp}',${item.buka_24_jam}, '${jsEscapedAlamat}', '${jenis}')">Edit</button>
`);
}
// attach jenis data for future reference
marker._jenis = jenis;
marker._data = item;
// add marker to appropriate group if not already added
if(jenis !== 'rumah' && jenis !== 'masjid'){
// SPBU or other — add to spbu groups by buka_24_jam
if(parseInt(item.buka_24_jam) === 1) spbu24Group.addLayer(marker);
else spbuNon24Group.addLayer(marker);
}
// Also add to lokasiGroup as general index (optional)
lokasiGroup.addLayer(marker);
});
});
}
// MASJID RADIUS CONTROL
let activeMasjid = null;
let masjidCircle = null;
let masjidControlEl = null;
function showMasjidRadius(masjidMarker){
// ensure only one active
hideMasjidRadius();
activeMasjid = masjidMarker;
const center = masjidMarker.getLatLng();
const defaultRadius = 200; // meters (default changed to 200m)
masjidCircle = L.circle(center, {radius: defaultRadius, color: '#1976d2', fillOpacity: 0.08}).addTo(map);
// create control UI
masjidControlEl = document.createElement('div');
masjidControlEl.id = 'masjid-radius-control';
masjidControlEl.style.position='absolute';
// place at bottom-left so it doesn't overlap layer controls / zoom
masjidControlEl.style.left='10px'; masjidControlEl.style.bottom='80px';
masjidControlEl.style.background='white'; masjidControlEl.style.padding='8px'; masjidControlEl.style.borderRadius='6px'; masjidControlEl.style.boxShadow='0 2px 6px rgba(0,0,0,0.2)';
masjidControlEl.innerHTML = `Radius: <span id="masjid-radius-value">${defaultRadius}</span> m<br><input id="masjid-radius-slider" type="range" min="50" max="3000" step="10" value="${defaultRadius}" style="width:200px"> <button id="masjid-radius-close">Close</button>`;
// append to the map container and ensure it's above map layers
try{
const mapContainer = map.getContainer && map.getContainer();
if(mapContainer){ mapContainer.appendChild(masjidControlEl); }
else { document.body.appendChild(masjidControlEl); }
}catch(e){ document.body.appendChild(masjidControlEl); }
masjidControlEl.style.zIndex = 10000;
masjidControlEl.style.pointerEvents = 'auto';
// prevent map dragging/interaction while user is interacting with the slider
const sliderEl = document.getElementById('masjid-radius-slider');
if(sliderEl){
const disableMapDrag = ()=>{ try{ if(map.dragging && map.dragging.enabled()) map.dragging.disable(); }catch(e){} };
const enableMapDrag = ()=>{ try{ if(map.dragging && !map.dragging.enabled()) map.dragging.enable(); }catch(e){} };
const startDrag = (ev)=>{ disableMapDrag(); sliderEl._dragging = true; };
const endDrag = (ev)=>{ sliderEl._dragging = false; enableMapDrag(); };
sliderEl.addEventListener('pointerdown', startDrag);
sliderEl.addEventListener('pointerup', endDrag);
sliderEl.addEventListener('pointercancel', endDrag);
// mouse/touch fallback
sliderEl.addEventListener('mousedown', startDrag);
document.addEventListener('mouseup', endDrag);
sliderEl.addEventListener('touchstart', startDrag, {passive:true});
document.addEventListener('touchend', endDrag);
// actual slider input handling (keep default behavior so slider remains draggable)
sliderEl.addEventListener('input', function(ev){
const r = parseInt(ev.target.value,10);
try{ masjidCircle.setRadius(r); }catch(e){}
const valEl = document.getElementById('masjid-radius-value'); if(valEl) valEl.innerText = r;
updateHomeColors(center, r);
});
}
document.getElementById('masjid-radius-close').addEventListener('click', function(){ hideMasjidRadius(); });
updateHomeColors(center, defaultRadius);
}
function hideMasjidRadius(){
if(masjidCircle){ map.removeLayer(masjidCircle); masjidCircle = null; }
if(masjidControlEl){ masjidControlEl.remove(); masjidControlEl = null; }
activeMasjid = null;
// reset rumah markers to default red
rumahMarkers.forEach(m=>{ try{ m.setStyle({color:'#d32f2f', fillColor:'#d32f2f'}); }catch(e){} });
}
function updateHomeColors(centerLatLng, radiusMeters){
rumahMarkers.forEach(m=>{
try{
const dist = m.getLatLng().distanceTo(centerLatLng);
if(dist <= radiusMeters){
m.setStyle({color:'#2e7d32', fillColor:'#2e7d32'}); // green inside
} else {
m.setStyle({color:'#d32f2f', fillColor:'#d32f2f'}); // red outside
}
}catch(e){ }
});
}
// add delegated click handler: when popup for masjid opens, show button to toggle radius
map.on('popupopen', function(e){
try{
const layer = e.popup._source;
if(layer && layer._jenis === 'masjid'){
const container = e.popup.getElement();
if(container && !container.querySelector('.show-masjid-radius')){
const btn = document.createElement('button'); btn.textContent='Show Radius'; btn.className='show-masjid-radius';
btn.style.marginLeft='6px';
btn.onclick = function(){ showMasjidRadius(layer); };
const content = container.querySelector('.leaflet-popup-content');
if(content) content.appendChild(btn);
}
}
}catch(e){}
});
function loadJalan(){
jalanGroup.clearLayers();
fetch('src/api/get_jalan.php')
.then(res=>res.json())
.then(data=>{
data.forEach(j=>{
let geo = null;
try {
geo = (typeof j.geom === 'string') ? JSON.parse(j.geom) : j.geom;
} catch(err) {
try {
// try double-encoded JSON
geo = JSON.parse(JSON.parse(j.geom));
} catch(err2){
console.error('Failed to parse jalan.geom for id', j.id, err2);
// fallback: show a marker at map center with raw info so user sees the record
const safeNama = escapeHtml(j.nama);
const m = L.marker(map.getCenter()).bindPopup(`<b>${safeNama}</b><br>Invalid geometry<br><pre style="max-height:120px;overflow:auto">${escapeHtml(String(j.geom)).slice(0,400)}</pre>`);
jalanGroup.addLayer(m);
map.panTo(m.getLatLng());
m.openPopup();
return; // skip adding geojson
}
}
const pj = parseFloat(j.panjang) || 0;
const safeNama = escapeHtml(j.nama);
const safeStatus = escapeHtml(j.status);
const jsEscapedNama = safeNama.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const popup = `<b>${safeNama}</b><br>${safeStatus}<br>${pj.toFixed(2)} m<br><br>
<button onclick="hapusJalan('${j.id}')">Hapus</button>
<button onclick="editJalan('${j.id}','${jsEscapedNama}', '${safeStatus}', ${pj})">Edit</button>`;
console.log('loadJalan: id=', j.id, 'geom=', geo);
const gj = L.geoJSON(geo,{
style:{color:
j.status=="Nasional"?"red":
j.status=="Provinsi"?"blue":"green"}
});
try{
gj.eachLayer(function(layer){
layer._fid = j.id;
layer.bindPopup(popup);
jalanGroup.addLayer(layer);
try{
if(layer.getBounds){
const c = layer.getBounds().getCenter();
if(!map.getBounds().contains(c)){
map.panTo(c);
layer.openPopup && layer.openPopup();
}
} else if(layer.getLatLng){
const p = layer.getLatLng();
if(!map.getBounds().contains(p)){
map.panTo(p);
layer.openPopup && layer.openPopup();
}
}
}catch(e){console.warn('pan check failed', e)}
});
} catch(e){
console.error('Error adding jalan geo layer', j.id, e);
const safeErrNama = escapeHtml(j.nama);
const m = L.marker(map.getCenter()).bindPopup(`<b>${safeErrNama}</b><br>Geo add error<br><pre style="max-height:120px;overflow:auto">${escapeHtml(String(j.geom)).slice(0,400)}</pre>`);
jalanGroup.addLayer(m);
map.panTo(m.getLatLng());
m.openPopup();
}
});
});
}
function loadTanah(){
tanahGroup.clearLayers();
fetch('src/api/get_tanah.php')
.then(res=>res.json())
.then(data=>{
data.forEach(t=>{
let geo = null;
try {
geo = (typeof t.geom === 'string') ? JSON.parse(t.geom) : t.geom;
} catch(err) {
try {
geo = JSON.parse(JSON.parse(t.geom));
} catch(err2){
console.error('Failed to parse tanah.geom for id', t.id, err2);
const safeNama = escapeHtml(t.nama);
const m = L.marker(map.getCenter()).bindPopup(`<b>${safeNama}</b><br>Invalid geometry<br><pre style="max-height:120px;overflow:auto">${escapeHtml(String(t.geom)).slice(0,400)}</pre>`);
tanahGroup.addLayer(m);
map.panTo(m.getLatLng());
m.openPopup();
return;
}
}
const luasNum = parseFloat(t.luas) || 0;
const safeNama = escapeHtml(t.nama);
const safeStatus = escapeHtml(t.status);
const jsEscapedNama = safeNama.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const popup = `<b>${safeNama}</b><br>${tanahStatusFull(t.status)}<br>${luasNum.toFixed(2)} m²<br><br>
<button onclick="hapusTanah('${t.id}')">Hapus</button>
<button onclick="editTanah('${t.id}','${jsEscapedNama}', '${safeStatus}', ${luasNum})">Edit</button>`;
console.log('loadTanah: id=', t.id, 'geom=', geo);
const gj = L.geoJSON(geo,{
style:{color:
t.status=="SHM"?"green":
t.status=="HGB"?"blue":
t.status=="HGU"?"orange":"purple"}
});
try{
gj.eachLayer(function(layer){
layer._fid = t.id;
layer.bindPopup(popup);
tanahGroup.addLayer(layer);
try{
if(layer.getBounds){
const c = layer.getBounds().getCenter();
if(!map.getBounds().contains(c)){
map.panTo(c);
layer.openPopup && layer.openPopup();
}
} else if(layer.getLatLng){
const p = layer.getLatLng();
if(!map.getBounds().contains(p)){
map.panTo(p);
layer.openPopup && layer.openPopup();
}
}
}catch(e){console.warn('pan check failed', e)}
});
} catch(e){
console.error('Error adding tanah geo layer', t.id, e);
const safeErrNama = escapeHtml(t.nama);
const m = L.marker(map.getCenter()).bindPopup(`<b>${safeErrNama}</b><br>Geo add error<br><pre style="max-height:120px;overflow:auto">${escapeHtml(String(t.geom)).slice(0,400)}</pre>`);
tanahGroup.addLayer(m);
map.panTo(m.getLatLng());
m.openPopup();
}
});
});
}
// ================= DELETE =================
function hapus(id){
fetch('src/api/hapus_lokasi.php',{
method: 'POST', headers: buildHeaders(),
body: JSON.stringify({id: id})
}).then(res => handleApiResponse(res, loadData));
}
function hapusJalan(id){
fetch('src/api/hapus_jalan.php',{
method: 'POST', headers: buildHeaders(),
body: JSON.stringify({id: id})
}).then(res => handleApiResponse(res, loadJalan));
}
function hapusTanah(id){
fetch('src/api/hapus_tanah.php',{
method: 'POST', headers: buildHeaders(),
body: JSON.stringify({id: id})
}).then(res => handleApiResponse(res, loadTanah));
}
// ================= UPDATE =================
function edit(id,nama,telp,buka,alamat,jenis){
openModal('Edit Lokasi', `
<input type="hidden" name="id" value="${id}">
<label>Nama <input name="nama" type="text" required value="${(nama||'').replace(/"/g,'&quot;')}"></label>
<label>Jenis <select name="jenis"><option value="rumah">Rumah</option><option value="masjid">Masjid</option><option value="spbu">SPBU</option></select></label>
<label>No Telp <input name="no_telp" type="text" value="${(telp||'').replace(/"/g,'&quot;')}"></label>
<label><input name="buka_24_jam" type="checkbox" ${buka? 'checked' : ''}> Buka 24 Jam</label>
<label>Alamat <input name="alamat" type="text" value="${(alamat||'').replace(/"/g,'&quot;')}"></label>
`, function(values){
fetch('src/api/update_lokasi.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({id:id,nama:values.nama, jenis: values.jenis || 'rumah', no_telp:values.no_telp||'',buka_24_jam:values.buka_24_jam?1:0, alamat: values.alamat || ''})
}).then(res => handleApiResponse(res, loadData));
});
// set current jenis selection after modal fields inserted
setTimeout(()=>{ const sel=document.querySelector('#modal-fields select[name="jenis"]'); if(sel) sel.value = (jenis||'rumah'); },0);
}
function editJalan(id,nama,status,panjang){
openModal('Edit Jalan', `
<input type="hidden" name="id" value="${id}">
<label>Nama <input name="nama" type="text" required value="${(nama||'').replace(/"/g,'&quot;')}"></label>
<label>Status <select name="status"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select></label>
<label>Panjang (m) <input name="panjang" type="text" readonly value="${parseFloat(panjang).toFixed(2)}"></label>
`, function(values){
fetch('src/api/update_jalan.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({id:id,nama:values.nama,status:values.status,panjang:parseFloat(values.panjang)||0})
}).then(res => handleApiResponse(res, loadJalan));
});
}
function editTanah(id,nama,status,luas){
const fields = `
<input type="hidden" name="id" value="${id}">
<label>Nama Pemilik <input name="nama" type="text" required value="${(nama||'').replace(/"/g,'&quot;')}"></label>
<label>Status <select name="status">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select></label>
<label>Luas (m²) <input name="luas" type="text" readonly value="${parseFloat(luas).toFixed(2)}"></label>
`;
const cb = function(values){
fetch('src/api/update_tanah.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({id:id,nama:values.nama,status:values.status,luas:parseFloat(values.luas)||0})
}).then(res => handleApiResponse(res, loadTanah));
};
openModal('Edit Tanah', fields, cb);
// set the select value after modal fields are inserted (avoid embedding closing script tag inside templates)
setTimeout(()=>{ const sel=document.querySelector('#modal-fields select[name="status"]'); if(sel) sel.value = status; },0);
}
// Using Draw's marker tool to create 'lokasi' (handled in draw:created)
function handlePolyline(layer) {
// open modal to collect jalan info, store layer pending until user submits
pendingType = 'jalan';
pendingLayer = layer;
let latlngs = layer.getLatLngs();
let panjang = 0;
for(let i=0;i<latlngs.length-1;i++){
panjang += latlngs[i].distanceTo(latlngs[i+1]);
}
openModal('Tambah Jalan', `
<label>Nama <input name="nama" type="text" required></label>
<label>Status <select name="status"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select></label>
<label>Panjang (m) <input name="panjang" type="text" readonly value="${panjang.toFixed(2)}"></label>
`, handleModalSubmit);
}
function handlePolygon(layer) {
// open modal to collect tanah info
pendingType = 'tanah';
pendingLayer = layer;
const latlngs = layer.getLatLngs()[0];
const luas = L.GeometryUtil.geodesicArea(latlngs);
openModal('Tambah Tanah', `
<label>Nama Pemilik <input name="nama" type="text" required></label>
<label>Status <select name="status">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select></label>
<label>Luas (m²) <input name="luas" type="text" readonly value="${luas.toFixed(2)}"></label>
`, handleModalSubmit);
}
// Reverse geocode helper using Nominatim (OpenStreetMap). Returns a Promise<string> of the display address.
function reverseGeocode(lat, lng){
const url = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${encodeURIComponent(lat)}&lon=${encodeURIComponent(lng)}`;
return fetch(url, { headers: { 'Accept': 'application/json' } })
.then(res => res.ok ? res.json() : Promise.reject('nores'))
.then(data => (data && data.display_name) ? data.display_name : '')
.catch(err => {
console.warn('reverseGeocode failed', err);
return '';
});
}
// Modal helpers
function openModal(title, fieldsHtml, submitHandler){
document.getElementById('modal-title').textContent = title;
document.getElementById('modal-fields').innerHTML = fieldsHtml;
const modal = document.getElementById('modal');
modal.classList.remove('hidden');
modal.setAttribute('aria-hidden','false');
const form = document.getElementById('modal-form');
form.onsubmit = function(ev){
ev.preventDefault();
const fd = new FormData(form);
const obj = {};
for(const [k,v] of fd.entries()){
obj[k] = v;
}
const bukaCheckbox = form.querySelector('input[name="buka_24_jam"]');
if(bukaCheckbox) obj['buka_24_jam'] = bukaCheckbox.checked?1:0;
submitHandler(obj);
closeModal();
};
document.getElementById('modal-cancel').onclick = function(){ closeModal(); pendingLayer=null; pendingType=null; };
}
function closeModal(){
try{ if(document.activeElement && typeof document.activeElement.blur === 'function') document.activeElement.blur(); }catch(e){}
const modal = document.getElementById('modal');
modal.classList.add('hidden');
modal.setAttribute('aria-hidden','true');
document.getElementById('modal-fields').innerHTML = '';
}
function handleModalSubmit(values){
if(pendingType === 'lokasi'){
const latlng = (typeof pendingLayer.getLatLng === 'function') ? pendingLayer.getLatLng() : pendingLayer;
fetch('src/api/tambah_lokasi.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({
nama: values.nama,
jenis: values.jenis || 'rumah',
no_telp: values.no_telp || '',
buka_24_jam: values.buka_24_jam?1:0,
alamat: values.alamat || '',
latitude: latlng.lat,
longitude: latlng.lng
})
}).then(res => handleApiResponse(res, () => {
if(pendingLayer && drawnItems.hasLayer && drawnItems.hasLayer(pendingLayer)) drawnItems.removeLayer(pendingLayer);
loadData();
pendingLayer=null;
pendingType=null;
}));
} else if(pendingType === 'jalan'){
const geojson = pendingLayer.toGeoJSON();
const panjang = parseFloat((document.querySelector('input[name="panjang"]').value)||0);
const status = values.status || 'Kabupaten';
const nama = values.nama || '';
let warna = status==='Nasional'?'red':(status==='Provinsi'?'blue':'green');
pendingLayer.setStyle({color:warna});
fetch('src/api/tambah_jalan.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({nama:nama,status:status,panjang:panjang,geom:geojson.geometry})
}).then(res => handleApiResponse(res, (resp) => {
const newId = resp.id || null;
if(drawnItems.hasLayer(pendingLayer)) drawnItems.removeLayer(pendingLayer);
pendingLayer._fid = newId;
const safeNama = escapeHtml(nama);
const jsEscapedNama = safeNama.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
pendingLayer.bindPopup(`<b>${safeNama}</b><br>${status}<br>Panjang: ${panjang.toFixed(2)} m<br><br>` +
`<button onclick="hapusJalan('${newId}')">Hapus</button>` +
`<button onclick="editJalan('${newId}','${jsEscapedNama}', '${status}', ${panjang})">Edit</button>`);
jalanGroup.addLayer(pendingLayer);
pendingLayer=null; pendingType=null;
}));
} else if(pendingType === 'tanah'){
const geojson = pendingLayer.toGeoJSON();
const luas = parseFloat((document.querySelector('input[name="luas"]').value)||0);
const status = values.status || 'SHM';
const nama = values.nama || '';
let warna = status==='SHM'?'green':(status==='HGB'?'blue':(status==='HGU'?'orange':'purple'));
pendingLayer.setStyle({color:warna});
fetch('src/api/tambah_tanah.php',{
method:'POST',headers: buildHeaders(),
body:JSON.stringify({nama:nama,status:status,luas:luas,geom:geojson.geometry})
}).then(res => handleApiResponse(res, (resp) => {
const newId = resp.id || null;
if(drawnItems.hasLayer(pendingLayer)) drawnItems.removeLayer(pendingLayer);
pendingLayer._fid = newId;
const safeT = escapeHtml(nama);
const jsEscapedT = safeT.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
pendingLayer.bindPopup(`<b>${safeT}</b><br>${status}<br>Luas: ${luas.toFixed(2)} m²<br><br>` +
`<button onclick="hapusTanah('${newId}')">Hapus</button>` +
`<button onclick="editTanah('${newId}','${jsEscapedT}', '${status}', ${luas})">Edit</button>`);
tanahGroup.addLayer(pendingLayer);
pendingLayer=null; pendingType=null;
}));
}
}
// ================= INIT =================
updateApiKeyStatus();
loadData();
loadJalan();
loadTanah();
</script>
</body>
</html>
+110
View File
@@ -0,0 +1,110 @@
-- MySQL dump 10.13 Distrib 8.0.45, for Linux (x86_64)
--
-- Host: localhost Database: webgis
-- ------------------------------------------------------
-- Server version 8.0.45-0ubuntu0.22.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `jalan`
--
DROP TABLE IF EXISTS `jalan`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jalan` (
`id` int NOT NULL AUTO_INCREMENT,
`nama` varchar(100) DEFAULT NULL,
`status` varchar(50) DEFAULT NULL,
`panjang` double DEFAULT NULL,
`geom` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `jalan`
--
LOCK TABLES `jalan` WRITE;
/*!40000 ALTER TABLE `jalan` DISABLE KEYS */;
/*!40000 ALTER TABLE `jalan` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `lokasi`
--
DROP TABLE IF EXISTS `lokasi`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `lokasi` (
`id` varchar(8) NOT NULL,
`nama` varchar(50) NOT NULL,
`no_telp` varchar(20) DEFAULT NULL,
`buka_24_jam` tinyint(1) DEFAULT NULL,
`latitude` decimal(10,6) DEFAULT NULL,
`longitude` decimal(10,6) DEFAULT NULL,
`alamat` text,
`jenis` varchar(50) NOT NULL DEFAULT 'spbu',
PRIMARY KEY (`id`),
KEY `idx_lokasi_alamat` ((left(`alamat`,255)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `lokasi`
--
LOCK TABLES `lokasi` WRITE;
/*!40000 ALTER TABLE `lokasi` DISABLE KEYS */;
INSERT INTO `lokasi` VALUES ('5092','Rumah','',0,-0.040882,109.335197,'Daeng Abdul Hadi, Akcaya, Pontianak Selatan, Pontianak, Kalimantan Barat, Kalimantan, 78117, Indonesia','rumah'),('7853','Masjid Mujahiddin','',1,-0.041462,109.336345,'Mujahiddin, Jalan Mujahidin, Akcaya, Pontianak Selatan, Pontianak, Kalimantan Barat, Kalimantan, 78121, Indonesia','masjid'),('8476','SPBU OSO MT. Haryono','',0,-0.044863,109.336726,'SPBU OSO MT. Haryono, Jalan M.T. Haryono, Akcaya, Pontianak Selatan, Pontianak, Kalimantan Barat, Kalimantan, 78121, Indonesia','spbu');
/*!40000 ALTER TABLE `lokasi` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `tanah`
--
DROP TABLE IF EXISTS `tanah`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tanah` (
`id` int NOT NULL AUTO_INCREMENT,
`nama` varchar(100) DEFAULT NULL,
`status` varchar(50) DEFAULT NULL,
`luas` double DEFAULT NULL,
`geom` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `tanah`
--
LOCK TABLES `tanah` WRITE;
/*!40000 ALTER TABLE `tanah` DISABLE KEYS */;
/*!40000 ALTER TABLE `tanah` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-04-23 9:46:58
+13
View File
@@ -0,0 +1,13 @@
<?php
include __DIR__ . '/../config/koneksi.php';
header('Content-Type: application/json; charset=utf-8');
$result = $conn->query("SELECT * FROM jalan");
$data = [];
while($row = $result->fetch_assoc()){
$data[] = $row;
}
echo json_encode($data);
?>
+14
View File
@@ -0,0 +1,14 @@
<?php
include __DIR__ . '/../config/koneksi.php';
header('Content-Type: application/json; charset=utf-8');
$result = $conn->query("SELECT * FROM lokasi");
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data);
?>
+15
View File
@@ -0,0 +1,15 @@
<?php
include __DIR__ . '/../config/koneksi.php';
header('Content-Type: application/json; charset=utf-8');
$result = $conn->query("SELECT * FROM tanah");
$data = [];
while($row = $result->fetch_assoc()){
// log a short preview of geom for debugging
@file_put_contents('/tmp/webgis_debug.log', date('c') . " get_tanah id=".$row['id']." geom_preview=".substr($row['geom'],0,400)."\n", FILE_APPEND);
$data[] = $row;
}
echo json_encode($data);
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$input = json_decode(file_get_contents('php://input'), true);
$id = null;
if($_SERVER['REQUEST_METHOD'] === 'DELETE'){
$id = isset($input['id']) ? $input['id'] : null;
} else {
$id = isset($_POST['id']) ? $_POST['id'] : (isset($input['id']) ? $input['id'] : null);
}
if(!$id){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$stmt = $conn->prepare('DELETE FROM jalan WHERE id = ?');
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('s', $id);
$res = $stmt->execute();
$stmt->close();
if($res){ echo json_encode(['success'=>true,'msg'=>'hapus']); }
else { http_response_code(500); echo json_encode(['success'=>false,'error'=>'delete_failed']); }
?>
+28
View File
@@ -0,0 +1,28 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
// require API key for delete
require_api_key();
// accept DELETE or POST with JSON body or form
$input = json_decode(file_get_contents('php://input'), true);
$id = null;
if($_SERVER['REQUEST_METHOD'] === 'DELETE'){
$id = isset($input['id']) ? $input['id'] : null;
} else {
$id = isset($_POST['id']) ? $_POST['id'] : (isset($input['id']) ? $input['id'] : null);
}
if(!$id){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$stmt = $conn->prepare('DELETE FROM lokasi WHERE id = ?');
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('s', $id);
$res = $stmt->execute();
$stmt->close();
if($res){ echo json_encode(['success'=>true,'message'=>'hapus']); }
else { http_response_code(500); echo json_encode(['success'=>false,'error'=>'delete_failed']); }
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$input = json_decode(file_get_contents('php://input'), true);
$id = null;
if($_SERVER['REQUEST_METHOD'] === 'DELETE'){
$id = isset($input['id']) ? $input['id'] : null;
} else {
$id = isset($_POST['id']) ? $_POST['id'] : (isset($input['id']) ? $input['id'] : null);
}
if(!$id){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$stmt = $conn->prepare('DELETE FROM tanah WHERE id = ?');
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('s', $id);
$res = $stmt->execute();
$stmt->close();
if($res){ echo json_encode(['success'=>true,'msg'=>'hapus']); }
else { http_response_code(500); echo json_encode(['success'=>false,'error'=>'delete_failed']); }
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
if(!$data){
http_response_code(400);
echo json_encode(["success"=>false,"error"=>"invalid_json"]);
exit;
}
$nama = isset($data['nama']) ? $data['nama'] : '';
$status = isset($data['status']) ? $data['status'] : '';
$panjang = isset($data['panjang']) ? floatval($data['panjang']) : 0;
$geom = isset($data['geom']) ? json_encode($data['geom']) : null;
$stmt = $conn->prepare("INSERT INTO jalan (nama,status,panjang,geom) VALUES (?, ?, ?, ?)");
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('sdds', $nama, $status, $panjang, $geom);
$res = $stmt->execute();
if($res){
$insert_id = $stmt->insert_id;
echo json_encode(["success"=>true,"id"=>$insert_id]);
} else {
http_response_code(500);
echo json_encode(["success"=>false,"error"=>$stmt->error]);
}
$stmt->close();
?>
+50
View File
@@ -0,0 +1,50 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
// require API key for create operations
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
if(!$data){
http_response_code(400);
echo json_encode(["success"=>false,"error"=>"invalid_json"]);
exit;
}
// generate safer unique id
$id = substr(uniqid('', true), -8);
$nama = isset($data['nama']) ? $data['nama'] : '';
$no_telp = isset($data['no_telp']) ? $data['no_telp'] : '';
$buka = isset($data['buka_24_jam']) ? intval($data['buka_24_jam']) : 0;
$alamat = isset($data['alamat']) ? $data['alamat'] : '';
$jenis = isset($data['jenis']) ? $data['jenis'] : '';
$lat = isset($data['latitude']) ? $data['latitude'] : null;
$lng = isset($data['longitude']) ? $data['longitude'] : null;
// basic validation
if($lat !== null){
$lat = floatval($lat);
if($lat < -90 || $lat > 90){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_latitude']); exit; }
}
if($lng !== null){
$lng = floatval($lng);
if($lng < -180 || $lng > 180){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_longitude']); exit; }
}
// prepared statement
$stmt = $conn->prepare("INSERT INTO lokasi (id, nama, jenis, no_telp, buka_24_jam, alamat, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('ssssisdd', $id, $nama, $jenis, $no_telp, $buka, $alamat, $lat, $lng);
$res = $stmt->execute();
$stmt->close();
if($res){
echo json_encode(["success"=>true, "id"=>$id]);
} else {
http_response_code(500);
echo json_encode(["success"=>false, "error"=> $conn->error]);
}
?>
+39
View File
@@ -0,0 +1,39 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
if(!$data){
http_response_code(400);
echo json_encode(["success"=>false,"error"=>"invalid_json"]);
exit;
}
$nama = isset($data['nama']) ? $data['nama'] : '';
$status = isset($data['status']) ? $data['status'] : '';
$luas = isset($data['luas']) ? floatval($data['luas']) : 0;
$geom = isset($data['geom']) ? json_encode($data['geom']) : null;
// prepared statement
$stmt = $conn->prepare("INSERT INTO tanah (nama,status,luas,geom) VALUES (?, ?, ?, ?)");
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$stmt->bind_param('sdis', $nama, $status, $luas, $geom);
$res = $stmt->execute();
if($res){
$insert_id = $stmt->insert_id;
echo json_encode(["success"=>true,"id"=>$insert_id]);
@file_put_contents('/tmp/webgis_debug.log', date('c') . " tambah_tanah OK id=".$insert_id." payload=".json_encode($data)."\n", FILE_APPEND);
} else {
http_response_code(500);
echo json_encode(["success"=>false,"error"=>$stmt->error]);
@file_put_contents('/tmp/webgis_debug.log', date('c') . " tambah_tanah ERR=".$stmt->error." payload=".json_encode($data)."\n", FILE_APPEND);
}
$stmt->close();
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
if(!$data){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_json']); exit; }
$id = isset($data['id']) ? $data['id'] : null;
if(!$id){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$sets = [];
$types = '';
$values = [];
if(isset($data['nama'])){ $sets[]='nama = ?'; $types.='s'; $values[]=$data['nama']; }
if(isset($data['status'])){ $sets[]='status = ?'; $types.='s'; $values[]=$data['status']; }
if(isset($data['panjang'])){ $sets[]='panjang = ?'; $types.='d'; $values[]=$data['panjang']; }
if(isset($data['geom'])){ $sets[]='geom = ?'; $types.='s'; $values[]=json_encode($data['geom']); }
if(count($sets) > 0){
$sql = 'UPDATE jalan SET ' . implode(', ', $sets) . ' WHERE id = ?';
$types .= 's';
$values[] = $id;
$stmt = $conn->prepare($sql);
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$bind_names[] = $types;
for($i=0;$i<count($values);$i++){ $bind_name='p'.$i; $$bind_name = $values[$i]; $bind_names[] = &$$bind_name; }
call_user_func_array([$stmt, 'bind_param'], $bind_names);
$res = $stmt->execute();
$stmt->close();
}
echo json_encode(["msg"=>"ok"]);
?>
+57
View File
@@ -0,0 +1,57 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? $data['id'] : '';
$nama = isset($data['nama']) ? $data['nama'] : null;
$no_telp = isset($data['no_telp']) ? $data['no_telp'] : null;
$buka = isset($data['buka_24_jam']) ? intval($data['buka_24_jam']) : null;
$alamat = isset($data['alamat']) ? $data['alamat'] : null;
$jenis = isset($data['jenis']) ? $data['jenis'] : null;
$sets = [];
$types = '';
$values = [];
if($nama !== null){ $sets[] = 'nama = ?'; $types .= 's'; $values[] = $nama; }
if($no_telp !== null){ $sets[] = 'no_telp = ?'; $types .= 's'; $values[] = $no_telp; }
if($buka !== null){ $sets[] = 'buka_24_jam = ?'; $types .= 'i'; $values[] = $buka; }
if($alamat !== null){ $sets[] = 'alamat = ?'; $types .= 's'; $values[] = $alamat; }
if($jenis !== null){ $sets[] = 'jenis = ?'; $types .= 's'; $values[] = $jenis; }
if(empty($sets) || empty($id)){
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'missing_id_or_fields']);
exit;
}
$sql = 'UPDATE lokasi SET ' . implode(', ', $sets) . ' WHERE id = ?';
$types .= 's';
$values[] = $id;
$stmt = $conn->prepare($sql);
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
// bind params dynamically
$bind_names[] = $types;
for ($i=0; $i<count($values); $i++){
$bind_name = 'bind' . $i;
$$bind_name = $values[$i];
$bind_names[] = &$$bind_name;
}
call_user_func_array([$stmt, 'bind_param'], $bind_names);
$res = $stmt->execute();
$stmt->close();
if($res){
echo json_encode(["success"=>true]);
} else {
http_response_code(500);
echo json_encode(["success"=>false, "error"=>$conn->error]);
}
?>
+40
View File
@@ -0,0 +1,40 @@
<?php
include __DIR__ . '/../config/koneksi.php';
include __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
require_api_key();
$data = json_decode(file_get_contents("php://input"), true);
$id = isset($data['id']) ? $data['id'] : null;
if(!$id){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$sets = [];
$types = '';
$values = [];
if(isset($data['nama'])){ $sets[]='nama = ?'; $types.='s'; $values[]=$data['nama']; }
if(isset($data['status'])){ $sets[]='status = ?'; $types.='s'; $values[]=$data['status']; }
if(isset($data['luas'])){ $sets[]='luas = ?'; $types.='d'; $values[]=$data['luas']; }
if(isset($data['geom'])){ $sets[]='geom = ?'; $types.='s'; $values[]=json_encode($data['geom']); }
if(count($sets) > 0){
$sql = 'UPDATE tanah SET ' . implode(', ', $sets) . ' WHERE id = ?';
$types .= 's';
$values[] = $id;
$stmt = $conn->prepare($sql);
if(!$stmt){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$bind_names[] = $types;
for($i=0;$i<count($values);$i++){ $bind_name='b'.$i; $$bind_name = $values[$i]; $bind_names[] = &$$bind_name; }
call_user_func_array([$stmt, 'bind_param'], $bind_names);
$res = $stmt->execute();
if($res){
@file_put_contents('/tmp/webgis_debug.log', date('c') . " update_tanah OK id=".$id." payload=".json_encode($data)."\n", FILE_APPEND);
} else {
@file_put_contents('/tmp/webgis_debug.log', date('c') . " update_tanah ERR=".$stmt->error." payload=".json_encode($data)."\n", FILE_APPEND);
}
$stmt->close();
}
echo json_encode(["msg"=>"ok"]);
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
include_once __DIR__ . '/config.php';
function get_request_api_key(){
// check header first
$headers = null;
if(function_exists('getallheaders')){
$headers = getallheaders();
}
if($headers){
foreach($headers as $k => $v){
if(strtolower($k) === 'x-api-key') return $v;
}
}
// fallback to header in $_SERVER
if(isset($_SERVER['HTTP_X_API_KEY'])) return $_SERVER['HTTP_X_API_KEY'];
// fallback to JSON body or POST
$input = json_decode(file_get_contents('php://input'), true);
if(isset($input['api_key'])) return $input['api_key'];
if(isset($_POST['api_key'])) return $_POST['api_key'];
return null;
}
function require_api_key(){
$key = get_request_api_key();
if(!$key || $key !== API_KEY){
http_response_code(401);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success'=>false,'error'=>'unauthorized']);
exit;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// Load environment from project root .env if present, then read API_KEY
function load_dotenv($path){
if(!file_exists($path)) return;
$lines = file($path, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
foreach($lines as $line){
if(trim($line)==='' || strpos(trim($line),'#')===0) continue;
if(strpos($line,'=')===false) continue;
list($k,$v) = explode('=', $line, 2);
$k = trim($k); $v = trim($v);
// strip optional quotes
if((substr($v,0,1)==='"' && substr($v,-1)==='"') || (substr($v,0,1)==="'" && substr($v,-1)==="'")){
$v = substr($v,1,-1);
}
putenv("$k=$v");
$_ENV[$k] = $v;
}
}
$envPath = realpath(__DIR__ . '/../../.env');
if($envPath){ load_dotenv($envPath); }
$apiKey = getenv('API_KEY');
if(!$apiKey) {
// fallback (development) - but prefer .env or system env in production
$apiKey = '8f3b7e6a9c4d2f1a6b8c9d0e4f7a1b2c';
}
define('API_KEY', $apiKey);
?>
+15
View File
@@ -0,0 +1,15 @@
<?php
include_once __DIR__ . '/config.php';
$dbHost = getenv('DB_HOST') ?: 'localhost';
$dbPort = (int)(getenv('DB_PORT') ?: 3306);
$dbUser = getenv('DB_USER') ?: 'root';
$dbPass = getenv('DB_PASS') ?: 'ilham';
$dbName = getenv('DB_NAME') ?: 'spbu_db';
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName, $dbPort);
if ($conn->connect_error) {
die("Koneksi gagal: " . $conn->connect_error);
}
?>