# Tutorial Hari 9: Edit Peta dengan Leaflet.js

## 🎯 Tujuan
- Memahami Leaflet.js dan cara kerjanya
- Membuat halaman edit peta interaktif
- Menggambar batas desa (polygon)
- Mengelola fasilitas (marker)

---

## 📚 Teori: Leaflet.js

### Apa itu Leaflet.js?

Leaflet adalah library JavaScript untuk membuat peta interaktif di web. Gratis, ringan (~42KB), dan sangat populer.

| Fitur | Penjelasan |
|-------|------------|
| Tile Layers | Gambar peta dari OSM, Google, dll |
| Markers | Pin lokasi |
| Polygons | Area/batas |
| Popups | Info popup |
| Events | Klik, drag, zoom |

### Komponen Utama

| Komponen | Fungsi | Contoh |
|----------|--------|--------|
| `L.map()` | Buat objek peta | `L.map('map')` |
| `L.tileLayer()` | Layer gambar peta | OpenStreetMap |
| `L.marker()` | Tambah pin | Lokasi fasilitas |
| `L.polygon()` | Gambar area | Batas desa |
| `L.popup()` | Popup info | Detail fasilitas |

### Plugin Leaflet.Draw

Plugin untuk menggambar di peta:

| Tool | Fungsi |
|------|--------|
| Polygon | Gambar area tertutup |
| Rectangle | Kotak |
| Circle | Lingkaran |
| Marker | Titik |
| Edit | Edit shape yang ada |
| Delete | Hapus shape |

---

## 📚 Teori: AJAX

### Apa itu AJAX?

AJAX (Asynchronous JavaScript and XML) adalah teknik untuk mengirim/menerima data dari server **tanpa refresh halaman**.

```javascript
// Kirim data ke server
fetch('/pages/edit-peta.php?id=1', {
    method: 'POST',
    body: new URLSearchParams({ action: 'add_fasilitas', nama: 'SDN 01' })
})
.then(response => response.json())
.then(data => {
    if (data.success) {
        alert('Berhasil!');
    }
});
```

| Method | Fungsi |
|--------|--------|
| `fetch()` | Kirim HTTP request |
| `method: 'POST'` | Kirim data (bukan GET) |
| `body` | Data yang dikirim |
| `.then()` | Handle response |

---

## 1. Buat edit-peta.php

### Langkah 1: Buat file baru

1. Klik kanan folder **pages**
2. New File → `edit-peta.php`

### Langkah 2: Bagian PHP

