add feature basemap
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebGIS - Peta Referensi Kota Pontianak</title>
|
||||
<meta name="description" content="Peta referensi batas kecamatan, jaringan jalan, dan sungai besar Kota Pontianak." />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/6.5.0/turf.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header id="header">
|
||||
<div class="brand">
|
||||
<a href="index.html" style="text-decoration:none; color:inherit"><div class="brand-title">WebGIS Poverty</div></a>
|
||||
<div class="brand-sub">Sistem Informasi Geografis Berbasis Web</div>
|
||||
</div>
|
||||
|
||||
<div class="header-search" id="header-search">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">⌕</span>
|
||||
<input id="search-input" type="search" autocomplete="off" placeholder="Telusuri Webgis Poverty" aria-label="Cari objek peta" />
|
||||
</div>
|
||||
<div class="search-results" id="search-results" aria-label="Hasil pencarian"></div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="coord-display" id="coord-display">Lat: - | Lng: -</div>
|
||||
<div class="status-pill"><span id="status-zoom">Zoom: 13</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
<section id="map-wrap">
|
||||
<div id="instruction-banner"></div>
|
||||
<div id="map"></div>
|
||||
</section>
|
||||
|
||||
<aside id="panel">
|
||||
<div id="panel-head">
|
||||
<a href="index.html" class="btn btn-ghost btn-sm" style="margin-bottom: 12px; display: inline-flex; align-items: center; gap: 6px; text-decoration: none;">
|
||||
<span style="font-size: 14px;">←</span> Kembali ke Beranda
|
||||
</a>
|
||||
<h2 id="panel-title">Peta Referensi Pontianak</h2>
|
||||
</div>
|
||||
<div id="panel-body">
|
||||
<div class="loading"><span class="spinner"></span>Memuat data...</div>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="modal-overlay" aria-hidden="true">
|
||||
<div id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
||||
<div class="modal-head"><h3 id="modal-title">Modal</h3></div>
|
||||
<div id="modal-body"></div>
|
||||
<div id="modal-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
|
||||
<script src="js/app-core.js"></script>
|
||||
<script src="js/feature-kemiskinan.js"></script>
|
||||
<script src="js/feature-spbu.js"></script>
|
||||
<script src="js/feature-jalan.js"></script>
|
||||
<script src="js/feature-persil.js"></script>
|
||||
<script src="js/feature-basemap.js"></script>
|
||||
<script>
|
||||
initPage('basemap');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,6 +46,12 @@
|
||||
<h2 class="feature-title">Bidang Persil Tanah</h2>
|
||||
<p class="feature-desc">Pemetaan bidang tanah bersertifikat lengkap dengan informasi status kepemilikan.</p>
|
||||
</a>
|
||||
|
||||
<a href="basemap.html" class="feature-card">
|
||||
<div class="feature-icon">🏙️</div>
|
||||
<h2 class="feature-title">Peta Referensi Pontianak</h2>
|
||||
<p class="feature-desc">Batas kecamatan, jaringan jalan, dan sungai besar Kota Pontianak dari data GeoJSON.</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<script src="js/feature-spbu.js"></script>
|
||||
<script src="js/feature-jalan.js"></script>
|
||||
<script src="js/feature-persil.js"></script>
|
||||
<script src="js/feature-basemap.js"></script>
|
||||
<script>
|
||||
initPage('jalan');
|
||||
</script>
|
||||
|
||||
+19
-5
@@ -35,14 +35,20 @@ const layers = {
|
||||
persilSHM: L.layerGroup(),
|
||||
persilHGB: L.layerGroup(),
|
||||
persilHGU: L.layerGroup(),
|
||||
persilHP: L.layerGroup()
|
||||
persilHP: L.layerGroup(),
|
||||
// Layer referensi GeoJSON statis
|
||||
basemap_kecamatan: L.layerGroup(),
|
||||
basemap_jalan: L.layerGroup(),
|
||||
basemap_sungai: L.layerGroup()
|
||||
};
|
||||
|
||||
const layerVisible = {
|
||||
rumahIbadah: true, ibadahRadius: true, pendudukMiskin: true,
|
||||
spbu24: true, spbuNon24: true,
|
||||
jalanNasional: true, jalanProvinsi: true, jalanKabupaten: true,
|
||||
persilSHM: true, persilHGB: true, persilHGU: true, persilHP: true
|
||||
persilSHM: true, persilHGB: true, persilHGU: true, persilHP: true,
|
||||
// Basemap GeoJSON (default tampil semua)
|
||||
basemap_kecamatan: true, basemap_jalan: true, basemap_sungai: true
|
||||
};
|
||||
|
||||
const searchEntries = [];
|
||||
@@ -320,7 +326,8 @@ function initPage(tab) {
|
||||
kemiskinan: ['rumahIbadah', 'ibadahRadius', 'pendudukMiskin'],
|
||||
spbu: ['spbu24', 'spbuNon24'],
|
||||
jalan: ['jalanNasional', 'jalanProvinsi', 'jalanKabupaten'],
|
||||
persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP']
|
||||
persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP'],
|
||||
basemap: ['basemap_kecamatan', 'basemap_jalan', 'basemap_sungai']
|
||||
};
|
||||
|
||||
if (tabLayers[tab]) {
|
||||
@@ -329,8 +336,14 @@ function initPage(tab) {
|
||||
});
|
||||
}
|
||||
|
||||
const titles = { kemiskinan: 'Ibadah & Kemiskinan', spbu: 'SPBU', jalan: 'Data Jalan', persil: 'Persil Tanah' };
|
||||
document.getElementById('panel-title').textContent = titles[tab];
|
||||
const titles = {
|
||||
kemiskinan: 'Ibadah & Kemiskinan',
|
||||
spbu: 'SPBU',
|
||||
jalan: 'Data Jalan',
|
||||
persil: 'Persil Tanah',
|
||||
basemap: 'Peta Referensi Pontianak'
|
||||
};
|
||||
document.getElementById('panel-title').textContent = titles[tab] || tab;
|
||||
renderPanel(tab);
|
||||
}
|
||||
|
||||
@@ -351,6 +364,7 @@ async function renderPanel(tab) {
|
||||
if (tab === 'spbu') await renderSpbuPanel(pb);
|
||||
if (tab === 'jalan') await renderJalanPanel(pb);
|
||||
if (tab === 'persil') await renderPersilPanel(pb);
|
||||
if (tab === 'basemap') await renderBasemapPanel(pb);
|
||||
}
|
||||
|
||||
function findGeomLayer(type, id) {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// ============================================================
|
||||
// feature-basemap.js
|
||||
// Layer referensi statis dari GeoJSON lokal:
|
||||
// - Batas Kecamatan Kota Pontianak (Admin_Kecamatan)
|
||||
// - Jaringan Jalan (Jaringan_Jalan)
|
||||
// - Sungai Besar (Sungai_Besar)
|
||||
// ============================================================
|
||||
|
||||
const basemapCfg = {
|
||||
kecamatan: {
|
||||
label: 'Batas Kecamatan',
|
||||
file: 'assets/map/Admin_Kecamatan.geojson',
|
||||
color: '#7c3aed',
|
||||
fillColor: '#7c3aed',
|
||||
fillOpacity: 0.08,
|
||||
weight: 2.5,
|
||||
icon: '🏙️',
|
||||
popupProp: ['KECAMATAN', 'KELURAHAN', 'KODE_KEC', 'LUAS_HA'],
|
||||
popupLabel: { KECAMATAN: 'Kecamatan', KELURAHAN: 'Kelurahan', KODE_KEC: 'Kode', LUAS_HA: 'Luas (ha)' }
|
||||
},
|
||||
jalan: {
|
||||
label: 'Jaringan Jalan',
|
||||
file: 'assets/map/Jaringan_Jalan.geojson',
|
||||
color: '#d97706',
|
||||
weight: 1.8,
|
||||
fillOpacity: 0,
|
||||
icon: '🛤️',
|
||||
popupProp: ['NAMOBJ', 'REMARK', 'FCODE'],
|
||||
popupLabel: { NAMOBJ: 'Nama', REMARK: 'Keterangan', FCODE: 'Kode' }
|
||||
},
|
||||
sungai: {
|
||||
label: 'Sungai Besar',
|
||||
file: 'assets/map/Sungai_Besar.geojson',
|
||||
color: '#0ea5e9',
|
||||
fillColor: '#0ea5e9',
|
||||
fillOpacity: 0.25,
|
||||
weight: 1.5,
|
||||
icon: '🌊',
|
||||
popupProp: ['NAMOBJ', 'REMARK', 'FCODE'],
|
||||
popupLabel: { NAMOBJ: 'Nama', REMARK: 'Keterangan', FCODE: 'Kode' }
|
||||
}
|
||||
};
|
||||
|
||||
// Cache untuk data GeoJSON yang sudah di-fetch agar tidak fetch ulang
|
||||
const _basemapCache = {};
|
||||
|
||||
/**
|
||||
* Fetch GeoJSON dan buat Leaflet GeoJSON layer dengan style + popup.
|
||||
*/
|
||||
async function loadBasemapLayer(key) {
|
||||
const cfg = basemapCfg[key];
|
||||
const layerKey = 'basemap_' + key;
|
||||
|
||||
// Jika layer sudah ada isinya, tidak perlu reload
|
||||
let hasLayers = false;
|
||||
layers[layerKey].eachLayer(() => { hasLayers = true; });
|
||||
if (hasLayers) return;
|
||||
|
||||
try {
|
||||
// Gunakan cache jika sudah pernah di-fetch
|
||||
let geojsonData = _basemapCache[key];
|
||||
if (!geojsonData) {
|
||||
const resp = await fetch(cfg.file);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
geojsonData = await resp.json();
|
||||
_basemapCache[key] = geojsonData;
|
||||
}
|
||||
|
||||
const styleOpts = {
|
||||
color: cfg.color,
|
||||
weight: cfg.weight,
|
||||
fillOpacity: cfg.fillOpacity ?? 0,
|
||||
fillColor: cfg.fillColor || cfg.color,
|
||||
opacity: 0.9
|
||||
};
|
||||
|
||||
const geoLayer = L.geoJSON(geojsonData, {
|
||||
style: styleOpts,
|
||||
pointToLayer: (feature, latlng) =>
|
||||
L.circleMarker(latlng, { radius: 5, ...styleOpts }),
|
||||
onEachFeature: (feature, layer) => {
|
||||
const props = feature.properties || {};
|
||||
// Cari nama property (case-insensitive fallback)
|
||||
const findVal = (keys) => {
|
||||
for (const k of keys) {
|
||||
const found = Object.keys(props).find(p => p.toUpperCase() === k.toUpperCase());
|
||||
if (found && props[found]) return props[found];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const rows = cfg.popupProp
|
||||
.map(p => {
|
||||
const found = Object.keys(props).find(k => k.toUpperCase() === p.toUpperCase());
|
||||
const val = found ? props[found] : null;
|
||||
if (!val) return '';
|
||||
return `<p><b>${cfg.popupLabel[p] || p}:</b> ${val}</p>`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('');
|
||||
|
||||
const nameKeys = ['NAMOBJ', 'NAMA', 'NAME', 'KECAMATAN', 'KELURAHAN'];
|
||||
const title = findVal(nameKeys) || cfg.label;
|
||||
|
||||
if (rows) {
|
||||
layer.bindPopup(`<h4>${cfg.icon} ${esc(title)}</h4>${rows}`, { maxWidth: 280 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
layers[layerKey].addLayer(geoLayer);
|
||||
} catch (err) {
|
||||
console.warn(`[basemap] Gagal memuat ${cfg.file}:`, err);
|
||||
toast(`Gagal memuat layer ${cfg.label}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render panel kontrol untuk layer basemap di sidebar.
|
||||
* Dipanggil dari renderPanel() di app-core.js jika tab === 'basemap'.
|
||||
*/
|
||||
async function renderBasemapPanel(pb) {
|
||||
pb.innerHTML = `
|
||||
<div class="layer-section">
|
||||
<div class="layer-section-title">Layer Referensi Kota Pontianak</div>
|
||||
${Object.entries(basemapCfg).map(([key, cfg]) => {
|
||||
const lk = 'basemap_' + key;
|
||||
const checked = layerVisible[lk];
|
||||
return `
|
||||
<div class="layer-item ${checked ? 'checked' : ''}" id="basemap-item-${key}" onclick="toggleBasemapLayer('${key}', this)">
|
||||
<div class="layer-check">${checked ? '✓' : ''}</div>
|
||||
<div class="layer-dot" style="background:${cfg.color};${cfg.fillOpacity > 0 ? '' : 'border-radius:2px;height:4px;width:18px;border-radius:2px'}"></div>
|
||||
<div class="layer-label">${cfg.icon} ${cfg.label}</div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<div class="layer-section">
|
||||
<div class="layer-section-title">Informasi</div>
|
||||
<div style="font-size:11px;line-height:1.8;color:var(--text2);padding:10px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius)">
|
||||
Data batas administrasi kecamatan, jaringan jalan, dan sungai besar Kota Pontianak.<br>
|
||||
Klik objek di peta untuk melihat informasi detail.<br>
|
||||
Centang/hilang centang untuk tampil/sembunyikan layer.
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Load semua layer yang aktif
|
||||
const loadPromises = Object.keys(basemapCfg)
|
||||
.filter(key => layerVisible['basemap_' + key])
|
||||
.map(key => loadBasemapLayer(key));
|
||||
await Promise.all(loadPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibilitas layer basemap dan load data jika belum ada.
|
||||
*/
|
||||
window.toggleBasemapLayer = async function(key, el) {
|
||||
const lk = 'basemap_' + key;
|
||||
layerVisible[lk] = !layerVisible[lk];
|
||||
el.classList.toggle('checked', layerVisible[lk]);
|
||||
el.querySelector('.layer-check').textContent = layerVisible[lk] ? '✓' : '';
|
||||
|
||||
if (layerVisible[lk]) {
|
||||
await loadBasemapLayer(key);
|
||||
map.addLayer(layers[lk]);
|
||||
} else {
|
||||
map.removeLayer(layers[lk]);
|
||||
}
|
||||
};
|
||||
@@ -81,6 +81,7 @@
|
||||
<script src="js/feature-spbu.js"></script>
|
||||
<script src="js/feature-jalan.js"></script>
|
||||
<script src="js/feature-persil.js"></script>
|
||||
<script src="js/feature-basemap.js"></script>
|
||||
<script>
|
||||
initPage('kemiskinan');
|
||||
</script>
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<script src="js/feature-spbu.js"></script>
|
||||
<script src="js/feature-jalan.js"></script>
|
||||
<script src="js/feature-persil.js"></script>
|
||||
<script src="js/feature-basemap.js"></script>
|
||||
<script>
|
||||
initPage('persil');
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user