Upload seluruh tugas SIG Point Line dan Polygon
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
/**
|
||||
* index.php
|
||||
* WebGIS Manajemen Data Jalan (Polyline) - PHP Native + MySQL + LeafletJS
|
||||
*
|
||||
* Contoh SQL untuk membuat database / tabel dan data sampel:
|
||||
*
|
||||
* CREATE DATABASE IF NOT EXISTS db_webgis CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
* USE db_webgis;
|
||||
*
|
||||
* CREATE TABLE IF NOT EXISTS t_jalan (
|
||||
* id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
* nama_jalan VARCHAR(255) NOT NULL,
|
||||
* status_jalan ENUM('nasional','provinsi','kabupaten') NOT NULL,
|
||||
* panjang_meter DECIMAL(10,2) NOT NULL,
|
||||
* koordinat_json LONGTEXT NOT NULL,
|
||||
* created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
* );
|
||||
*
|
||||
* INSERT INTO t_jalan (nama_jalan, status_jalan, panjang_meter, koordinat_json) VALUES
|
||||
* ('Jalan Ahmad Yani', 'nasional', 3200, '[[-0.0251,109.3292],[-0.0266,109.3338],[-0.0263,109.3425]]'),
|
||||
* ('Jalan Supadio', 'provinsi', 2400, '[[-0.0300,109.3270],[-0.0285,109.3335],[-0.0262,109.3420]]'),
|
||||
* ('Jalan Gajah Mada', 'kabupaten', 1800, '[[-0.0288,109.3389],[-0.0274,109.3452],[-0.0252,109.3501]]');
|
||||
*/
|
||||
|
||||
// Konfigurasi koneksi MySQL: sesuaikan dengan kredensial lokal Anda.
|
||||
$dbHost = 'localhost';
|
||||
$dbName = 'webgis_spbu';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
|
||||
$dsn = "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
$errors = [];
|
||||
$success = '';
|
||||
$input = [
|
||||
'nama_jalan' => '',
|
||||
'status_jalan' => '',
|
||||
'panjang_meter' => '',
|
||||
'koordinat_json' => '',
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $dbUser, $dbPass, $options);
|
||||
|
||||
// Pastikan tabel t_jalan ada.
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS t_jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(255) NOT NULL,
|
||||
status_jalan ENUM('nasional','provinsi','kabupaten') NOT NULL,
|
||||
panjang_meter DECIMAL(10,2) NOT NULL,
|
||||
koordinat_json LONGTEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$input['nama_jalan'] = trim($_POST['nama_jalan'] ?? '');
|
||||
$input['status_jalan'] = $_POST['status_jalan'] ?? '';
|
||||
$input['panjang_meter'] = $_POST['panjang_meter'] ?? '';
|
||||
$input['koordinat_json'] = $_POST['koordinat_json'] ?? '';
|
||||
|
||||
if ($input['nama_jalan'] === '') {
|
||||
$errors[] = 'Nama Jalan harus diisi.';
|
||||
}
|
||||
if (!in_array($input['status_jalan'], ['nasional', 'provinsi', 'kabupaten'], true)) {
|
||||
$errors[] = 'Status Jalan tidak valid.';
|
||||
}
|
||||
if ($input['panjang_meter'] === '' || !is_numeric($input['panjang_meter']) || (float)$input['panjang_meter'] <= 0) {
|
||||
$errors[] = 'Panjang Jalan harus dihitung otomatis dari peta dan tidak boleh kosong.';
|
||||
}
|
||||
$coords = json_decode($input['koordinat_json'], true);
|
||||
if (!is_array($coords) || count($coords) < 2) {
|
||||
$errors[] = 'Koordinat JSON tidak valid: minimal 2 titik diperlukan.';
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
$stmt = $pdo->prepare('INSERT INTO t_jalan (nama_jalan, status_jalan, panjang_meter, koordinat_json) VALUES (:nama, :status, :panjang, :koordinat)');
|
||||
$stmt->execute([
|
||||
':nama' => $input['nama_jalan'],
|
||||
':status' => $input['status_jalan'],
|
||||
':panjang' => (float)$input['panjang_meter'],
|
||||
':koordinat' => json_encode($coords, JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
$success = 'Data jalan berhasil disimpan ke database.';
|
||||
$input = ['nama_jalan' => '', 'status_jalan' => '', 'panjang_meter' => '', 'koordinat_json' => ''];
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->query('SELECT id, nama_jalan, status_jalan, panjang_meter, koordinat_json FROM t_jalan ORDER BY id DESC');
|
||||
$jalanList = $stmt->fetchAll();
|
||||
} catch (PDOException $e) {
|
||||
$errors[] = 'Koneksi database gagal: ' . htmlspecialchars($e->getMessage());
|
||||
$jalanList = [];
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebGIS Manajemen Data Jalan</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f4f7fb;
|
||||
color: #333;
|
||||
}
|
||||
header {
|
||||
background: #1f2937;
|
||||
color: #fff;
|
||||
padding: 18px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
main {
|
||||
max-width: 1160px;
|
||||
margin: 24px auto;
|
||||
padding: 0 16px 32px;
|
||||
}
|
||||
#map {
|
||||
width: 100% !important;
|
||||
height: 520px !important;
|
||||
display: block !important;
|
||||
position: relative !important;
|
||||
visibility: visible !important;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 24px rgba(15,23,42,.08);
|
||||
}
|
||||
.panel {
|
||||
margin-top: 24px;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 8px 20px rgba(15,23,42,.06);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0 0 14px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 10px;
|
||||
font-size: 0.98rem;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.form-group input[readonly] {
|
||||
background: #e2e8f0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px 18px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #64748b;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #475569;
|
||||
}
|
||||
.alert {
|
||||
padding: 14px 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.alert-error {
|
||||
background: #fee2e2;
|
||||
border-color: #fca5a5;
|
||||
color: #b91c1c;
|
||||
}
|
||||
.alert-success {
|
||||
background: #dcfce7;
|
||||
border-color: #a7f3d0;
|
||||
color: #166534;
|
||||
}
|
||||
.note {
|
||||
font-size: 0.95rem;
|
||||
color: #475569;
|
||||
}
|
||||
.road-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.road-item {
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.road-item strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.meta {
|
||||
color: #475569;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>WebGIS Manajemen Data Jalan</h1>
|
||||
<p>Tambah, simpan, dan tampilkan polyline jalan dengan LeafletJS dan MySQL.</p>
|
||||
</header>
|
||||
<main>
|
||||
<section class="card">
|
||||
<h2>Petunjuk</h2>
|
||||
<p class="note">Klik peta berurutan untuk membuat garis jalan sementara. Sistem akan menghitung panjang otomatis dan menyimpan koordinat JSON ke database.</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div id="map"></div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<section class="alert alert-error card">
|
||||
<strong>Terjadi kesalahan:</strong>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= htmlspecialchars($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<section class="alert alert-success card">
|
||||
<?= htmlspecialchars($success) ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card">
|
||||
<h2>Form Tambah Jalan</h2>
|
||||
<form method="post" action="<?= htmlspecialchars($_SERVER['PHP_SELF']) ?>">
|
||||
<div class="form-group">
|
||||
<label for="nama_jalan">Nama Jalan</label>
|
||||
<input type="text" id="nama_jalan" name="nama_jalan" required value="<?= htmlspecialchars($input['nama_jalan']) ?>" placeholder="e.g. Jalan Ahmad Yani">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status_jalan">Status Jalan</label>
|
||||
<select id="status_jalan" name="status_jalan" required>
|
||||
<option value="">Pilih status jalan</option>
|
||||
<option value="nasional" <?= $input['status_jalan'] === 'nasional' ? 'selected' : '' ?>>nasional</option>
|
||||
<option value="provinsi" <?= $input['status_jalan'] === 'provinsi' ? 'selected' : '' ?>>provinsi</option>
|
||||
<option value="kabupaten" <?= $input['status_jalan'] === 'kabupaten' ? 'selected' : '' ?>>kabupaten</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Panjang Jalan (otomatis)</label>
|
||||
<input type="text" id="panjang_tampil" readonly value="<?= $input['panjang_meter'] !== '' ? htmlspecialchars($input['panjang_meter']) . ' meter' : '0 meter' ?>">
|
||||
<input type="hidden" name="panjang_meter" id="panjang_meter" value="<?= htmlspecialchars($input['panjang_meter']) ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Koordinat JSON (otomatis)</label>
|
||||
<textarea readonly rows="3" id="koordinat_tampil" style="resize: vertical; background: #e2e8f0;"><?= htmlspecialchars($input['koordinat_json']) ?></textarea>
|
||||
<input type="hidden" name="koordinat_json" id="koordinat_json" value="<?= htmlspecialchars($input['koordinat_json']) ?>">
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button type="button" class="btn btn-secondary" id="reset-button">Reset Garis</button>
|
||||
<button type="submit" class="btn btn-primary">Simpan Data Jalan</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Daftar Jalan Tersimpan</h2>
|
||||
<div class="road-list">
|
||||
<?php if (count($jalanList) === 0): ?>
|
||||
<div class="road-item">Belum ada data jalan yang tersimpan.</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($jalanList as $jalan): ?>
|
||||
<div class="road-item">
|
||||
<strong><?= htmlspecialchars($jalan['nama_jalan']) ?></strong>
|
||||
<div class="meta">Status: <?= htmlspecialchars($jalan['status_jalan']) ?> | Panjang: <?= htmlspecialchars($jalan['panjang_meter']) ?> meter</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
const map = L.map('map').setView([-0.0263, 109.3425], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
|
||||
const statusColors = {
|
||||
nasional: '#DC3545',
|
||||
provinsi: '#007BFF',
|
||||
kabupaten: '#28A745'
|
||||
};
|
||||
|
||||
const jalanData = <?= json_encode(array_map(function ($row) {
|
||||
return [
|
||||
'id' => $row['id'],
|
||||
'nama_jalan' => $row['nama_jalan'],
|
||||
'status_jalan' => $row['status_jalan'],
|
||||
'panjang_meter' => $row['panjang_meter'],
|
||||
'koordinat' => json_decode($row['koordinat_json'], true)
|
||||
];
|
||||
}, $jalanList), JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
jalanData.forEach(jalan => {
|
||||
if (!Array.isArray(jalan.koordinat) || jalan.koordinat.length < 2) {
|
||||
return;
|
||||
}
|
||||
const line = L.polyline(jalan.koordinat.map(([lat, lng]) => [lat, lng]), {
|
||||
color: statusColors[jalan.status_jalan] || '#444',
|
||||
weight: 6,
|
||||
opacity: 0.8,
|
||||
smoothFactor: 1
|
||||
}).addTo(map);
|
||||
line.bindPopup(`
|
||||
<strong>${jalan.nama_jalan}</strong><br>
|
||||
Status: ${jalan.status_jalan}<br>
|
||||
Panjang: ${jalan.panjang_meter} meter
|
||||
`);
|
||||
});
|
||||
|
||||
let currentPoints = [];
|
||||
let currentLine = null;
|
||||
|
||||
const panjangMeterInput = document.getElementById('panjang_meter');
|
||||
const panjangTampil = document.getElementById('panjang_tampil');
|
||||
const koordinatInput = document.getElementById('koordinat_json');
|
||||
const koordinatTampil = document.getElementById('koordinat_tampil');
|
||||
const resetButton = document.getElementById('reset-button');
|
||||
|
||||
map.on('click', function (event) {
|
||||
currentPoints.push(event.latlng);
|
||||
updateTemporaryPolyline();
|
||||
updateFormFields();
|
||||
});
|
||||
|
||||
resetButton.addEventListener('click', function () {
|
||||
currentPoints = [];
|
||||
if (currentLine) {
|
||||
map.removeLayer(currentLine);
|
||||
currentLine = null;
|
||||
}
|
||||
updateFormFields();
|
||||
});
|
||||
|
||||
function updateTemporaryPolyline() {
|
||||
if (currentLine) {
|
||||
map.removeLayer(currentLine);
|
||||
}
|
||||
if (currentPoints.length >= 2) {
|
||||
currentLine = L.polyline(currentPoints, {
|
||||
color: '#0f172a',
|
||||
weight: 5,
|
||||
dashArray: '8, 8',
|
||||
opacity: 0.85
|
||||
}).addTo(map);
|
||||
} else if (currentPoints.length === 1) {
|
||||
currentLine = L.circleMarker(currentPoints[0], {
|
||||
radius: 6,
|
||||
fillColor: '#0f172a',
|
||||
color: '#ffffff',
|
||||
weight: 2,
|
||||
fillOpacity: 1
|
||||
}).addTo(map);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormFields() {
|
||||
let totalDistance = 0;
|
||||
for (let i = 1; i < currentPoints.length; i++) {
|
||||
totalDistance += currentPoints[i - 1].distanceTo(currentPoints[i]);
|
||||
}
|
||||
const roundedDistance = Math.round(totalDistance);
|
||||
panjangMeterInput.value = roundedDistance;
|
||||
panjangTampil.value = `${roundedDistance} meter`;
|
||||
|
||||
const coordsArray = currentPoints.map(point => [point.lat, point.lng]);
|
||||
koordinatInput.value = JSON.stringify(coordsArray);
|
||||
koordinatTampil.value = koordinatInput.value;
|
||||
}
|
||||
|
||||
updateFormFields();
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
map.invalidateSize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user