```php
<?php
/**
 * Halaman Edit Peta
 * Untuk menggambar batas dan mengelola fasilitas
 */

require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/spatial.php';
require_once __DIR__ . '/../lib/helpers.php';

requireAuth();

$user = getCurrentUser();
$userId = getCurrentUserId();

// Ambil ID peta dari URL
$petaId = $_GET['id'] ?? null;
if (!$petaId) {
    header('Location: /pages/dashboard.php');
    exit;
}

// Verifikasi kepemilikan
$peta = getPetaById($petaId, $userId);
if (!$peta) {
    setFlash('danger', 'Peta tidak ditemukan');
    header('Location: /pages/dashboard.php');
    exit;
}

// ============================================
// HANDLE AJAX REQUESTS
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
    header('Content-Type: application/json');
    
    $action = $_POST['action'] ?? '';
    
    // Simpan batas desa
    if ($action === 'save_boundary') {
        $geojson = $_POST['geojson'] ?? '';
        $centerLat = $_POST['center_lat'] ?? null;
        $centerLng = $_POST['center_lng'] ?? null;
        
        $result = updatePeta($petaId, $userId, [
            'geojson_data' => $geojson,
            'center_lat' => $centerLat,
            'center_lng' => $centerLng
        ]);
        
        echo json_encode($result);
        exit;
    }
    
    // Tambah fasilitas
    if ($action === 'add_fasilitas') {
        $result = addFasilitas($petaId, [
            'nama' => $_POST['nama'] ?? '',
            'jenis' => $_POST['jenis'] ?? '',
            'lat' => $_POST['lat'] ?? 0,
            'lng' => $_POST['lng'] ?? 0,
            'alamat' => $_POST['alamat'] ?? '',
            'keterangan' => $_POST['keterangan'] ?? ''
        ]);
        
        echo json_encode($result);
        exit;
    }
    
    // Hapus fasilitas
    if ($action === 'delete_fasilitas') {
        $fasId = $_POST['fasilitas_id'] ?? 0;
        $result = deleteFasilitas($fasId, $petaId);
        echo json_encode($result);
        exit;
    }
}

// Ambil data
$geojsonData = $peta['geojson_data'] ?? '{}';
$fasilitasList = getFasilitasByPeta($petaId);
$fasilitasGeoJSON = getFasilitasGeoJSON($petaId);

// Helper functions
function getFasilitasColor($jenis) {
    $colors = [
        'pendidikan' => '#3b82f6', 'kesehatan' => '#ef4444',
        'ibadah' => '#22c55e', 'pemerintahan' => '#f59e0b',
        'umkm' => '#ec4899', 'wisata' => '#8b5cf6'
    ];
    return $colors[$jenis] ?? '#64748b';
}

function getFasilitasIcon($jenis) {
    $icons = [
        'pendidikan' => 'fa-school', 'kesehatan' => 'fa-hospital',
        'ibadah' => 'fa-place-of-worship', 'pemerintahan' => 'fa-landmark',
        'umkm' => 'fa-store', 'wisata' => 'fa-mountain-sun'
    ];
    return $icons[$jenis] ?? 'fa-map-marker-alt';
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Edit Peta - WebGIS</title>
    
    <!-- Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css">
    
    <!-- Font Awesome & CSS -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
    <link rel="stylesheet" href="/assets/css/style.css">
    
    <style>
        .edit-container { display: flex; height: calc(100vh - 60px); }
        
        .sidebar {
            width: 350px;
            background: white;
            border-right: 1px solid #e2e8f0;
            overflow-y: auto;
            padding: 1rem;
        }
        
        .map-area { flex: 1; position: relative; }
        #map { width: 100%; height: 100%; }
        
        .tabs { display: flex; border-bottom: 2px solid #e2e8f0; margin-bottom: 1rem; }
        .tab {
            padding: 0.75rem 1rem;
            cursor: pointer;
            border-bottom: 2px solid transparent;
            margin-bottom: -2px;
            font-size: 0.875rem;
        }
        .tab.active { color: #2563eb; border-bottom-color: #2563eb; }
        
        .tab-content { display: none; }
        .tab-content.active { display: block; }
        
        .form-group { margin-bottom: 1rem; }
        .form-group label { display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 0.5rem; }
        .form-group input, .form-group select, .form-group textarea {
            width: 100%;
            padding: 0.5rem;
            border: 1px solid #d1d5db;
            border-radius: 0.375rem;
            font-size: 0.875rem;
        }
        
        .fasilitas-item {
            display: flex;
            align-items: center;
            gap: 0.75rem;
            padding: 0.75rem;
            border: 1px solid #e2e8f0;
            border-radius: 0.5rem;
            margin-bottom: 0.5rem;
            cursor: pointer;
        }
        .fasilitas-item:hover { background: #f8fafc; }
        
        .fasilitas-icon {
            width: 36px; height: 36px;
            border-radius: 0.5rem;
            display: flex; align-items: center; justify-content: center;
            color: white;
        }
    </style>
</head>
<body>
    <!-- Navbar -->
    <nav class="navbar">
        <div class="container nav-container">
            <a href="/pages/dashboard.php" class="navbar-brand">
                <i class="fas fa-arrow-left"></i> Kembali
            </a>
            <div><strong><?= sanitize($peta['nama_peta']) ?></strong></div>
            <button onclick="saveBoundary()" class="btn btn-primary btn-sm">
                <i class="fas fa-save"></i> Simpan
            </button>
        </div>
    </nav>
    
    <div class="edit-container">
        <!-- Sidebar -->
        <div class="sidebar">
            <div class="tabs">
                <div class="tab active" data-tab="boundary">
                    <i class="fas fa-draw-polygon"></i> Batas
                </div>
                <div class="tab" data-tab="fasilitas">
                    <i class="fas fa-map-marker-alt"></i> Fasilitas
                </div>
            </div>
            
            <!-- Tab Batas -->
            <div id="tab-boundary" class="tab-content active">
                <h3>Gambar Batas Desa</h3>
                <p style="color: #64748b; font-size: 0.875rem;">Gunakan tool di peta untuk menggambar.</p>
                
                <div style="margin-top: 1rem;">
                    <button onclick="startDrawPolygon()" class="btn btn-primary">
                        <i class="fas fa-draw-polygon"></i> Mulai Gambar
                    </button>
                    <button onclick="clearBoundary()" class="btn btn-secondary">
                        <i class="fas fa-trash"></i> Hapus
                    </button>
                </div>
                
                <div style="margin-top: 1rem; padding: 1rem; background: #f8fafc; border-radius: 0.5rem;">
                    <h4 style="font-size: 0.875rem;">Cara Menggambar:</h4>
                    <ol style="font-size: 0.75rem; color: #64748b; padding-left: 1rem;">
                        <li>Klik "Mulai Gambar"</li>
                        <li>Klik titik-titik di peta</li>
                        <li>Double-click untuk selesai</li>
                        <li>Klik "Simpan"</li>
                    </ol>
                </div>
            </div>
            
            <!-- Tab Fasilitas -->
            <div id="tab-fasilitas" class="tab-content">
                <h3>Fasilitas Desa</h3>
                
                <form id="fasilitasForm" style="margin-bottom: 1rem;">
                    <div class="form-group">
                        <label>Nama Fasilitas</label>
                        <input type="text" name="nama" required placeholder="Contoh: SDN 01">
                    </div>
                    
                    <div class="form-group">
                        <label>Jenis</label>
                        <select name="jenis" required>
                            <option value="">Pilih jenis...</option>
                            <option value="pendidikan">Pendidikan</option>
                            <option value="kesehatan">Kesehatan</option>
                            <option value="ibadah">Ibadah</option>
                            <option value="pemerintahan">Pemerintahan</option>
                            <option value="umkm">UMKM</option>
                            <option value="wisata">Wisata</option>
                        </select>
                    </div>
                    
                    <div class="form-group">
                        <label>Alamat</label>
                        <input type="text" name="alamat" placeholder="Jl. Raya No. 1">
                    </div>
                    
                    <input type="hidden" name="lat" id="inputLat">
                    <input type="hidden" name="lng" id="inputLng">
                    
                    <p style="font-size: 0.75rem; color: #64748b;">
                        Klik lokasi di peta, lalu isi form.
                    </p>
                    
                    <button type="submit" class="btn btn-success">
                        <i class="fas fa-plus"></i> Tambah
                    </button>
                </form>
                
                <hr>
                
                <h4>Daftar Fasilitas</h4>
                <div id="fasilitasList">
                    <?php if (empty($fasilitasList)): ?>
                        <p style="color: #64748b;">Belum ada fasilitas.</p>
                    <?php else: ?>
                        <?php foreach ($fasilitasList as $fas): ?>
                            <div class="fasilitas-item" onclick="zoomToFasilitas(<?= $fas['lat'] ?>, <?= $fas['lng'] ?>)">
                                <div class="fasilitas-icon" style="background: <?= getFasilitasColor($fas['jenis']) ?>">
                                    <i class="fas <?= getFasilitasIcon($fas['jenis']) ?>"></i>
                                </div>
                                <div style="flex: 1;">
                                    <strong><?= sanitize($fas['nama']) ?></strong>
                                    <small style="display: block; color: #64748b;"><?= ucfirst($fas['jenis']) ?></small>
                                </div>
                                <button onclick="event.stopPropagation(); deleteFasilitas(<?= $fas['id'] ?>)" 
                                        class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
                            </div>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </div>
            </div>
        </div>
        
        <!-- Map -->
        <div class="map-area">
            <div id="map"></div>
        </div>
    </div>
    
    <!-- Leaflet JS -->
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
    
    <script>
        // Variabel global
        const petaId = <?= $petaId ?>;
        let map, drawnItems, drawControl, fasilitasLayer, selectedLatLng = null;
        
        const fasilitasTypes = {
            pendidikan: { icon: 'fa-school', color: '#3b82f6' },
            kesehatan: { icon: 'fa-hospital', color: '#ef4444' },
            ibadah: { icon: 'fa-place-of-worship', color: '#22c55e' },
            pemerintahan: { icon: 'fa-landmark', color: '#f59e0b' },
            umkm: { icon: 'fa-store', color: '#ec4899' },
            wisata: { icon: 'fa-mountain-sun', color: '#8b5cf6' }
        };
        
        // Init peta
        document.addEventListener('DOMContentLoaded', function() {
            map = L.map('map').setView([
                <?= $peta['center_lat'] ?? -6.2 ?>, 
                <?= $peta['center_lng'] ?? 106.8 ?>
            ], <?= $peta['zoom_level'] ?? 13 ?>);
            
            L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                attribution: '© OpenStreetMap'
            }).addTo(map);
            
            // Draw control
            drawnItems = new L.FeatureGroup();
            map.addLayer(drawnItems);
            
            drawControl = new L.Control.Draw({
                edit: { featureGroup: drawnItems, remove: true },
                draw: { polygon: true, polyline: false, rectangle: false, circle: false, circlemarker: false, marker: false }
            });
            map.addControl(drawControl);
            
            map.on('draw:created', e => drawnItems.addLayer(e.layer));
            
            // Klik peta untuk pilih lokasi
            map.on('click', function(e) {
                selectedLatLng = e.latlng;
                document.getElementById('inputLat').value = e.latlng.lat;
                document.getElementById('inputLng').value = e.latlng.lng;
                L.popup().setLatLng(e.latlng).setContent('Lokasi dipilih!<br>Isi form di sidebar.').openOn(map);
            });
            
            loadExistingData();
            setupTabs();
            setupFasilitasForm();
        });
        
        // Load data
        function loadExistingData() {
            const boundaryData = <?= $geojsonData ?: '{}' ?>;
            if (boundaryData?.features?.length > 0) {
                L.geoJSON(boundaryData, {
                    style: { color: '#2563eb', weight: 3, fillColor: '#3b82f6', fillOpacity: 0.2 }
                }).eachLayer(layer => drawnItems.addLayer(layer));
                map.fitBounds(drawnItems.getBounds());
            }
            
            const fasilitasData = <?= json_encode($fasilitasGeoJSON) ?>;
            if (fasilitasData?.features) {
                fasilitasLayer = L.geoJSON(fasilitasData, {
                    pointToLayer: (feature, latlng) => {
                        const type = feature.properties.jenis || 'wisata';
                        const cfg = fasilitasTypes[type] || fasilitasTypes.wisata;
                        return L.marker(latlng, { icon: createMarkerIcon(cfg.color, cfg.icon) });
                    },
                    onEachFeature: (feature, layer) => {
                        layer.bindPopup(`<strong>${feature.properties.nama}</strong><br><small>${feature.properties.jenis}</small>`);
                    }
                }).addTo(map);
            }
        }
        
        // Custom marker
        function createMarkerIcon(color, iconClass) {
            return L.divIcon({
                className: 'custom-marker',
                html: `<div style="background:${color};width:36px;height:36px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);display:flex;align-items:center;justify-content:center;border:3px solid white;box-shadow:0 2px 5px rgba(0,0,0,0.3);"><i class="fas ${iconClass}" style="transform:rotate(45deg);color:white;font-size:14px;"></i></div>`,
                iconSize: [36, 36], iconAnchor: [18, 36], popupAnchor: [0, -36]
            });
        }
        
        // Drawing
        function startDrawPolygon() { new L.Draw.Polygon(map, drawControl.options.draw.polygon).enable(); }
        function clearBoundary() { if (confirm('Hapus semua batas?')) drawnItems.clearLayers(); }
        
        // Simpan batas
        function saveBoundary() {
            const geojson = drawnItems.toGeoJSON();
            const center = map.getCenter();
            
            fetch('/pages/edit-peta.php?id=' + petaId, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' },
                body: new URLSearchParams({ action: 'save_boundary', geojson: JSON.stringify(geojson), center_lat: center.lat, center_lng: center.lng })
            })
            .then(r => r.json())
            .then(data => alert(data.success ? '✅ Berhasil disimpan!' : '❌ ' + data.message));
        }
        
        // Fasilitas form
        function setupFasilitasForm() {
            document.getElementById('fasilitasForm').addEventListener('submit', function(e) {
                e.preventDefault();
                if (!document.getElementById('inputLat').value) { alert('Klik lokasi di peta!'); return; }
                
                fetch('/pages/edit-peta.php?id=' + petaId, {
                    method: 'POST',
                    headers: { 'X-Requested-With': 'XMLHttpRequest' },
                    body: new URLSearchParams(new FormData(this)).toString() + '&action=add_fasilitas'
                })
                .then(r => r.json())
                .then(data => { if (data.success) location.reload(); else alert('❌ ' + data.message); });
            });
        }
        
        // Hapus fasilitas
        function deleteFasilitas(id) {
            if (!confirm('Hapus fasilitas?')) return;
            fetch('/pages/edit-peta.php?id=' + petaId, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' },
                body: new URLSearchParams({ action: 'delete_fasilitas', fasilitas_id: id })
            })
            .then(r => r.json())
            .then(data => { if (data.success) location.reload(); });
        }
        
        // Helpers
        function zoomToFasilitas(lat, lng) { map.setView([lat, lng], 17); }
        
        function setupTabs() {
            document.querySelectorAll('.tab').forEach(tab => {
                tab.addEventListener('click', function() {
                    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
                    document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
                    this.classList.add('active');
                    document.getElementById('tab-' + this.dataset.tab).classList.add('active');
                });
            });
        }
    </script>
</body>
</html>
```

