Initial commit of unified WebGIS projects
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
/.env
|
||||
/.env.local
|
||||
/*.env
|
||||
# ignore common editor temp files
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode/
|
||||
@@ -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
|
||||
@@ -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).
|
||||
|
||||
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
}
|
||||
|
||||
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,'"')}"></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,'"')}"></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,'"')}"></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,'"')}"></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,'"')}"></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>
|
||||
@@ -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
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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']); }
|
||||
?>
|
||||
@@ -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']); }
|
||||
?>
|
||||
@@ -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']); }
|
||||
?>
|
||||
@@ -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();
|
||||
|
||||
?>
|
||||
@@ -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]);
|
||||
}
|
||||
?>
|
||||
@@ -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();
|
||||
|
||||
?>
|
||||
@@ -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"]);
|
||||
?>
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -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"]);
|
||||
?>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user