---

## 📚 Penjelasan Kode Penting

### 1. Inisialisasi Peta

```javascript
map = L.map('map').setView([-6.2, 106.8], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap'
}).addTo(map);
```

| Baris | Fungsi |
|-------|--------|
| `L.map('map')` | Buat peta di div id="map" |
| `.setView([-6.2, 106.8], 13)` | Center & zoom level |
| `L.tileLayer()` | Gambar peta dari OSM |
| `{s}` | Subdomain (a, b, c) |
| `{z}/{x}/{y}` | Tile coordinates |

### 2. AJAX POST

```javascript
fetch('/pages/edit-peta.php?id=' + petaId, {
    method: 'POST',
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
    body: new URLSearchParams({ action: 'save_boundary', ... })
})
```

| Property | Fungsi |
|----------|--------|
| `method: 'POST'` | HTTP method |
| `X-Requested-With` | Identifikasi AJAX |
| `body` | Data yang dikirim |

---

## 📁 Struktur Akhir Hari 9

```
📁 pemetaan-desa/
├── 📁 pages/
│   ├── 📄 dashboard.php
│   ├── 📄 edit-peta.php    ← BARU
│   └── 📄 login.php
└── ...
```

---

**← [Hari 8: Dashboard](hari8.md) | [Hari 10: Halaman Publik](hari10.md) →**
